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を使用しません)。
ARel を使用します
t = Person.arel_table
results = Person.where(
t[:name].eq("John").
or(t[:lastname].eq("Smith"))
)
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)
)
same column ORクエリの配列構文を投稿して、覗き見を助けます。
Person.where(name: ["John", "Steve"])
誰かがこれに対する更新された答えを探している場合、これを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')
これを使用するための最良の例ではありませんが、質問に合わせようとしています。
MetaWhere gemを使用して、コードをSQLのものと混同しないようにすることもできます。
Person.where((:name => "John") | (:lastname => "Smith"))
私(Rails 4.2.5)の場合、次のようにしか機能しません。
{ where("name = ? or name = ?", a, b) }
Rails/ActiveRecordの更新バージョンは、この構文をネイティブにサポートする場合があります。次のようになります。
Foo.where(foo: 'bar').or.where(bar: 'bar')
このプルリクエストで述べたように、 https://github.com/Rails/rails/pull/9052
今のところ、単に次の素晴らしい作品にこだわるだけです:
Foo.where('foo= ? OR bar= ?', 'bar', 'bar')
Rails 3.0+を使用している場合、これはMetaWhereに適していますが、Rails 3.1では動作しません。代わりに squeel を試してください。それは同じ著者によって作られました。 ORベースのチェーンを実行する方法は次のとおりです。
Person.where{(name == "John") | (lastname == "Smith")}
他の多くの要素の中でAND/ORを組み合わせて一致させることができます awesome things 。
Rails 4 + Scope + 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 4
scope :combined_scope, -> { where("name = ? or name = ?", 'a', 'b') }
Where句を手動で記述して "or"ステートメントを含めることができない場合(つまり、2つのスコープを結合する場合)、unionを使用できます。
Model.find_by_sql("#{Model.scope1.to_sql} UNION #{Model.scope2.to_sql}")
(ソース: ActiveRecord Query Union )
これは、いずれかのクエリに一致するすべてのレコードを返します。ただし、これはアレルではなく配列を返します。本当にアールを返したい場合は、この要点を確認してください: https://Gist.github.com/j-mcnally/250eaaceef234dd8971b 。
Railsにパッチを適用することを気にしない限り、これでうまくいきます。
(データセット全体を明示的に処理する代わりに)スコープを提供する場合は、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
次の関連質問も参照してください: here 、 here および here
Rails 4の場合は、 この記事 および この元の回答 に基づきます
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の間にステートメントを追加または追加します。それは良くないね。
これを解決するには、クエリのスコープを解除するだけです。
これは非常に便利な方法であり、Rails 5で正常に機能します。
Transaction
.where(transaction_type: ["Create", "Correspond"])
.or(
Transaction.where(
transaction_type: "Status",
field: "Status",
newvalue: ["resolved", "deleted"]
)
)
.or(
Transaction.where(transaction_type: "Set", field: "Queue")
)
squeel
gemは、これを実現する非常に簡単な方法を提供します(これに先立ち、@ 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'フィールドはhstoreであり、pg_searchでは処理できませんまだ。そのため、名前によるスコープは手作業で作成し、pg_searchスコープと結合する必要があります。