Jay
Jay

Reputation: 2902

Rails: Conditionally loading associations

I have models club and course where a course belongs to a club and and a club has many courses.

When I load a club, I also want to load it's associated courses, but I only want to load those that meet a conditional test (approved? == true).

It is straightforward how to do this if I were working directly with the courses:

@courses = Course.find( :all, :conditions => {:approved => true } )

But I would like to do this as part of the statement:

@club = Club.find(params[:id])

because my views are built that way and I would rather not have to change all of them.

Thanks!

Upvotes: 2

Views: 966

Answers (3)

Jay
Jay

Reputation: 2902

The ultimate solution came from a combination of answers, but neither quite got all the way.

Current solution: I utilize a couple of named scopes in the course model to achieve the functionality I wanted while keeping my views as universal as possible (being able to dry up code is a must).

So the course model looks a bit like this:

class Course < ActiveRecord::Base
  belongs_to :club

  named_scope :have_approval, :conditions => { :approved => true }
  named_scope :need_approval, :conditions => { :approved => false }
end

And to gather all approved courses it is as easy as:

@approved_courses = Course.have_approval

Or when working with a club, getting the approved courses in a club is as easy as:

@club = Club.find(:first)
@approved_courses_in_club = @club.courses.have_approval

Named scope is the man!

Upvotes: 0

Harish Shetty
Harish Shetty

Reputation: 64363

You can use default_scope for this:

class Club < ActiveRecord::Base
  has_many :courses, conditions => {:approved => true}
  default_scope :include => :courses
end

class Course < ActiveRecord::Base
  default_scope :conditions => {:approved => true}
end

Now you can do this:

@club = Club.find(1) # this will eager load approved courses.

Reference:

Article about default_scope.

Note 1

I changed the courses assocation in Club class to select approved courses. In theory, this is not required as the Course class has a default scope. But, it looks like default scope is not applied for eager loaded queries.

Note 2

I personally would not eager load the Course objects through default_scope. Doing it through a default_scope gives you an unobtrusive solution as desired by you.

I would add the include clause to the find call to eager load the Course objects only when it's required.

Note 3

@Ryan Bigg:

Ryan Bates talks about default scopes half way through this his screen cast. He gives an example of using the default scopes to exclude deleted records, i.e.

default_scope :conditions => "delete_at IS NULL"

I consider this use case to be similar. As I perceive the problem, primary operations on the Course model is on approved records and default_scope with the conditions option ensures that. To override the default_scope, user can use the with_exclusive_scope method.

Club.with_exclusive_scope{find(1)}

Upvotes: 1

Chubas
Chubas

Reputation: 18043

If you only consider a club's course if it has been approved, you can do

class Club < ActiveRecord::Base
  has_many :courses, :conditions => {:approved => true}
end

and in your controller

@club = Club.find(params[:id], :include => :courses)

Now, I don't know if I misunderstood you, but you said "your views are built that way". Do you mean your controllers? Because if you have such logic in your views... DHH kills a kitten every time someone does that.

Upvotes: 2

Related Questions