diff options
Diffstat (limited to 'activerecord/lib/active_record/associations')
6 files changed, 43 insertions, 57 deletions
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index 84d0493a60..1cb2b2d7c6 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -83,7 +83,7 @@ module ActiveRecord end def scope - target_scope.merge(association_scope) + target_scope.merge!(association_scope) end # The scope for this association. diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index badde9973f..120d75416c 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -25,7 +25,7 @@ module ActiveRecord chain_head, chain_tail = get_chain(reflection, association, alias_tracker) scope.extending! Array(reflection.options[:extend]) - add_constraints(scope, owner, klass, reflection, chain_head, chain_tail) + add_constraints(scope, owner, reflection, chain_head, chain_tail) end def join_type @@ -60,8 +60,8 @@ module ActiveRecord table.create_join(table, table.create_on(constraint), join_type) end - def last_chain_scope(scope, table, reflection, owner, association_klass) - join_keys = reflection.join_keys(association_klass) + def last_chain_scope(scope, table, reflection, owner) + join_keys = reflection.join_keys key = join_keys.key foreign_key = join_keys.foreign_key @@ -80,8 +80,8 @@ module ActiveRecord value_transformation.call(value) end - def next_chain_scope(scope, table, reflection, association_klass, foreign_table, next_reflection) - join_keys = reflection.join_keys(association_klass) + def next_chain_scope(scope, table, reflection, foreign_table, next_reflection) + join_keys = reflection.join_keys key = join_keys.key foreign_key = join_keys.foreign_key @@ -120,10 +120,10 @@ module ActiveRecord [runtime_reflection, previous_reflection] end - def add_constraints(scope, owner, association_klass, refl, chain_head, chain_tail) + def add_constraints(scope, owner, refl, chain_head, chain_tail) owner_reflection = chain_tail table = owner_reflection.alias_name - scope = last_chain_scope(scope, table, owner_reflection, owner, association_klass) + scope = last_chain_scope(scope, table, owner_reflection, owner) reflection = chain_head while reflection @@ -132,7 +132,7 @@ module ActiveRecord unless reflection == chain_tail foreign_table = next_reflection.alias_name - scope = next_chain_scope(scope, table, reflection, association_klass, foreign_table, next_reflection) + scope = next_chain_scope(scope, table, reflection, foreign_table, next_reflection) end # Exclude the scope of the association itself, because that diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 0437a79b84..77282e6463 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -30,13 +30,7 @@ module ActiveRecord reload end - if null_scope? - # Cache the proxy separately before the owner has an id - # or else a post-save proxy will still lack the id - @null_proxy ||= CollectionProxy.create(klass, self) - else - @proxy ||= CollectionProxy.create(klass, self) - end + CollectionProxy.create(klass, self) end # Implements the writer method, e.g. foo.items= for Foo.has_many :items @@ -315,9 +309,9 @@ module ActiveRecord record end - def scope(opts = {}) - scope = super() - scope.none! if opts.fetch(:nullify, true) && null_scope? + def scope + scope = super + scope.none! if null_scope? scope end diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 0d84805b4d..bc2f359c65 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -28,12 +28,9 @@ module ActiveRecord # is computed directly through SQL and does not trigger by itself the # instantiation of the actual post records. class CollectionProxy < Relation - delegate :exists?, :update_all, :arel, to: :scope - def initialize(klass, association) #:nodoc: @association = association super klass, klass.arel_table, klass.predicate_builder - merge! association.scope(nullify: false) end def target @@ -81,7 +78,7 @@ module ActiveRecord # # #<Pet id: nil, name: "Choo-Choo"> # # ] # - # person.pets.select(:id, :name ) + # person.pets.select(:id, :name) # # => [ # # #<Pet id: 1, name: "Fancy-Fancy">, # # #<Pet id: 2, name: "Spook">, @@ -956,19 +953,10 @@ module ActiveRecord @association end - # We don't want this object to be put on the scoping stack, because - # that could create an infinite loop where we call an @association - # method, which gets the current scope, which is this object, which - # delegates to @association, and so on. - def scoping - @association.scope.scoping { yield } - end - # Returns a <tt>Relation</tt> object for the records in this association def scope - @association.scope + @scope ||= @association.scope end - alias spawn scope # Equivalent to <tt>Array#==</tt>. Returns +true+ if the two arrays # contain the same number of elements and if each element is equal @@ -1100,6 +1088,7 @@ module ActiveRecord # person.pets(true) # fetches pets from the database # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>] def reload + @scope = nil proxy_association.reload self end @@ -1121,11 +1110,21 @@ module ActiveRecord # person.pets # fetches pets from the database # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>] def reset + @scope = nil proxy_association.reset proxy_association.reset_scope self end + delegate_methods = [ + QueryMethods, + SpawnMethods, + ].flat_map { |klass| + klass.public_instance_methods(false) + } - self.public_instance_methods(false) - [:select] + [:scoping] + + delegate(*delegate_methods, to: :scope) + private def find_nth_with_limit(index, limit) @@ -1149,6 +1148,18 @@ module ActiveRecord def exec_queries load_target end + + def respond_to_missing?(method, _) + scope.respond_to?(method) || super + end + + def method_missing(method, *args, &block) + if scope.respond_to?(method) + scope.public_send(method, *args, &block) + else + super + end + end end end end diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb index 87e0847ec1..8995b1e352 100644 --- a/activerecord/lib/active_record/associations/join_dependency.rb +++ b/activerecord/lib/active_record/associations/join_dependency.rb @@ -171,7 +171,7 @@ module ActiveRecord chain = child.reflection.chain foreign_table = parent.table foreign_klass = parent.base_klass - child.join_constraints(foreign_table, foreign_klass, child, join_type, tables, chain) + child.join_constraints(foreign_table, foreign_klass, join_type, tables, chain) end def make_outer_joins(parent, child) 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 f5fcba1236..97cfec0302 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb @@ -23,7 +23,7 @@ module ActiveRecord JoinInformation = Struct.new :joins, :binds - def join_constraints(foreign_table, foreign_klass, node, join_type, tables, chain) + def join_constraints(foreign_table, foreign_klass, join_type, tables, chain) joins = [] binds = [] tables = tables.reverse @@ -34,35 +34,16 @@ module ActiveRecord table = tables.shift klass = reflection.klass - join_keys = reflection.join_keys(klass) + join_keys = reflection.join_keys key = join_keys.key foreign_key = join_keys.foreign_key constraint = build_constraint(klass, table, key, foreign_table, foreign_key) predicate_builder = PredicateBuilder.new(TableMetadata.new(klass, table)) - scope_chain_items = reflection.scopes.map do |item| - if item.is_a?(Relation) - item - else - ActiveRecord::Relation.create(klass, table, predicate_builder) - .instance_exec(node, &item) - end - end + scope_chain_items = reflection.join_scopes(table, predicate_builder) + klass_scope = reflection.klass_join_scope(table, predicate_builder) - klass_scope = - if klass.current_scope - klass.current_scope.clone.tap { |scope| - scope.joins_values = [] - } - else - relation = ActiveRecord::Relation.create( - klass, - table, - predicate_builder, - ) - klass.send(:build_default_scope, relation) - end scope_chain_items.concat [klass_scope].compact rel = scope_chain_items.inject(scope_chain_items.shift) do |left, right| |