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


104

オブジェクトの配列があります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に変換する必要があります。たとえば、これはメソッドの1つに含まれています。

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. def self.subjectsActiveRecord :: Relationでtypeメソッドを実行するとき、そのActiveRecord :: Relationオブジェクト自体にどのようにアクセスしますか?

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


3
その配列をリレーションに変換しようとする唯一の理由がを介してそれを取得したためである場合は.all.scopedAndrew Marshallの回答が示すように使用します(ただし、rails 4ではで動作します.all)。配列を関係に変換する必要があることに
気付い

回答:


46

オブジェクトの配列をActiveRecord :: Relationに変換するにはどうすればよいですか?毎回whereを行わないことが望ましい。

RelationはSQLクエリのビルダーにすぎず、そのメソッドは実際のデータを操作しないため、配列をActiveRecord :: Relationに変換することはできません。

ただし、関係が必要な場合は、次のようにします。

  • ActiveRecord 3.xの場合は、呼び出さallずにを呼び出しますscoped。これallにより、配列で与えられるのと同じレコードを表すRelationが返されます。

  • ActiveRecord 4.xの場合allは、Relationを返すを呼び出すだけです。

def self.subjectsActiveRecord :: Relationでtypeメソッドを実行するとき、そのActiveRecord :: Relationオブジェクト自体にどのようにアクセスしますか?

メソッドがRelationオブジェクトで呼び出されると、それselfは関係です(それが定義されているモデルクラスとは対照的です)。


1
解決策については、以下の@Marco Prinsを参照してください。
Justin

レール5の.push非推奨メソッドについてはどうでしょう
Jaswinder

@GstjiSainiあなたが参照している正確な方法がわからないので、ドキュメントまたはソースリンクを提供してください。ただし、廃止予定の場合は、すぐになくなるため、あまり実行可能なソリューションではありません。
アンドリューマーシャル

そしてclass User; def self.somewhere; where(location: "somewhere"); end; end、それがあなたができる理由ですUser.limit(5).somewhere
ドリアンは

これは、OPを真に啓蒙する唯一の答えです。この質問は、ActiveRecordがどのように機能するかについての不完全な知識を明らかにしています。@Justinは、オブジェクトの配列を渡し続けてオブジェクトをマッピングし、別の不要なクエリを作成することがなぜ悪いのかについての理解の欠如を補強しています。
james2m

161

オブジェクトの配列を次の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, ....

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


3
よくやった、まさに私が必要としたもの。これは、モデルの任意の属性に使用できます。私にとっては完璧に機能します。
nfriend21 14

7
なぜリテラルSQLを自分で作成できるのですwhere(id: arr.map(&:id))か?そして厳密に言えば、これは配列をリレーションに変換しませんが、代わりにオブジェクトの新しいインスタンスを取得し(リレーションが実現すると)、配列内のすでにメモリ内のインスタンスとは異なる属性値を持つ可能性のあるIDを持ちます。
アンドリューマーシャル

7
ただし、これは順序を失います。
Velizar Hristov 2015

1
@VelizarHristovこれは、列によってのみ順序付けできるリレーションになり、希望する方法ではないためです。関係は大きなデータセットを処理する方が速く、いくつかのトレードオフがあります。
Marco Prins 2015年

8
非常に非効率的です。すでにメモリにあるオブジェクトのコレクションを、データベースクエリを実行してアクセスするオブジェクトに変換しました。配列を反復処理したいクラスのメソッドをリファクタリングすることを検討します。
james2m 2016年

5

まあ、私の場合、私がする必要がある::のActiveRecordの関係をオブジェクトの配列を変換するだけでなく、並べ替え、特定の列(例えばID)でそれらを。私はMySQLを使用しているので、フィールド関数が役に立ちます。

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フィールド関数


PostgreSQLで動作するこのバージョンについては、次のスレッドをご覧
armchairdj

0

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返しRelationますが、その背後にあるデータベースクエリをバインドして表示できます。

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

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


0

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

ここで言われているのは私の解決策です、私は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週間に生産されて検査されたすべての要素を取り出します。新しいスコープの作成を回避するために、配列にフィルターを適用してから元に戻すことができます。まだDBに対してクエリを実行していることに注意してくださいid

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.