views:

52

answers:

3

Hey!

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!

+2  A: 

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.

Chubas
Poor kittens, but no. I was afraid of having to load both an `@club` AND `@courses` variable...which would mean getting rid of all of the `@club.courses` references in my views.I have an admin namespace where I would like to load the unapproved courses (as if the condition wasn't in the model), how would I do that using your solution?
James
+1  A: 

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)}
KandadaBoggu
I wouldn't use :conditions **ever** in a `default_scope`: How does Rails differentiate between your `default_scope` conditions and the other group of conditions you specify? I would instead use a `named_scope` for this and call that instead. @Chubas' solution's good too.
Ryan Bigg
I disagree, filtering `deleted`, and `approved` records is a suitable usage for default_scope. In this case every `find` operation on the `Course` model requires filtering of unapproved courses. In cases where user needs to include the unapproved records, he can use `with_exclusive_scope`.
KandadaBoggu
The user says: `because my views are built that way and I would rather not have to change all of them.`, this solution enables the user to achieve the functionality un-obtrusively.
KandadaBoggu
I have updated my answer to include a sample from Ryan Bates where `:conditions` option is used in `default_scope`.
KandadaBoggu
This looks like the closest solution. Is there any way to declare a named scope to use when loading a model in the controller?
James
What should the named scope return?
KandadaBoggu
A: 

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!

James
@james, you can use dynamic scope instead of named_scope i.e. `@club.courses.scoped_by_approval(true)` OR `@club.courses.scoped_by_approval(false)`.
KandadaBoggu
Hey now that's just nifty!
James