OR operator in WHERE clause with Arel in Rails 4.2
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))
How to accomplish OR operator with Rails 4 (and lower) query , You know how to query models when several conditions need to be true at It is using the Arel library, which ActiveRecord uses itself to create Arel gem: "SELECT *" missing from where clause in rails; Arel Query CAST operation; Grouping ands and ors in AREL; OR operator in WHERE clause with Arel in Rails 4.2; How to join on subqueries using ARel? How to rewrite this RGeo query using AR/Arel? Arel - Inserting paretheses in the right place; uninitialized constant Arel::SqlLiteral in
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)
OR queries with arrays as arguments in Rails 4 (Example), A protip by ravicious about rails, activerecord, sql, and arel. the where_values result and inject(:or) will add or statement between the default Here is a whirlwind tour through the most common relational operators. These will probably cover 80% of all interaction with the database. First is the 'restriction' operator, where: users.where(users[:name].eq('amy')) # => SELECT * FROM users WHERE users.name = 'amy' What would, in SQL, be part of the SELECT clause is called in Arel a projection:
Using raw arel might be a better option:
t = MyModel.arel_table MyModel.where( t[:attribute1].eq(1).or( t[:attribute2].eq(2) ) )
Using Arel to Compose SQL Queries, When faced with a query that requires an OR statement, or when needing to do numeric comparisons such as <= , many Rails developers will Rails 4.2 changed some internals of ActiveRecord query-building, so your code for or-conditions doesn't work. More specifically, or_query method in manager.rb fails to build a valid query. The problem is that setting where_values does not affect bind_values , and the
Creating Advanced Active Record DB Queries with Arel, For now, let's focus on passing in custom-built Arel queries. :lteq_any, :lteq_all, :eql_any] # Rails 4.2 (Arel 6.0) adds the following [:between, since it is a false statement will not return any additional data from the query. Arel is smart-- taking care of SQL subtleties like precedence and correct ordering of SQL clauses for you. For example, when generating a SQL query containing an ORDER BY and a WHERE clause, Arel ensures the WHERE clause comes first. Simply put, Arel allows Rails developers to focus on the domain in their language.
where (ActiveRecord::QueryMethods), #where will also accept a hash condition, in which the keys are fields and the values are values to be searched for. Fields can be symbols or strings. Values can Dismiss Join GitHub today. GitHub is home to over 40 million developers working together to host and review code, manage projects, and build software together.
Composable SQL Queries in Rails Using Arel, Arel wraps each component of the SQL query language with Ruby project is how you tell Arel what to return in the SELECT statement. Mastering ActiveRecord and ARel Dan Shultz. @danshultz • das0118@gmail.com. Hello, my name is Dan Shultz and I have been using rails for a little over three years now although most of my time now is spent working in python and django.
Comments
- where_values was removed from Rails 4.2 ... at least when I search the APIs I can find it in 3.x but not in 4.2. That may well be the source of your issue.
- where_values is part of the private API in Rails 4.2... it still exists. The bind values are in bind_values... also private. The recent changes to Arel via AdequateRecord are the cause for the issue.
- Ok, good deal. Was not 100% certain since it was 'removed' from the public API (hence the comment v. answer).
- What does
Model.where(conditions).where_values.reduce(:or).to_sql
return? "(attribute1 = ? OR attribute2 = ?)"
- Where do i define the where chain method?
- where do I put this?
- For anyone reading this and still at rails 4+. This goes as an initializer, it's a monkey patch