Reputation: 2927
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)
Upvotes: 0
Views: 979
Reputation: 5155
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.
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.
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.
Upvotes: 1