Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

いくつかのコールバックの落とし穴(そしてRails 5の修正)

ActiveRecordコールバックは、モデルのライフサイクルのさまざまな段階でコードを実行する簡単な方法です。

たとえば、Q&Aサイトがあり、すべての質問を検索できるようにしたいとします。質問に変更を加えるたびに、ElasticSearchのようなものでインデックスを作成する必要があります。インデックス作成には時間がかかり、緊急ではないため、Sidekiqを使用してバックグラウンドでインデックス作成を行います。

これは、after_saveを使用するのに最適な時期のようです。 コールバック! したがって、モデルでは、次のように記述します。

app / models / question.rb
class Question < ActiveRecord::Base
  after_save :index_for_search

  # ...

  private
  
  def index_for_search
    QuestionIndexerJob.perform_later(self)
  end
end
app / jobs / question_indexer_job.rb
class QuestionIndexerJob < ActiveJob::Base
  queue_as :default

  def perform(question)
    # ... index the question ...
  end
end

これはうまくいきます!または、少なくとも、そうです。 さらに多くのジョブをキューに入れて、これらのエラーが表示されるまで:

2015-03-10T05:29:02.881Z 52530 TID-oupf889w4 WARN: Error while trying to deserialize arguments: Couldn't find Question with 'id'=3

確かに、Sidekiqはジョブを再試行し、次回はおそらく機能します。しかし、それでも少し奇妙です。 保存したばかりの質問がSidekiqで見つからないのはなぜですか?

プロセス間の競合状態

Railsはafter_saveを呼び出します レコードが保存された直後のコールバック。 ただし、そのレコードは、データベースがトランザクションするまで、Sidekiqが使用しているような他のデータベース接続では表示できません。 コミットされますが、これは少し後で発生します。 これは、Sidekiqが質問を保存した後、コミットする前に、質問を見つけようとする可能性があることを意味します。記録が見つからず、爆発します。

この問題は非常に一般的であるため、Sidekiqにはそれに関するFAQエントリがあります。そして、簡単な修正があります。

after_saveの代わりに :

app / models / question.rb
class Question < ActiveRecord::Base
  after_save :index_for_search

  # ...
end

after_commitを使用する :

app / models / question.rb
class Question < ActiveRecord::Base
  after_commit :index_for_search

  # ...
end

そして、Sidekiqがあなたのモデルを見ることができるまで、あなたの仕事はキューに入れられません。

したがって、バックグラウンドジョブをキューに入れたり、行った変更について別のプロセスに通知したりする場合は、after_commitを使用してください。 。そうしないと、触れたばかりのレコードが見つからない可能性があります。

しかし、もう1つ問題があります…

OK、after_saveの束を切り替えました after_commitを使用するためのフック 代わりは。すべてがうまくいくようです。すべてをチェックインして家に帰る時間ですよね?

まず、テストを実行する必要があります:

test / models / question_test.rb
require 'test_helper'

class QuestionTest < ActiveSupport::TestCase
  test "A saved question is queued for indexing" do
    assert_enqueued_with(job: QuestionIndexerJob) do
      Question.create(title: "Is it legal to kill a zombie?")
    end
  end
end
  1) Failure:
QuestionTest#test_A_saved_question_is_queued_for_indexing [/Users/jweiss/Source/testapps/after_commit/test/models/question_test.rb:7]:
No enqueued job found with {:job=>QuestionIndexerJob}

おっと!テストはジョブをキューに入れるべきではありませんか? そこで何が起こったのですか?

デフォルトでは、Railsは各テストケースを独自のデータベーストランザクションでラップします。これは本当に物事をスピードアップすることができます。テスト中に行ったすべての変更を元に戻すには、1つのデータベースコマンドだけで済みます。

ただし、これはafter_commitも意味します コールバックは実行されません。 after_commitのため コールバックは、最も外側の場合にのみ実行されます トランザクションがコミットされました。

saveを呼び出すとき テストケース内では、(多かれ少なかれ)トランザクションをコミットしますが、それは2番目に外側です。 今トランザクション。つまり、after_commit コールバックは、期待どおりに実行されません。そして、それらの内部で何が起こっているかをテストすることはできません。

この問題も簡単に修正できます。 test_after_commitを含めます Gemfile内のgem:

Gemfile
group :test do
  gem "test_after_commit"
end

そしてあなたのafter_commit フックは最後から2番目の後に実行されます トランザクションがコミットされます。 これがあなたが期待していたことです。

「それは変だ。 Railsに付属するコールバックをテストするために、まったく別のgemを使用する必要があるのはなぜですか?自動的に発生するのではないでしょうか?」

あなたが正しい。それは変だね。しかし、それは長い間奇妙なままではありません。

Rails 5が出荷されたら、test_after_commitについて心配する必要はありません。 。この問題は約1か月前にRailsで修正されたためです。

私自身のコードでは、after_commitを使用しています 多くの。私はおそらくafter_saveを使用するよりも多く使用しています !しかし、問題と奇妙なエッジケースがないわけではありません。

バージョンごとですが、改善されています。 また、after_commitを使用する場合 適切な場所では、多くの奇妙でランダムな例外が発生しなくなります。


  1. Vue、Vuex、Railsを使用したフルスタックアプリケーションの構築

    スケーラビリティを念頭に置いてフルスタックアプリケーションを構築することは、特に、完全なタイプスクリプトをサポートする最新バージョンのVueおよびVuexを使用して構築する場合、威圧的になる可能性があります。この記事では、不健康な家畜への治療の処方を管理するCRUDアプリケーションを探索することで、APIリクエストとデータベースの相互作用を処理するVuex4.0を使用した状態管理からスケーラブルなフルスタックアプリケーションを構築するために知っておく必要のあるすべてを読者に教えます。バックエンドはRailsで構築され、フロントエンドによる統合のために基本的なCRUDAPIを公開します。 ほと

  2. Chrome と Edge で RESULT_CODE_HUNG を修正

    複数のブラウザーがインターネット ドメインの拠点を占めていますが、Google Chrome と Microsoft Edge はそのリストの中で際立っています。 Chrome は世界中の何百万人ものユーザーにとって頼りになる選択肢ですが、私の数人の Windows ユーザーには Edge が好まれています。しかし、これらの優れたブラウザーにもいくつかの欠陥があります。ユーザーは、インターネット サーフィン中にいくつかのよくあるエラーに気を取られることがよくあります。そのようなよくあるエラーの 1 つに Aw Snap! RESULT_CODE_HUNG . Chrome、Edge、Brave