Rails: Como encadear consultas de escopo com OR em vez de AND?

Estou usando o Rails3, ActiveRecord

Basta saber como posso encadear os escopos com instruções OR em vez de AND.

por exemplo

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

Isso normalmente retorna o nome = ‘John’ E lastname = ‘Smith’, mas eu gostaria de:

nome = ‘John’ OU sobrenome = ‘Smith’

    Você faria

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

    Neste momento, não há nenhum outro suporte OR pela nova syntax AR3 (ou seja, sem usar alguma gem de terceiros).

    Use o ARel

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

    Atualização para Rails4

    não requer gemas de terceiros

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

    Resposta antiga

    não requer gemas de terceiros

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

    Basta postar a syntax da matriz para a mesma coluna ou consultas para ajudar a espreitar.

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

    Você também pode usar o MetaWhere gem para não misturar seu código com coisas SQL:

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

    Caso alguém esteja procurando por uma resposta atualizada para esta, parece que existe uma solicitação pull para colocar isso no Rails: https://github.com/rails/rails/pull/9052 .

    Graças ao macacão do @ j-mcnally para o ActiveRecord ( https://gist.github.com/j-mcnally/250eaaceef234dd8971b ) você pode fazer o seguinte:

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

    Ainda mais valioso é a capacidade de encadear escopos com 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) } 

    Então você pode encontrar todas as pessoas com nome ou sobrenome ou cujo pai com sobrenome

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

    Não é o melhor exemplo para o uso disso, mas apenas tentando encaixá-lo na questão.

    De acordo com essa solicitação pull , o Rails 5 agora suporta a seguinte syntax para encadear consultas:

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

    Há também um backport da funcionalidade no Rails 4.2 através desta joia.

    Este seria um bom candidato para o MetaWhere se você estiver usando o Rails 3.0+, mas ele não funciona no Rails 3.1. Você pode querer experimentar squeel vez. É feito pelo mesmo autor. Veja como você executaria uma cadeia baseada em OR:

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

    Você pode misturar e combinar E / OU, entre muitas outras coisas impressionantes .

    Uma versão atualizada do Rails / ActiveRecord pode suportar esta syntax nativamente. Seria semelhante a:

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

    Conforme observado neste pedido pull https://github.com/rails/rails/pull/9052

    Por enquanto, basta seguir as seguintes funções:

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

    Para mim (Rails 4.2.5) só funciona assim:

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

    rails 4 + Escopo + 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 

    Eu tentei encadear escopos nomeados com .ou sem sorte, mas isso funcionou para encontrar qualquer coisa com o conjunto booleano. Gera SQL como

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

    Se você não pode escrever a cláusula where manualmente para include a instrução “ou” (ou seja, você quer combinar dois escopos), você pode usar união:

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

    (fonte: ActiveRecord Query Union )

    Isso retornará todos os registros correspondentes a qualquer consulta. No entanto, isso retorna um array, não um arel. Se você realmente quiser devolver um arel, confira esta questão: https://gist.github.com/j-mcnally/250eaaceef234dd8971b .

    Isso fará o trabalho, contanto que você não se importe com os rails dos macacos.

    Veja também estas questões relacionadas: aqui , aqui e aqui

    Para rails 4, com base neste artigo e na resposta original

     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 

    Observe a caucanvas do artigo no final:

    rails 4.1+

    O Rails 4.1 trata o default_scope apenas como um escopo regular. O escopo padrão (se você tiver algum) é incluído no resultado where_values ​​e injetar (: ou) adicionará ou instrução entre o escopo padrão e seus usuários. Isso é ruim.

    Para resolver isso, você só precisa excluir a consulta.

    squeel joia squeel fornece uma maneira incrivelmente fácil de realizar isso (antes disso eu usei algo como o método @ coloradoblue):

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

    Então a resposta para a pergunta original, você pode juntar escopos com ‘ou’ ao invés de ‘e’ parece ser ‘não, você não pode’. Mas você pode codificar manualmente um escopo ou consulta completamente diferente que faça o trabalho ou usar uma estrutura diferente do ActiveRecord, por exemplo, MetaWhere ou Squeel. Não é útil no meu caso

    Eu estou ‘or’ing um escopo gerado por pg_search, que faz um pouco mais do que selecionar, inclui ordem por ASC, o que faz uma bagunça de uma união limpa. Eu quero ‘ou’ com um escopo artesanal que faz coisas que não posso fazer em pg_search. Então eu tive que fazer assim.

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

    Ou seja, transformar os escopos em sql, colocar colchetes ao redor de cada um, uni-los e, em seguida, find_by_sql usando o sql gerado. É um pouco lixo, mas funciona.

    Não, não me diga que eu posso usar “contra: [: name,: code]” em pg_search, eu gostaria de fazer assim, mas o campo “name” é um hstore, que o pg_search não pode manipular ainda. Portanto, o escopo por nome deve ser criado manualmente e depois unido ao escopo pg_search.

     names = ["tim", "tom", "bob", "alex"] sql_string = names.map { |t| "name = '#{t}'" }.join(" OR ") @people = People.where(sql_string)