djoll
djoll

Reputation: 1229

Using OR with queries in a scope

In Rails3 I have:

Class Teacher
  #  active                 :boolean
  has_and_belongs_to_many :subjects

Class Subject
  #  active                 :boolean
  has_and_belongs_to_many :teachers

I am trying to construct a Teacher scope that returns all Teachers that are active or are associated with a Subject that is active.

These scopes work individually, but how to combine them as a single scope with an OR?

scope :active_teachers, where(active: true)
scope :more_active_teachers, joins(:subjects).where(:subjects => {active: true})

I've tried this without success:

scope :active_teachers, where(active: true).or(joins(:subjects)
      .where(:subjects => {active: true}))

UPDATE:

I thought I had a solution, but this no longer lazy loads, hits the database twice and — most importantly — returns an array rather than an AR object!

scope :active_teachers, where(active: true) |
                        joins(:subjects).where(:subjects => {active: true})

Upvotes: 2

Views: 2703

Answers (4)

keithepley
keithepley

Reputation: 4802

There's a rails pull request for this (https://github.com/rails/rails/pull/9052), but in the meantime, some one has created a monkey patch that you can include in your initializers that will allow you to do this and still give you an ActiveRecord::Relation:

https://gist.github.com/j-mcnally/250eaaceef234dd8971b

With that, you'll be able to OR your scopes like this

Teacher.active_teachers.or.more_active_teachers

or write a new scope

scope :combined_scopes, active_teachers.or.more_active_teachers

Upvotes: 1

engineerDave
engineerDave

Reputation: 3935

You can solve this by dropping to AREL. See this SO Question for how to do that.

AREL OR Condition

Or from the AREL Source Code README.md. I think (but haven't verified) that this would translate to the following for your particular example.

teachers.where(teachers[:active].eq(true).or(subjects[:active].eq(true)))

Good Luck!

Upvotes: 3

Toby Hede
Toby Hede

Reputation: 37133

I think the short answer is you can't.

Oring in the code is going to break lazy loading ... really no way around it as you need the database to make the evaluation. ActiveRecord can't make the evaluations on the scopes without executing each subclause individually.

Something like this the following should work:

joins(:subjects).where("subjects.active = true OR teachers.active = true")

Not quite as elegant, but can be wrapped into a method for reuse.

Upvotes: 3

pungoyal
pungoyal

Reputation: 1798

You have Squeel to your rescue. More details here.

Using that, you could define something like:

class Teacher
  ...
  scope :active_teachers, joins{subjects}.where {(active == true) | (subjects.active == true)}
  ...
end

Upvotes: 5

Related Questions