4

Is there any way in Rails 5 that I can use the opposite (negation) of a scope? For example, given I have the following scope

scope :missing_coordinates, -> { where(latitude: [nil, '']).or(where(longitude: [nil, ''])) }

is there any way I can use the opposite of that scope like:

Places.not(missing_coordinates)

which would return the ones that have both the latitude and longitude?

Leticia Esperon
  • 2,499
  • 1
  • 18
  • 40
  • Possible duplicate of [Is it possible to negate a scope in Rails 3?](https://stackoverflow.com/questions/7054188/is-it-possible-to-negate-a-scope-in-rails-3) – bkunzi01 Jun 13 '18 at 19:34
  • I added Rails 5 to my question. Also the answer given is really not negating the scope but solving his problem in a different way (creating two different scopes), which is not what I'm looking for. – Leticia Esperon Jun 13 '18 at 19:47
  • Yes unfortunately even Rails5 doesn't have a built in modifier for getting the opposite of a scope. – bkunzi01 Jun 13 '18 at 20:30

4 Answers4

3

How about creating a method that takes the entire collection, and subtracts the missing_coordinates.

self.not_missing_coordinates
    Places.all - Places.missing_coordinates
end

This lets you call Places.not_missing_coordinates. I looked around the docs and couldn't find anything quite like negation of a scope in the way you've described.

Joseph Cho
  • 4,033
  • 4
  • 26
  • 33
  • You are creating the opposite scope but I don't want to repeat the `latitude: [nil, '']` part. – Leticia Esperon Jun 13 '18 at 19:51
  • So basically you want the results of an opposite scope, without creating another one? (I could be misunderstanding here) I think it is better to simply create two different scopes for concision. – Joseph Cho Jun 13 '18 at 19:53
  • I don't know about Rails 5, but couldn't you just negate the other scope? – Dave Newton Jun 13 '18 at 19:59
  • Yes, that's what I want. Something like `not(name_of_other_scope)`. But it's not working for me. – Leticia Esperon Jun 13 '18 at 19:59
  • This link by pivotal labs ends with a solution similar to what you're looking for: https://content.pivotal.io/blog/logically-negating-an-activerecord-scope. If done correctly the end solution should look like `Places.not(:missing_coordinates)`. – Joseph Cho Jun 13 '18 at 20:12
  • 1
    you could also turn this solution into a scope by doing `scope :not_missing_coordinates, -> { all - missing_coordinates }` if using a scope not a method is important – ian root Aug 27 '20 at 22:56
3

There is no built in solution, but you can pass params to scope:

scope :missing_coordinates, ->(missing = true){ missing ?
       where(latitude: [nil, '']).or(where(longitude: [nil, ''])) :
       where.not(latitude: '').where.not(longitude: '') 
}

Call:

Places.missing_coordinates
Places.missing_coordinates(false) #opposite

Or you can define two scopes as following:

scope :missing_coordinates, -> { where(latitude: [nil, '']).or(where(longitude: [nil, '']))}
scope :not_missing_coordinates, -> { where.not(id: missing_coordinates) }
Moamen Naanou
  • 1,683
  • 1
  • 21
  • 45
  • Is your `:not_missing_coordinates` scope correct? You might need to add `.ids` like this: `scope :not_missing_coordinates, -> { where.not(id: missing_coordinates.ids) }` – Purplejacket Nov 22 '19 at 00:26
1

Try this!

scope :not, ->(scope_name) { 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))}

usage

Model.not(:scope_name)
Farhana Naaz Ansari
  • 7,524
  • 26
  • 65
  • 105
0

@harsimar-sandu's answer gave me this idea:

scope :not, ->(scope_name) { where.not(id: send(scope_name).select(:id))}

AlphaCarinae
  • 170
  • 1
  • 6