diff options
Diffstat (limited to 'activerecord/lib/active_record/associations')
11 files changed, 99 insertions, 32 deletions
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index 1b9dbb1790..db0553ea76 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -236,6 +236,7 @@ module ActiveRecord skip_assign = [reflection.foreign_key, reflection.type].compact attributes = create_scope.except(*(record.changed - skip_assign)) record.assign_attributes(attributes) + set_inverse_instance(record) end end end diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index a9525436fb..aa5551fe0c 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -101,6 +101,7 @@ module ActiveRecord scope.includes! item.includes_values scope.where_values += item.where_values + scope.order_values |= item.order_values end end diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb index 9ac561b997..3ba6a71366 100644 --- a/activerecord/lib/active_record/associations/builder/belongs_to.rb +++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb @@ -66,8 +66,19 @@ module ActiveRecord::Associations::Builder def add_touch_callbacks(reflection) mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 def belongs_to_touch_after_save_or_destroy_for_#{name} - record = #{name} + foreign_key_field = #{reflection.foreign_key.inspect} + old_foreign_id = attribute_was(foreign_key_field) + + if old_foreign_id + reflection_klass = #{reflection.klass} + old_record = reflection_klass.find_by(reflection_klass.primary_key => old_foreign_id) + + if old_record + old_record.touch #{options[:touch].inspect if options[:touch] != true} + end + end + record = #{name} unless record.nil? || record.new_record? record.touch #{options[:touch].inspect if options[:touch] != true} end diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 2385c90c1a..2a00ac1386 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -79,8 +79,20 @@ module ActiveRecord if block_given? load_target.find(*args) { |*block_args| yield(*block_args) } else - if options[:finder_sql] || options[:inverse_of] + if options[:finder_sql] find_by_scan(*args) + elsif options[:inverse_of] + args = args.flatten + raise RecordNotFound, "Couldn't find #{scope.klass.name} without an ID" if args.blank? + + result = find_by_scan(*args) + + result_size = Array(result).size + if !result || result_size != args.size + scope.raise_record_not_found_exception!(args, result_size, args.size) + else + result + end else scope.find(*args) end diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index fd084548c5..56e57cc36e 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -228,6 +228,7 @@ module ActiveRecord def build(attributes = {}, &block) @association.build(attributes, &block) end + alias_method :new, :build # Returns a new object of the collection type that has been instantiated with # attributes, linked to this object and that has already been saved (if it @@ -832,8 +833,6 @@ module ActiveRecord @association.include?(record) end - alias_method :new, :build - def proxy_association @association end @@ -848,10 +847,8 @@ module ActiveRecord # Returns a <tt>Relation</tt> object for the records in this association def scope - association = @association - - @association.scope.extending! do - define_method(:proxy_association) { association } + @association.scope.tap do |scope| + scope.proxy_association = @association end end diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb index f40368cfeb..28e081c03c 100644 --- a/activerecord/lib/active_record/associations/join_dependency.rb +++ b/activerecord/lib/active_record/associations/join_dependency.rb @@ -5,10 +5,31 @@ module ActiveRecord autoload :JoinBase, 'active_record/associations/join_dependency/join_base' autoload :JoinAssociation, 'active_record/associations/join_dependency/join_association' - attr_reader :join_parts, :reflections, :alias_tracker, :active_record - + attr_reader :join_parts, :reflections, :alias_tracker, :base_klass + + # base is the base class on which operation is taking place. + # associations is the list of associations which are joined using hash, symbol or array. + # joins is the list of all string join commnads and arel nodes. + # + # Example : + # + # class Physician < ActiveRecord::Base + # has_many :appointments + # has_many :patients, through: :appointments + # end + # + # If I execute `@physician.patients.to_a` then + # base #=> Physician + # associations #=> [] + # joins #=> [#<Arel::Nodes::InnerJoin: ...] + # + # However if I execute `Physician.joins(:appointments).to_a` then + # base #=> Physician + # associations #=> [:appointments] + # joins #=> [] + # def initialize(base, associations, joins) - @active_record = base + @base_klass = base @table_joins = joins @join_parts = [JoinBase.new(base)] @associations = {} @@ -54,10 +75,12 @@ module ActiveRecord parent }.uniq - remove_duplicate_results!(active_record, records, @associations) + remove_duplicate_results!(base_klass, records, @associations) records end + protected + def remove_duplicate_results!(base, records, associations) case associations when Symbol, String @@ -88,8 +111,6 @@ module ActiveRecord end end - protected - def cache_joined_association(association) associations = [] parent = association.parent @@ -108,8 +129,8 @@ module ActiveRecord parent ||= join_parts.last case associations when Symbol, String - reflection = parent.reflections[associations.to_s.intern] or - raise ConfigurationError, "Association named '#{ associations }' was not found; perhaps you misspelled it?" + reflection = parent.reflections[associations.intern] or + raise ConfigurationError, "Association named '#{ associations }' was not found on #{ parent.base_klass.name }; perhaps you misspelled it?" unless join_association = find_join_association(reflection, parent) @reflections << reflection join_association = build_join_association(reflection, parent) diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb index 0d3b4dbab1..e4d17451dc 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb @@ -55,14 +55,19 @@ module ActiveRecord def find_parent_in(other_join_dependency) other_join_dependency.join_parts.detect do |join_part| - parent == join_part + case parent + when JoinBase + parent.base_klass == join_part.base_klass + else + parent == join_part + end end end - def join_to(relation) + def join_to(manager) tables = @tables.dup foreign_table = parent_table - foreign_klass = parent.active_record + foreign_klass = parent.base_klass # The chain starts with the target table, but we want to end with it here (makes # more sense in this context), so we reverse @@ -75,7 +80,7 @@ module ActiveRecord foreign_key = reflection.foreign_key when :has_and_belongs_to_many # Join the join table first... - relation.from(join( + manager.from(join( table, table[reflection.foreign_key]. eq(foreign_table[reflection.active_record_primary_key]) @@ -109,15 +114,30 @@ module ActiveRecord constraint = constraint.and(item.arel.constraints) unless item.arel.constraints.empty? end - relation.from(join(table, constraint)) + manager.from(join(table, constraint)) # The current table in this iteration becomes the foreign table in the next foreign_table, foreign_klass = table, reflection.klass end - relation + manager end + # Builds equality condition. + # + # Example: + # + # class Physician < ActiveRecord::Base + # has_many :appointments + # end + # + # If I execute `Physician.joins(:appointments).to_a` then + # reflection #=> #<ActiveRecord::Reflection::AssociationReflection @macro=:has_many ...> + # table #=> #<Arel::Table @name="appointments" ...> + # key #=> physician_id + # foreign_table #=> #<Arel::Table @name="physicians" ...> + # foreign_key #=> id + # def build_constraint(reflection, table, key, foreign_table, foreign_key) constraint = table[key].eq(foreign_table[foreign_key]) diff --git a/activerecord/lib/active_record/associations/join_dependency/join_base.rb b/activerecord/lib/active_record/associations/join_dependency/join_base.rb index 3920e84976..a7dacdbfd6 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_base.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_base.rb @@ -4,7 +4,7 @@ module ActiveRecord class JoinBase < JoinPart # :nodoc: def ==(other) other.class == self.class && - other.active_record == active_record + other.base_klass == base_klass end def aliased_prefix @@ -16,7 +16,7 @@ module ActiveRecord end def aliased_table_name - active_record.table_name + base_klass.table_name end end end diff --git a/activerecord/lib/active_record/associations/join_dependency/join_part.rb b/activerecord/lib/active_record/associations/join_dependency/join_part.rb index 411a2ca7e4..b534569063 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_part.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_part.rb @@ -11,12 +11,12 @@ module ActiveRecord # The Active Record class which this join part is associated 'about'; for a JoinBase # this is the actual base model, for a JoinAssociation this is the target model of the # association. - attr_reader :active_record + attr_reader :base_klass - delegate :table_name, :column_names, :primary_key, :reflections, :arel_engine, :to => :active_record + delegate :table_name, :column_names, :primary_key, :reflections, :arel_engine, :to => :base_klass - def initialize(active_record) - @active_record = active_record + def initialize(base_klass) + @base_klass = base_klass @cached_record = {} @column_names_with_alias = nil end @@ -70,7 +70,7 @@ module ActiveRecord end def instantiate(row) - @cached_record[record_id(row)] ||= active_record.instantiate(extract_record(row)) + @cached_record[record_id(row)] ||= base_klass.instantiate(extract_record(row)) end end end diff --git a/activerecord/lib/active_record/associations/preloader/has_many_through.rb b/activerecord/lib/active_record/associations/preloader/has_many_through.rb index 38bc7ce7da..157b627ad5 100644 --- a/activerecord/lib/active_record/associations/preloader/has_many_through.rb +++ b/activerecord/lib/active_record/associations/preloader/has_many_through.rb @@ -5,9 +5,13 @@ module ActiveRecord include ThroughAssociation def associated_records_by_owner - super.each do |owner, records| - records.uniq! if reflection_scope.distinct_value + records_by_owner = super + + if reflection_scope.distinct_value + records_by_owner.each_value { |records| records.uniq! } end + + records_by_owner end end end diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb index 43520142bf..35f29b37a2 100644 --- a/activerecord/lib/active_record/associations/through_association.rb +++ b/activerecord/lib/active_record/associations/through_association.rb @@ -14,7 +14,7 @@ module ActiveRecord def target_scope scope = super chain[1..-1].each do |reflection| - scope = scope.merge( + scope.merge!( reflection.klass.all.with_default_scope. except(:select, :create_with, :includes, :preload, :joins, :eager_load) ) |