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:
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)?
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
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.
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.