オブジェクトの配列があるので、Indicator
と呼びましょう。この配列でインジケータークラスメソッド(def self.subjects
多様性、スコープなど)を実行します。オブジェクトのグループでクラスメソッドを実行する唯一の方法は、それらをActiveRecord :: Relationにすることです。そのため、to_indicators
メソッドをArray
に追加することになりました。
def to_indicators
# TODO: Make this less terrible.
Indicator.where id: self.pluck(:id)
end
クラスメソッド内で、結果をフィルターダウンするために、これらのスコープのかなりの部分をチェーンすることがあります。そのため、ActiveRecord :: Relationでメソッドを呼び出しても、そのオブジェクトにアクセスする方法がわかりません。 all
を介してのみコンテンツにアクセスできます。ただし、all
は配列です。そのため、その配列をActiveRecord :: Relationに変換する必要があります。たとえば、これはいずれかのメソッドの一部です。
all.to_indicators.applicable_for_bank(id).each do |indicator|
total += indicator.residual_risk_for(id)
indicator_count += 1 if indicator.completed_by?(id)
end
これは2つの質問に要約されると思います。
where
を実行しないでください。def self.subjects
タイプのメソッドを実行する場合、ActiveRecord :: Relationオブジェクト自体にどのようにアクセスしますか?ありがとう。明確にする必要がある場合はお知らせください。
オブジェクトの配列をActiveRecord :: Relationに変換するにはどうすればよいですか?できれば、毎回whereを実行しないでください。
関係はSQLクエリの単なるビルダーであり、そのメソッドは実際のデータを操作しないため、配列をActiveRecord :: Relationに変換することはできません。
ただし、必要なのがリレーションの場合:
activeRecord 3.xの場合、all
を呼び出さず、代わりに scoped
を呼び出します。これは、all
が配列で提供するのと同じレコードを表すリレーションを返します。
activeRecord 4.xの場合、単に all
を呼び出すと、Relationが返されます。
ActiveRecord :: Relationで
def self.subjects
タイプのメソッドを実行する場合、ActiveRecord :: Relationオブジェクト自体にどのようにアクセスしますか?
Relationオブジェクトでメソッドが呼び出されると、self
がリレーションになります(定義されているモデルクラスとは異なります)。
このようなオブジェクトの配列arr
をActiveRecord :: Relationに変換できます(オブジェクトがどのクラスであるか、おそらくどのクラスであるかを知っていると仮定)
MyModel.where(id: arr.map(&:id))
where
を使用する必要がありますが、使用することをためらうべきではない便利なツールです。これで、配列をリレーションに変換するワンライナーができました。
map(&:id)
は、オブジェクトの配列をIDのみを含む配列に変換します。そして、where句に配列を渡すと、次のようなIN
を持つSQLステートメントが生成されます。
SELECT .... WHERE `my_models`.id IN (2, 3, 4, 6, ....
配列の順序が失われることに注意してください-しかし、あなたの目的はこれらのオブジェクトのコレクションでクラスメソッドを実行することだけなので、問題になりません。
さて、私の場合、オブジェクトの配列をActiveRecord :: Relationとソートに変換する必要があります特定の列(たとえば、id)を使用します。私はMySQLを使用しているので、field関数が役立つ場合があります。
MyModel.where('id in (?)',ids).order("field(id,#{ids.join(",")})")
SQLは次のようになります。
SELECT ... FROM ... WHERE (id in (11,5,6,7,8,9,10))
ORDER BY field(id,11,5,6,7,8,9,10)
ActiveRecord::Relation
は、データベースからデータを取得するデータベースクエリをバインドします。
理にかなっていると仮定し、同じクラスのオブジェクトを持つ配列があり、それらをバインドするクエリはどれですか?
走ると
users = User.where(id: [1,3,4,5])
User Load (0.6ms) SELECT `users`.* FROM `users` WHERE `users`.`id` IN (1, 3, 4, 5) ORDER BY created_at desc
上記のusers
return Relation
オブジェクトですが、データベースクエリをその背後にバインドし、表示できます。
users.to_sql
=> "SELECT `users`.* FROM `users` WHERE `users`.`id` IN (1, 3, 4, 5) ORDER BY created_at desc"
そのため、SQLクエリに依存しないオブジェクトの配列からActiveRecord::Relation
を返すことはできません。
まず第一に、これは特効薬ではありません。私の経験から、リレーションへの変換は他の方法よりも簡単な場合があることがわかりました。私はこのアプローチを非常に控えめに使用しようとしますが、代替手段がより複雑になる場合にのみ使用します。
ここで言われたことは私の解決策であり、Array
クラスを拡張しました。
# lib/core_ext/array.rb
class Array
def to_activerecord_relation
return ApplicationRecord.none if self.empty?
clazzes = self.collect(&:class).uniq
raise 'Array cannot be converted to ActiveRecord::Relation since it does not have same elements' if clazzes.size > 1
clazz = clazzes.first
raise 'Element class is not ApplicationRecord and as such cannot be converted' unless clazz.ancestors.include? ApplicationRecord
clazz.where(id: self.collect(&:id))
end
end
使用例はarray.to_activerecord_relation.update_all(status: 'finished')
です。今、どこで使用しますか?
場合によっては、ActiveRecord::Relation
を除外する必要があります。たとえば、未完成の要素を取り出します。そのような場合、スコープelements.not_finished
を使用するのが最善ですが、ActiveRecord::Relation
を保持します。
しかし、時にはその状態はより複雑です。終了しておらず、過去4週間で生産され、検査されたすべての要素を取り出します。新しいスコープを作成しないようにするには、配列にフィルターをかけてから元に戻すことができます。 id
で検索しますが、まだクエリであるため、DBに対してクエリを実行することに注意してください。