Rails3、ActiveRecordを使用しています
ANDではなくORステートメントを使用してスコープをチェーンするにはどうすればよいのでしょうか。
例えば
Person.where(:name => "John").where(:lastname => "Smith")
それは通常戻ります:
name = 'John' AND lastname = 'Smith'
しかし、私は欲しい:
`name = 'John' OR lastname = 'Smith'
Rails3、ActiveRecordを使用しています
ANDではなくORステートメントを使用してスコープをチェーンするにはどうすればよいのでしょうか。
例えば
Person.where(:name => "John").where(:lastname => "Smith")
それは通常戻ります:
name = 'John' AND lastname = 'Smith'
しかし、私は欲しい:
`name = 'John' OR lastname = 'Smith'
回答:
あなたはするだろう
Person.where('name=? OR lastname=?', 'John', 'Smith')
現在、新しいAR3構文による他のORサポートはありません(つまり、サードパーティのgemを使用していません)。
.or
組み込まれます。
このプルリクエストによると、Rails 5はクエリをチェーンするための次の構文をサポートしています。
Post.where(id: 1).or(Post.where(id: 2))
ARelを使用する
t = Person.arel_table
results = Person.where(
t[:name].eq("John").
or(t[:lastname].eq("Smith"))
)
覗き見に役立つように、同じ列またはクエリの配列構文をポストするだけです。
Person.where(name: ["John", "Steve"])
Rails4の更新
サードパーティの宝石は必要ありません
a = Person.where(name: "John") # or any scope
b = Person.where(lastname: "Smith") # or any scope
Person.where([a, b].map{|s| s.arel.constraints.reduce(:and) }.reduce(:or))\
.tap {|sc| sc.bind_values = [a, b].map(&:bind_values) }
古い答え
サードパーティの宝石は必要ありません
Person.where(
Person.where(:name => "John").where(:lastname => "Smith")
.where_values.reduce(:or)
)
or
テキストブロック内の補間またはその他の演算子が必要で、どちらもOPのコードには反映されていません。
...where_values.join(' OR ')
私の場合に使用
.tap {|sc| sc.bind_values = [a, b].map(&:bind_values) }
スコープにバインドが必要な変数がない場合は、この部分を省略できます。
MetaWhere gemを使用して、コードをSQLのものと混同しないようにすることもできます。
Person.where((:name => "John") | (:lastname => "Smith"))
これに対する更新された回答を誰かが探している場合、これをRailsに取り込むための既存のプルリクエストがあるようです:https : //github.com/rails/rails/pull/9052。
ActiveRecordの@ j-mcnallyのモンキーパッチ(https://gist.github.com/j-mcnally/250eaaceef234dd8971b)のおかげで、次のことができます。
Person.where(name: 'John').or.where(last_name: 'Smith').all
さらに価値があるのは、スコープをOR
次のようにチェーンする機能です。
scope :first_or_last_name, ->(name) { where(name: name.split(' ').first).or.where(last_name: name.split(' ').last) }
scope :parent_last_name, ->(name) { includes(:parents).where(last_name: name) }
次に、姓または名を持つ、または姓を持つ親を持つすべての人物を検索できます
Person.first_or_last_name('John Smith').or.parent_last_name('Smith')
これを使用するための最良の例ではありませんが、それを質問に合わせようとしています。
Rails / ActiveRecordの更新バージョンは、この構文をネイティブでサポートする場合があります。次のようになります。
Foo.where(foo: 'bar').or.where(bar: 'bar')
このプルリクエストに記載されているようにhttps://github.com/rails/rails/pull/9052
とりあえず、次のことを続けるだけでうまくいきます。
Foo.where('foo= ? OR bar= ?', 'bar', 'bar')
Foo.where(foo: 'bar1').or.where(bar: 'bar2')
動作しません。Foo.where(foo: 'bar1')。or.where(Foo.where(bar: 'bar2'))である必要があります
Rails 4 +スコープ+ Arel
class Creature < ActiveRecord::Base
scope :is_good_pet, -> {
where(
arel_table[:is_cat].eq(true)
.or(arel_table[:is_dog].eq(true))
.or(arel_table[:eats_children].eq(false))
)
}
end
名前付きスコープを.orで連鎖させてみましたが、これはこれらのブール値セットで何かを見つけるのに役立ちました。のようなSQLを生成します
SELECT 'CREATURES'.* FROM 'CREATURES' WHERE ((('CREATURES'.'is_cat' = 1 OR 'CREATURES'.'is_dog' = 1) OR 'CREATURES'.'eats_children' = 0))
(データセット全体を明示的に操作するのではなく)スコープを提供する場合は、Rails 5で次のことを行う必要があります。
scope :john_or_smith, -> { where(name: "John").or(where(lastname: "Smith")) }
または:
def self.john_or_smith
where(name: "John").or(where(lastname: "Smith"))
end
where句を手動で記述して「or」ステートメントを含めることができない場合(つまり、2つのスコープを結合したい場合)、ユニオンを使用できます。
Model.find_by_sql("#{Model.scope1.to_sql} UNION #{Model.scope2.to_sql}")
(ソース:ActiveRecord Query Union)
これは、いずれかのクエリに一致するすべてのレコードを返します。ただし、これは配列ではなく配列を返します。本当にアールを返したい場合は、この要点をチェックアウトしてください:https ://gist.github.com/j-mcnally/250eaaceef234dd8971b。
モンキーパッチレールを気にしない限り、これでうまくいきます。
Person
.unscoped # See the caution note below. Maybe you want default scope here, in which case just remove this line.
.where( # Begin a where clause
where(:name => "John").where(:lastname => "Smith") # join the scopes to be OR'd
.where_values # get an array of arel where clause conditions based on the chain thus far
.inject(:or) # inject the OR operator into the arels
# ^^ Inject may not work in Rails3. But this should work instead:
.joins(" OR ")
# ^^ Remember to only use .inject or .joins, not both
) # Resurface the arels inside the overarching query
なお、最後に記事の注意を:
Rails 4.1以降
Rails 4.1はdefault_scopeを通常のスコープと同様に扱います。デフォルトのスコープ(ある場合)はwhere_valuesの結果に含まれ、inject(:or)はデフォルトのスコープとwheresの間にステートメントを追加または追加します。それは良くないね。
これを解決するには、クエリのスコープを解除するだけです。
squeel
宝石は、これを実現するために、信じられないほど簡単な方法(この前、私にはcoloradoblueのメソッド@のようなものを使用)提供しています。
names = ["Kroger", "Walmart", "Target", "Aldi"]
matching_stores = Grocery.where{name.like_any(names)}
したがって、元の質問への答えは、スコープを「または」ではなく「または」で結合できますか、そして「できません」のようです。しかし、完全に異なるスコープまたはクエリを実行するクエリを手作業でコーディングするか、ActiveRecordとは異なるフレームワーク、たとえばMetaWhereやSqueelを使用できます。私の場合は役に立たない
私はpg_searchによって生成されたスコープを「or」しています。これは、selectよりも少し多く、ASCによる順序が含まれているため、クリーンなユニオンを混乱させます。pg_searchでは実行できないことを行う手作りのスコープを使用して「または」したいと思います。だから私はこのようにそれをしなければならなかった。
Product.find_by_sql("(#{Product.code_starts_with('Tom').to_sql}) union (#{Product.name_starts_with('Tom').to_sql})")
つまり、スコープをsqlに変換し、それぞれを角かっこで囲み、それらを結合して、生成されたsqlを使用してfind_by_sqlにします。少しゴミですが、機能します。
いいえ、pg_searchで "against:[:name、:code]"を使用できるとは言わないでください。そのようにしたいのですが、 'name'フィールドはpg_searchが処理できないhstoreですまだ。そのため、名前によるスコープは手動で作成し、pg_searchスコープと結合する必要があります。