Support OR in Active Record

Posted Almost 8 years ago. Visible to the public.

Rails 5 has added OR method to Active Relation for generating queries with OR clause.

>> Post.where(id: 1).or(Post.where(title: 'Learn Rails'))
   SELECT "posts".* FROM "posts" WHERE ("posts"."id" = ? OR "posts"."title" = ?)  [["id", 1], ["title", "Learn Rails"]]

=> <ActiveRecord::Relation [#<Post id: 1, title: 'Rails'>]>

This returns ActiveRecord::Relation object, which is logical union of two relations.

Some Examples of OR usage

With group and having

>> posts = Post.group(:user_id)
>> posts.having('id > 3').or(posts.having('title like "Hi%"'))
SELECT "posts".* FROM "posts" GROUP BY "posts"."user_id" HAVING ((id > 2) OR (title like "Rails%"))

=> <ActiveRecord::Relation [#<Post id: 3, title: "Hi", user_id: 4>,
#<Post id: 6, title: "Another new blog", user_id: 6>]>

With scope

class Post < ApplicationRecord
  scope :contains_blog_keyword, -> { where("title LIKE '%blog%'") }
end

>> Post.contains_blog_keyword.or(Post.where('id > 3'))
SELECT "posts".* FROM "posts" WHERE ((title LIKE '%blog%') OR (id > 3))

=> <ActiveRecord::Relation [#<Post id: 4, title: "A new blog", user_id: 6>,
#<Post id: 5, title: "Rails blog", user_id: 4>,
#<Post id: 6, title: "Another new blog", user_id: 6>]>

With combination of scopes

class Post < ApplicationRecord

  scope :contains_blog_keyword, -> { where("title LIKE '%blog%'") }
  scope :id_greater_than, -> (id) {where("id > ?", id)}

  scope :containing_blog_keyword_with_id_greater_than, ->(id) { contains_blog_keyword.or(id_greater_than(id)) }
end

>> Post.containing_blog_keyword_with_id_greater_than(2)
SELECT "posts".* FROM "posts" WHERE ((title LIKE '%blog%') OR (id > 2)) ORDER BY "posts"."id" DESC

=> <ActiveRecord::Relation [#<Post id: 3, title: "Hi", user_id: 6>,
#<Post id: 4, title: "A new blog", user_id: 6>,
#<Post id: 5, title: "Another new blog", user_id: 6>,
<#Post id: 6, title: "Another new blog", user_id: 6>]>

Constraints for using OR method

The two relations must be structurally compatible, they must be scoping the same model, and they must differ only by WHERE or HAVING. In order to use OR operator, neither relation should have a limit, offset, or distinct.

>> Post.where(id: 1).limit(1).or(Post.where(:id => [2, 3]))

ArgumentError: Relation passed to #or must be structurally compatible. Incompatible values: [:limit]

When limit, offset or distinct is passed only with one relation, then it throws ArgumentError as shown above. As of now, we can use limit, offset or distinct when passed with both the relations and with same the parameters.

>> Post.where(id: 1).limit(2).or(Post.where(:id => [2, 3]).limit(2))

SELECT  "posts".* FROM "posts" WHERE ("posts"."id" = ? OR "posts"."id" IN (2, 3)) LIMIT ?  [["id", 1], ["LIMIT", 2]]

=> <ActiveRecord::Relation [#<Post id: 1, title: 'Blog', user_id: 3, published: true>,
#<Post id: 2, title: 'Rails 5 post', user_id: 4, published: true>]>
Alexander M
Last edit
About 7 years ago
Alexander M
Tags
Posted by Alexander M to Ruby and RoR knowledge base (2016-06-15 15:11)