I have the following scope for my class called Collection
:
scope :with_missing_coins, joins(:coins).where("coins.is_missing = ?", true)
I can run Collection.with_missing_coins.count
and get a result back -- it works great!
Currently, if I want to get collections without missing coins, I add another scope:
scope :without_missing_coins, joins(:coins).where("coins.is_missing = ?", false)
I find myself writing a lot of these "opposite" scopes. Is it possible to get the opposite of a scope without sacrificing readability or resorting to a lambda/method (that takes true
or false
as a parameter)?
Something like this:
Collection.!with_missing_coins
I wouldn't use a single scope for this, but two:
scope :with_missing_coins, joins(:coins).where("coins.is_missing = ?", true)
scope :without_missing_coins, joins(:coins).where("coins.is_missing = ?", false)
That way, when these scopes are used then it's explicit what's happening. With what numbers1311407 suggests, it is not immediately clear what the false
argument to with_missing_coins
is doing.
We should try to write code as clear as possible and if that means being less of a zealot about DRY once in while then so be it.
In Rails 4.2, you can do:
scope :original, -> { ... original scope definition ... }
scope :not_original, -> { where.not(id: original) }
It'll use a subquery.
There's no "reversal" of a scope per se, although I don't think resorting to a lambda method is a problem.
scope :missing_coins, lambda {|status|
joins(:coins).where("coins.is_missing = ?", status)
}
# you could still implement your other scopes, but using the first
scope :with_missing_coins, lambda { missing_coins(true) }
scope :without_missing_coins, lambda { missing_coins(false) }
then:
Collection.with_missing_coins
Collection.without_missing_coins
this might just work, did not test it much. uses rails 5 I guess rails 3 has where_values method instead of where_clause.
scope :not, ->(scope_name) do
query = self
send(scope_name).joins_values.each do |join|
query = query.joins(join)
end
query.where((send(scope_name).
where_clause.send(:predicates).reduce(:and).not))
end
usage
Model.not(:scope_name)
Update . Now Rails 6 adds convenient and nifty negative enum methods.
# All inactive devices
# Before
Device.where.not(status: :active)
#After
Device.not_active
Blog post
here