Skip to content
Browse files

Split AssociationProxy into an Association class (and subclasses) whi…

…ch manages the association, and a CollectionProxy class which is *only* a proxy. Singular associations no longer have a proxy. See CHANGELOG for more.
  • Loading branch information...
1 parent 1d9f26e commit 1644663ba7f678d178deab2bf1629dc05626f85b @jonleighton jonleighton committed
Showing with 392 additions and 502 deletions.
  1. +12 −0 activerecord/CHANGELOG
  2. +11 −11 activerecord/lib/active_record/association_preload.rb
  3. +30 −34 activerecord/lib/active_record/associations.rb
  4. +65 −152 activerecord/lib/active_record/associations/{association_proxy.rb → association.rb}
  5. +7 −7 activerecord/lib/active_record/associations/class_methods/join_dependency.rb
  6. +54 −77 activerecord/lib/active_record/associations/{association_collection.rb → collection_association.rb}
  7. +127 −0 activerecord/lib/active_record/associations/collection_proxy.rb
  8. +17 −19 activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
  9. +5 −7 activerecord/lib/active_record/associations/has_many_association.rb
  10. +9 −11 activerecord/lib/active_record/associations/has_many_through_association.rb
  11. +1 −1 activerecord/lib/active_record/associations/has_one_through_association.rb
  12. +2 −2 activerecord/lib/active_record/associations/singular_association.rb
  13. +26 −22 activerecord/lib/active_record/autosave_association.rb
  14. +5 −5 activerecord/lib/active_record/nested_attributes.rb
  15. +1 −1 activerecord/lib/active_record/reflection.rb
  16. +1 −1 activerecord/lib/active_record/relation/predicate_builder.rb
  17. +0 −52 activerecord/test/cases/associations/association_proxy_test.rb
  18. +5 −8 activerecord/test/cases/associations/belongs_to_associations_test.rb
  19. +2 −7 activerecord/test/cases/associations/has_one_associations_test.rb
  20. +2 −2 activerecord/test/cases/associations/has_one_through_associations_test.rb
  21. +2 −39 activerecord/test/cases/associations/inverse_associations_test.rb
  22. +1 −1 activerecord/test/cases/associations/join_model_test.rb
  23. +0 −36 activerecord/test/cases/associations_test.rb
  24. +1 −1 activerecord/test/cases/autosave_association_test.rb
  25. +1 −1 activerecord/test/cases/named_scope_test.rb
  26. +5 −5 activerecord/test/cases/nested_attributes_test.rb
View
12 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:
View
22 activerecord/lib/active_record/association_preload.rb
@@ -124,16 +124,16 @@ def preload_one_association(records, association, preload_options={})
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 @@ def set_association_single_records(id_to_record_map, reflection_name, associated
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 @@ def preload_has_and_belongs_to_many_association(records, reflection, preload_opt
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 @@ def preload_has_and_belongs_to_many_association(records, reflection, preload_opt
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 @@ def preload_has_many_association(records, reflection, preload_options={})
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 @@ def preload_through_records(records, reflection, through_association)
# 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 @@ def preload_through_records(records, reflection, through_association)
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 = {}
View
64 activerecord/lib/active_record/associations.rb
@@ -118,17 +118,19 @@ module Associations # :nodoc:
# 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 @@ def clear_association_cache #:nodoc:
# :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 @@ def join_table_name(first_table_name, second_table_name)
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 @@ def association_accessor_methods(reflection)
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 @@ def collection_reader_method(reflection)
association.reload
end
- association
+ association.proxy
end
redefine_method("#{reflection.name.to_s.singularize}_ids") do
@@ -1621,7 +1617,7 @@ def collection_accessor_methods(reflection, writer = true)
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 @@ def association_constructor_methods(reflection)
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
View
217 ..._record/associations/association_proxy.rb → ...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 @@ def stale_target?
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
View
14 activerecord/lib/active_record/associations/class_methods/join_dependency.rb
@@ -209,10 +209,10 @@ def construct_association(record, join_part, row)
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 @@ def construct_association(record, join_part, row)
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
View
131 ...rd/associations/association_collection.rb → ...rd/associations/collection_association.rb
@@ -17,8 +17,26 @@ module Associations
#
# 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 @@ def last(*args)
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 @@ def create!(attrs = {}, &block)
# 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 @@ def <<(*records)
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 @@ def delete_all
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 @@ def many?
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 @@ def include?(record)
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 @@ def fetch_first_or_last_using_find?(args)
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
View
127 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
View
36 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 @@ def initialize(owner, reflection)
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
View
12 activerecord/lib/active_record/associations/has_many_association.rb
@@ -5,13 +5,11 @@ module Associations
#
# 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
View
20 activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -22,7 +22,7 @@ def size
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 @@ def <<(*records)
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 @@ def update_through_counter?(method)
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
View
2 activerecord/lib/active_record/associations/has_one_through_association.rb
@@ -12,7 +12,7 @@ def replace(record)
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
View
4 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 @@ def set_new_record(record)
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
View
48 activerecord/lib/active_record/autosave_association.rb
@@ -227,7 +227,7 @@ def changed_for_autosave?
# 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 @@ def nested_records_changed_for_autosave?
# 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 @@ def validate_collection_association(reflection)
# 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 @@ def save_collection_association(reflection)
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 @@ def save_collection_association(reflection)
# 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 @@ def save_has_one_association(reflection)
#
# 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
View
10 activerecord/lib/active_record/nested_attributes.rb
@@ -387,13 +387,13 @@ def assign_nested_attributes_for_collection_association(association_name, attrib
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 @@ def assign_nested_attributes_for_collection_association(association_name, attrib
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
View
2 activerecord/lib/active_record/reflection.rb
@@ -313,7 +313,7 @@ def belongs_to?
macro == :belongs_to
end
- def proxy_class
+ def association_class
case macro
when :belongs_to
if options[:polymorphic]
View
2 activerecord/lib/active_record/relation/predicate_builder.rb
@@ -21,7 +21,7 @@ def self.build_from_hash(engine, attributes, default_table)
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
}
View
52 activerecord/test/cases/associations/association_proxy_test.rb
@@ -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
View
13 activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -50,11 +50,6 @@ def test_proxy_assignment
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 @@ def test_polymorphic_reassignment_of_associated_type_updates_the_object
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
View
9 activerecord/test/cases/associations/has_one_associations_test.rb
@@ -66,11 +66,6 @@ def test_proxy_assignment
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 @@ def test_attributes_are_being_set_when_initialized_from_has_one_association_with
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 @@ def test_creation_failure_without_dependent_option
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?
View
4 activerecord/test/cases/associations/has_one_through_associations_test.rb
@@ -139,7 +139,7 @@ def test_uninitialized_has_one_through_should_return_nil_for_unsaved_record
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 @@ def test_preloading_has_one_through_on_belongs_to
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
View
41 activerecord/test/cases/associations/inverse_associations_test.rb
@@ -137,7 +137,7 @@ def test_parent_instance_should_be_shared_with_newly_created_child
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 @@ def test_parent_instance_should_be_shared_with_replaced_via_accessor_child
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 @@ def test_parent_instance_should_be_shared_with_replaced_via_accessor_children
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 @@ def test_child_instance_should_be_shared_with_replaced_via_accessor_parent
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 @@ def test_child_instance_should_be_shared_with_replaced_via_method_parent
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'
View
2 activerecord/test/cases/associations/join_model_test.rb
@@ -153,7 +153,7 @@ def test_create_bang_polymorphic_with_has_many_scope
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
View
36 activerecord/test/cases/associations_test.rb
@@ -133,25 +133,6 @@ def test_force_reload_is_uncached
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 @@ def test_create_with_bang_via_association_with_block
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 @@ def test_splat_does_not_invoke_to_a_on_singular_targets
[*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
View
2 activerecord/test/cases/autosave_association_test.rb
@@ -112,7 +112,7 @@ def test_save_succeeds_for_invalid_has_one_with_validate_false
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
View
2 activerecord/test/cases/named_scope_test.rb
@@ -448,7 +448,7 @@ def test_scopes_are_reset_on_association_reload
[: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
View
10 activerecord/test/cases/nested_attributes_test.rb
@@ -155,7 +155,7 @@ def test_first_and_array_index_zero_methods_return_the_same_value_when_nested_at
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 @@ def setup
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

0 comments on commit 1644663

Please sign in to comment.
Something went wrong with that request. Please try again.