Techioz Blog

Ruby on Rails で ActiveRecordModel に繰り返しアクセスすると同じオブジェクトが返される: 意図した動作かバグ?

概要

Ruby on Rails アプリケーションの ActiveRecordModel で奇妙な動作が発生しています。モデルにアクセスするたびに、同じオブジェクト インスタンスが返されるように見えます。これが標準的な動作なのか、それとも私の実装に潜在的な問題があるのかはわかりません。

シナリオは次のとおりです。

ApplicationRecord を継承するクラス MyModel があります。このクラスでは、test と test2 という 2 つのクラス メソッドを定義しました。テスト メソッドは、特定の条件に基づいて MyModel レコードをフィルターし、test2 を呼び出します。 test2 メソッドはコレクションを反復処理して各レコードを更新し、ID で同じレコードを検索しようとします。

class MyModel < ApplicationRecord
  def self.test
    MyModel.where(some_column: "A").test2
  end

  def self.test2
    all.each do |my_model|
      my_model.update(some_column: "B")
      MyModel.find(my_model.id)
    end
  end
end

ただし、このプロセス中に、珍しいと思われる ActiveRecord::RecordNotFound 例外が発生しました。例外メッセージは次のとおりです。

ActiveRecord::RecordNotFound (Couldn't find MyModel with 'id'='the_id_of_record' [WHERE "my_models"."some_column" = $1])

このエラーは、レコードを更新した後、後続の検索操作でレコードを見つけることができないことを示しています。これが何らかの ActiveRecord キャッシュ メカニズムによる予期される動作なのか、それとも私のアプローチの欠陥なのかを理解しようとしています。さらに、この状況に効果的に対処する方法についての洞察や推奨事項があれば幸いです。

解決策

この動作は意図されたものです。リレーションに対して test2 メソッドを呼び出しています。これは、単なるメソッド呼び出しではなくスコープとして機能することを意味します。

https://translate.google.com/translate?hl=ja&sl=en&tl=ja&u=https://github.com/rails/rails/blob/v7.1.3/activerecord/lib/active_record/relation/delegation.rb#L78-L80

>> puts MyModel.where(some_column: "A").method(:test2).source
def #{method}(...)
  scoping { klass.#{method}(...) }
end

メソッド呼び出しはスコープ メソッドを通じて MyModel.test2 に委任されることに注意してください。

https://translate.google.com/translate?hl=ja&sl=en&tl=ja&u=https://api.rubyonrails.org/classes/ActiveRecord/Relation.html#method-i-scoping

class MyModel < ApplicationRecord
  def self.test
    MyModel.where(some_column: "A").test2
  end

  def self.test2
    all.each do |my_model|
      my_model.update(some_column: "B")

      # NOTE: when you do this, `current_scope` is taken into account
      #       which you can take a look at:
      p "current_scope"
      p current_scope.to_sql
      MyModel.find(my_model.id)
    end
  end
end
#=>
# "current_scope"
# "SELECT \"my_models\".* FROM \"my_models\" WHERE \"my_models\".\"some_column\" = 'A'"

MyModel.unscoped.find(my_model.id) を使用できますが、なぜこれを行う必要があるのかわかりません。また、メソッド呼び出しをリレーションに連鎖させることもできません。

class MyModel < ApplicationRecord
  def self.test
    MyModel.where(some_column: "A")
  end

  def self.test2 q
    q.each do |my_model|
      my_model.update(some_column: "B")
      MyModel.find(my_model.id)
    end
  end
end

>> MyModel.test2(MyModel.test)