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

Changeset 3213

Show
Ignore:
Timestamp:
12/03/05 04:29:55 (3 years ago)
Author:
david
Message:

Added preliminary support for join models [DHH] Added preliminary support for polymorphic associations [DHH] Refactored associations to use reflections to get DRYer, beware, major refactoring -- double check before deploying anything with this (all tests pass, but..)

Files:

Legend:

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

    r3206 r3213  
    11*SVN* 
     2 
     3* Added preliminary support for polymorphic associations [DHH] 
     4 
     5* Added preliminary support for join models [DHH] 
    26 
    37* Allow validate_uniqueness_of to be scoped by more than just one column.  #1559. [jeremy@jthopple.com, Marcel Molina Jr.] 
  • trunk/activerecord/lib/active_record.rb

    r3052 r3213  
    3939require 'active_record/validations' 
    4040require 'active_record/callbacks' 
     41require 'active_record/reflection' 
    4142require 'active_record/associations' 
    4243require 'active_record/aggregations' 
    4344require 'active_record/transactions' 
    44 require 'active_record/reflection' 
    4545require 'active_record/timestamp' 
    4646require 'active_record/acts/list' 
  • trunk/activerecord/lib/active_record/aggregations.rb

    r2744 r3213  
    11module ActiveRecord 
    22  module Aggregations # :nodoc: 
    3     def self.append_features(base) 
    4       super 
     3    def self.included(base) 
    54      base.extend(ClassMethods) 
    65    end 
     
    129128        reader_method(name, class_name, mapping) 
    130129        writer_method(name, class_name, mapping) 
     130         
     131        create_reflection(:composed_of, part_id, options, self) 
    131132      end 
    132133 
  • trunk/activerecord/lib/active_record/associations.rb

    r3209 r3213  
    55require 'active_record/associations/has_one_association' 
    66require 'active_record/associations/has_many_association' 
     7require 'active_record/associations/has_many_through_association' 
    78require 'active_record/associations/has_and_belongs_to_many_association' 
    89require 'active_record/deprecated_associations' 
     
    342343      #       'ORDER BY p.first_name' 
    343344      def has_many(association_id, options = {}, &extension) 
    344         options.assert_valid_keys( 
    345           :foreign_key, :class_name, :exclusively_dependent, :dependent,  
    346           :conditions, :order, :include, :finder_sql, :counter_sql,  
    347           :before_add, :after_add, :before_remove, :after_remove, :extend, 
    348           :group, :as 
    349         ) 
    350  
    351         options[:extend] = create_extension_module(association_id, extension) if block_given? 
    352  
    353         association_name, association_class_name, association_class_primary_key_name = 
    354               associate_identification(association_id, options[:class_name], options[:foreign_key]) 
    355   
    356         require_association_class(association_class_name) 
    357  
    358         raise ArgumentError, ':dependent and :exclusively_dependent are mutually exclusive options.  You may specify one or the other.' if options[:dependent] and options[:exclusively_dependent] 
    359  
    360         if options[:exclusively_dependent] 
    361           options[:dependent] = :delete_all 
    362           #warn "The :exclusively_dependent option is deprecated.  Please use :dependent => :delete_all instead.") 
    363         end 
    364  
    365         # See HasManyAssociation#delete_records.  Dependent associations 
    366         # delete children, otherwise foreign key is set to NULL. 
    367         case options[:dependent] 
    368           when :destroy, true   
    369             module_eval "before_destroy '#{association_name}.each { |o| o.destroy }'" 
    370           when :delete_all 
    371             module_eval "before_destroy { |record| #{association_class_name}.delete_all(%(#{association_class_primary_key_name} = \#{record.quoted_id})) }" 
    372           when :nullify 
    373             module_eval "before_destroy { |record| #{association_class_name}.update_all(%(#{association_class_primary_key_name} = NULL),  %(#{association_class_primary_key_name} = \#{record.quoted_id})) }" 
    374           when nil, false 
    375             # pass 
    376           else 
    377             raise ArgumentError, 'The :dependent option expects either true, :destroy, :delete_all, or :nullify'  
    378         end 
    379  
    380  
    381         add_multiple_associated_save_callbacks(association_name) 
    382         add_association_callbacks(association_name, options) 
    383  
    384         collection_accessor_methods(association_name, association_class_name, association_class_primary_key_name, options, HasManyAssociation) 
    385          
    386         # deprecated api 
    387         deprecated_collection_count_method(association_name) 
    388         deprecated_add_association_relation(association_name) 
    389         deprecated_remove_association_relation(association_name) 
    390         deprecated_has_collection_method(association_name) 
    391         deprecated_find_in_collection_method(association_name) 
    392         deprecated_find_all_in_collection_method(association_name) 
    393         deprecated_collection_create_method(association_name) 
    394         deprecated_collection_build_method(association_name) 
     345        reflection = create_has_many_reflection(association_id, options, &extension) 
     346 
     347        configure_dependency_for_has_many(reflection) 
     348 
     349        if options[:through] 
     350          collection_reader_method(reflection, HasManyThroughAssociation) 
     351        else 
     352          add_multiple_associated_save_callbacks(reflection.name) 
     353          add_association_callbacks(reflection.name, reflection.options) 
     354          collection_accessor_methods(reflection, HasManyAssociation) 
     355        end 
     356 
     357        add_deprecated_api_for_has_many(reflection.name) 
    395358      end 
    396359 
     
    437400      #   has_one :project_manager, :class_name => "Person", :conditions => "role = 'project_manager'" 
    438401      def has_one(association_id, options = {}) 
    439         options.assert_valid_keys(:class_name, :foreign_key, :remote, :conditions, :order, :include, :dependent, :counter_cache, :extend) 
    440  
    441         association_name, association_class_name, association_class_primary_key_name = 
    442             associate_identification(association_id, options[:class_name], options[:foreign_key], false) 
    443  
    444         require_association_class(association_class_name) 
     402        reflection = create_has_one_reflection(association_id, options) 
    445403 
    446404        module_eval do 
    447405          after_save <<-EOF 
    448             association = instance_variable_get("@#{association_name}") 
     406            association = instance_variable_get("@#{reflection.name}") 
    449407            unless association.nil? 
    450               association["#{association_class_primary_key_name}"] = id 
     408              association["#{reflection.primary_key_name}"] = id 
    451409              association.save(true) 
    452               association.send(:construct_sql) 
    453410            end 
    454411          EOF 
    455412        end 
    456413       
    457         association_accessor_methods(association_name, association_class_name, association_class_primary_key_name, options, HasOneAssociation) 
    458         association_constructor_method(:build, association_name, association_class_name, association_class_primary_key_name, options, HasOneAssociation) 
    459         association_constructor_method(:create, association_name, association_class_name, association_class_primary_key_name, options, HasOneAssociation) 
     414        association_accessor_methods(reflection, HasOneAssociation) 
     415        association_constructor_method(:build, reflection, HasOneAssociation) 
     416        association_constructor_method(:create, reflection, HasOneAssociation) 
    460417         
    461         case options[:dependent] 
    462           when :destroy, true 
    463             module_eval "before_destroy '#{association_name}.destroy unless #{association_name}.nil?'" 
    464           when :nullify 
    465             module_eval "before_destroy '#{association_name}.update_attribute(\"#{association_class_primary_key_name}\", nil)'" 
    466           when nil, false 
    467             # pass 
    468           else 
    469             raise ArgumentError, "The :dependent option expects either :destroy or :nullify." 
    470         end 
     418        configure_dependency_for_has_one(reflection) 
    471419 
    472420        # deprecated api 
    473         deprecated_has_association_method(association_name) 
    474         deprecated_association_comparison_method(association_name, association_class_name) 
     421        deprecated_has_association_method(reflection.name) 
     422        deprecated_association_comparison_method(reflection.name, reflection.class_name) 
    475423      end 
    476424 
     
    518466      #              :conditions => 'discounts > #{payments_count}' 
    519467      def belongs_to(association_id, options = {}) 
    520         options.assert_valid_keys(:class_name, :foreign_key, :foreign_type, :remote, :conditions, :order, :include, :dependent, :counter_cache, :extend, :polymorphic) 
    521  
    522         association_name, association_class_name, class_primary_key_name = 
    523             associate_identification(association_id, options[:class_name], options[:foreign_key], false) 
    524  
    525         association_class_primary_key_name = options[:foreign_key] || association_class_name.foreign_key 
    526  
    527         if options[:polymorphic] 
    528           options[:foreign_type] ||= association_class_name.underscore + "_type" 
    529  
    530           association_accessor_methods(association_name, association_class_name, association_class_primary_key_name, options, BelongsToPolymorphicAssociation) 
     468        reflection = create_belongs_to_reflection(association_id, options) 
     469         
     470        if reflection.options[:polymorphic] 
     471          association_accessor_methods(reflection, BelongsToPolymorphicAssociation) 
    531472 
    532473          module_eval do 
    533474            before_save <<-EOF 
    534               association = instance_variable_get("@#{association_name}") 
     475              association = instance_variable_get("@#{reflection.name}") 
    535476              if !association.nil?  
    536477                if association.new_record? 
    537478                  association.save(true) 
    538                   association.send(:construct_sql) 
    539479                end 
    540480                 
    541481                if association.updated? 
    542                   self["#{association_class_primary_key_name}"] = association.id 
    543                   self["#{options[:foreign_type]}"] = ActiveRecord::Base.send(:class_name_of_active_record_descendant, association.class).to_s 
     482                  self["#{reflection.primary_key_name}"] = association.id 
     483                  self["#{reflection.options[:foreign_type]}"] = ActiveRecord::Base.send(:class_name_of_active_record_descendant, association.class).to_s 
    544484                end 
    545485              end 
     
    547487          end 
    548488        else 
    549           require_association_class(association_class_name) 
    550  
    551           association_accessor_methods(association_name, association_class_name, association_class_primary_key_name, options, BelongsToAssociation) 
    552           association_constructor_method(:build, association_name, association_class_name, association_class_primary_key_name, options, BelongsToAssociation) 
    553           association_constructor_method(:create, association_name, association_class_name, association_class_primary_key_name, options, BelongsToAssociation) 
     489          association_accessor_methods(reflection, BelongsToAssociation) 
     490          association_constructor_method(:build,  reflection, BelongsToAssociation) 
     491          association_constructor_method(:create, reflection, BelongsToAssociation) 
    554492 
    555493          module_eval do 
    556494            before_save <<-EOF 
    557               association = instance_variable_get("@#{association_name}") 
     495              association = instance_variable_get("@#{reflection.name}") 
    558496              if !association.nil?  
    559497                if association.new_record? 
    560498                  association.save(true) 
    561                   association.send(:construct_sql) 
    562499                end 
    563500                 
    564501                if association.updated? 
    565                   self["#{association_class_primary_key_name}"] = association.id 
     502                  self["#{reflection.primary_key_name}"] = association.id 
    566503                end 
    567504              end             
     
    571508          if options[:counter_cache] 
    572509            module_eval( 
    573               "after_create '#{association_class_name}.increment_counter(\"#{self.to_s.underscore.pluralize + "_count"}\", #{association_class_primary_key_name})" + 
    574               " unless #{association_name}.nil?'" 
     510              "after_create '#{reflection.class_name}.increment_counter(\"#{self.to_s.underscore.pluralize + "_count"}\", #{reflection.primary_key_name})" + 
     511              " unless #{reflection.name}.nil?'" 
    575512            ) 
    576513 
    577514            module_eval( 
    578               "before_destroy '#{association_class_name}.decrement_counter(\"#{self.to_s.underscore.pluralize + "_count"}\", #{association_class_primary_key_name})" + 
    579               " unless #{association_name}.nil?'" 
     515              "before_destroy '#{reflection.class_name}.decrement_counter(\"#{self.to_s.underscore.pluralize + "_count"}\", #{reflection.primary_key_name})" + 
     516              " unless #{reflection.name}.nil?'" 
    580517            )           
    581518          end 
    582519 
    583520          # deprecated api 
    584           deprecated_has_association_method(association_name) 
    585           deprecated_association_comparison_method(association_name, association_class_name) 
     521          deprecated_has_association_method(reflection.name) 
     522          deprecated_association_comparison_method(reflection.name, reflection.class_name) 
    586523        end 
    587524      end 
     
    664601      #   'DELETE FROM developers_projects WHERE active=1 AND developer_id = #{id} AND project_id = #{record.id}' 
    665602      def has_and_belongs_to_many(association_id, options = {}, &extension) 
    666         options.assert_valid_keys( 
    667           :class_name, :table_name, :foreign_key, :association_foreign_key, :conditions, :include, 
    668           :join_table, :finder_sql, :delete_sql, :insert_sql, :order, :uniq, :before_add, :after_add,  
    669           :before_remove, :after_remove, :extend 
    670         ) 
    671  
    672         options[:extend] = create_extension_module(association_id, extension) if block_given? 
    673  
    674         association_name, association_class_name, association_class_primary_key_name = 
    675               associate_identification(association_id, options[:class_name], options[:foreign_key]) 
    676  
    677         require_association_class(association_class_name) 
    678  
    679         options[:join_table] ||= join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(association_class_name)) 
    680  
    681         add_multiple_associated_save_callbacks(association_name) 
    682        
    683         collection_accessor_methods(association_name, association_class_name, association_class_primary_key_name, options, HasAndBelongsToManyAssociation) 
     603        reflection = create_has_and_belongs_to_many_reflection(association_id, options, &extension) 
     604         
     605        add_multiple_associated_save_callbacks(reflection.name) 
     606        collection_accessor_methods(reflection, HasAndBelongsToManyAssociation) 
    684607 
    685608        # Don't use a before_destroy callback since users' before_destroy 
    686609        # callbacks will be executed after the association is wiped out. 
    687         old_method = "destroy_without_habtm_shim_for_#{association_name}" 
     610        old_method = "destroy_without_habtm_shim_for_#{reflection.name}" 
    688611        class_eval <<-end_eval 
    689612          alias_method :#{old_method}, :destroy_without_callbacks 
    690613          def destroy_without_callbacks 
    691             #{association_name}.clear 
     614            #{reflection.name}.clear 
    692615            #{old_method} 
    693616          end 
    694617        end_eval 
    695618 
    696         add_association_callbacks(association_name, options) 
     619        add_association_callbacks(reflection.name, options) 
    697620         
    698621        # deprecated api 
    699         deprecated_collection_count_method(association_name) 
    700         deprecated_add_association_relation(association_name) 
    701         deprecated_remove_association_relation(association_name) 
    702         deprecated_has_collection_method(association_name) 
     622        deprecated_collection_count_method(reflection.name) 
     623        deprecated_add_association_relation(reflection.name) 
     624        deprecated_remove_association_relation(reflection.name) 
     625        deprecated_has_collection_method(reflection.name) 
    703626      end 
    704627 
     
    714637        end 
    715638         
    716         def associate_identification(association_id, association_class_name, foreign_key, plural = true) 
    717           if association_class_name !~ /::/ 
    718             association_class_name = type_name_with_module( 
    719               association_class_name ||  
    720                 Inflector.camelize(plural ? Inflector.singularize(association_id.id2name) : association_id.id2name) 
    721             ) 
    722           end 
    723  
    724           primary_key_name = foreign_key || name.foreign_key 
    725          
    726           return association_id.id2name, association_class_name, primary_key_name 
    727         end 
    728          
    729         def association_accessor_methods(association_name, association_class_name, association_class_primary_key_name, options, association_proxy_class) 
    730           define_method(association_name) do |*params| 
     639        def association_accessor_methods(reflection, association_proxy_class) 
     640          define_method(reflection.name) do |*params| 
    731641            force_reload = params.first unless params.empty? 
    732             association = instance_variable_get("@#{association_name}") 
    733             if association.nil? or force_reload 
    734               association = association_proxy_class.new(self, 
    735                 association_name, association_class_name, 
    736                 association_class_primary_key_name, options) 
     642            association = instance_variable_get("@#{reflection.name}") 
     643 
     644            if association.nil? || force_reload 
     645              association = association_proxy_class.new(self, reflection) 
    737646              retval = association.reload 
    738647              unless retval.nil? 
    739                 instance_variable_set("@#{association_name}", association) 
     648                instance_variable_set("@#{reflection.name}", association) 
    740649              else 
    741                 instance_variable_set("@#{association_name}", nil) 
     650                instance_variable_set("@#{reflection.name}", nil) 
    742651                return nil 
    743652              end 
     
    746655          end 
    747656 
    748           define_method("#{association_name}=") do |new_value| 
    749             association = instance_variable_get("@#{association_name}") 
     657          define_method("#{reflection.name}=") do |new_value| 
     658            association = instance_variable_get("@#{reflection.name}") 
    750659            if association.nil? 
    751               association = association_proxy_class.new(self, 
    752                 association_name, association_class_name, 
    753                 association_class_primary_key_name, options) 
     660              association = association_proxy_class.new(self, reflection) 
    754661            end 
     662 
    755663            association.replace(new_value) 
     664 
    756665            unless new_value.nil? 
    757               instance_variable_set("@#{association_name}", association) 
     666              instance_variable_set("@#{reflection.name}", association) 
    758667            else 
    759               instance_variable_set("@#{association_name}", nil) 
     668              instance_variable_set("@#{reflection.name}", nil) 
    760669              return nil 
    761670            end 
     671 
    762672            association 
    763673          end 
    764674 
    765           define_method("set_#{association_name}_target") do |target| 
     675          define_method("set_#{reflection.name}_target") do |target| 
    766676            return if target.nil? 
    767             association = association_proxy_class.new(self, 
    768               association_name, association_class_name, 
    769               association_class_primary_key_name, options) 
     677            association = association_proxy_class.new(self, reflection) 
    770678            association.target = target 
    771             instance_variable_set("@#{association_name}", association) 
    772           end 
    773         end 
    774  
    775         def collection_accessor_methods(association_name, association_class_name, association_class_primary_key_name, options, association_proxy_class) 
    776           define_method(association_name) do |*params| 
     679            instance_variable_set("@#{reflection.name}", association) 
     680          end 
     681        end 
     682 
     683        def collection_reader_method(reflection, association_proxy_class) 
     684          define_method(reflection.name) do |*params| 
    777685            force_reload = params.first unless params.empty? 
    778             association = instance_variable_get("@#{association_name}") 
     686            association = instance_variable_get("@#{reflection.name}") 
     687 
    779688            unless association.respond_to?(:loaded?) 
    780               association = association_proxy_class.new(self, 
    781                 association_name, association_class_name, 
    782                 association_class_primary_key_name, options) 
    783               instance_variable_set("@#{association_name}", association) 
     689              association = association_proxy_class.new(self, reflection) 
     690              instance_variable_set("@#{reflection.name}", association) 
    784691            end 
     692 
    785693            association.reload if force_reload 
     694 
    786695            association 
    787696          end 
    788  
    789           define_method("#{association_name}=") do |new_value| 
    790             association = instance_variable_get("@#{association_name}") 
     697        end 
     698 
     699        def collection_accessor_methods(reflection, association_proxy_class) 
     700          collection_reader_method(reflection, association_proxy_class) 
     701 
     702          define_method("#{reflection.name}=") do |new_value| 
     703            association = instance_variable_get("@#{reflection.name}") 
    791704            unless association.respond_to?(:loaded?) 
    792               association = association_proxy_class.new(self, 
    793                 association_name, association_class_name, 
    794                 association_class_primary_key_name, options) 
    795               instance_variable_set("@#{association_name}", association) 
     705              association = association_proxy_class.new(self, reflection) 
     706              instance_variable_set("@#{reflection.name}", association) 
    796707            end 
    797708            association.replace(new_value) 
     
    799710          end 
    800711 
    801           define_method("#{Inflector.singularize(association_name)}_ids=") do |new_value| 
    802             send("#{association_name}=", association_class_name.constantize.find(new_value)) 
     712          define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value| 
     713            send("#{reflection.name}=", reflection.class_name.constantize.find(new_value)) 
    803714          end 
    804715        end 
     
    848759        end 
    849760 
    850         def association_constructor_method(constructor, association_name, association_class_name, association_class_primary_key_name, options, association_proxy_class) 
    851           define_method("#{constructor}_#{association_name}") do |*params| 
     761        def association_constructor_method(constructor, reflection, association_proxy_class) 
     762          define_method("#{constructor}_#{reflection.name}") do |*params| 
    852763            attributees      = params.first unless params.empty? 
    853764            replace_existing = params[1].nil? ? true : params[1] 
    854             association      = instance_variable_get("@#{association_name}") 
     765            association      = instance_variable_get("@#{reflection.name}") 
    855766 
    856767            if association.nil? 
    857               association = association_proxy_class.new(self, 
    858                 association_name, association_class_name, 
    859                 association_class_primary_key_name, options) 
    860               instance_variable_set("@#{association_name}", association) 
     768              association = association_proxy_class.new(self, reflection) 
     769              instance_variable_set("@#{reflection.name}", association) 
    861770            end 
    862771 
     
    910819        end 
    911820 
     821 
     822        def configure_dependency_for_has_many(reflection) 
     823          if reflection.options[:dependent] && reflection.options[:exclusively_dependent] 
     824            raise ArgumentError, ':dependent and :exclusively_dependent are mutually exclusive options.  You may specify one or the other.' 
     825          end 
     826 
     827          if reflection.options[:exclusively_dependent] 
     828            reflection.options[:dependent] = :delete_all 
     829            #warn "The :exclusively_dependent option is deprecated.  Please use :dependent => :delete_all instead.") 
     830          end 
     831 
     832          # See HasManyAssociation#delete_records.  Dependent associations 
     833          # delete children, otherwise foreign key is set to NULL. 
     834          case reflection.options[:dependent] 
     835            when :destroy, true   
     836              module_eval "before_destroy '#{reflection.name}.each { |o| o.destroy }'" 
     837            when :delete_all 
     838              module_eval "before_destroy { |record| #{reflection.class_name}.delete_all(%(#{reflection.primary_key_name} = \#{record.quoted_id})) }" 
     839            when :nullify 
     840              module_eval "before_destroy { |record| #{reflection.class_name}.update_all(%(#{reflection.primary_key_name} = NULL),  %(#{reflection.primary_key_name} = \#{record.quoted_id})) }" 
     841            when nil, false 
     842              # pass 
     843            else 
     844              raise ArgumentError, 'The :dependent option expects either true, :destroy, :delete_all, or :nullify'  
     845          end 
     846        end 
     847         
     848        def configure_dependency_for_has_one(reflection) 
     849          case reflection.options[:dependent] 
     850            when :destroy, true 
     851              module_eval "before_destroy '#{reflection.name}.destroy unless #{reflection.name}.nil?'" 
     852            when :nullify 
     853              module_eval "before_destroy '#{reflection.name}.update_attribute(\"#{reflection.primary_key_name}\", nil)'" 
     854            when nil, false 
     855              # pass 
     856            else 
     857              raise ArgumentError, "The :dependent option expects either :destroy or :nullify." 
     858          end 
     859        end 
     860         
     861         
     862        def add_deprecated_api_for_has_many(association_name) 
     863          deprecated_collection_count_method(association_name) 
     864          deprecated_add_association_relation(association_name) 
     865          deprecated_remove_association_relation(association_name) 
     866          deprecated_has_collection_method(association_name) 
     867          deprecated_find_in_collection_method(association_name) 
     868          deprecated_find_all_in_collection_method(association_name) 
     869          deprecated_collection_create_method(association_name) 
     870          deprecated_collection_build_method(association_name) 
     871        end 
     872 
     873        def create_has_many_reflection(association_id, options, &extension) 
     874          options.assert_valid_keys( 
     875            :foreign_key, :class_name, :exclusively_dependent, :dependent,  
     876            :conditions, :order, :include, :finder_sql, :counter_sql,  
     877            :before_add, :after_add, :before_remove, :after_remove, :extend, 
     878            :group, :as, :through 
     879          ) 
     880 
     881          options[:extend] = create_extension_module(association_id, extension) if block_given? 
     882 
     883          reflection = create_reflection(:has_many, association_id, options, self) 
     884          reflection.require_class 
     885 
     886          reflection 
     887        end 
     888 
     889        def create_has_one_reflection(association_id, options) 
     890          options.assert_valid_keys( 
     891            :class_name, :foreign_key, :remote, :conditions, :order, :include, :dependent, :counter_cache, :extend 
     892          ) 
     893 
     894          reflection = create_reflection(:has_one, association_id, options, self) 
     895          reflection.require_class 
     896 
     897          reflection 
     898        end 
     899 
     900        def create_belongs_to_reflection(association_id, options) 
     901          options.assert_valid_keys( 
     902            :class_name, :foreign_key, :foreign_type, :remote, :conditions, :order, :include, :dependent,  
     903            :counter_cache, :extend, :polymorphic 
     904          ) 
     905           
     906          reflection = create_reflection(:belongs_to, association_id, options, self) 
     907 
     908          if options[:polymorphic] 
     909            reflection.options[:foreign_type] ||= reflection.class_name.underscore + "_type" 
     910          else 
     911            reflection.require_class 
     912          end 
     913 
     914          reflection 
     915        end 
     916         
     917        def create_has_and_belongs_to_many_reflection(association_id, options, &extension) 
     918          options.assert_valid_keys( 
     919            :class_name, :table_name, :foreign_key, :association_foreign_key, :conditions, :include, 
     920            :join_table, :finder_sql, :delete_sql, :insert_sql, :order, :uniq, :before_add, :after_add,  
     921            :before_remove, :after_remove, :extend 
     922          ) 
     923 
     924          options[:extend] = create_extension_module(association_id, extension) if block_given? 
     925 
     926          reflection = create_reflection(:has_and_belongs_to_many, association_id, options, self) 
     927          reflection.require_class 
     928 
     929          reflection.options[:join_table] ||= join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(reflection.class_name)) 
     930           
     931          reflection 
     932        end 
    912933 
    913934        def reflect_on_included_associations(associations) 
  • trunk/activerecord/lib/active_record/associations/association_collection.rb

    r2872 r3213  
    1919        result = true 
    2020        load_target 
     21 
    2122        @owner.transaction do 
    2223          flatten_deeper(records).each do |record| 
     
    2930        end 
    3031                                 
    31         result and self 
     32        result && self 
    3233      end 
    3334 
     
    6162      def clear 
    6263        return self if length.zero? # forces load_target if hasn't happened already 
    63         if @options[:exclusively_dependent] 
     64 
     65        if @reflection.options[:exclusively_dependent] 
    6466          destroy_all 
    6567        else           
    6668          delete_all 
    6769        end 
     70 
    6871        self 
    6972      end 
     
    125128 
    126129      private 
    127         def raise_on_type_mismatch(record) 
    128           raise ActiveRecord::AssociationTypeMismatch, "#{@association_class} expected, got #{record.class}" unless record.is_a?(@association_class) 
    129         end 
    130  
    131         def target_obsolete? 
    132           false 
    133         end 
    134  
    135130        # Array#flatten has problems with recursive arrays. Going one level deeper solves the majority of the problems. 
    136131        def flatten_deeper(array) 
     
    156151         
    157152        def callbacks_for(callback_name) 
    158           full_callback_name = "#{callback_name.to_s}_for_#{@association_name.to_s}" 
    159           @owner.class.read_inheritable_attribute(full_callback_name.to_sym) or [] 
     153          full_callback_name = "#{callback_name}_for_#{@reflection.name}" 
     154          @owner.class.read_inheritable_attribute(full_callback_name.to_sym) || [] 
    160155        end 
    161156         
  • trunk/activerecord/lib/active_record/associations/association_proxy.rb

    r3209 r3213  
    66      instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?|^proxy_respond_to\?|^proxy_extend|^send)/ } 
    77 
    8       def initialize(owner, association_name, association_class_name, association_class_primary_key_name, options) 
    9         @owner = owner 
    10         @options = options 
    11         @association_name = association_name 
    12         @association_class = eval(association_class_name, nil, __FILE__, __LINE__) 
    13         @association_class_primary_key_name = association_class_primary_key_name 
    14  
    15         proxy_extend(options[:extend]) if options[:extend] 
    16  
     8      def initialize(owner, reflection) 
     9        @owner, @reflection = owner, reflection 
     10        proxy_extend(reflection.options[:extend]) if reflection.options[:extend] 
    1711        reset 
    1812      end 
     
    2721        load_target 
    2822        other === @target 
     23      end 
     24 
     25      def reset 
     26        @target = nil 
     27        @loaded = false 
    2928      end 
    3029 
     
    4645      end 
    4746       
    48       def target=(t
    49         @target = t 
    50         @loaded = true 
     47      def target=(target
     48        @target = target 
     49        loaded 
    5150      end 
    5251       
    5352      protected 
    5453        def dependent? 
    55           @options[:dependent] || false 
     54          @reflection.options[:dependent] || false 
    5655        end 
    5756         
     
    6968 
    7069        def sanitize_sql(sql) 
    71           @association_class.send(:sanitize_sql, sql) 
     70          @reflection.klass.send(:sanitize_sql, sql) 
    7271        end 
    7372 
     
    8584          if !@owner.new_record? || foreign_key_present 
    8685            begin 
    87               @target = find_target if not loaded? 
     86              @target = find_target if !loaded? 
    8887            rescue ActiveRecord::RecordNotFound 
    8988              reset 
    9089            end 
    9190          end 
    92           @loaded = true if @target 
    93           @target 
     91 
     92          loaded if target 
     93          target 
    9494        end 
    9595 
     
    101101 
    102102        def raise_on_type_mismatch(record) 
    103           raise ActiveRecord::AssociationTypeMismatch, "#{@association_class} expected, got #{record.class}" unless record.is_a?(@association_class) 
     103          unless record.is_a?(@reflection.klass) 
     104            raise ActiveRecord::AssociationTypeMismatch, "#{@reflection.class_name} expected, got #{record.class}" 
     105          end 
    104106        end 
    105107    end 
  • trunk/activerecord/lib/active_record/associations/belongs_to_association.rb

    r3209 r3213  
    22  module Associations 
    33    class BelongsToAssociation < AssociationProxy #:nodoc: 
    4       def initialize(owner, association_name, association_class_name, association_class_primary_key_name, options) 
    5         super         
    6         construct_sql         
    7       end 
    8        
    9       def reset 
    10         @target = nil 
    11         @loaded = false 
    12       end 
    13  
    144      def create(attributes = {}) 
    15         record = @association_class.create(attributes) 
    16         replace(record, true) 
    17         record 
     5        replace(@reflection.klass.create(attributes)) 
    186      end 
    197 
    208      def build(attributes = {}) 
    21         record = @association_class.new(attributes) 
    22         replace(record, true) 
    23         record 
     9        replace(@reflection.klass.new(attributes)) 
    2410      end 
    2511 
    26       def replace(obj, dont_save = false
    27         if obj.nil? 
    28           @target = @owner[@association_class_primary_key_name] = nil 
     12      def replace(record
     13        if record.nil? 
     14          @target = @owner[@reflection.primary_key_name] = nil 
    2915        else 
    30           raise_on_type_mismatch(obj) unless obj.nil? 
     16          raise_on_type_mismatch(record) 
    3117 
    32           @target = (AssociationProxy === obj ? obj.target : obj
    33           @owner[@association_class_primary_key_name] = obj.id unless obj.new_record? 
     18          @target = (AssociationProxy === record ? record.target : record
     19          @owner[@reflection.primary_key_name] = record.id unless record.new_record? 
    3420          @updated = true 
    3521        end 
    36         @loaded = true 
    3722 
    38         return (@target.nil? ? nil : self) 
     23        loaded 
     24        record 
    3925      end 
    4026       
     
    4531      private 
    4632        def find_target 
    47           if @options[:conditions] 
    48             @association_class.find( 
    49               @owner[@association_class_primary_key_name],  
    50               :conditions => interpolate_sql(@options[:conditions]), 
    51               :include    => @options[:include] 
    52             ) 
    53           else 
    54             @association_class.find(@owner[@association_class_primary_key_name], :include => @options[:include]) 
    55           end 
     33          @reflection.klass.find( 
     34            @owner[@reflection.primary_key_name],  
     35            :conditions => @reflection.options[:conditions] ? interpolate_sql(@reflection.options[:conditions]) : nil, 
     36            :include    => @reflection.options[:include] 
     37          ) 
    5638        end 
    5739 
    5840        def foreign_key_present 
    59           !@owner[@association_class_primary_key_name].nil? 
    60         end 
    61  
    62         def target_obsolete? 
    63           @owner[@association_class_primary_key_name] != @target.id 
    64         end 
    65          
    66         def construct_sql 
    67           @finder_sql = "#{@association_class.table_name}.#{@association_class.primary_key} = #{@owner.id}" 
     41          !@owner[@reflection.primary_key_name].nil? 
    6842        end 
    6943    end 
  • trunk/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb

    r3209 r3213  
    11module ActiveRecord 
    22  module Associations 
    3     class BelongsToPolymorphicAssociation < BelongsToAssociation #:nodoc: 
    4       def initialize(owner, association_name, association_class_name, association_class_primary_key_name, options
    5         @owner = owner 
    6         @options = options 
    7         @association_name = association_nam
    8         @association_class_primary_key_name = association_class_primary_key_name 
     3    class BelongsToPolymorphicAssociation < AssociationProxy #:nodoc: 
     4      def replace(record
     5        if record.nil? 
     6          @target = @owner[@reflection.primary_key_name] = @owner[@reflection.options[:foreign_type]] = nil 
     7        els
     8          @target = (AssociationProxy === record ? record.target : record) 
    99 
    10         proxy_extend(options[:extend]) if options[:extend] 
    11  
    12         reset 
    13       end 
    14        
    15       def create(attributes = {}) 
    16         raise ActiveRecord::ActiveRecordError, "Can't create an abstract polymorphic object" 
    17       end 
    18  
    19       def build(attributes = {}) 
    20         raise ActiveRecord::ActiveRecordError, "Can't build an abstract polymorphic object" 
    21       end 
    22  
    23       def replace(obj, dont_save = false) 
    24         if obj.nil? 
    25           @target = @owner[@association_class_primary_key_name] = @owner[@options[:foreign_type]] = nil 
    26         else 
    27           @target = (AssociationProxy === obj ? obj.target : obj) 
    28  
    29           unless obj.new_record? 
    30             @owner[@association_class_primary_key_name] = obj.id 
    31             @owner[@options[:foreign_type]] = ActiveRecord::Base.send(:class_name_of_active_record_descendant, obj.class).to_s 
     10          unless record.new_record? 
     11            @owner[@reflection.primary_key_name] = record.id 
     12            @owner[@reflect