Rails:如何链接范围查询与OR而不是AND?

我正在使用Rails3,ActiveRecord

只是想知道如何用OR语句而不是AND连接范围。

例如

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

通常返回name ='John'和lastname ='Smith',但是我想:

name ='John'OR lastname ='Smith'

你会做的

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

现在,新的AR3语法(没有使用第三方gem)没有任何其他的OR支持。

使用ARel

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

更新Rails4

不需要第三方gem

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

老答案

不需要第三方gem

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

您也可以使用MetaWhere创业板不要混淆你的代码与SQL的东西:

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

如果有人正在寻找这个更新的答案,看起来有一个现有的拉取请求到这个Rails: https : //github.com/rails/rails/pull/9052 。

感谢@ j-mcnally的ActiveRecord猴子补丁( https://gist.github.com/j-mcnally/250eaefef234dd8971b ),你可以这样做:

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

然后你可以find所有名字的姓氏或名字的父母

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

不是使用这个的最好的例子,只是试图使它符合这个问题。

只需发布相同列OR查询的Array语法即可帮助查看。

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

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的一个很好的select,但是它不适用于Rails 3.1。 你可能想尝试一下squeel 。 这是由同一个作者。 以下是你如何执行一个基于OR的链:

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

你可以混合搭配和/或其他许多令人敬畏的东西 。

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命名作用域,并且没有运气,但是这个工作可以用这些布尔值来find任何东西。 像生成SQL一样

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

根据这个拉取请求 ,Rails 5现在支持链接查询的以下语法:

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

还有一个通过这个gem的function到Rails 4.2的backport 。

对我来说(Rails 4.2.5)只能这样工作:

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

Rails 4

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

如果你不能手动写出where子句来包含“或”语句(即你想合并两个范围),你可以使用union:

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

(来源: ActiveRecord查询联盟 )

这将返回匹配任一查询的所有logging。 但是,这将返回一个数组,而不是一个arel。 如果你真的想要返回一个arel,你签出这个要点: https ://gist.github.com/j-mcnally/250eaefef234dd8971b。

只要你不介意猴子修补栏杆,这将做这项工作。

也看到这些相关的问题: 在这里 , 这里和这里

对于铁轨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结果中,注入(:或者)将在默认范围和您的wheres之间添加或声明。 那很糟。

为了解决这个问题,你只需要查看查询。

squeel gem提供了一个非常简单的方法来实现这个(在此之前,我使用了@ coloradoblue的方法):

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

因此,对原始问题的答案,你可以用“或”而不是“和”来连接作用域,似乎是“不可以”。 但是您可以手动编写一个完全不同的作用域或查询来完成这个工作,或者使用ActiveRecord中的不同框架,例如MetaWhere或Squeel。 在我的情况下没有用

我'或者'是由pg_search生成的作用域,它比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范围结合。

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