ruby - Is it possible to negate a scope in Rails?

ID : 131385

viewed : 6

Tags : ruby-on-railsrubynamed-scoperuby-on-railsruby

Top 5 Answer for ruby - Is it possible to negate a scope in Rails?

vote vote

95

In Rails 4.2, you can do:

scope :original, -> { ... original scope definition ... } scope :not_original, -> { where.not(id: original) } 

It'll use a subquery.

vote vote

90

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.

vote vote

79

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 
vote vote

69

You can do this programmatically, without a subquery:

scope :my_scope, ...  Entity.where.not(Entity.my_scope.where_values_hash) 

See also: https://makandracards.com/makandra/486959-how-to-negate-scope-conditions-in-rails

Note this only works with AR queries and does not work with SQL queries. E.g. where('updated_at < ?', 1.hour.ago) would not be negated.

vote vote

60

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)

Top 3 video Explaining ruby - Is it possible to negate a scope in Rails?

Related QUESTION?