web-dev-qa-db-ja.com

ANDではなくORを使用してスコープクエリをチェーンする方法は?

Rails3、ActiveRecordを使用しています

ANDではなくORステートメントを使用してスコープを連鎖させるにはどうすればよいのでしょうか。

例えば.

Person.where(:name => "John").where(:lastname => "Smith")

それは通常返されます:

name = 'John' AND lastname = 'Smith'

しかし、私はしたい:

`name = 'John' OR lastname = 'Smith'
129
user410715

あなたがするだろう

Person.where('name=? OR lastname=?', 'John', 'Smith')

現時点では、新しいAR3構文による他のORサポートはありません(サードパーティのgemを使用しません)。

98
Petros

ARel を使用します

t = Person.arel_table

results = Person.where(
  t[:name].eq("John").
  or(t[:lastname].eq("Smith"))
)
48
Dan McNevin

このプルリクエスト によると、Rails 5はクエリのチェーンのために次の構文をサポートするようになりました。

Post.where(id: 1).or(Post.where(id: 2))

this gem。 を介したRails 4.2への機能のバックポートもあります

45
Ryan Leaf

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)
)
38
kissrobber

same column ORクエリの配列構文を投稿して、覗き見を助けます。

Person.where(name: ["John", "Steve"])
36
daino3

誰かがこれに対する更新された答えを探している場合、これを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')

これを使用するための最良の例ではありませんが、質問に合わせようとしています。

22
codenamev

MetaWhere gemを使用して、コードをSQLのものと混同しないようにすることもできます。

Person.where((:name => "John") | (:lastname => "Smith"))
22

私(Rails 4.2.5)の場合、次のようにしか機能しません。

{ where("name = ? or name = ?", a, b) }
9
Zsolt

Rails/ActiveRecordの更新バージョンは、この構文をネイティブにサポートする場合があります。次のようになります。

Foo.where(foo: 'bar').or.where(bar: 'bar')

このプルリクエストで述べたように、 https://github.com/Rails/rails/pull/9052

今のところ、単に次の素晴らしい作品にこだわるだけです:

Foo.where('foo= ? OR bar= ?', 'bar', 'bar')
8

Rails 3.0+を使用している場合、これはMetaWhereに適していますが、Rails 3.1では動作しません。代わりに squeel を試してください。それは同じ著者によって作られました。 ORベースのチェーンを実行する方法は次のとおりです。

Person.where{(name == "John") | (lastname == "Smith")}

他の多くの要素の中でAND/ORを組み合わせて一致させることができます awesome things

8
dhulihan

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))
6
genkilabs

Rails 4

scope :combined_scope, -> { where("name = ? or name = ?", 'a', 'b') }
4
Abs

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にパッチを適用することを気にしない限り、これでうまくいきます。

3
HashFail

(データセット全体を明示的に処理する代わりに)スコープを提供する場合は、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
3
thisismydesign

次の関連質問も参照してください: herehere および 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の間にステートメントを追加または追加します。それは良くないね。

これを解決するには、クエリのスコープを解除するだけです。

2
HeyZiko

これは非常に便利な方法であり、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")
  )
0
Jigar Bhatt

squeel gemは、これを実現する非常に簡単な方法を提供します(これに先立ち、@ coloradoblueのメソッドのようなものを使用しました)。

names = ["Kroger", "Walmart", "Target", "Aldi"]
matching_stores = Grocery.where{name.like_any(names)}
0
boulder_ruby

したがって、元の質問への答えは、スコープを「および」の代わりに「または」で結合できますか?「できない」と思われます。ただし、ジョブを実行するまったく異なるスコープまたはクエリを手動でコーディングしたり、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スコープと結合する必要があります。

0
John Small