diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 1056c51a3d2..b49cf8de95d 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -120,6 +120,8 @@ module ActiveRecord # So there is no need to eager load them. autoload :AssociationCollection, 'active_record/associations/association_collection' autoload :AssociationProxy, 'active_record/associations/association_proxy' + autoload :HasAssociation, 'active_record/associations/has_association' + autoload :ThroughAssociation, 'active_record/associations/through_association' autoload :BelongsToAssociation, 'active_record/associations/belongs_to_association' autoload :BelongsToPolymorphicAssociation, 'active_record/associations/belongs_to_polymorphic_association' autoload :HasAndBelongsToManyAssociation, 'active_record/associations/has_and_belongs_to_many_association' diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index c9f9c925b07..86cc17394c8 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -18,6 +18,8 @@ module ActiveRecord # If you need to work on all current children, new and existing records, # +load_target+ and the +loaded+ flag are your friends. class AssociationCollection < AssociationProxy #:nodoc: + include HasAssociation + delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :to => :scoped def select(select = nil) diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index c7b171b41dc..b60aa33c98d 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -4,17 +4,17 @@ module ActiveRecord module Associations # = Active Record Associations # - # This is the root class of all association proxies: + # This is the root class of all association proxies ('+ Foo' signifies an included module Foo): # # AssociationProxy # BelongsToAssociation - # HasOneAssociation # BelongsToPolymorphicAssociation - # AssociationCollection + # AssociationCollection + HasAssociation # HasAndBelongsToManyAssociation # HasManyAssociation - # HasManyThroughAssociation - # HasOneThroughAssociation + # HasManyThroughAssociation + ThroughAssociation + # HasOneAssociation + HasAssociation + # HasOneThroughAssociation + ThroughAssociation # # Association proxies in Active Record are middlemen between the object that # holds the association, known as the @owner, and the actual associated @@ -176,43 +176,6 @@ module ActiveRecord @reflection.klass.send(:sanitize_sql, sql, table_name) end - # Sets the owner attributes on the given record - # Note: does not really make sense for belongs_to associations, but this method is not - # used by belongs_to - def set_owner_attributes(record) - if @owner.persisted? - construct_owner_attributes.each { |key, value| record[key] = value } - end - end - - # Returns a has linking the owner to the association represented by the reflection - def construct_owner_attributes(reflection = @reflection) - attributes = {} - if reflection.macro == :belongs_to - attributes[reflection.association_primary_key] = @owner.send(reflection.primary_key_name) - else - attributes[reflection.primary_key_name] = @owner.send(reflection.active_record_primary_key) - - if reflection.options[:as] - attributes["#{reflection.options[:as]}_type"] = @owner.class.base_class.name - end - end - attributes - end - - # Builds an array of arel nodes from the owner attributes hash - def construct_owner_conditions(table = aliased_table, reflection = @reflection) - construct_owner_attributes(reflection).map do |attr, value| - table[attr].eq(value) - end - end - - def construct_conditions - conditions = construct_owner_conditions - conditions << Arel.sql(sql_conditions) if sql_conditions - aliased_table.create_and(conditions) - end - # Merges into +options+ the ones coming from the reflection. def merge_options_from_reflection!(options) options.reverse_merge!( @@ -312,18 +275,6 @@ module ActiveRecord end end - if RUBY_VERSION < '1.9.2' - # Array#flatten has problems with recursive arrays before Ruby 1.9.2. - # Going one level deeper solves the majority of the problems. - def flatten_deeper(array) - array.collect { |element| (element.respond_to?(:flatten) && !element.is_a?(Hash)) ? element.flatten : element }.flatten - end - else - def flatten_deeper(array) - array.flatten - end - end - # Returns the ID of the owner, quoted if needed. def owner_quoted_id @owner.quoted_id diff --git a/activerecord/lib/active_record/associations/has_association.rb b/activerecord/lib/active_record/associations/has_association.rb new file mode 100644 index 00000000000..4407e2ea9a4 --- /dev/null +++ b/activerecord/lib/active_record/associations/has_association.rb @@ -0,0 +1,54 @@ +module ActiveRecord + module Associations + # Included in all has_* associations (i.e. everything except belongs_to) + module HasAssociation #:nodoc: + protected + # Sets the owner attributes on the given record + def set_owner_attributes(record) + if @owner.persisted? + construct_owner_attributes.each { |key, value| record[key] = value } + end + end + + # Returns a hash linking the owner to the association represented by the reflection + def construct_owner_attributes(reflection = @reflection) + attributes = {} + if reflection.macro == :belongs_to + attributes[reflection.association_primary_key] = @owner.send(reflection.primary_key_name) + else + attributes[reflection.primary_key_name] = @owner.send(reflection.active_record_primary_key) + + if reflection.options[:as] + attributes["#{reflection.options[:as]}_type"] = @owner.class.base_class.name + end + end + attributes + end + + # Builds an array of arel nodes from the owner attributes hash + def construct_owner_conditions(table = aliased_table, reflection = @reflection) + construct_owner_attributes(reflection).map do |attr, value| + table[attr].eq(value) + end + end + + def construct_conditions + conditions = construct_owner_conditions + conditions << Arel.sql(sql_conditions) if sql_conditions + aliased_table.create_and(conditions) + end + + if RUBY_VERSION < '1.9.2' + # Array#flatten has problems with recursive arrays before Ruby 1.9.2. + # Going one level deeper solves the majority of the problems. + def flatten_deeper(array) + array.collect { |element| (element.respond_to?(:flatten) && !element.is_a?(Hash)) ? element.flatten : element }.flatten + end + else + def flatten_deeper(array) + array.flatten + end + end + end + end +end diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb index 4dabfe2ea38..8569a2f0044 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -1,11 +1,10 @@ -require "active_record/associations/through_association_scope" require 'active_support/core_ext/object/blank' module ActiveRecord # = Active Record Has Many Through Association module Associations class HasManyThroughAssociation < HasManyAssociation #:nodoc: - include ThroughAssociationScope + include ThroughAssociation alias_method :new, :build diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb index 654157d9981..ca0828ea7b8 100644 --- a/activerecord/lib/active_record/associations/has_one_association.rb +++ b/activerecord/lib/active_record/associations/has_one_association.rb @@ -2,6 +2,8 @@ module ActiveRecord # = Active Record Belongs To Has One Association module Associations class HasOneAssociation < AssociationProxy #:nodoc: + include HasAssociation + def create(attrs = {}, replace_existing = true) new_record(replace_existing) do |reflection| attrs = merge_with_conditions(attrs) diff --git a/activerecord/lib/active_record/associations/has_one_through_association.rb b/activerecord/lib/active_record/associations/has_one_through_association.rb index 8c8c9fbe5d9..c9ae930e937 100644 --- a/activerecord/lib/active_record/associations/has_one_through_association.rb +++ b/activerecord/lib/active_record/associations/has_one_through_association.rb @@ -1,10 +1,8 @@ -require "active_record/associations/through_association_scope" - module ActiveRecord # = Active Record Has One Through Association module Associations class HasOneThroughAssociation < HasOneAssociation - include ThroughAssociationScope + include ThroughAssociation def replace(new_value) create_through_record(new_value) diff --git a/activerecord/lib/active_record/associations/through_association_scope.rb b/activerecord/lib/active_record/associations/through_association.rb similarity index 98% rename from activerecord/lib/active_record/associations/through_association_scope.rb rename to activerecord/lib/active_record/associations/through_association.rb index 1abeb178ff5..57718285f87 100644 --- a/activerecord/lib/active_record/associations/through_association_scope.rb +++ b/activerecord/lib/active_record/associations/through_association.rb @@ -1,7 +1,7 @@ module ActiveRecord - # = Active Record Through Association Scope + # = Active Record Through Association module Associations - module ThroughAssociationScope + module ThroughAssociation def scoped with_scope(@scope) do