Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #11556 (new enhancement)

Opened 1 year ago

Last modified 1 year ago

has_many :through named_scope

Reported by: Zargony Assigned to: core
Priority: normal Milestone: 2.x
Component: ActiveRecord Version: edge
Severity: normal Keywords: has_many through named_scope
Cc:

Description

It would be nice to be able to use a has_many association through a named scope. E.g. if a user has many posts, and a post has many comments, one can use a named_scope in posts to define a scope for recent posts and a has_many association for users to get all comments of a user through his/her posts. However, you cannot get all comments on recent posts, because that'd require to do something like :through => 'posts.recent', which obviously doesn't work.

class Comment < ActiveRecord::Base
  belongs_to :post
end

class Post < ActiveRecord::Base
  belongs_to :user
  has_many :comments
  named_scope :recent, lambda { { :conditions => ['created_at > ?', 1.week.ago] } }
end

class User < ActiveRecord::Base
  has_many :posts

  # all comments of all posts of a user
  has_many :comments, :through => :posts

  # Doesn't work: all comments of all recent posts of a user
  #has_many :comments_on_recent_posts, :through => 'posts.recent', :source => :comments
end

Sure, one could use :conditions on the has_many association to scope the find, but that would mean that the advantage of the named_scope (defining the scope conditions in the model that it applies to) would be gone.

So, it would be nice if has_many :through could also work with a named scope, maybe by introducing a new option named :scope or :named_scope or :through_scope:

  has_many :comments_on_recent_posts, :through => :posts, :through_scope => :recent, :source => :comments

Change History

04/16/08 17:17:04 changed by Zargony

Reported as Ticket #11 to the new bug tracker: http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/11

07/07/08 04:04:02 changed by johnnyb

This isn't _quite_ what you are asking for, but this snippet works for my needs, and it may help you:

module ActiveRecord
  class Base
    def self.scoped_has_many_through(assoc, opts)
      
      has_many_scope = opts.delete(:scope)
        
      
      conditions_scope = Hash[*has_many_scope.to_a.map{|ent|
        ["#{opts[:through].to_s.pluralize}.#{ent[0]}", ent[1]]
      }.flatten]
      
      opts[:conditions] ||= {}      
      if opts[:conditions].is_a?(Hash)
        opts[:conditions] = opts[:conditions].merge(conditions_scope)
      else
        raise "Conditions must be a hash for conditions_scope!"
      end

      has_many assoc, opts do        
        #Using define_method instead of def in order to make it lexically scoped
        define_method(:construct_owner_attributes) do |reflection|
          atts = super(reflection)
          return atts.merge(has_many_scope)
        end
      end
      
    end
  end
end

This allows you to do:

  scoped_has_many_through :reviewer_users, :through => :user_project_roles, :source => :user, :scope => { :role => "REVIEWER" }