0

Consider these scopes on an ActiveRecord model:

scope :onsale, where(:sales_state => "onsale")
scope :outdated, where(:sales_state => "outdated")
scope :onsale_or_outdated, where(" sales_state = 'onsale' OR sales_state = 'outdated' ")

Why is it that Rails knows to override :onsale with :outdated, but not :onsale with :onsale_or_outdated?

My use case:

I have a relation object with many scopes (let's say it's a saved search), and one of those is :onsale. I want to make another relation starting from that, but this time I want the sales_state to be either onsale or outdated.

If I use relation.onsale_or_outdated, the sales_state is not overridden, it just adds a new condition.

[...] WHERE "cars"."sales_state" = 'onsale' [...] AND (("cars"."sales_state" = 'onsale' OR "cars"."sales_state" = 'outdated'))

How can I use my 'or'-ed condition in this context?

Cristian
  • 5,877
  • 6
  • 46
  • 55

1 Answers1

1

If I use relation.onsale_or_outdated, the sales_state is not overridden, it just adds a new condition.

That's how scopes work. They append, not replace. If you have two mutually exclusive scopes, you need to use one or the other, not both. The special case is when you have a scope involves a single field in :symbol = <value> syntax. Rails is smart enough to allow one scope to cancel the other out. In your case, the onsale_or_updated scope is simply a string, Rails has no means to tell which fields are involved and so the scopes are chained.

You should rewrite your scope to use fields/values instead of a blob of SQL, so Rails knows which fields are involved.

scope :onsale_or_outdated, where(:sales_state => %w(onsale outdated))

Alternatively, if you want to use only your onsale_or_outdated scope, you can unscope the relationship and reapply a scope:

relation.unscoped.onsale_or_outdated

Note that this will remove any previously applied scopes.

user229044
  • 232,980
  • 40
  • 330
  • 338
  • I'm pretty sure if I do `relation.onsale.outdated` it replaces, not appends, so I end up with the outdated records. `unscoped` is not an option, since I have many custom-added scopes. I'd better reset the default of my Search object so it doesn't append the `onsale` scope initially. – Cristian Oct 18 '12 at 16:18
  • See update. Your third scope contains a string condition instead of a field/value in `:key => ` form; Rails can't automagically guess which fields are involved in your scope. – user229044 Oct 18 '12 at 16:27
  • Awesome, thanks! It works! The only thing that worries me is performance of using `IN (..)` instead of `param = 'a' OR param = 'b'`. Would you recommend this approach for a large database? – Cristian Oct 18 '12 at 16:34