LoginSignup
21
21

More than 5 years have passed since last update.

DelayedJob に ActiveRecord インスタンスを渡す時の注意

Last updated at Posted at 2014-09-05

結論

ハマったら .as_json で渡しましょう。
*** ActiveJob は GlobalID に対応しているので以下の心配は不要です**

理由

user.rb
class User < ActiveRecord::Base
  before_save :notify

  def before_save
    Notifier.delay.push(self)
  end
end

これは期待通りに動かない (ことがある)。
delay は巧妙な構文で仕組みを隠蔽しているが、実際に内部で起きていることは 'Notifier' と 'push' と 'self' を全部ひとまとめにした yaml 文字列を作って Delayed::Job のインスタンスを生成しているだけだ。
分かりやすく書けばこんな感じ。

Delayed::Job.new(PerformableMethod.new(Notifier, :push, [self]))

PerformableMethod は渡された3つのパラメタを纏めて1つのyaml文字列を生成する。これが delayed_jobs テーブルに保存される。
一方、別プロセスで動作している delayed_job デーモンは、このyaml文字列から元のオブジェクトを復元する。
これらの一連の変換は
https://github.com/collectiveidea/delayed_job/blob/master/lib/delayed/psych_ext.rb
に記述されている。

さて、save 前の ActiveRecord インスタンスはどのように delayed_job デーモンに渡るのか。実際に delayed_jobs テーブルの実行前のレコードを見てみると、各カラムの値が記録されているのがわかる。
しかし油断してはいけない。先ほどの psych_ext.rb の、 yaml からインスタンスを復元する部分のコードを確認してみよう。

psych_ext.rb
        case object.tag
        when /^!ruby\/ActiveRecord:(.+)$/
          klass = resolve_class(Regexp.last_match[1])
          payload = Hash[*object.children.map { |c| accept c }]
          id = payload['attributes'][klass.primary_key]
          begin
            klass.unscoped.find(id)
          rescue ActiveRecord::RecordNotFound => error
            raise Delayed::DeserializationError, "ActiveRecord::RecordNotFound, class: #{klass}, primary key: #{id} (#{error.message})"
          end

この通り、 klass.unscoped.find(id) となっていて、 idしか見ていない ことがわかる。
user が new インスタンスで id が振られてなければ、 RecordNotFound になってしまう。
あるいは id を既に持っていても、この job が実行される時点でもしまだレコードが保存されてなければ、 notify メソッドは保存前のレコードの内容で実行されることになる。
あるいは、 delayed_job の実行が十分に遅れて、その間に DB の内容が変わっていたら、最新のデータで notify が呼ばれることになる。

レシーバが ActiveRecord インスタンスの場合も同様の問題が起きる。

def before_save
  self.delay.notify
end

せっかく全カラムの情報渡してるんだから、その通りに復元してくれればいいのにと思いました。

余談

DelayedJob 公式には、 Delayed::Job レコードの id を取得する方法が用意されてないので実行結果の追跡が困難なのだが、実は

job = Notifier.delay.push(self)

とすると Delayed::Job インスタンスが取得できるので job.id で id が簡単に取れる。

21
21
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
21