How to make named_scope aware of association context?


#1

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


#2

On Nov 24, 5:43 pm, Sven removed_email_address@domain.invalid wrote:

# Has boolean column 'featured'

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

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

Fred


#3

On Mon, Nov 24, 2008 at 12:49 PM, Frederick C. <
removed_email_address@domain.invalid> wrote:

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]


#4

On 24 Nov 2008, at 18:07, Sven Aas wrote:

                   :conditions =>  

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


#5

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