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

Changeset 4022

Show
Ignore:
Timestamp:
03/24/06 14:46:17 (2 years ago)
Author:
rick
Message:

Change has_many :through to use the :source option to specify the source association. :class_name is now ignored. [Rick Olson]

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/activerecord/CHANGELOG

    r4013 r4022  
    11*SVN* 
     2 
     3* Change has_many :through to use the :source option to specify the source association.  :class_name is now ignored. [Rick Olson] 
     4 
     5    class Connection < ActiveRecord::Base 
     6      belongs_to :user 
     7      belongs_to :channel 
     8    end 
     9 
     10    class Channel < ActiveRecord::Base 
     11      has_many :connections 
     12      has_many :contacts, :through => :connections, :class_name => 'User' # OLD 
     13      has_many :contacts, :through => :connections, :source => :user      # NEW 
     14    end 
    215 
    316* Fixed DB2 adapter so nullable columns will be determines correctly now and quotes from column default values will be removed #4350 [contact@maik-schmidt.de] 
  • trunk/activerecord/lib/active_record/associations.rb

    r4015 r4022  
    1616     
    1717    def message 
    18       "Could not find the association '#{@reflection.options[:through]}' in model #{@reflection.klass}" 
     18      "Could not find the association #{@reflection.options[:through].inspect} in model #{@reflection.klass}" 
    1919    end 
    2020  end 
     
    3333 
    3434  class HasManyThroughSourceAssociationNotFoundError < ActiveRecordError 
    35     def initialize(through_reflection, source_reflection_names) 
    36       @through_reflection      = through_reflection 
    37       @source_reflection_names = source_reflection_names 
     35    def initialize(reflection) 
     36      @reflection              = reflection 
     37      @through_reflection      = reflection.through_reflection 
     38      @source_reflection_names = reflection.source_reflection_names 
     39      @source_associations     = reflection.through_reflection.klass.reflect_on_all_associations.collect { |a| a.name.inspect } 
    3840    end 
    3941     
    4042    def message 
    41       "Could not find the source associations #{@source_reflection_names.to_sentence} in model #{@through_reflection.klass}
     43      "Could not find the source association(s) #{@source_reflection_names.collect(&:inspect).to_sentence :connector => 'or'} in model #{@through_reflection.klass}.  Try 'has_many #{@reflection.name.inspect}, :through => #{@through_reflection.name.inspect}, :source => <name>'.  Is it one of #{@source_associations.to_sentence :connector => 'or'}?
    4244    end 
    4345  end 
     
    4951     
    5052    def message 
    51       "Can not eagerly load the polymorphic association '#{@reflection.name}'
     53      "Can not eagerly load the polymorphic association #{@reflection.name.inspect}
    5254    end 
    5355  end 
     
    202204    #   end 
    203205    # 
     206    # == Association Join Models 
     207    #  
     208    # Has Many associations can be configured with the :through option to use an explicit join model to retrieve the data.  This 
     209    # operates similarly to a <tt>has_and_belongs_to_many</tt> association.  The advantage is that you're able to add validations, 
     210    # callbacks, and extra attributes on the join model.  Consider the following schema: 
     211    #  
     212    #   class Author < ActiveRecord::Base 
     213    #     has_many :authorships 
     214    #     has_many :books, :through => :authorships 
     215    #   end 
     216    #  
     217    #   class Authorship < ActiveRecord::Base 
     218    #     belongs_to :author 
     219    #     belongs_to :book 
     220    #   end 
     221    #  
     222    #   @author = Author.find :first 
     223    #   @author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to. 
     224    #   @author.books                              # selects all books by using the Authorship join model 
     225    #  
     226    # You can also go through a has_many association on the join model: 
     227    #  
     228    #   class Firm < ActiveRecord::Base 
     229    #     has_many   :clients 
     230    #     has_many   :invoices, :through => :clients 
     231    #   end 
     232    #    
     233    #   class Client < ActiveRecord::Base 
     234    #     belongs_to :firm 
     235    #     has_many   :invoices 
     236    #   end 
     237    #    
     238    #   class Invoice < ActiveRecord::Base 
     239    #     belongs_to :client 
     240    #   end 
     241    # 
     242    #   @firm = Firm.find :first 
     243    #   @firm.clients.collect { |c| c.invoices }.flatten # select all invoices for all clients of the firm 
     244    #   @firm.invoices                                   # selects all invoices by going through the Client join model. 
     245    # 
    204246    # == Caching 
    205247    # 
     
    264306    # It's currently not possible to use eager loading on multiple associations from the same table. Eager loading will not pull 
    265307    # additional attributes on join tables, so "rich associations" with has_and_belongs_to_many is not a good fit for eager loading. 
    266     # 
     308    #  
    267309    # == Table Aliasing 
    268310    # 
     
    424466      # * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you for example want to do a join, but not 
    425467      #   include the joined columns. 
     468      # * <tt>:through</tt>: Specifies a Join Model to perform the query through.  Options for <tt>:class_name</tt> and <tt>:foreign_key</tt>  
     469      #   are ignored, as the association uses the source reflection.  You can only use a <tt>:through</tt> query through a <tt>belongs_to</tt> 
     470      #   or <tt>has_many</tt> association. 
     471      # * <tt>:source</tt>: Specifies the source association name used by <tt>has_many :through</tt> queries.  Only use it if the name cannot be  
     472      #   inferred from the association.  <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either +:subscribers+ or 
     473      #   +:subscriber+ on +Subscription+, unless a +:source+ is given. 
    426474      # 
    427475      # Option examples: 
     
    431479      #   has_many :tracks, :order => "position", :dependent => :destroy 
    432480      #   has_many :comments, :dependent => :nullify 
     481      #   has_many :subscribers, :through => :subscriptions, :source => :user 
    433482      #   has_many :subscribers, :class_name => "Person", :finder_sql => 
    434483      #       'SELECT DISTINCT people.* ' + 
     
    436485      #       'WHERE ps.post_id = #{id} AND ps.person_id = p.id ' + 
    437486      #       'ORDER BY p.first_name' 
     487      #  
     488      # Specifying the :through option 
     489      #  
    438490      def has_many(association_id, options = {}, &extension) 
    439491        reflection = create_has_many_reflection(association_id, options, &extension) 
     
    9541006            :exclusively_dependent, :dependent, 
    9551007            :select, :conditions, :include, :order, :group, :limit, :offset, 
    956             :as, :through,  
     1008            :as, :through, :source, 
    9571009            :finder_sql, :counter_sql,  
    9581010            :before_add, :after_add, :before_remove, :after_remove,  
     
    13211373 
    13221374              if reflection.macro == :has_and_belongs_to_many || (reflection.macro == :has_many && reflection.options[:through]) 
    1323                 @aliased_join_table_name = reflection.macro == :has_and_belongs_to_many ? reflection.options[:join_table] : parent.active_record.reflect_on_association(reflection.options[:through]).klass.table_name 
     1375                @aliased_join_table_name = reflection.macro == :has_and_belongs_to_many ? reflection.options[:join_table] : reflection.through_reflection.klass.table_name 
    13241376                unless join_dependency.table_aliases[aliased_join_table_name].zero? 
    13251377                  @aliased_join_table_name = active_record.connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}_join" 
  • trunk/activerecord/lib/active_record/associations/has_many_through_association.rb

    r4007 r4022  
    7373                "#{@reflection.through_reflection.table_name}.#{@reflection.through_reflection.primary_key_name} = #{@owner.quoted_id}" 
    7474              else 
    75                 raise ActiveRecordError, "Invalid source reflection macro :#{@reflection.source_reflection.macro} for has_many #{@reflection.name}, :through => #{@reflection.through_reflection.name}
     75                raise ActiveRecordError, "Invalid source reflection macro :#{@reflection.source_reflection.macro} for has_many #{@reflection.name}, :through => #{@reflection.through_reflection.name}.  Use :source to specify the source reflection.
    7676            end 
    7777          end 
  • trunk/activerecord/lib/active_record/reflection.rb

    r3973 r4022  
    145145      end 
    146146 
    147       # Gets an array of possible :through reflection names 
     147      # Gets an array of possible :through source reflection names 
    148148      # 
    149149      #   [singularized, pluralized] 
    150150      def source_reflection_names 
    151         @source_reflection_names ||= (options[:class_name] ?  
    152           [options[:class_name].underscore, options[:class_name].underscore.pluralize] :  
    153           [name.to_s.singularize, name] 
    154         ).collect { |n| n.to_sym } 
     151        @source_reflection_names ||= (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym } 
    155152      end 
    156153 
     
    174171           
    175172          if source_reflection.nil? 
    176             raise HasManyThroughSourceAssociationNotFoundError.new(through_reflection, source_reflection_names
     173            raise HasManyThroughSourceAssociationNotFoundError.new(self
    177174          end 
    178175           
  • trunk/activerecord/test/associations_cascaded_eager_loading_test.rb

    r3921 r4022  
    8888 
    8989  def test_eager_association_loading_with_multiple_stis_and_order 
    90     author = Author.find(:first, :include => { :posts => [ :special_comments , :very_special_comment ] }, :order => 'authors.name, special_comments.body, very_special_comments.body', :conditions => 'posts.id = 4') 
     90    author = Author.find(:first, :include => { :posts => [ :special_comments , :very_special_comment ] }, :order => 'authors.name, comments.body, very_special_comments_posts.body', :conditions => 'posts.id = 4') 
    9191    assert_equal authors(:david), author 
    9292    assert_no_queries do 
     
    9797   
    9898  def test_eager_association_loading_of_stis_with_multiple_references 
    99     authors = Author.find(:all, :include => { :posts => { :special_comments => { :post => [ :special_comments, :very_special_comment ] } } }, :order => 'special_comments.body, very_special_comments.body', :conditions => 'posts.id = 4') 
     99    authors = Author.find(:all, :include => { :posts => { :special_comments => { :post => [ :special_comments, :very_special_comment ] } } }, :order => 'comments.body, very_special_comments_posts.body', :conditions => 'posts.id = 4') 
    100100    assert_equal [authors(:david)], authors 
    101101    assert_no_queries do 
  • trunk/activerecord/test/fixtures/author.rb

    r4006 r4022  
    55  has_many :posts_with_comments_and_categories, :include => [ :comments, :categories ], :order => "posts.id", :class_name => "Post" 
    66  has_many :comments, :through => :posts 
    7   has_many :funky_comments, :through => :posts, :class_name => 'Comment' 
     7  has_many :funky_comments, :through => :posts, :source => :comments 
    88 
    99  has_many :special_posts,        :class_name => "Post" 
  • trunk/activerecord/test/fixtures/post.rb

    r4007 r4022  
    2929  end 
    3030   
    31   has_many :funky_tags, :through => :taggings, :class_name => 'Tag' 
     31  has_many :funky_tags, :through => :taggings, :source => :tag 
    3232  has_many :super_tags, :through => :taggings 
    3333  has_one :tagging, :as => :taggable 
    3434 
    3535  has_many :invalid_taggings, :as => :taggable, :class_name => "Tagging", :conditions => 'taggings.id < 0' 
    36   has_many :invalid_tags, :through => :invalid_taggings, :class_name => "Tag" 
     36  has_many :invalid_tags, :through => :invalid_taggings, :source => :tag 
    3737 
    3838  has_many :categorizations, :foreign_key => :category_id