2
votes

We are updating a rails 2 app. We have happily been using fake_arel which provides a very nice 'or' scope.

Now, with rails 3 I can't find a way to replicate this.

We have code like this:

scope.or(Event.horse, Event.dog, Event.trap).today

The model looks like this:

class Event < ActiveRecord::Base
 named_scope :horse, lambda { {:conditions => ["sport_category_id in (?)", SportCategory.find_horse_ids] }}
 named_scope :dog, lambda { {:conditions => ["sport_category_id in (?)", SportCategory.find_dog_ids] }}
 named_scope :trap, lambda { {:conditions => ["sport_category_id in (?)", SportCategory.find_trap_ids] }}
end

These scopes need to be separate and are used all over the place. This model actually has dozens of scopes on it that are used in combination, so rewriting it all is the last thing we want to do.

It seems strange that you can't 'or' scopes together.

Can someone propose a way to do this as nicely in Rails 3? Even using arel I don't see how to. We are using meta_where in a different project, but it doesn't offer any such thing either.

3

3 Answers

1
votes

Well, the way to do that is, in your model (adapt it to your needs!) :

where(:event => ['horse', 'dog', 'trap'])

An array will produce a IN statement, which is what you want there. Furthermore, you can use rails 3 scopes to achieve that :

scope :my_scope, where(:event => ['horse', 'dog', 'trap'])

Then you can use it this way :

mymodel.my_scope # and possibility to chain, like :
mymodel.my_scope.where(:public => true)
0
votes

I ripped out the 'or' function from fake_arel and got it working with rails 3.0x (not sure if it will work with 3.1 as we don't use that here)

I case anyone is interested I have put it in a gist:

0
votes

I was unable to get the code from the Github-gist by Phil working, but it inspired me to come up with the following, which I think is a simpler, solution. It uses a class-method that returns an ActiveRecord::Relation class nonetheless.

def self.or alt_scope
  either = scoped.where_clauses.join(' AND ') 
  alternative = alt_scope.where_clauses.join(' AND ')

  Locatie.unscoped.where("(#{either}) OR (#{alternative})").
    joins(scoped.joins_values).joins(alt_scope.joins_values).
    group(scoped.group_values).group(alt_scope.group_values).
    having(scoped.having_values).having(alt_scope.having_values).
    includes(scoped.includes_values).includes(alt_scope.includes_values).
    order(scoped.order_values).order(alt_scope.order_values).
    select(scoped.select_values).select(alt_scope.select_values)
end

Just add this to your class. You'll then get the ability to create a multiple OR query as follows:

Event.horse.or(Event.dog).or(Event.trap)

Which you can consequently 'store' in a scope:

scope :horse_dog_or_trap, horse.or(Event.dog).or(Event.trap)

and also extend the scope even further, such as:

Event.horse.or(Event.dog).or(Event.trap).today