0

In model:

 scope :verified, -> { where(verified: true)}
 scope :active, -> { where(active: true) }

Now, Model.active.verified results as active and verified. How can I chain scopes as OR? Please note that I don't want to combine both scopes as one like:
where("active = ? OR verified = ?", true, true)

Imran Ahmad
  • 2,798
  • 3
  • 28
  • 49
  • 1
    If you don't want to do it like `where("active = ? OR verified = ?", true, true)`, then I think you have to wait for *Rails 5* in which `ActiveRecord` uses `.or` operator. So you can combine the scopes like `Model.active.or.verified` – Pavan Jul 16 '16 at 10:09
  • I am curious to know, is there any particular reason to not to use or query? – power Jul 16 '16 at 10:16
  • There are more than 10 scopes and they are used conditionally. So if i use it in that way, I will have to write many permutation-combination cases. – Imran Ahmad Jul 16 '16 at 10:20
  • As you said I will have to wait for rails 5, how will it be written in rails 5? – Imran Ahmad Jul 16 '16 at 10:21
  • Ok. In rails 5 Its like, Model.active.or(Model.verified) – Imran Ahmad Jul 16 '16 at 10:23

1 Answers1

1

You don't have to upgrade to Rails 5 in order to use or. There are ways of doing that in Rails 4. Although these do not lend themselves very well for being used in chained scopes, you can achieve similar level of convenience in building up your criterias.

  1. Arel tables. Arel is ActiveRecord's underlying implementation of relational algebra. It does support or among other powerful things.

    arel_table = Model.arel_table
    
    Model.where(
      arel_table[:active].eq(true).
      or(
        arel_table[:verified].eq(true)
      )
    )
    
    # => SELECT "models".* FROM "models" WHERE ("models"."active" = 't' OR "models"."verified" = 't')
    

As you can see, chaining criteria is complicated by the fact the or has to be applied inside where. Criteria could be built externally before passing to where but again, their hierarchical structure makes it less straightforward.

  1. Monkeypatching ActiveRecord, adding or implementation. Put this into config/initializers/active_record.rb:

    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 this you will be able to do or queries like this:

Model.where.or(verified: true, active: true)

# => SELECT "models".* FROM "models" WHERE ("models"."verified" = $1 OR "models"."active" = $2)  [["verified", "t"], ["active", "t"]]

You can add more criteria like so:

Model.where.or(verified: true, active: true, other: false)

The query can be put in a class method like this:

def self.filtered(criteria)
  where.or(criteria)    # This is chainable!
end

or in scope, which is basically the same:

scope :filtered, lambda { |criteria| where.or(criteria) }

Since criteria is just a hash, you can build it in a convenient way with as many elements as you like:

criteria = {}
criteria[:verified] = true if include_verified?
criteria[:active] = true if include_active?
...

Model.filtered(criteria).where(... more criteria ...)

And there you have it. Read for more details these SO questions:

ActiveRecord Arel OR condition

OR operator in WHERE clause with Arel in Rails 4.2

Finally, in case you are not opposed to third-party solutions, take a look at the Squeel gem.

Community
  • 1
  • 1
Nic Nilov
  • 5,056
  • 2
  • 22
  • 37