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)