Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bf4/315a08632cfbd94c9fd33762e90457d5 to your computer and use it in GitHub Desktop.
Save bf4/315a08632cfbd94c9fd33762e90457d5 to your computer and use it in GitHub Desktop.
Rails 4.2.6 patch for autosave and accepts_nested_attributes_for from issuing N+1 queries on each eager loaded association
# We noticed that adding `accepts_nested_attributes_for :comments, allow_destroy: true`
# caused extra queries to be run when serializing comments in a post
# and including the post name in each comment resource object (this is JSON API, names changed):
#
# blog_id = params.permit(:id)[:id]
# Blog = Blog.where(id: blog_id).includes(posts: [:comments]).first
# comments = blog.posts.flat_map(&:comments)
# comment_fields = {comments: [:id, :title, :body, :post], posts: [:name]}
# comment_includes = [:post]
#
# This was strange, so we found in ActiveRecord::NestedAttributes::ClassMethods
#
# https://github.com/rails/rails/blob/4-2-stable/activerecord/lib/active_record/nested_attributes.rb#L309
# # Note that the <tt>:autosave</tt> option is automatically enabled on every
# # association that accepts_nested_attributes_for is used for.
# def accepts_nested_attributes_for(*attr_names)
# #....
# attr_names.each do |association_name|
# if reflection = _reflect_on_association(association_name)
# reflection.autosave = true
#
# i.e. it was setting `autosave: true` on the reflection.
#
# It turns out that `ActiveRecord::Reflection::MacroReflection.autosave=`
# is setting an instance variable `@automatic_inverse_of = false` which
# disables Rails from automatically adding `inverse_of` as if we had declared `has_many :comments, inverse_of: :post`,
# Post._reflect_on_association(:comments).send(:automatic_inverse_of) #=> :post
# which meant that when we found the comments for the post and then asked for the post,
# it was issuing a NEW QUERY FOR EACH resource object.
#
# In the PR to Rails that introduced automatic inverse_of look up https://github.com/rails/rails/pull/9522
# Sean Griffin comments: https://github.com/rails/rails/commit/26d19b4661f3d89a075b5f05d926c578ff0c730f#commitcomment-9482625
# > people don't expect accepts_nested_attributes_for to break automatic inverses.
# and then
# > Turns out having autosave and inverse_of can cause a record to be saved more than once,
# > because it's still marked as changed? inside of after_save, after_update, and after_create.
# > And fixing that turned out to be an insurmountable task right now
# > (assuming it's a breaking change we'd like to make which I'm not sure we do).
#
# And right now there's an open PR to resolve this issue that is applied below:
# Patch from https://github.com/rails/rails/pull/23197 at ref: 2577c78e0a19ac0af2b54e6316791f32c787c62c
ActiveSupport.on_load(:active_record) do
ActiveRecord::Reflection::MacroReflection.class_eval do
def autosave=(autosave)
# - @automatic_inverse_of = false
@options[:autosave] = autosave
_, parent_reflection = self.parent_reflection
# changed in master to:
# parent_reflection = self.parent_reflection
if parent_reflection
parent_reflection.autosave = autosave
end
end
end
ActiveRecord::AutosaveAssociation.class_eval do
# Returns true if this record has triggered saving of other records through
# autosave associations, and will remain true for the length of the cycle
# until all autosaves have completed.
#
# This is necessary so that saves triggered on associated records know not
# to re-save the parent if the autosave is bidirectional.
def _triggering_record? # :nodoc:
defined?(@_triggering_record) ? @_triggering_record : false
end
# Temporarily mark this record as a triggering_record for the duration of
# the block call
def with_self_as_triggering_record
@_triggering_record = true
yield
ensure
@_triggering_record = false
end
private
# Saves any new associated records, or all loaded autosave associations if
# <tt>:autosave</tt> is enabled on the association.
#
# In addition, it destroys all children that were marked for destruction
# with #mark_for_destruction.
#
# 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_collection_association(reflection)
if association = association_instance_get(reflection.name)
autosave = reflection.options[:autosave]
if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
if autosave
records_to_destroy = records.select(&:marked_for_destruction?)
records_to_destroy.each { |record| association.destroy(record) }
records -= records_to_destroy
end
records.each do |record|
# - next if record.destroyed?
next if record.destroyed? || record._triggering_record?
saved = true
# + added wrapping method: with_self_as_triggering_record
with_self_as_triggering_record do
if autosave != false && (@new_record_before_save || record.new_record?)
if autosave
saved = association.insert_record(record, false)
else
association.insert_record(record) unless reflection.nested?
end
elsif autosave
saved = record.save(validate: false)
end
end
raise ActiveRecord::Rollback unless saved
end
end
# reconstruct the scope now that we know the owner's id
association.reset_scope if association.respond_to?(:reset_scope)
end
end
# Saves the associated record if it's new or <tt>:autosave</tt> is enabled
# on the association.
#
# In addition, it will destroy the association if it was marked for
# destruction with #mark_for_destruction.
#
# 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)
association = association_instance_get(reflection.name)
record = association && association.load_target
# - if record && !record.destroyed?
if record && !record.destroyed? && !record._triggering_record?
autosave = reflection.options[:autosave]
if autosave && record.marked_for_destruction?
record.destroy
elsif autosave != false
key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id
if (autosave && record.changed_for_autosave?) || new_record? || record_changed?(reflection, record, key)
unless reflection.through_reflection
record[reflection.foreign_key] = key
end
# + added wrapping method: with_self_as_triggering_record
saved = with_self_as_triggering_record do
record.save(validate: !autosave)
end
raise ActiveRecord::Rollback if !saved && autosave
saved
end
end
end
# Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
#
# In addition, it will destroy the association if it was marked for destruction.
def save_belongs_to_association(reflection)
association = association_instance_get(reflection.name)
record = association && association.load_target
# - if record && !record.destroyed?
if record && !record.destroyed? && !record._triggering_record?
autosave = reflection.options[:autosave]
if autosave && record.marked_for_destruction?
self[reflection.foreign_key] = nil
record.destroy
elsif autosave != false
# - saved = record.save(:validate => !autosave) if record.new_record? || (autosave && record.changed_for_autosave?)
# + BEGIN
if record.new_record? || (autosave && record.changed_for_autosave?)
saved = with_self_as_triggering_record do
record.save(validate: !autosave)
end
end
# + END
if association.updated?
association_id = record.send(reflection.options[:primary_key] || :id)
self[reflection.foreign_key] = association_id
association.loaded!
end
saved if autosave
end
end
end
end
end
end
# These files haven't changed much between Rails 4.2.x and master
# git diff 4-2-stable..2577c78 activerecord/lib/active_record/autosave_association.rb activerecord/lib/active_record/reflection.rb | pbcopy
# ActiveRecord::AutosaveAssociation
# diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
# index b7402d1..ba8c6a7 100644
# --- a/activerecord/lib/active_record/autosave_association.rb
# +++ b/activerecord/lib/active_record/autosave_association.rb
# @@ -1,10 +1,10 @@
# module ActiveRecord
# # = Active Record Autosave Association
# #
# - # +AutosaveAssociation+ is a module that takes care of automatically saving
# + # AutosaveAssociation is a module that takes care of automatically saving
# # associated records when their parent is saved. In addition to saving, it
# # also destroys any associated records that were marked for destruction.
# - # (See +mark_for_destruction+ and <tt>marked_for_destruction?</tt>).
# + # (See #mark_for_destruction and #marked_for_destruction?).
# #
# # Saving of the parent, its associations, and the destruction of marked
# # associations, all happen inside a transaction. This should never leave the
# @@ -22,7 +22,7 @@ module ActiveRecord
# #
# # == Validation
# #
# - # Children records are validated unless <tt>:validate</tt> is +false+.
# + # Child records are validated unless <tt>:validate</tt> is +false+.
# #
# # == Callbacks
# #
# @@ -125,7 +125,6 @@ module ActiveRecord
# # Now it _is_ removed from the database:
# #
# # Comment.find_by(id: id).nil? # => true
# -
# module AutosaveAssociation
# extend ActiveSupport::Concern
#
# @@ -141,9 +140,11 @@ module ActiveRecord
#
# included do
# Associations::Builder::Association.extensions << AssociationBuilderExtension
# + mattr_accessor :index_nested_attribute_errors, instance_writer: false
# + self.index_nested_attribute_errors = false
# end
#
# - module ClassMethods
# + module ClassMethods # :nodoc:
# private
#
# def define_non_cyclic_method(name, &block)
# @@ -198,7 +199,7 @@ module ActiveRecord
# after_create save_method
# after_update save_method
# else
# - define_non_cyclic_method(save_method) { save_belongs_to_association(reflection) }
# + define_non_cyclic_method(save_method) { throw(:abort) if save_belongs_to_association(reflection) == false }
# before_save save_method
# end
#
# @@ -214,8 +215,15 @@ module ActiveRecord
# method = :validate_single_association
# end
#
# - define_non_cyclic_method(validation_method) { send(method, reflection) }
# + define_non_cyclic_method(validation_method) do
# + send(method, reflection)
# + # TODO: remove the following line as soon as the return value of
# + # callbacks is ignored, that is, returning `false` does not
# + # display a deprecation warning or halts the callback chain.
# + true
# + end
# validate validation_method
# + after_validation :_ensure_no_duplicate_errors
# end
# end
# end
# @@ -227,7 +235,7 @@ module ActiveRecord
# super
# end
#
# - # Marks this record to be destroyed as part of the parents save transaction.
# + # Marks this record to be destroyed as part of the parent's save transaction.
# # This does _not_ actually destroy the record instantly, rather child record will be destroyed
# # when <tt>parent.save</tt> is called.
# #
# @@ -236,7 +244,7 @@ module ActiveRecord
# @marked_for_destruction = true
# end
#
# - # Returns whether or not this record will be destroyed as part of the parents save transaction.
# + # Returns whether or not this record will be destroyed as part of the parent's save transaction.
# #
# # Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model.
# def marked_for_destruction?
# @@ -262,6 +270,16 @@ module ActiveRecord
# new_record? || changed? || marked_for_destruction? || nested_records_changed_for_autosave?
# end
#
# + # Returns true if this record has triggered saving of other records through
# + # autosave associations, and will remain true for the length of the cycle
# + # until all autosaves have completed.
# + #
# + # This is necessary so that saves triggered on associated records know not
# + # to re-save the parent if the autosave is bidirectional.
# + def _triggering_record? # :nodoc:
# + defined?(@_triggering_record) ? @_triggering_record : false
# + end
# +
# private
#
# # Returns the record for an association collection that should be validated
# @@ -271,9 +289,9 @@ module ActiveRecord
# if new_record
# association && association.target
# elsif autosave
# - association.target.find_all { |record| record.changed_for_autosave? }
# + association.target.find_all(&:changed_for_autosave?)
# else
# - association.target.find_all { |record| record.new_record? }
# + association.target.find_all(&:new_record?)
# end
# end
#
# @@ -309,7 +327,7 @@ module ActiveRecord
# def validate_collection_association(reflection)
# if association = association_instance_get(reflection.name)
# if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave])
# - records.each { |record| association_valid?(reflection, record) }
# + records.each_with_index { |record, index| association_valid?(reflection, record, index) }
# end
# end
# end
# @@ -317,17 +335,36 @@ 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, record)
# + def association_valid?(reflection, record, index=nil)
# return true if record.destroyed? || (reflection.options[:autosave] && record.marked_for_destruction?)
#
# validation_context = self.validation_context unless [:create, :update].include?(self.validation_context)
# unless valid = record.valid?(validation_context)
# if reflection.options[:autosave]
# + indexed_attribute = !index.nil? && (reflection.options[:index_errors] || ActiveRecord::Base.index_nested_attribute_errors)
# +
# record.errors.each do |attribute, message|
# - attribute = "#{reflection.name}.#{attribute}"
# + if indexed_attribute
# + attribute = "#{reflection.name}[#{index}].#{attribute}"
# + else
# + attribute = "#{reflection.name}.#{attribute}"
# + end
# errors[attribute] << message
# errors[attribute].uniq!
# end
# +
# + record.errors.details.each_key do |attribute|
# + if indexed_attribute
# + reflection_attribute = "#{reflection.name}[#{index}].#{attribute}"
# + else
# + reflection_attribute = "#{reflection.name}.#{attribute}"
# + end
# +
# + record.errors.details[attribute].each do |error|
# + errors.details[reflection_attribute] << error
# + errors.details[reflection_attribute].uniq!
# + end
# + end
# else
# errors.add(reflection.name)
# end
# @@ -342,11 +379,20 @@ module ActiveRecord
# true
# end
#
# + # Temporarily mark this record as a triggering_record for the duration of
# + # the block call
# + def with_self_as_triggering_record
# + @_triggering_record = true
# + yield
# + ensure
# + @_triggering_record = false
# + end
# +
# # Saves any new associated records, or all loaded autosave associations if
# # <tt>:autosave</tt> is enabled on the association.
# #
# # In addition, it destroys all children that were marked for destruction
# - # with mark_for_destruction.
# + # with #mark_for_destruction.
# #
# # This all happens inside a transaction, _if_ the Transactions module is included into
# # ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
# @@ -362,18 +408,20 @@ module ActiveRecord
# end
#
# records.each do |record|
# - next if record.destroyed?
# + next if record.destroyed? || record._triggering_record?
#
# saved = true
#
# - if autosave != false && (@new_record_before_save || record.new_record?)
# - if autosave
# - saved = association.insert_record(record, false)
# - else
# - association.insert_record(record) unless reflection.nested?
# + with_self_as_triggering_record do
# + if autosave != false && (@new_record_before_save || record.new_record?)
# + if autosave
# + saved = association.insert_record(record, false)
# + else
# + association.insert_record(record) unless reflection.nested?
# + end
# + elsif autosave
# + saved = record.save(validate: false)
# end
# - elsif autosave
# - saved = record.save(:validate => false)
# end
#
# raise ActiveRecord::Rollback unless saved
# @@ -389,7 +437,7 @@ module ActiveRecord
# # on the association.
# #
# # In addition, it will destroy the association if it was marked for
# - # destruction with mark_for_destruction.
# + # destruction with #mark_for_destruction.
# #
# # This all happens inside a transaction, _if_ the Transactions module is included into
# # ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
# @@ -397,7 +445,7 @@ module ActiveRecord
# association = association_instance_get(reflection.name)
# record = association && association.load_target
#
# - if record && !record.destroyed?
# + if record && !record.destroyed? && !record._triggering_record?
# autosave = reflection.options[:autosave]
#
# if autosave && record.marked_for_destruction?
# @@ -410,7 +458,10 @@ module ActiveRecord
# record[reflection.foreign_key] = key
# end
#
# - saved = record.save(:validate => !autosave)
# + saved = with_self_as_triggering_record do
# + record.save(validate: !autosave)
# + end
# +
# raise ActiveRecord::Rollback if !saved && autosave
# saved
# end
# @@ -431,14 +482,18 @@ module ActiveRecord
# def save_belongs_to_association(reflection)
# association = association_instance_get(reflection.name)
# record = association && association.load_target
# - if record && !record.destroyed?
# + if record && !record.destroyed? && !record._triggering_record?
# autosave = reflection.options[:autosave]
#
# if autosave && record.marked_for_destruction?
# self[reflection.foreign_key] = nil
# record.destroy
# elsif autosave != false
# - saved = record.save(:validate => !autosave) if record.new_record? || (autosave && record.changed_for_autosave?)
# + if record.new_record? || (autosave && record.changed_for_autosave?)
# + saved = with_self_as_triggering_record do
# + record.save(validate: !autosave)
# + end
# + end
#
# if association.updated?
# association_id = record.send(reflection.options[:primary_key] || :id)
# @@ -450,5 +505,11 @@ module ActiveRecord
# end
# end
# end
# +
# + def _ensure_no_duplicate_errors
# + errors.messages.each_key do |attribute|
# + errors[attribute].uniq!
# + end
# + end
# end
# end
# ActiveRecord::Reflection::MacroReflection
# diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
# index b46df26..e86b030 100644
# --- a/activerecord/lib/active_record/reflection.rb
# +++ b/activerecord/lib/active_record/reflection.rb
# @@ -40,9 +40,9 @@ module ActiveRecord
# ar.aggregate_reflections = ar.aggregate_reflections.merge(name.to_s => reflection)
# end
#
# - # \Reflection enables interrogating of Active Record classes and objects
# - # about their associations and aggregations. This information can,
# - # for example, be used in a form builder that takes an Active Record object
# + # \Reflection enables the ability to examine the associations and aggregations of
# + # Active Record classes and objects. This information, for example,
# + # can be used in a form builder that takes an Active Record object
# # and creates input fields for all of the attributes depending on their type
# # and displays the associations to other objects.
# #
# @@ -62,20 +62,20 @@ module ActiveRecord
# aggregate_reflections[aggregation.to_s]
# end
#
# - # Returns a Hash of name of the reflection as the key and a AssociationReflection as the value.
# + # Returns a Hash of name of the reflection as the key and an AssociationReflection as the value.
# #
# # Account.reflections # => {"balance" => AggregateReflection}
# #
# - # @api public
# def reflections
# @__reflections ||= begin
# ref = {}
#
# _reflections.each do |name, reflection|
# - parent_name, parent_reflection = reflection.parent_reflection
# + parent_reflection = reflection.parent_reflection
#
# - if parent_name
# - ref[parent_name] = parent_reflection
# + if parent_reflection
# + parent_name = parent_reflection.name
# + ref[parent_name.to_s] = parent_reflection
# else
# ref[name] = reflection
# end
# @@ -95,10 +95,10 @@ module ActiveRecord
# # Account.reflect_on_all_associations # returns an array of all associations
# # Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
# #
# - # @api public
# def reflect_on_all_associations(macro = nil)
# association_reflections = reflections.values
# - macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
# + association_reflections.select! { |reflection| reflection.macro == macro } if macro
# + association_reflections
# end
#
# # Returns the AssociationReflection object for the +association+ (use the symbol).
# @@ -106,31 +106,42 @@ module ActiveRecord
# # Account.reflect_on_association(:owner) # returns the owner AssociationReflection
# # Invoice.reflect_on_association(:line_items).macro # returns :has_many
# #
# - # @api public
# def reflect_on_association(association)
# reflections[association.to_s]
# end
#
# - # @api private
# def _reflect_on_association(association) #:nodoc:
# _reflections[association.to_s]
# end
#
# # Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
# - #
# - # @api public
# def reflect_on_all_autosave_associations
# reflections.values.select { |reflection| reflection.options[:autosave] }
# end
#
# - def clear_reflections_cache #:nodoc:
# + def clear_reflections_cache # :nodoc:
# @__reflections = nil
# end
# end
#
# - # Holds all the methods that are shared between MacroReflection, AssociationReflection
# - # and ThroughReflection
# + # Holds all the methods that are shared between MacroReflection and ThroughReflection.
# + #
# + # AbstractReflection
# + # MacroReflection
# + # AggregateReflection
# + # AssociationReflection
# + # HasManyReflection
# + # HasOneReflection
# + # BelongsToReflection
# + # HasAndBelongsToManyReflection
# + # ThroughReflection
# + # PolymorphicReflection
# + # RuntimeReflection
# class AbstractReflection # :nodoc:
# + def through_reflection?
# + false
# + end
# +
# def table_name
# klass.table_name
# end
# @@ -159,17 +170,24 @@ module ActiveRecord
#
# JoinKeys = Struct.new(:key, :foreign_key) # :nodoc:
#
# - def join_keys(assoc_klass)
# + def join_keys(association_klass)
# JoinKeys.new(foreign_key, active_record_primary_key)
# end
#
# - def source_macro
# - ActiveSupport::Deprecation.warn(<<-MSG.squish)
# - ActiveRecord::Base.source_macro is deprecated and will be removed
# - without replacement.
# - MSG
# + def constraints
# + scope_chain.flatten
# + end
#
# - macro
# + def counter_cache_column
# + if belongs_to?
# + if options[:counter_cache] == true
# + "#{active_record.name.demodulize.underscore.pluralize}_count"
# + elsif options[:counter_cache]
# + options[:counter_cache].to_s
# + end
# + else
# + options[:counter_cache] ? options[:counter_cache].to_s : "#{name}_count"
# + end
# end
#
# def inverse_of
# @@ -185,17 +203,54 @@ module ActiveRecord
# end
# end
# end
# +
# + # This shit is nasty. We need to avoid the following situation:
# + #
# + # * An associated record is deleted via record.destroy
# + # * Hence the callbacks run, and they find a belongs_to on the record with a
# + # :counter_cache options which points back at our owner. So they update the
# + # counter cache.
# + # * In which case, we must make sure to *not* update the counter cache, or else
# + # it will be decremented twice.
# + #
# + # Hence this method.
# + def inverse_which_updates_counter_cache
# + return @inverse_which_updates_counter_cache if defined?(@inverse_which_updates_counter_cache)
# + @inverse_which_updates_counter_cache = klass.reflect_on_all_associations(:belongs_to).find do |inverse|
# + inverse.counter_cache_column == counter_cache_column
# + end
# + end
# + alias inverse_updates_counter_cache? inverse_which_updates_counter_cache
# +
# + def inverse_updates_counter_in_memory?
# + inverse_of && inverse_which_updates_counter_cache == inverse_of
# + end
# +
# + # Returns whether a counter cache should be used for this association.
# + #
# + # The counter_cache option must be given on either the owner or inverse
# + # association, and the column must be present on the owner.
# + def has_cached_counter?
# + options[:counter_cache] ||
# + inverse_which_updates_counter_cache && inverse_which_updates_counter_cache.options[:counter_cache] &&
# + !!active_record.columns_hash[counter_cache_column]
# + end
# +
# + def counter_must_be_updated_by_has_many?
# + !inverse_updates_counter_in_memory? && has_cached_counter?
# + end
# +
# + def alias_candidate(name)
# + "#{plural_name}_#{name}"
# + end
# +
# + def chain
# + collect_join_chain
# + end
# end
# +
# # Base class for AggregateReflection and AssociationReflection. Objects of
# # AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
# - #
# - # MacroReflection
# - # AssociationReflection
# - # AggregateReflection
# - # HasManyReflection
# - # HasOneReflection
# - # BelongsToReflection
# - # ThroughReflection
# class MacroReflection < AbstractReflection
# # Returns the name of the macro.
# #
# @@ -226,9 +281,8 @@ module ActiveRecord
# end
#
# def autosave=(autosave)
# - @automatic_inverse_of = false
# @options[:autosave] = autosave
# - _, parent_reflection = self.parent_reflection
# + parent_reflection = self.parent_reflection
# if parent_reflection
# parent_reflection.autosave = autosave
# end
# @@ -296,7 +350,7 @@ module ActiveRecord
# end
#
# attr_reader :type, :foreign_type
# - attr_accessor :parent_reflection # [:name, Reflection]
# + attr_accessor :parent_reflection # Reflection
#
# def initialize(name, scope, options, active_record)
# super
# @@ -343,14 +397,6 @@ module ActiveRecord
# @active_record_primary_key ||= options[:primary_key] || primary_key(active_record)
# end
#
# - def counter_cache_column
# - if options[:counter_cache] == true
# - "#{active_record.name.demodulize.underscore.pluralize}_count"
# - elsif options[:counter_cache]
# - options[:counter_cache].to_s
# - end
# - end
# -
# def check_validity!
# check_validity_of_inverse!
# end
# @@ -359,13 +405,10 @@ module ActiveRecord
# return unless scope
#
# if scope.arity > 0
# - ActiveSupport::Deprecation.warn(<<-MSG.squish)
# + raise ArgumentError, <<-MSG.squish
# The association scope '#{name}' is instance dependent (the scope
# - block takes an argument). Preloading happens before the individual
# - instances are created. This means that there is no instance being
# - passed to the association scope. This will most likely result in
# - broken or incorrect behavior. Joining, Preloading and eager loading
# - of these associations is deprecated and will be removed in the future.
# + block takes an argument). Preloading instance dependent scopes is
# + not supported.
# MSG
# end
# end
# @@ -385,10 +428,16 @@ module ActiveRecord
#
# # A chain of reflections from this one back to the owner. For more see the explanation in
# # ThroughReflection.
# - def chain
# + def collect_join_chain
# [self]
# end
#
# + # This is for clearing cache on the reflection. Useful for tests that need to compare
# + # SQL queries on associations.
# + def clear_association_scope_cache # :nodoc:
# + @association_scope_cache.clear
# + end
# +
# def nested?
# false
# end
# @@ -399,6 +448,10 @@ module ActiveRecord
# scope ? [[scope]] : [[]]
# end
#
# + def has_scope?
# + scope
# + end
# +
# def has_inverse?
# inverse_name
# end
# @@ -444,28 +497,7 @@ module ActiveRecord
# # Returns +true+ if +self+ is a +has_one+ reflection.
# def has_one?; false; end
#
# - def association_class
# - case macro
# - when :belongs_to
# - if polymorphic?
# - Associations::BelongsToPolymorphicAssociation
# - else
# - Associations::BelongsToAssociation
# - end
# - when :has_many
# - if options[:through]
# - Associations::HasManyThroughAssociation
# - else
# - Associations::HasManyAssociation
# - end
# - when :has_one
# - if options[:through]
# - Associations::HasOneThroughAssociation
# - else
# - Associations::HasOneAssociation
# - end
# - end
# - end
# + def association_class; raise NotImplementedError; end
#
# def polymorphic?
# options[:polymorphic]
# @@ -474,6 +506,18 @@ module ActiveRecord
# VALID_AUTOMATIC_INVERSE_MACROS = [:has_many, :has_one, :belongs_to]
# INVALID_AUTOMATIC_INVERSE_OPTIONS = [:conditions, :through, :polymorphic, :foreign_key]
#
# + def add_as_source(seed)
# + seed
# + end
# +
# + def add_as_polymorphic_through(reflection, seed)
# + seed + [PolymorphicReflection.new(self, reflection)]
# + end
# +
# + def add_as_through(seed)
# + seed + [self]
# + end
# +
# protected
#
# def actual_source_reflection # FIXME: this is a horrible name
# @@ -483,14 +527,7 @@ module ActiveRecord
# private
#
# def calculate_constructable(macro, options)
# - case macro
# - when :belongs_to
# - !polymorphic?
# - when :has_one
# - !options[:through]
# - else
# - true
# - end
# + true
# end
#
# # Attempts to find the inverse association name automatically.
# @@ -506,7 +543,7 @@ module ActiveRecord
# end
# end
#
# - # returns either nil or the inverse association name that it finds.
# + # returns either false or the inverse association name that it finds.
# def automatic_inverse_of
# if can_find_inverse_of_automatically?(self)
# inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name.demodulize).to_sym
# @@ -583,42 +620,66 @@ module ActiveRecord
# end
#
# class HasManyReflection < AssociationReflection # :nodoc:
# - def initialize(name, scope, options, active_record)
# - super(name, scope, options, active_record)
# - end
# -
# def macro; :has_many; end
#
# def collection?; true; end
# - end
#
# - class HasOneReflection < AssociationReflection # :nodoc:
# - def initialize(name, scope, options, active_record)
# - super(name, scope, options, active_record)
# + def association_class
# + if options[:through]
# + Associations::HasManyThroughAssociation
# + else
# + Associations::HasManyAssociation
# + end
# end
# + end
#
# + class HasOneReflection < AssociationReflection # :nodoc:
# def macro; :has_one; end
#
# def has_one?; true; end
# - end
#
# - class BelongsToReflection < AssociationReflection # :nodoc:
# - def initialize(name, scope, options, active_record)
# - super(name, scope, options, active_record)
# + def association_class
# + if options[:through]
# + Associations::HasOneThroughAssociation
# + else
# + Associations::HasOneAssociation
# + end
# end
#
# + private
# +
# + def calculate_constructable(macro, options)
# + !options[:through]
# + end
# + end
# +
# + class BelongsToReflection < AssociationReflection # :nodoc:
# def macro; :belongs_to; end
#
# def belongs_to?; true; end
#
# - def join_keys(assoc_klass)
# - key = polymorphic? ? association_primary_key(assoc_klass) : association_primary_key
# + def association_class
# + if polymorphic?
# + Associations::BelongsToPolymorphicAssociation
# + else
# + Associations::BelongsToAssociation
# + end
# + end
# +
# + def join_keys(association_klass)
# + key = polymorphic? ? association_primary_key(association_klass) : association_primary_key
# JoinKeys.new(key, foreign_key)
# end
#
# def join_id_for(owner) # :nodoc:
# owner[foreign_key]
# end
# +
# + private
# +
# + def calculate_constructable(macro, options)
# + !polymorphic?
# + end
# end
#
# class HasAndBelongsToManyReflection < AssociationReflection # :nodoc:
# @@ -646,6 +707,10 @@ module ActiveRecord
# @source_reflection_name = delegate_reflection.options[:source]
# end
#
# + def through_reflection?
# + true
# + end
# +
# def klass
# @klass ||= delegate_reflection.compute_class(class_name)
# end
# @@ -704,14 +769,16 @@ module ActiveRecord
# # # => [<ActiveRecord::Reflection::ThroughReflection: @delegate_reflection=#<ActiveRecord::Reflection::HasManyReflection: @name=:tags...>,
# # <ActiveRecord::Reflection::HasManyReflection: @name=:taggings, @options={}, @active_record=Post>]
# #
# - def chain
# - @chain ||= begin
# - a = source_reflection.chain
# - b = through_reflection.chain
# - chain = a + b
# - chain[0] = self # Use self so we don't lose the information from :source_type
# - chain
# - end
# + def collect_join_chain
# + collect_join_reflections [self]
# + end
# +
# + # This is for clearing cache on the reflection. Useful for tests that need to compare
# + # SQL queries on associations.
# + def clear_association_scope_cache # :nodoc:
# + delegate_reflection.clear_association_scope_cache
# + source_reflection.clear_association_scope_cache
# + through_reflection.clear_association_scope_cache
# end
#
# # Consider the following example:
# @@ -755,23 +822,19 @@ module ActiveRecord
# end
# end
#
# - def join_keys(assoc_klass)
# - source_reflection.join_keys(assoc_klass)
# + def has_scope?
# + scope || options[:source_type] ||
# + source_reflection.has_scope? ||
# + through_reflection.has_scope?
# end
#
# - # The macro used by the source association
# - def source_macro
# - ActiveSupport::Deprecation.warn(<<-MSG.squish)
# - ActiveRecord::Base.source_macro is deprecated and will be removed
# - without replacement.
# - MSG
# -
# - source_reflection.source_macro
# + def join_keys(association_klass)
# + source_reflection.join_keys(association_klass)
# end
#
# # A through association is nested if there would be more than one join table
# def nested?
# - chain.length > 2
# + source_reflection.through_reflection? || through_reflection.through_reflection?
# end
#
# # We want to use the klass from this reflection, rather than just delegate straight to
# @@ -801,7 +864,7 @@ module ActiveRecord
# def source_reflection_name # :nodoc:
# return @source_reflection_name if @source_reflection_name
#
# - names = [name.to_s.singularize, name].collect { |n| n.to_sym }.uniq
# + names = [name.to_s.singularize, name].collect(&:to_sym).uniq
# names = names.find_all { |n|
# through_reflection.klass._reflect_on_association(n)
# }
# @@ -865,6 +928,33 @@ module ActiveRecord
# check_validity_of_inverse!
# end
#
# + def constraints
# + scope_chain = source_reflection.constraints
# + scope_chain << scope if scope
# + scope_chain
# + end
# +
# + def add_as_source(seed)
# + collect_join_reflections seed
# + end
# +
# + def add_as_polymorphic_through(reflection, seed)
# + collect_join_reflections(seed + [PolymorphicReflection.new(self, reflection)])
# + end
# +
# + def add_as_through(seed)
# + collect_join_reflections(seed + [self])
# + end
# +
# + def collect_join_reflections(seed)
# + a = source_reflection.add_as_source seed
# + if options[:source_type]
# + through_reflection.add_as_polymorphic_through self, a
# + else
# + through_reflection.add_as_through a
# + end
# + end
# +
# protected
#
# def actual_source_reflection # FIXME: this is a horrible name
# @@ -889,5 +979,81 @@ module ActiveRecord
# delegate(*delegate_methods, to: :delegate_reflection)
#
# end
# +
# + class PolymorphicReflection < ThroughReflection # :nodoc:
# + def initialize(reflection, previous_reflection)
# + @reflection = reflection
# + @previous_reflection = previous_reflection
# + end
# +
# + def klass
# + @reflection.klass
# + end
# +
# + def scope
# + @reflection.scope
# + end
# +
# + def table_name
# + @reflection.table_name
# + end
# +
# + def plural_name
# + @reflection.plural_name
# + end
# +
# + def join_keys(association_klass)
# + @reflection.join_keys(association_klass)
# + end
# +
# + def type
# + @reflection.type
# + end
# +
# + def constraints
# + @reflection.constraints + [source_type_info]
# + end
# +
# + def source_type_info
# + type = @previous_reflection.foreign_type
# + source_type = @previous_reflection.options[:source_type]
# + lambda { |object| where(type => source_type) }
# + end
# + end
# +
# + class RuntimeReflection < PolymorphicReflection # :nodoc:
# + attr_accessor :next
# +
# + def initialize(reflection, association)
# + @reflection = reflection
# + @association = association
# + end
# +
# + def klass
# + @association.klass
# + end
# +
# + def table_name
# + klass.table_name
# + end
# +
# + def constraints
# + @reflection.constraints
# + end
# +
# + def source_type_info
# + @reflection.source_type_info
# + end
# +
# + def alias_candidate(name)
# + "#{plural_name}_#{name}_join"
# + end
# +
# + def alias_name
# + Arel::Table.new(table_name)
# + end
# +
# + def all_includes; yield; end
# + end
# end
# end
@bf4
Copy link
Author

bf4 commented Jun 10, 2016

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment