diff options
Diffstat (limited to 'activerecord')
26 files changed, 392 insertions, 502 deletions
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index e434372fb7..1f343f690c 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,17 @@ *Rails 3.1.0 (unreleased)* +* ActiveRecord::Associations::AssociationProxy has been split. There is now an Association class + (and subclasses) which are responsible for operating on associations, and then a separate, + thin wrapper called CollectionProxy, which proxies collection associations. + + This prevents namespace pollution, separates concerns, and will allow further refactorings. + + Singular associations (has_one, belongs_to) no longer have a proxy at all. They simply return + the associated record or nil. This means that you should not use undocumented methods such + as bob.mother.create - use bob.create_mother instead. + + [Jon Leighton] + * Make has_many :through associations work correctly when you build a record and then save it. This requires you to set the :inverse_of option on the source reflection on the join model, like so: diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb index 52d2c49836..a34a73cf5d 100644 --- a/activerecord/lib/active_record/association_preload.rb +++ b/activerecord/lib/active_record/association_preload.rb @@ -124,16 +124,16 @@ module ActiveRecord def add_preloaded_records_to_collection(parent_records, reflection_name, associated_record) parent_records.each do |parent_record| - association_proxy = parent_record.send(reflection_name) - association_proxy.loaded! - association_proxy.target.concat(Array.wrap(associated_record)) - association_proxy.send(:set_inverse_instance, associated_record) + association = parent_record.association(reflection_name) + association.loaded! + association.target.concat(Array.wrap(associated_record)) + association.set_inverse_instance(associated_record) end end def add_preloaded_record_to_collection(parent_records, reflection_name, associated_record) parent_records.each do |parent_record| - parent_record.send(:association_proxy, reflection_name).target = associated_record + parent_record.association(reflection_name).target = associated_record end end @@ -158,7 +158,7 @@ module ActiveRecord seen_keys[seen_key] = true mapped_records = id_to_record_map[seen_key] mapped_records.each do |mapped_record| - association_proxy = mapped_record.send(:association_proxy, reflection_name) + association_proxy = mapped_record.association(reflection_name) association_proxy.target = associated_record association_proxy.send(:set_inverse_instance, associated_record) end @@ -187,7 +187,7 @@ module ActiveRecord id_to_record_map = construct_id_map(records) - records.each { |record| record.send(reflection.name).loaded! } + records.each { |record| record.association(reflection.name).loaded! } options = reflection.options right = Arel::Table.new(options[:join_table]).alias('t0') @@ -233,7 +233,7 @@ module ActiveRecord end def preload_has_one_association(records, reflection, preload_options={}) - return if records.first.send(:association_proxy, reflection.name).loaded? + return if records.first.association(reflection.name).loaded? id_to_record_map = construct_id_map(records, reflection.options[:primary_key]) options = reflection.options @@ -268,7 +268,7 @@ module ActiveRecord foreign_key = reflection.through_reflection_foreign_key id_to_record_map = construct_id_map(records, foreign_key || reflection.options[:primary_key]) - records.each { |record| record.send(reflection.name).loaded! } + records.each { |record| record.association(reflection.name).loaded! } if options[:through] through_records = preload_through_records(records, reflection, options[:through]) @@ -298,7 +298,7 @@ module ActiveRecord # Dont cache the association - we would only be caching a subset records.map { |record| - proxy = record.send(through_association) + proxy = record.association(through_association) if proxy.respond_to?(:target) Array.wrap(proxy.target).tap { proxy.reset } @@ -320,7 +320,7 @@ module ActiveRecord end def preload_belongs_to_association(records, reflection, preload_options={}) - return if records.first.send(:association_proxy, reflection.name).loaded? + return if records.first.association(reflection.name).loaded? options = reflection.options klasses_and_ids = {} diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index bf0f6dc542..77110c721b 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -118,17 +118,19 @@ module ActiveRecord # These classes will be loaded when associations are created. # So there is no need to eager load them. - autoload :AssociationCollection, 'active_record/associations/association_collection' - autoload :SingularAssociation, 'active_record/associations/singular_association' - autoload :AssociationProxy, 'active_record/associations/association_proxy' - autoload :ThroughAssociation, 'active_record/associations/through_association' - autoload :BelongsToAssociation, 'active_record/associations/belongs_to_association' + autoload :Association, 'active_record/associations/association' + autoload :SingularAssociation, 'active_record/associations/singular_association' + autoload :CollectionAssociation, 'active_record/associations/collection_association' + autoload :CollectionProxy, 'active_record/associations/collection_proxy' + + 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' - autoload :HasManyAssociation, 'active_record/associations/has_many_association' - autoload :HasManyThroughAssociation, 'active_record/associations/has_many_through_association' - autoload :HasOneAssociation, 'active_record/associations/has_one_association' - autoload :HasOneThroughAssociation, 'active_record/associations/has_one_through_association' + autoload :HasAndBelongsToManyAssociation, 'active_record/associations/has_and_belongs_to_many_association' + autoload :HasManyAssociation, 'active_record/associations/has_many_association' + autoload :HasManyThroughAssociation, 'active_record/associations/has_many_through_association' + autoload :HasOneAssociation, 'active_record/associations/has_one_association' + autoload :HasOneThroughAssociation, 'active_record/associations/has_one_through_association' + autoload :ThroughAssociation, 'active_record/associations/through_association' # Clears out the association cache. def clear_association_cache #:nodoc: @@ -138,29 +140,23 @@ module ActiveRecord # :nodoc: attr_reader :association_cache - protected - - # Returns the proxy for the given association name, instantiating it if it doesn't - # already exist - def association_proxy(name) - association = association_instance_get(name) - - if association.nil? - reflection = self.class.reflect_on_association(name) - association = reflection.proxy_class.new(self, reflection) - association_instance_set(name, association) - end + # Returns the association instance for the given name, instantiating it if it doesn't already exist + def association(name) #:nodoc: + association = association_instance_get(name) - association + if association.nil? + reflection = self.class.reflect_on_association(name) + association = reflection.association_class.new(self, reflection) + association_instance_set(name, association) end + association + end + private # Returns the specified association instance if it responds to :loaded?, nil otherwise. def association_instance_get(name) - if @association_cache.key? name - association = @association_cache[name] - association if association.respond_to?(:loaded?) - end + @association_cache[name] end # Set the specified association instance. @@ -1574,7 +1570,7 @@ module ActiveRecord def association_accessor_methods(reflection) redefine_method(reflection.name) do |*params| force_reload = params.first unless params.empty? - association = association_proxy(reflection.name) + association = association(reflection.name) if force_reload reflection.klass.uncached { association.reload } @@ -1582,18 +1578,18 @@ module ActiveRecord association.reload end - association.target.nil? ? nil : association + association.target end redefine_method("#{reflection.name}=") do |record| - association_proxy(reflection.name).replace(record) + association(reflection.name).replace(record) end end def collection_reader_method(reflection) redefine_method(reflection.name) do |*params| force_reload = params.first unless params.empty? - association = association_proxy(reflection.name) + association = association(reflection.name) if force_reload reflection.klass.uncached { association.reload } @@ -1601,7 +1597,7 @@ module ActiveRecord association.reload end - association + association.proxy end redefine_method("#{reflection.name.to_s.singularize}_ids") do @@ -1621,7 +1617,7 @@ module ActiveRecord if writer redefine_method("#{reflection.name}=") do |new_value| - association_proxy(reflection.name).replace(new_value) + association(reflection.name).replace(new_value) end redefine_method("#{reflection.name.to_s.singularize}_ids=") do |new_value| @@ -1643,7 +1639,7 @@ module ActiveRecord constructors.each do |name, proxy_name| redefine_method(name) do |*params| attributes = params.first unless params.empty? - association_proxy(reflection.name).send(proxy_name, attributes) + association(reflection.name).send(proxy_name, attributes) end end end diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association.rb index fc03a4b619..2264631584 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -4,102 +4,33 @@ module ActiveRecord module Associations # = Active Record Associations # - # This is the root class of all association proxies ('+ Foo' signifies an included module Foo): + # This is the root class of all associations ('+ Foo' signifies an included module Foo): # - # AssociationProxy + # Association # SingularAssociaton # HasOneAssociation # HasOneThroughAssociation + ThroughAssociation # BelongsToAssociation # BelongsToPolymorphicAssociation - # AssociationCollection + # CollectionAssociation # HasAndBelongsToManyAssociation # HasManyAssociation # HasManyThroughAssociation + ThroughAssociation - # - # Association proxies in Active Record are middlemen between the object that - # holds the association, known as the <tt>@owner</tt>, and the actual associated - # object, known as the <tt>@target</tt>. The kind of association any proxy is - # about is available in <tt>@reflection</tt>. That's an instance of the class - # ActiveRecord::Reflection::AssociationReflection. - # - # For example, given - # - # class Blog < ActiveRecord::Base - # has_many :posts - # end - # - # blog = Blog.find(:first) - # - # the association proxy in <tt>blog.posts</tt> has the object in +blog+ as - # <tt>@owner</tt>, the collection of its posts as <tt>@target</tt>, and - # the <tt>@reflection</tt> object represents a <tt>:has_many</tt> macro. - # - # This class has most of the basic instance methods removed, and delegates - # unknown methods to <tt>@target</tt> via <tt>method_missing</tt>. As a - # corner case, it even removes the +class+ method and that's why you get - # - # blog.posts.class # => Array - # - # though the object behind <tt>blog.posts</tt> is not an Array, but an - # ActiveRecord::Associations::HasManyAssociation. - # - # The <tt>@target</tt> object is not \loaded until needed. For example, - # - # blog.posts.count - # - # is computed directly through SQL and does not trigger by itself the - # instantiation of the actual post records. - class AssociationProxy #:nodoc: - alias_method :proxy_extend, :extend + class Association #:nodoc: + attr_reader :owner, :target, :reflection - instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id|to_a)$|^__|^respond_to|proxy_/ } + delegate :options, :klass, :to => :reflection def initialize(owner, reflection) + reflection.check_validity! + @owner, @reflection = owner, reflection @updated = false - reflection.check_validity! - Array.wrap(reflection.options[:extend]).each { |ext| proxy_extend(ext) } + reset construct_scope end - def to_param - proxy_target.to_param - end - - # Returns the owner of the proxy. - def proxy_owner - @owner - end - - # Returns the reflection object that represents the association handled - # by the proxy. - def proxy_reflection - @reflection - end - - # Does the proxy or its \target respond to +symbol+? - def respond_to?(*args) - super || (load_target && @target.respond_to?(*args)) - end - - # Forwards any missing method call to the \target. - def method_missing(method, *args, &block) - if load_target - return super unless @target.respond_to?(method) - @target.send(method, *args, &block) - end - rescue NoMethodError => e - raise e, e.message.sub(/ for #<.*$/, " via proxy for #{@target}") - end - - # Forwards <tt>===</tt> explicitly to the \target because the instance method - # removal above doesn't catch it. Loads the \target if needed. - def ===(other) - other === load_target - end - # Returns the name of the table of the related class: # # post.comments.aliased_table_name # => "comments" @@ -143,97 +74,79 @@ module ActiveRecord loaded? && @stale_state != stale_state end - # Returns the target of this proxy, same as +proxy_target+. - attr_reader :target - - # Returns the \target of the proxy, same as +target+. - alias :proxy_target :target - - # Sets the target of this proxy to <tt>\target</tt>, and the \loaded flag to +true+. + # Sets the target of this association to <tt>\target</tt>, and the \loaded flag to +true+. def target=(target) @target = target loaded! end - # Forwards the call to the target. Loads the \target if needed. - def inspect - load_target.inspect - end - - def send(method, *args) - return super if respond_to?(method) - load_target.send(method, *args) - end - def scoped target_scope.merge(@association_scope) end - protected - - # Construct the scope for this association. - # - # Note that the association_scope is merged into the targed_scope only when the - # scoped method is called. This is because at that point the call may be surrounded - # by scope.scoping { ... } or with_scope { ... } etc, which affects the scope which - # actually gets built. - def construct_scope - @association_scope = association_scope if target_klass - end + # Construct the scope for this association. + # + # Note that the association_scope is merged into the targed_scope only when the + # scoped method is called. This is because at that point the call may be surrounded + # by scope.scoping { ... } or with_scope { ... } etc, which affects the scope which + # actually gets built. + def construct_scope + @association_scope = association_scope if target_klass + end - def association_scope - scope = target_klass.unscoped - scope = scope.create_with(creation_attributes) - scope = scope.apply_finder_options(@reflection.options.slice(:readonly, :include)) - scope = scope.where(interpolate(@reflection.options[:conditions])) - if select = select_value - scope = scope.select(select) - end - scope = scope.extending(*Array.wrap(@reflection.options[:extend])) - scope.where(construct_owner_conditions) + def association_scope + scope = target_klass.unscoped + scope = scope.create_with(creation_attributes) + scope = scope.apply_finder_options(@reflection.options.slice(:readonly, :include)) + scope = scope.where(interpolate(@reflection.options[:conditions])) + if select = select_value + scope = scope.select(select) end + scope = scope.extending(*Array.wrap(@reflection.options[:extend])) + scope.where(construct_owner_conditions) + end - def aliased_table - target_klass.arel_table - end + def aliased_table + target_klass.arel_table + end - # Set the inverse association, if possible - def set_inverse_instance(record) - if record && invertible_for?(record) - inverse = record.send(:association_proxy, inverse_reflection_for(record).name) - inverse.target = @owner - end + # Set the inverse association, if possible + def set_inverse_instance(record) + if record && invertible_for?(record) + inverse = record.association(inverse_reflection_for(record).name) + inverse.target = @owner end + end - # This class of the target. belongs_to polymorphic overrides this to look at the - # polymorphic_type field on the owner. - def target_klass - @reflection.klass - end + # This class of the target. belongs_to polymorphic overrides this to look at the + # polymorphic_type field on the owner. + def target_klass + @reflection.klass + end - # Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the - # through association's scope) - def target_scope - target_klass.scoped - end + # Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the + # through association's scope) + def target_scope + target_klass.scoped + end - # Loads the \target if needed and returns it. - # - # This method is abstract in the sense that it relies on +find_target+, - # which is expected to be provided by descendants. - # - # If the \target is already \loaded it is just returned. Thus, you can call - # +load_target+ unconditionally to get the \target. - # - # ActiveRecord::RecordNotFound is rescued within the method, and it is - # not reraised. The proxy is \reset and +nil+ is the return value. - def load_target - @target = find_target if find_target? - loaded! - target - rescue ActiveRecord::RecordNotFound - reset - end + # Loads the \target if needed and returns it. + # + # This method is abstract in the sense that it relies on +find_target+, + # which is expected to be provided by descendants. + # + # If the \target is already \loaded it is just returned. Thus, you can call + # +load_target+ unconditionally to get the \target. + # + # ActiveRecord::RecordNotFound is rescued within the method, and it is + # not reraised. The proxy is \reset and +nil+ is the return value. + def load_target + @target = find_target if find_target? + loaded! + target + rescue ActiveRecord::RecordNotFound + reset + end private diff --git a/activerecord/lib/active_record/associations/class_methods/join_dependency.rb b/activerecord/lib/active_record/associations/class_methods/join_dependency.rb index fdd4fe8946..89503ccafa 100644 --- a/activerecord/lib/active_record/associations/class_methods/join_dependency.rb +++ b/activerecord/lib/active_record/associations/class_methods/join_dependency.rb @@ -209,10 +209,10 @@ module ActiveRecord association = join_part.instantiate(row) case macro when :has_many, :has_and_belongs_to_many - collection = record.send(join_part.reflection.name) - collection.loaded! - collection.target.push(association) - collection.send(:set_inverse_instance, association) + other = record.association(join_part.reflection.name) + other.loaded! + other.target.push(association) + other.set_inverse_instance(association) when :belongs_to set_target_and_inverse(join_part, association, record) else @@ -223,9 +223,9 @@ module ActiveRecord end def set_target_and_inverse(join_part, association, record) - association_proxy = record.send(:association_proxy, join_part.reflection.name) - association_proxy.target = association - association_proxy.send(:set_inverse_instance, association) + other = record.association(join_part.reflection.name) + other.target = association + other.set_inverse_instance(association) end end end diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/collection_association.rb index ca350f51c9..68631681e4 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -17,8 +17,26 @@ 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: - delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :to => :scoped + class CollectionAssociation < Association #:nodoc: + attr_reader :proxy + + def initialize(owner, reflection) + # When scopes are created via method_missing on the proxy, they are stored so that + # any records fetched from the database are kept around for future use. + @scopes_cache = Hash.new do |hash, method| + hash[method] = { } + end + + super + + @proxy = CollectionProxy.new(self) + end + + def reset + @loaded = false + @target = [] + @scopes_cache.clear + end def select(select = nil) if block_given? @@ -44,17 +62,6 @@ module ActiveRecord first_or_last(:last, *args) end - def to_ary - load_target.dup - end - alias_method :to_a, :to_ary - - def reset - @_scopes_cache = {} - @loaded = false - @target = [] - end - def build(attributes = {}, &block) build_or_create(attributes, :build, &block) end @@ -75,7 +82,7 @@ module ActiveRecord # Add +records+ to this association. Returns +self+ so method calls may be chained. # Since << flattens its argument list and inserts each record, +push+ and +concat+ behave identically. - def <<(*records) + def concat(*records) result = true load_target if @owner.new_record? @@ -88,12 +95,9 @@ module ActiveRecord end end - result && self + result && records end - alias_method :push, :<< - alias_method :concat, :<< - # Starts a transaction in the association class's database connection. # # class Author < ActiveRecord::Base @@ -119,13 +123,6 @@ module ActiveRecord end end - # Identical to delete_all, except that the return value is the association (for chaining) - # rather than the records which have been removed. - def clear - delete_all - self - end - # Destroy all the records from this association. # # See destroy for more info. @@ -254,7 +251,7 @@ module ActiveRecord end end - def uniq(collection = self) + def uniq(collection = load_target) seen = {} collection.find_all do |record| seen[record.id] = true unless seen.key?(record.id) @@ -291,70 +288,50 @@ module ActiveRecord end end - def respond_to?(method, include_private = false) - super || @reflection.klass.respond_to?(method, include_private) + def cached_scope(method, args) + @scopes_cache[method][args] ||= scoped.readonly(nil).send(method, *args) end - def method_missing(method, *args, &block) - match = DynamicFinderMatch.match(method) - if match && match.creator? - attributes = match.attribute_names - return send(:"find_by_#{attributes.join('_and_')}", *args) || create(Hash[attributes.zip(args)]) - end - - if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method)) - super - elsif @reflection.klass.scopes[method] - @_scopes_cache ||= {} - @_scopes_cache[method] ||= {} - @_scopes_cache[method][args] ||= scoped.readonly(nil).send(method, *args) - else - scoped.readonly(nil).send(method, *args, &block) - end + def association_scope + options = @reflection.options.slice(:order, :limit, :joins, :group, :having, :offset) + super.apply_finder_options(options) end - protected - - def association_scope - options = @reflection.options.slice(:order, :limit, :joins, :group, :having, :offset) - super.apply_finder_options(options) - end - - def load_target - if find_target? - targets = [] + def load_target + if find_target? + targets = [] - begin - targets = find_target - rescue ActiveRecord::RecordNotFound - reset - end - - @target = merge_target_lists(targets, @target) + begin + targets = find_target + rescue ActiveRecord::RecordNotFound + reset end - loaded! - target + @target = merge_target_lists(targets, @target) end - def add_to_target(record) - transaction do - callback(:before_add, record) - yield(record) if block_given? + loaded! + target + end - if @reflection.options[:uniq] && index = @target.index(record) - @target[index] = record - else - @target << record - end + def add_to_target(record) + transaction do + callback(:before_add, record) + yield(record) if block_given? - callback(:after_add, record) - set_inverse_instance(record) + if @reflection.options[:uniq] && index = @target.index(record) + @target[index] = record + else + @target << record end - record + callback(:after_add, record) + set_inverse_instance(record) end + record + end + private def select_value @@ -498,8 +475,8 @@ module ActiveRecord def include_in_memory?(record) if @reflection.is_a?(ActiveRecord::Reflection::ThroughReflection) - @owner.send(proxy_reflection.through_reflection.name).any? { |source| - target = source.send(proxy_reflection.source_reflection.name) + @owner.send(@reflection.through_reflection.name).any? { |source| + target = source.send(@reflection.source_reflection.name) target.respond_to?(:include?) ? target.include?(record) : target == record } || @target.include?(record) else diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb new file mode 100644 index 0000000000..cf77d770c9 --- /dev/null +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -0,0 +1,127 @@ +module ActiveRecord + module Associations + # Association proxies in Active Record are middlemen between the object that + # holds the association, known as the <tt>@owner</tt>, and the actual associated + # object, known as the <tt>@target</tt>. The kind of association any proxy is + # about is available in <tt>@reflection</tt>. That's an instance of the class + # ActiveRecord::Reflection::AssociationReflection. + # + # For example, given + # + # class Blog < ActiveRecord::Base + # has_many :posts + # end + # + # blog = Blog.find(:first) + # + # the association proxy in <tt>blog.posts</tt> has the object in +blog+ as + # <tt>@owner</tt>, the collection of its posts as <tt>@target</tt>, and + # the <tt>@reflection</tt> object represents a <tt>:has_many</tt> macro. + # + # This class has most of the basic instance methods removed, and delegates + # unknown methods to <tt>@target</tt> via <tt>method_missing</tt>. As a + # corner case, it even removes the +class+ method and that's why you get + # + # blog.posts.class # => Array + # + # though the object behind <tt>blog.posts</tt> is not an Array, but an + # ActiveRecord::Associations::HasManyAssociation. + # + # The <tt>@target</tt> object is not \loaded until needed. For example, + # + # blog.posts.count + # + # is computed directly through SQL and does not trigger by itself the + # instantiation of the actual post records. + class CollectionProxy # :nodoc: + alias :proxy_extend :extend + + instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id|to_a)$|^__|^respond_to|proxy_/ } + + delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, + :lock, :readonly, :having, :to => :scoped + + delegate :target, :load_target, :loaded?, :scoped, + :to => :@association + + delegate :select, :find, :first, :last, + :build, :create, :create!, + :concat, :delete_all, :destroy_all, :delete, :destroy, :uniq, + :sum, :count, :size, :length, :empty?, + :any?, :many?, :include?, + :to => :@association + + def initialize(association) + @association = association + Array.wrap(association.options[:extend]).each { |ext| proxy_extend(ext) } + end + + def respond_to?(*args) + super || + (load_target && target.respond_to?(*args)) || + @association.klass.respond_to?(*args) + end + + def method_missing(method, *args, &block) + match = DynamicFinderMatch.match(method) + if match && match.creator? + attributes = match.attribute_names + return send(:"find_by_#{attributes.join('_and_')}", *args) || create(Hash[attributes.zip(args)]) + end + + if target.respond_to?(method) || (!@association.klass.respond_to?(method) && Class.respond_to?(method)) + if load_target + if target.respond_to?(method) + target.send(method, *args, &block) + else + begin + super + rescue NoMethodError => e + raise e, e.message.sub(/ for #<.*$/, " via proxy for #{target}") + end + end + end + + elsif @association.klass.scopes[method] + @association.cached_scope(method, args) + else + scoped.readonly(nil).send(method, *args, &block) + end + end + + # Forwards <tt>===</tt> explicitly to the \target because the instance method + # removal above doesn't catch it. Loads the \target if needed. + def ===(other) + other === load_target + end + + def to_ary + load_target.dup + end + alias_method :to_a, :to_ary + + def <<(*records) + @association.concat(records) && self + end + alias_method :push, :<< + + def clear + delete_all + self + end + + def reload + @association.reload + self + end + + def new(*args, &block) + if @association.is_a?(HasManyThroughAssociation) + @association.build(*args, &block) + else + method_missing(:new, *args, &block) + end + end + end + end +end diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb index b9c9919e7a..bcaea5ded4 100644 --- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb +++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb @@ -1,7 +1,7 @@ module ActiveRecord # = Active Record Has And Belongs To Many Association module Associations - class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc: + class HasAndBelongsToManyAssociation < CollectionAssociation #:nodoc: attr_reader :join_table def initialize(owner, reflection) @@ -9,28 +9,26 @@ module ActiveRecord super end - protected + def insert_record(record, validate = true) + return if record.new_record? && !record.save(:validate => validate) - def insert_record(record, validate = true) - return if record.new_record? && !record.save(:validate => validate) + if @reflection.options[:insert_sql] + @owner.connection.insert(interpolate(@reflection.options[:insert_sql], record)) + else + stmt = join_table.compile_insert( + join_table[@reflection.foreign_key] => @owner.id, + join_table[@reflection.association_foreign_key] => record.id + ) - if @reflection.options[:insert_sql] - @owner.connection.insert(interpolate(@reflection.options[:insert_sql], record)) - else - stmt = join_table.compile_insert( - join_table[@reflection.foreign_key] => @owner.id, - join_table[@reflection.association_foreign_key] => record.id - ) - - @owner.connection.insert stmt.to_sql - end - - record + @owner.connection.insert stmt.to_sql end - def association_scope - super.joins(construct_joins) - end + record + end + + def association_scope + super.joins(construct_joins) + end private diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index 543b073393..91565b247a 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -5,13 +5,11 @@ module ActiveRecord # # If the association has a <tt>:through</tt> option further specialization # is provided by its child HasManyThroughAssociation. - class HasManyAssociation < AssociationCollection #:nodoc: - protected - - def insert_record(record, validate = true) - set_owner_attributes(record) - record.save(:validate => validate) - end + class HasManyAssociation < CollectionAssociation #:nodoc: + def insert_record(record, validate = true) + set_owner_attributes(record) + record.save(:validate => validate) + end private 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 c4d3ef8fef..664c284d45 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -22,7 +22,7 @@ module ActiveRecord end end - def <<(*records) + def concat(*records) unless @owner.new_record? records.flatten.each do |record| raise_on_type_mismatch(record) @@ -33,19 +33,17 @@ module ActiveRecord super end - protected - - def insert_record(record, validate = true) - return if record.new_record? && !record.save(:validate => validate) - through_record(record).save! - update_counter(1) - record - end + def insert_record(record, validate = true) + return if record.new_record? && !record.save(:validate => validate) + through_record(record).save! + update_counter(1) + record + end private def through_record(record) - through_association = @owner.send(:association_proxy, @reflection.through_reflection.name) + through_association = @owner.association(@reflection.through_reflection.name) attributes = construct_join_attributes(record) through_record = Array.wrap(through_association.target).find { |candidate| @@ -95,7 +93,7 @@ module ActiveRecord end def delete_records(records, method) - through = @owner.send(:association_proxy, @reflection.through_reflection.name) + through = @owner.association(@reflection.through_reflection.name) scope = through.scoped.where(construct_join_attributes(*records)) case method 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 149daf69f7..112b773ec4 100644 --- a/activerecord/lib/active_record/associations/has_one_through_association.rb +++ b/activerecord/lib/active_record/associations/has_one_through_association.rb @@ -12,7 +12,7 @@ module ActiveRecord private def create_through_record(record) - through_proxy = @owner.send(:association_proxy, @reflection.through_reflection.name) + through_proxy = @owner.association(@reflection.through_reflection.name) through_record = through_proxy.send(:load_target) if through_record && !record diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb index 7f92d9712a..0aa647c63d 100644 --- a/activerecord/lib/active_record/associations/singular_association.rb +++ b/activerecord/lib/active_record/associations/singular_association.rb @@ -1,6 +1,6 @@ module ActiveRecord module Associations - class SingularAssociation < AssociationProxy #:nodoc: + class SingularAssociation < Association #:nodoc: def create(attributes = {}) new_record(:create, attributes) end @@ -29,7 +29,7 @@ module ActiveRecord end def check_record(record) - record = record.target if AssociationProxy === record + record = record.target if Association === record raise_on_type_mismatch(record) if record record end diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index 95357ee5dc..72499ea5b8 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -227,7 +227,7 @@ module ActiveRecord # unless the parent is/was a new record itself. def associated_records_to_validate_or_save(association, new_record, autosave) if new_record - association + association && association.target elsif autosave association.target.find_all { |record| record.changed_for_autosave? } else @@ -247,9 +247,9 @@ module ActiveRecord # Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is # turned on for the association. def validate_single_association(reflection) - if (association = association_instance_get(reflection.name)) && !association.target.nil? - association_valid?(reflection, association) - end + association = association_instance_get(reflection.name) + record = association && association.target + association_valid?(reflection, record) if record end # Validate the associated records if <tt>:validate</tt> or @@ -266,12 +266,12 @@ module ActiveRecord # Returns whether or not the association is valid and applies any errors to # the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt> # enabled records if they're marked_for_destruction? or destroyed. - def association_valid?(reflection, association) - return true if association.destroyed? || association.marked_for_destruction? + def association_valid?(reflection, record) + return true if record.destroyed? || record.marked_for_destruction? - unless valid = association.valid? + unless valid = record.valid? if reflection.options[:autosave] - association.errors.each do |attribute, message| + record.errors.each do |attribute, message| attribute = "#{reflection.name}.#{attribute}" errors[attribute] << message errors[attribute].uniq! @@ -309,12 +309,12 @@ module ActiveRecord saved = true if autosave && record.marked_for_destruction? - association.destroy(record) + association.proxy.destroy(record) elsif autosave != false && (@new_record_before_save || record.new_record?) if autosave - saved = association.send(:insert_record, record, false) + saved = association.insert_record(record, false) else - association.send(:insert_record, record) + association.insert_record(record) end elsif autosave saved = record.save(:validate => false) @@ -338,16 +338,18 @@ module ActiveRecord # This all happens inside a transaction, _if_ the Transactions module is included into # ActiveRecord::Base after the AutosaveAssociation module, which it does by default. def save_has_one_association(reflection) - if (association = association_instance_get(reflection.name)) && !association.target.nil? && !association.destroyed? + association = association_instance_get(reflection.name) + record = association && association.load_target + if record && !record.destroyed? autosave = reflection.options[:autosave] - if autosave && association.marked_for_destruction? - association.destroy + if autosave && record.marked_for_destruction? + record.destroy else key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id - if autosave != false && (new_record? || association.new_record? || association[reflection.foreign_key] != key || autosave) - association[reflection.foreign_key] = key - saved = association.save(:validate => !autosave) + if autosave != false && (new_record? || record.new_record? || record[reflection.foreign_key] != key || autosave) + record[reflection.foreign_key] = key + saved = record.save(:validate => !autosave) raise ActiveRecord::Rollback if !saved && autosave saved end @@ -359,16 +361,18 @@ module ActiveRecord # # In addition, it will destroy the association if it was marked for destruction. def save_belongs_to_association(reflection) - if (association = association_instance_get(reflection.name)) && !association.destroyed? + association = association_instance_get(reflection.name) + record = association && association.load_target + if record && !record.destroyed? autosave = reflection.options[:autosave] - if autosave && association.marked_for_destruction? - association.destroy + if autosave && record.marked_for_destruction? + record.destroy elsif autosave != false - saved = association.save(:validate => !autosave) if association.new_record? || (autosave && association.changed_for_autosave?) + saved = record.save(:validate => !autosave) if record.new_record? || (autosave && record.changed_for_autosave?) if association.updated? - association_id = association.send(reflection.options[:primary_key] || :id) + association_id = record.send(reflection.options[:primary_key] || :id) self[reflection.foreign_key] = association_id association.loaded! end diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index fc68d7254a..9bbcf71603 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -387,13 +387,13 @@ module ActiveRecord end end - association = send(association_name) + association = association(association_name) existing_records = if association.loaded? - association.to_a + association.target else attribute_ids = attributes_collection.map {|a| a['id'] || a[:id] }.compact - attribute_ids.empty? ? [] : association.all(:conditions => {association.primary_key => attribute_ids}) + attribute_ids.empty? ? [] : association.scoped.where(association.klass.primary_key => attribute_ids) end attributes_collection.each do |attributes| @@ -408,12 +408,12 @@ module ActiveRecord unless association.loaded? || call_reject_if(association_name, attributes) # Make sure we are operating on the actual object which is in the association's # proxy_target array (either by finding it, or adding it if not found) - target_record = association.proxy_target.detect { |record| record == existing_record } + target_record = association.target.detect { |record| record == existing_record } if target_record existing_record = target_record else - association.send(:add_to_target, existing_record) + association.add_to_target(existing_record) end end diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 0a0e8d650c..4093a1a209 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -313,7 +313,7 @@ module ActiveRecord macro == :belongs_to end - def proxy_class + def association_class case macro when :belongs_to if options[:polymorphic] diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 65852cb281..9633fd3d82 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -21,7 +21,7 @@ module ActiveRecord when ActiveRecord::Relation value.select_values = [value.klass.arel_table['id']] if value.select_values.empty? attribute.in(value.arel.ast) - when Array, ActiveRecord::Associations::AssociationCollection + when Array, ActiveRecord::Associations::CollectionProxy values = value.to_a.map { |x| x.is_a?(ActiveRecord::Base) ? x.id : x } diff --git a/activerecord/test/cases/associations/association_proxy_test.rb b/activerecord/test/cases/associations/association_proxy_test.rb deleted file mode 100644 index 55d8da4c4e..0000000000 --- a/activerecord/test/cases/associations/association_proxy_test.rb +++ /dev/null @@ -1,52 +0,0 @@ -require "cases/helper" - -module ActiveRecord - module Associations - class AsssociationProxyTest < ActiveRecord::TestCase - class FakeOwner - attr_accessor :new_record - alias :new_record? :new_record - - def initialize - @new_record = false - end - end - - class FakeReflection < Struct.new(:options, :klass) - def initialize options = {}, klass = nil - super - end - - def check_validity! - true - end - end - - class FakeTarget - end - - class FakeTargetProxy < AssociationProxy - def association_scope - true - end - - def find_target - FakeTarget.new - end - end - - def test_method_missing_error - reflection = FakeReflection.new({}, Object.new) - owner = FakeOwner.new - proxy = FakeTargetProxy.new(owner, reflection) - - exception = assert_raises(NoMethodError) do - proxy.omg - end - - assert_match('omg', exception.message) - assert_match(FakeTarget.name, exception.message) - end - end - end -end diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index 01073bca3d..9006914508 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -50,11 +50,6 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_nothing_raised { account.firm = account.firm } end - def test_triple_equality - assert Client.find(3).firm === Firm - assert Firm === Client.find(3).firm - end - def test_type_mismatch assert_raise(ActiveRecord::AssociationTypeMismatch) { Account.find(1).firm = 1 } assert_raise(ActiveRecord::AssociationTypeMismatch) { Account.find(1).firm = Project.find(1) } @@ -569,13 +564,15 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase def test_reloading_association_with_key_change client = companies(:second_client) - firm = client.firm # note this is a proxy object + firm = client.association(:firm) client.firm = companies(:another_firm) - assert_equal companies(:another_firm), firm.reload + firm.reload + assert_equal companies(:another_firm), firm.target client.client_of = companies(:first_firm).id - assert_equal companies(:first_firm), firm.reload + firm.reload + assert_equal companies(:first_firm), firm.target end def test_polymorphic_counter_cache diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index 6b73a35905..c1dad5e246 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -66,11 +66,6 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert_nothing_raised { company.account = company.account } end - def test_triple_equality - assert Account === companies(:first_firm).account - assert companies(:first_firm).account === Account - end - def test_type_mismatch assert_raise(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).account = 1 } assert_raise(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).account = Project.find(1) } @@ -320,7 +315,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase def test_creation_failure_without_dependent_option pirate = pirates(:blackbeard) - orig_ship = pirate.ship.target + orig_ship = pirate.ship assert_equal ships(:black_pearl), orig_ship new_ship = pirate.create_ship @@ -333,7 +328,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase def test_creation_failure_with_dependent_option pirate = pirates(:blackbeard).becomes(DestructivePirate) - orig_ship = pirate.dependent_ship.target + orig_ship = pirate.dependent_ship new_ship = pirate.create_dependent_ship assert new_ship.new_record? diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb index 91d3025468..9ca5f88330 100644 --- a/activerecord/test/cases/associations/has_one_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb @@ -139,7 +139,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase def test_assigning_association_correctly_assigns_target new_member = Member.create(:name => "Chris") new_member.club = new_club = Club.create(:name => "LRUG") - assert_equal new_club, new_member.club.target + assert_equal new_club, new_member.association(:club).target end def test_has_one_through_proxy_should_not_respond_to_private_methods @@ -197,7 +197,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase MemberDetail.find(:all, :include => :member_type) end @new_detail = @member_details[0] - assert @new_detail.send(:association_proxy, :member_type).loaded? + assert @new_detail.send(:association, :member_type).loaded? assert_not_nil assert_no_queries { @new_detail.member_type } end diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb index e9a57a00a0..76282213d8 100644 --- a/activerecord/test/cases/associations/inverse_associations_test.rb +++ b/activerecord/test/cases/associations/inverse_associations_test.rb @@ -137,7 +137,7 @@ class InverseHasOneTests < ActiveRecord::TestCase def test_parent_instance_should_be_shared_with_newly_created_child_via_bang_method m = Man.find(:first) - f = m.face.create!(:description => 'haunted') + f = m.create_face!(:description => 'haunted') assert_not_nil f.man assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance" m.name = 'Bongo' @@ -158,18 +158,6 @@ class InverseHasOneTests < ActiveRecord::TestCase assert_equal m.name, f.man.name, "Name of man should be the same after changes to replaced-child-owned instance" end - def test_parent_instance_should_be_shared_with_replaced_via_method_child - m = Man.find(:first) - f = Face.new(:description => 'haunted') - m.face.replace(f) - assert_not_nil f.man - assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance" - m.name = 'Bongo' - assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance" - f.man.name = 'Mungo' - assert_equal m.name, f.man.name, "Name of man should be the same after changes to replaced-child-owned instance" - end - def test_trying_to_use_inverses_that_dont_exist_should_raise_an_error assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Man.find(:first).dirty_face } end @@ -271,18 +259,6 @@ class InverseHasManyTests < ActiveRecord::TestCase assert_equal m.name, i.man.name, "Name of man should be the same after changes to replaced-child-owned instance" end - def test_parent_instance_should_be_shared_with_replaced_via_method_children - m = Man.find(:first) - i = Interest.new(:topic => 'Industrial Revolution Re-enactment') - m.interests.replace([i]) - assert_not_nil i.man - assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance" - m.name = 'Bongo' - assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance" - i.man.name = 'Mungo' - assert_equal m.name, i.man.name, "Name of man should be the same after changes to replaced-child-owned instance" - end - def test_trying_to_use_inverses_that_dont_exist_should_raise_an_error assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Man.find(:first).secret_interests } end @@ -366,19 +342,6 @@ class InverseBelongsToTests < ActiveRecord::TestCase assert_equal f.description, m.face.description, "Description of face should be the same after changes to replaced-parent-owned instance" end - def test_child_instance_should_be_shared_with_replaced_via_method_parent - f = faces(:trusting) - assert_not_nil f.man - m = Man.new(:name => 'Charles') - f.man.replace(m) - assert_not_nil m.face - assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance" - f.description = 'gormless' - assert_equal f.description, m.face.description, "Description of face should be the same after changes to child instance" - m.face.description = 'pleasing' - assert_equal f.description, m.face.description, "Description of face should be the same after changes to replaced-parent-owned instance" - end - def test_trying_to_use_inverses_that_dont_exist_should_raise_an_error assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Face.find(:first).horrible_man } end @@ -434,7 +397,7 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase new_man = Man.new assert_not_nil face.polymorphic_man - face.polymorphic_man.replace(new_man) + face.polymorphic_man = new_man assert_equal face.description, new_man.polymorphic_face.description, "Description of face should be the same before changes to parent instance" face.description = 'Bongo' diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb index c50fcd3f33..0e2f4a33cc 100644 --- a/activerecord/test/cases/associations/join_model_test.rb +++ b/activerecord/test/cases/associations/join_model_test.rb @@ -153,7 +153,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase def test_create_polymorphic_has_one_with_scope old_count = Tagging.count - tagging = posts(:welcome).tagging.create(:tag => tags(:misc)) + tagging = posts(:welcome).create_tagging(:tag => tags(:misc)) assert_equal "Post", tagging.taggable_type assert_equal old_count+1, Tagging.count end diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb index 83c605d2bb..a1996fcf27 100644 --- a/activerecord/test/cases/associations_test.rb +++ b/activerecord/test/cases/associations_test.rb @@ -133,25 +133,6 @@ end class AssociationProxyTest < ActiveRecord::TestCase fixtures :authors, :posts, :categorizations, :categories, :developers, :projects, :developers_projects - def test_proxy_accessors - welcome = posts(:welcome) - assert_equal welcome, welcome.author.proxy_owner - assert_equal welcome.class.reflect_on_association(:author), welcome.author.proxy_reflection - welcome.author.class # force load target - assert_equal welcome.author, welcome.author.proxy_target - - david = authors(:david) - assert_equal david, david.posts.proxy_owner - assert_equal david.class.reflect_on_association(:posts), david.posts.proxy_reflection - david.posts.class # force load target - assert_equal david.posts, david.posts.proxy_target - - assert_equal david, david.posts_with_extension.testing_proxy_owner - assert_equal david.class.reflect_on_association(:posts_with_extension), david.posts_with_extension.testing_proxy_reflection - david.posts_with_extension.class # force load target - assert_equal david.posts_with_extension, david.posts_with_extension.testing_proxy_target - end - def test_push_does_not_load_target david = authors(:david) @@ -216,16 +197,6 @@ class AssociationProxyTest < ActiveRecord::TestCase assert_equal post.body, "More cool stuff!" end - def test_failed_reload_returns_nil - p = setup_dangling_association - assert_nil p.author.reload - end - - def test_failed_reset_returns_nil - p = setup_dangling_association - assert_nil p.author.reset - end - def test_reload_returns_assocition david = developers(:david) assert_nothing_raised do @@ -240,13 +211,6 @@ class AssociationProxyTest < ActiveRecord::TestCase [*author] end end - - def setup_dangling_association - josh = Author.create(:name => "Josh") - p = Post.create(:title => "New on Edge", :body => "More cool stuff!", :author => josh) - josh.destroy - p - end end class OverridingAssociationsTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index dd2ce786aa..8998879261 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -112,7 +112,7 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas def test_build_before_child_saved firm = Firm.find(1) - account = firm.account.build("credit_limit" => 1000) + account = firm.build_account("credit_limit" => 1000) assert_equal account, firm.account assert !account.persisted? assert firm.save diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb index ed5e1e0cba..d05b0ff947 100644 --- a/activerecord/test/cases/named_scope_test.rb +++ b/activerecord/test/cases/named_scope_test.rb @@ -448,7 +448,7 @@ class NamedScopeTest < ActiveRecord::TestCase [:destroy_all, :reset, :delete_all].each do |method| before = post.comments.containing_the_letter_e - post.comments.send(method) + post.association(:comments).send(method) assert before.object_id != post.comments.containing_the_letter_e.object_id, "AssociationCollection##{method} should reset the named scopes cache" end end diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index d1afe7376a..c57ab7ed28 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -155,7 +155,7 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase man = Man.find man.id man.interests_attributes = [{:id => interest.id, :topic => 'gardening'}] assert_equal man.interests.first.topic, man.interests[0].topic - end + end end class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase @@ -918,16 +918,16 @@ class TestHasManyAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveR test "if association is not loaded and association record is saved and then in memory record attributes should be saved" do @ship.parts_attributes=[{:id => @part.id,:name =>'Deck'}] - assert_equal 1, @ship.parts.proxy_target.size + assert_equal 1, @ship.association(:parts).target.size assert_equal 'Deck', @ship.parts[0].name end test "if association is not loaded and child doesn't change and I am saving a grandchild then in memory record should be used" do @ship.parts_attributes=[{:id => @part.id,:trinkets_attributes =>[{:id => @trinket.id, :name => 'Ruby'}]}] - assert_equal 1, @ship.parts.proxy_target.size + assert_equal 1, @ship.association(:parts).target.size assert_equal 'Mast', @ship.parts[0].name - assert_no_difference("@ship.parts[0].trinkets.proxy_target.size") do - @ship.parts[0].trinkets.proxy_target.size + assert_no_difference("@ship.parts[0].association(:trinkets).target.size") do + @ship.parts[0].association(:trinkets).target.size end assert_equal 'Ruby', @ship.parts[0].trinkets[0].name @ship.save |