WHERE子句中的OR运算符与Rails 4.2中的Arel

以下代码在Rails 4.1中构造了一个带有OR运算符的有效where子句

 MyModel.where( MyModel.where(attribute1: 1, attribute2: 2).where_values.reduce(:or) ) 

这大致相当于SQL

 select * from my_models where (attribute1 = 1 OR attribute2 = 2) 

在Rails 4.2中,相同的代码生成一个SQL查询,其缺少值的绑定参数

 select * from my_models where attribute1 = OR attribute2 = 

…并且由于缺少绑定值的值而生成错误。

Rails 4.2中用OR运算符生成有效查询的等效代码是什么?

编辑:

该解决方案需要使用Arel::Nodes::Node派生对象,以便它本身可以通过AND和OR分组与其他条件组合。

 rel = MyModel.where(attribute1: 1, attribute2: 2) conditions = [rel.where_values.reduce(:or).to_sql, *rel.bind_values.map(&:last)] MyModel.where(conditions) 

conditions var必须是Arel::Nodes::Node的衍生物。 上述解决方案适用于简单查询,但对于更复杂的查询, conditions必须是要传递给最终查询方法的Arel节点。

我正在使用下面的东西,直到导轨5出来(在导轨5 AR支持.or ):

 ActiveRecord::QueryMethods::WhereChain.class_eval do def or(*scopes) scopes_where_values = [] scopes_bind_values = [] scopes.each do |scope| case scope when ActiveRecord::Relation scopes_where_values += scope.where_values scopes_bind_values += scope.bind_values when Hash temp_scope = @scope.model.where(scope) scopes_where_values += temp_scope.where_values scopes_bind_values += temp_scope.bind_values end end scopes_where_values = scopes_where_values.inject(:or) @scope.where_values += [scopes_where_values] @scope.bind_values += scopes_bind_values @scope end end 

有了以上你可以做到:

 MyModel.where.or(attribute1: 1, attribute2: 2) # or MyModel.where.or(MyModel.where(some conditions), MyModel.where(some other conditions)) 

更正确的解决方案基于@bsd答案,但允许输入任意范围

  ActiveRecord::QueryMethods::WhereChain.class_eval do def or(*scopes) scopes_where_values = [] scopes_bind_values = [] scopes.each do |scope| case scope when ActiveRecord::Relation scopes_where_values << scope.where_values.reduce(:and) scopes_bind_values += scope.bind_values when Hash temp_scope = @scope.model.where(scope) scopes_where_values << temp_scope.where_values.reduce(:and) scopes_bind_values += temp_scope.bind_values end end scopes_where_values = scopes_where_values.inject(:or) @scope.where_values += [scopes_where_values] @scope.bind_values += scopes_bind_values @scope end end 

PS @bsd以前的代码无法正常工作:User.where.or(User.where(rating:3),User.where(startups:{progress:100,rating:nil})

旧代码的结果是错误的:

SELECT“users”。* FROM“users”WHERE((“startups”。“rating”= 3 OR“startups”。“progress”= 100)或“startups”。“rating”IS NULL)

更改的代码生成正确:

SELECT“users”。* FROM“users”WHERE(“startups”。“rating”= 3 OR“startups”。“progress”= 100 AND“startups”。“rating”IS NULL)

使用raw arel可能是更好的选择:

 t = MyModel.arel_table MyModel.where( t[:attribute1].eq(1).or( t[:attribute2].eq(2) ) ) 

我想出了一个非常干净的解决方案(至少在Rails 5 +中)…我的回答是在另一个问题中发布的无法使用Arel以编程方式组合AND和OR条件

答案如下:

 and_conds = Table.arel_table.grouping( Table.where(a: 'test', b: 2).arel.constraints.reduce(:and) ) Table.where(and_conds).or(Table.where(a: 'blah')) 

结果类似于……

 select * from table where ((a = 'test' and b = 2) or a = 'blah')