Forum: Ruby on Rails How to make named_scope aware of association context?

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Sven (Guest)
on 2008-11-24 19:44
(Received via mailing list)
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
Frederick C. (Guest)
on 2008-11-24 19:50
(Received via mailing list)
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
Sven Aas (Guest)
on 2008-11-24 20:08
(Received via mailing list)
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]
Frederick C. (Guest)
on 2008-11-24 20:26
(Received via mailing list)
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
Sven (Guest)
on 2008-11-25 19:33
(Received via mailing list)
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
This topic is locked and can not be replied to.