How to make named_scope aware of association context?

Given these models

  class Category < ActiveRecord     has_many :category_assignments     has_many :posts, :through => :category_assignments   end

  class CategoryAssignment < ActiveRecord::Base     belongs_to :post     belongs_to :category     # Has boolean column 'featured'   end

  class Post < ActiveRecord::Base     has_many :category_assignments     has_many :categories, :through => :category_assignments   end

I want to add a named_scope to Post that will return all featured posts. But the semantics of this named_scope vary depending upon whether I am calling the named scope on Post or on category.posts:   Post.featured is simple enough. The definition   named_scope :featured,               :joins => :category_assignments,               :conditions => {:category_assignments.featured => true } will return all posts for which any associated category_assignment record has featured = true.

But when I take a category instance and call category.posts.featured I want to get all of the posts which are featured *in that category*, which requires restricting by category_id. To do this properly the named_scope code needs to be able to determine whether it's being called in a context in which category_assignments has already been joined (and/or whether a category_id is specified in the conditions). I have not figured out how to do this.

If I can detect whether or not the named_scope is being called within the context of an association then I can do something like the following, which changes named_scope semantics based on whether or not a category _argument_ is explicitly provided:

  named_scope :featured, lambda { |*args|     if (category = args.first)       { :joins => :category_assignments,         :conditions => {:category_id => category.id,                         :featured => true} }     else       { :joins => :category_assignments,         :conditions => {:featured => true} }     end   }

Is there some way to inspect a joins or conditions hash to determine association state from within a named_scope? And if so, is it reliable regardless of the order of named_scope stacking (i.e. will it work for either category.posts.other.named.scopes.featured or category.posts.featured.other.named.scopes)?

Thanks,

Sven

It should do that by itself. You shouldn't need to do anything. Have you tried it?

Fred

Yes, with code like this:

named_scope :featured, :joins => :category_assignments, :conditions => [‘category_assignments.featured = ?’, true ]

When I call

Post.featured

it works fine. But when I call, category.posts.featured I get

ActiveRecord::StatementInvalid: SQLite3::SQLException: ambiguous column name: category_assignments.post_id

because Rails generates this query:

SELECT “posts”.* FROM “posts”

INNER JOIN “category_assignments” ON category_assignments.post_id = post.id

INNER JOIN category_assignments ON posts.id = category_assignments.post_id

WHERE ((“category_assignments”.category_id = 1))

AND ((category_assignments.featured = ‘t’))

As you can see, category_assignments is being joined twice. The named_scope needs to omit the category_assignments join when it gets called on a Category instance.

I’m running this on Rails 2.1.1, by the way.

-Sven

PS: there was an error in my initial post. I specified :conditions => {:category_assignments.featured => true } where I ought to have written

:conditions => [‘category_assignments.featured = ?’, true]

It should do that by itself. You shouldn't need to do anything. Have you tried it?

Fred

Yes, with code like this:

  named_scope :featured, :joins => :category_assignments,                        :conditions =>
['category_assignments.featured = ?', true ]

When I call

  Post.featured

it works fine. But when I call, category.posts.featured I get

  ActiveRecord::StatementInvalid: SQLite3::SQLException: ambiguous
column name: category_assignments.post_id

because Rails generates this query:

  SELECT "posts".* FROM "posts"   INNER JOIN "category_assignments" ON category_assignments.post_id
= post.id   INNER JOIN category_assignments ON posts.id =
category_assignments.post_id   WHERE (("category_assignments".category_id = 1))   AND ((category_assignments.featured = 't'))

As you can see, category_assignments is being joined twice. The
named_scope needs to omit the category_assignments join when it gets
called on a Category instance.

Oops. I skimmed over the definition of your scope and didn't notice
that there was a join. I'm going out on a limb here but from inside a
procedural named scope then if there is a @proxy_owner instance
variable or if it responds to proxy_owner/proxy_target then it;s an
association. You could also check the current scope and see what
joins are in there.

Fred

That does seem promising, but I haven't gotten it to work yet. Both proxy_owner and proxy_target remain undefined (on self) and @proxy_owner remains null regardless of which way I invoke the named_scope lambda. You also suggested checking the current scope for joins but that's precisely the thing I don't know how to do, which prompted my initial post. I'm still working on it, though.

-Sven