wake-up-neo.com

Rails: Verketten von Scope-Abfragen mit OR anstelle von UND?

Ich verwende Rails3, ActiveRecord

Ich frage mich nur, wie ich die Gültigkeitsbereiche mit OR -Anweisungen anstatt mit AND verketten kann.

z.B. 

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

Das gibt normalerweise name = 'John' AND lastname = 'Smith' zurück, aber ich möchte:

name = 'John' OR lastname = 'Smith'

124
user410715

Du würdest

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

Im Moment gibt es keine andere OR -Unterstützung durch die neue AR3-Syntax (d. H. Ohne Verwendung eines Drittanbieter-Gems).

94
Petros

Verwenden Sie ARel

t = Person.arel_table

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

Laut dieser Pull-Anfrage unterstützt Rails 5 nun folgende Syntax für die Verkettung von Abfragen:

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

Es gibt auch einen Backport der Funktionalität in Rails 4.2 über dieses Juwel.

39
Ryan Leaf

Update für Rails4

erfordert keine Edelsteine ​​von Drittanbietern

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) }

Alte Antwort

erfordert keine Edelsteine ​​von Drittanbietern

Person.where(
    Person.where(:name => "John").where(:lastname => "Smith")
      .where_values.reduce(:or)
)
37
kissrobber

Sie müssen lediglich die Array-Syntax für same column OR - Abfragen bereitstellen, um den Blick zu erleichtern.

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

Falls jemand nach einer aktualisierten Antwort auf diese Frage sucht, scheint es eine vorhandene Pull-Anforderung zu geben, um dies in Rails zu erhalten: https://github.com/Rails/rails/pull/9052 .

Dank des @ j-mcnally-Affenpatches für ActiveRecord ( https://Gist.github.com/j-mcnally/250eaaceef234dd8971b ) können Sie Folgendes tun:

Person.where(name: 'John').or.where(last_name: 'Smith').all

Noch wertvoller ist die Möglichkeit, Bereiche mit OR zu verketten:

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) }

Dann können Sie alle Personen mit Vor- oder Nachnamen oder deren Eltern mit Nachnamen finden

Person.first_or_last_name('John Smith').or.parent_last_name('Smith')

Nicht das beste Beispiel dafür, sondern nur die Frage zu beantworten.

22
codenamev

Sie können auch MetaWhere gem verwenden, um Ihren Code nicht mit SQL-Informationen zu verwechseln:

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

Eine aktualisierte Version von Rails/ActiveRecord kann diese Syntax nativ unterstützen. Es würde ähnlich aussehen:

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

Wie in dieser Pull-Anfrage angegeben, https://github.com/Rails/rails/pull/9052

Für den Moment ist es einfach toll, bei den folgenden Arbeiten zu bleiben:

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

Dies wäre ein guter Kandidat für MetaWhere, wenn Sie Rails 3.0+ verwenden, aber es funktioniert nicht auf Rails 3.1. Vielleicht möchten Sie stattdessen squeel ausprobieren. Es wurde vom selben Autor erstellt. So würden Sie eine OR basierte Kette ausführen:

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

Sie können UND/ODER unter vielen anderen großartigen Dingen kombinieren.

8
dhulihan

Für mich (Rails 4.2.5) funktioniert es nur so:

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

Schienen 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

Ich habe versucht, benannte Bereiche mit .or und ohne Glück zu verketten, aber dies funktionierte, um etwas mit diesen Booleschen Geräten zu finden. Erzeugt SQL wie

SELECT 'CREATURES'.* FROM 'CREATURES' WHERE ((('CREATURES'.'is_cat' = 1 OR 'CREATURES'.'is_dog' = 1) OR 'CREATURES'.'eats_children' = 0))
6
genkilabs

Schienen 4

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

Wenn Sie die where-Klausel nicht manuell ausschreiben können, um die Anweisung "oder" einzuschließen (dh Sie möchten zwei Bereiche kombinieren), können Sie union verwenden:

Model.find_by_sql("#{Model.scope1.to_sql} UNION #{Model.scope2.to_sql}")

(Quelle: ActiveRecord Query Union )

Auf diese Weise werden alle Datensätze zurückgegeben, die mit einer der Abfragen übereinstimmen. Dies gibt jedoch ein Array zurück, nicht ein Feld. Wenn Sie wirklich einen Artikel zurückgeben möchten, checken Sie diesen Gist aus: https://Gist.github.com/j-mcnally/250eaaceef234dd8971b

Dies erledigt den Job, solange Sie nichts dagegen haben, dass Affen Rails patchen. 

3

Siehe auch diese verwandten Fragen: hier , hier und hier

Für Rails 4, basierend auf diesem Artikel und dieser Originalantwort

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

Beachten Sie die Warnung des Artikels am Ende:

Schienen 4.1+

In Rails 4.1 wird default_scope wie ein regulärer Bereich behandelt. Der Standard scope (falls vorhanden) ist im Ergebnis von where_values ​​und .__ enthalten. inject (: oder) fügt zwischen dem Standardbereich und Ihrem .__ eine Anweisung oder eine Anweisung ein. wo ist. Das ist schlecht.

Um dies zu lösen, müssen Sie die Abfrage nur abbrechen.

2
HeyZiko

Dies ist ein sehr praktischer Weg und funktioniert in Rails 5 einwandfrei:

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

der squeel Gem bietet eine unglaublich einfache Möglichkeit, dies zu erreichen (zuvor habe ich etwas wie die Methode von @ coloradoblue verwendet):

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

Wenn Sie einen Geltungsbereich bereitstellen möchten (anstatt explizit an der gesamten Datenmenge zu arbeiten), sollten Sie Folgendes mit Rails 5 tun:

scope :john_or_smith, -> { where(name: "John").or(where(lastname: "Smith")) }

Oder:

def self.john_or_smith
  where(name: "John").or(where(lastname: "Smith"))
end
0
thisismydesign

Die Antwort auf die ursprüngliche Frage: Können Sie Bereiche mit 'oder' anstelle von 'verbinden und' scheint nicht zu sein '. Sie können jedoch einen vollständig anderen Bereich oder eine andere Abfrage eingeben, die den Job ausführt, oder ein anderes Framework als ActiveRecord verwenden, z. MetaWhere oder Squeel. In meinem Fall nicht nützlich

Ich bin 'oder' ein von pg_search generierter Bereich, der ein bisschen mehr tut als select, er enthält die Reihenfolge nach ASC, wodurch eine saubere Vereinigung entsteht. Ich möchte 'oder' es mit einem handgefertigten Bereich machen, der Dinge erledigt, die ich in pg_search nicht tun kann. Also musste ich es so machen.

Product.find_by_sql("(#{Product.code_starts_with('Tom').to_sql}) union (#{Product.name_starts_with('Tom').to_sql})")

Das heißt Verwandeln Sie die Bereiche in SQL, setzen Sie sie in Klammern, verbinden Sie sie miteinander und suchen Sie dann mit dem generierten SQL die Suche nach _by_sql. Es ist ein bisschen Müll, aber es funktioniert.

Nein, sagen Sie mir nicht, dass ich in pg_search "gegen: [: name,: code]" verwenden kann. Ich würde das gerne so machen, aber das 'name'-Feld ist ein Hstore, das pg_search nicht verarbeiten kann noch. Daher muss der Gültigkeitsbereich anhand des Namens von Hand erstellt und dann mit dem Gültigkeitsbereich pg_search vereinigt werden. 

0
John Small