web-dev-qa-db-ja.com

オブジェクトの配列をActiveRecord :: Relationに変換する

オブジェクトの配列があるので、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つの質問に要約されると思います。

  1. オブジェクトの配列をActiveRecord :: Relationに変換するにはどうすればよいですか?できれば毎回whereを実行しないでください。
  2. ActiveRecord :: Relationでdef self.subjectsタイプのメソッドを実行する場合、ActiveRecord :: Relationオブジェクト自体にどのようにアクセスしますか?

ありがとう。明確にする必要がある場合はお知らせください。

93
Nathan

オブジェクトの配列を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がリレーションになります(定義されているモデルクラスとは異なります)。

44
Andrew Marshall

このようなオブジェクトの配列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, ....

配列の順序が失われることに注意してください-しかし、あなたの目的はこれらのオブジェクトのコレクションでクラスメソッドを実行することだけなので、問題になりません。

151
Marco Prins

さて、私の場合、オブジェクトの配列を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)

MySQLフィールド関数

5
Xingcheng Xia

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

上記のusersreturn Relationオブジェクトですが、データベースクエリをその背後にバインドし、表示できます。

users.to_sql
 => "SELECT `users`.* FROM `users` WHERE `users`.`id` IN (1, 3, 4, 5)  ORDER BY created_at desc"

そのため、SQLクエリに依存しないオブジェクトの配列からActiveRecord::Relationを返すことはできません。

0
ray

まず第一に、これは特効薬ではありません。私の経験から、リレーションへの変換は他の方法よりも簡単な場合があることがわかりました。私はこのアプローチを非常に控えめに使用しようとしますが、代替手段がより複雑になる場合にのみ使用します。

ここで言われたことは私の解決策であり、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に対してクエリを実行することに注意してください。

0
Haris Krajina