The following code constructed a valid where
clause with an OR
operator in Rails 4.1
MyModel.where(
MyModel.where(attribute1: 1, attribute2: 2).where_values.reduce(:or)
)
Which is roughly equivalent to the SQL
select * from my_models where (attribute1 = 1 OR attribute2 = 2)
In Rails 4.2, the same code generates an SQL query with missing values for it's bind parameters
select * from my_models where attribute1 = OR attribute2 =
... and generates an error due to the missing values for the bound values.
What is the equivalent code in Rails 4.2 to generate a valid query with an OR operator?
Edit:
The solution requires an Arel::Nodes::Node
derived object to be used so that it can itself be combined with other conditions via AND and OR groupings.
rel = MyModel.where(attribute1: 1, attribute2: 2)
conditions = [rel.where_values.reduce(:or).to_sql, *rel.bind_values.map(&:last)]
MyModel.where(conditions)
The conditions
var must be a derivative of Arel::Nodes::Node
. The above solution works for simple queries, but for more complicated queries, conditions
must be an Arel Node to be passed to a final query method.
I'm using the below until rails 5 is out (in rails 5 AR supports .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
With the above you can do:
MyModel.where.or(attribute1: 1, attribute2: 2)
# or
MyModel.where.or(MyModel.where(some conditions), MyModel.where(some other conditions))
Using raw arel might be a better option:
t = MyModel.arel_table
MyModel.where(
t[:attribute1].eq(1).or(
t[:attribute2].eq(2)
)
)
More correctly solution based on @bsd answer, but allow arbitrary scopes on input
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
P.S. Previous code by @bsd can't correctly work in little difficult case: User.where.or(User.where(rating: 3), User.where(startups: { progress: 100, rating: nil })
Result of old code is wrong:
SELECT "users".* FROM "users" WHERE (("startups"."rating" = 3 OR "startups"."progress" = 100) OR "startups"."rating" IS NULL)
Changed code generate correct:
SELECT "users".* FROM "users" WHERE ("startups"."rating" = 3 OR "startups"."progress" = 100 AND "startups"."rating" IS NULL)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With