mirror of
https://github.com/github/rails.git
synced 2026-04-26 03:00:59 -04:00
Associations - where possible, call attributes methods rather than directly accessing the instance variables
This commit is contained in:
committed by
Aaron Patterson
parent
f826e05835
commit
1d85a73ceb
@@ -37,13 +37,13 @@ module ActiveRecord
|
||||
# post.comments.aliased_table_name # => "comments"
|
||||
#
|
||||
def aliased_table_name
|
||||
@reflection.klass.table_name
|
||||
reflection.klass.table_name
|
||||
end
|
||||
|
||||
# Resets the \loaded flag to +false+ and sets the \target to +nil+.
|
||||
def reset
|
||||
@loaded = false
|
||||
IdentityMap.remove(@target) if IdentityMap.enabled? && @target
|
||||
IdentityMap.remove(target) if IdentityMap.enabled? && target
|
||||
@target = nil
|
||||
end
|
||||
|
||||
@@ -52,7 +52,7 @@ module ActiveRecord
|
||||
reset
|
||||
construct_scope
|
||||
load_target
|
||||
self unless @target.nil?
|
||||
self unless target.nil?
|
||||
end
|
||||
|
||||
# Has the \target been already \loaded?
|
||||
@@ -99,12 +99,12 @@ module ActiveRecord
|
||||
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]))
|
||||
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 = scope.extending(*Array.wrap(reflection.options[:extend]))
|
||||
scope.where(construct_owner_conditions)
|
||||
end
|
||||
|
||||
@@ -116,14 +116,14 @@ module ActiveRecord
|
||||
def set_inverse_instance(record)
|
||||
if record && invertible_for?(record)
|
||||
inverse = record.association(inverse_reflection_for(record).name)
|
||||
inverse.target = @owner
|
||||
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
|
||||
reflection.klass
|
||||
end
|
||||
|
||||
# Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the
|
||||
@@ -146,7 +146,7 @@ module ActiveRecord
|
||||
if find_target?
|
||||
begin
|
||||
if IdentityMap.enabled? && association_class && association_class.respond_to?(:base_class)
|
||||
@target = IdentityMap.get(association_class, @owner[@reflection.foreign_key])
|
||||
@target = IdentityMap.get(association_class, owner[reflection.foreign_key])
|
||||
end
|
||||
rescue NameError
|
||||
nil
|
||||
@@ -163,19 +163,19 @@ module ActiveRecord
|
||||
private
|
||||
|
||||
def find_target?
|
||||
!loaded? && (!@owner.new_record? || foreign_key_present?) && target_klass
|
||||
!loaded? && (!owner.new_record? || foreign_key_present?) && target_klass
|
||||
end
|
||||
|
||||
def interpolate(sql, record = nil)
|
||||
if sql.respond_to?(:to_proc)
|
||||
@owner.send(:instance_exec, record, &sql)
|
||||
owner.send(:instance_exec, record, &sql)
|
||||
else
|
||||
sql
|
||||
end
|
||||
end
|
||||
|
||||
def select_value
|
||||
@reflection.options[:select]
|
||||
reflection.options[:select]
|
||||
end
|
||||
|
||||
# Implemented by (some) subclasses
|
||||
@@ -184,22 +184,22 @@ module ActiveRecord
|
||||
end
|
||||
|
||||
# Returns a hash linking the owner to the association represented by the reflection
|
||||
def construct_owner_attributes(reflection = @reflection)
|
||||
def construct_owner_attributes(reflection = reflection)
|
||||
attributes = {}
|
||||
if reflection.macro == :belongs_to
|
||||
attributes[reflection.association_primary_key] = @owner[reflection.foreign_key]
|
||||
attributes[reflection.association_primary_key] = owner[reflection.foreign_key]
|
||||
else
|
||||
attributes[reflection.foreign_key] = @owner[reflection.active_record_primary_key]
|
||||
attributes[reflection.foreign_key] = owner[reflection.active_record_primary_key]
|
||||
|
||||
if reflection.options[:as]
|
||||
attributes["#{reflection.options[:as]}_type"] = @owner.class.base_class.name
|
||||
attributes["#{reflection.options[:as]}_type"] = owner.class.base_class.name
|
||||
end
|
||||
end
|
||||
attributes
|
||||
end
|
||||
|
||||
# Builds an array of arel nodes from the owner attributes hash
|
||||
def construct_owner_conditions(table = aliased_table, reflection = @reflection)
|
||||
def construct_owner_conditions(table = aliased_table, reflection = reflection)
|
||||
conditions = construct_owner_attributes(reflection).map do |attr, value|
|
||||
table[attr].eq(value)
|
||||
end
|
||||
@@ -208,14 +208,14 @@ module ActiveRecord
|
||||
|
||||
# Sets the owner attributes on the given record
|
||||
def set_owner_attributes(record)
|
||||
if @owner.persisted?
|
||||
if owner.persisted?
|
||||
construct_owner_attributes.each { |key, value| record[key] = value }
|
||||
end
|
||||
end
|
||||
|
||||
# Should be true if there is a foreign key present on the @owner which
|
||||
# Should be true if there is a foreign key present on the owner which
|
||||
# references the target. This is used to determine whether we can load
|
||||
# the target if the @owner is currently a new record (and therefore
|
||||
# the target if the owner is currently a new record (and therefore
|
||||
# without a key).
|
||||
#
|
||||
# Currently implemented by belongs_to (vanilla and polymorphic) and
|
||||
@@ -228,8 +228,8 @@ module ActiveRecord
|
||||
# the kind of the class of the associated objects. Meant to be used as
|
||||
# a sanity check when you are about to assign an associated record.
|
||||
def raise_on_type_mismatch(record)
|
||||
unless record.is_a?(@reflection.klass) || record.is_a?(@reflection.class_name.constantize)
|
||||
message = "#{@reflection.class_name}(##{@reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
|
||||
unless record.is_a?(reflection.klass) || record.is_a?(reflection.class_name.constantize)
|
||||
message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
|
||||
raise ActiveRecord::AssociationTypeMismatch, message
|
||||
end
|
||||
end
|
||||
@@ -238,7 +238,7 @@ module ActiveRecord
|
||||
# The record parameter is necessary to support polymorphic inverses as we must check for
|
||||
# the association in the specific class of the record.
|
||||
def inverse_reflection_for(record)
|
||||
@reflection.inverse_of
|
||||
reflection.inverse_of
|
||||
end
|
||||
|
||||
# Is this association invertible? Can be redefined by subclasses.
|
||||
|
||||
@@ -21,9 +21,9 @@ module ActiveRecord
|
||||
private
|
||||
|
||||
def update_counters(record)
|
||||
counter_cache_name = @reflection.counter_cache_column
|
||||
counter_cache_name = reflection.counter_cache_column
|
||||
|
||||
if counter_cache_name && @owner.persisted? && different_target?(record)
|
||||
if counter_cache_name && owner.persisted? && different_target?(record)
|
||||
if record
|
||||
record.class.increment_counter(counter_cache_name, record.id)
|
||||
end
|
||||
@@ -36,16 +36,16 @@ module ActiveRecord
|
||||
|
||||
# Checks whether record is different to the current target, without loading it
|
||||
def different_target?(record)
|
||||
record.nil? && @owner[@reflection.foreign_key] ||
|
||||
record.id != @owner[@reflection.foreign_key]
|
||||
record.nil? && owner[reflection.foreign_key] ||
|
||||
record.id != owner[reflection.foreign_key]
|
||||
end
|
||||
|
||||
def replace_keys(record)
|
||||
@owner[@reflection.foreign_key] = record && record[@reflection.association_primary_key]
|
||||
owner[reflection.foreign_key] = record && record[reflection.association_primary_key]
|
||||
end
|
||||
|
||||
def foreign_key_present?
|
||||
@owner[@reflection.foreign_key]
|
||||
owner[reflection.foreign_key]
|
||||
end
|
||||
|
||||
# NOTE - for now, we're only supporting inverse setting from belongs_to back onto
|
||||
@@ -56,15 +56,15 @@ module ActiveRecord
|
||||
end
|
||||
|
||||
def target_id
|
||||
if @reflection.options[:primary_key]
|
||||
@owner.send(@reflection.name).try(:id)
|
||||
if reflection.options[:primary_key]
|
||||
owner.send(reflection.name).try(:id)
|
||||
else
|
||||
@owner[@reflection.foreign_key]
|
||||
owner[reflection.foreign_key]
|
||||
end
|
||||
end
|
||||
|
||||
def stale_state
|
||||
@owner[@reflection.foreign_key].to_s
|
||||
owner[reflection.foreign_key].to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,7 +6,7 @@ module ActiveRecord
|
||||
|
||||
def replace_keys(record)
|
||||
super
|
||||
@owner[@reflection.foreign_type] = record && record.class.base_class.name
|
||||
owner[reflection.foreign_type] = record && record.class.base_class.name
|
||||
end
|
||||
|
||||
def different_target?(record)
|
||||
@@ -14,11 +14,11 @@ module ActiveRecord
|
||||
end
|
||||
|
||||
def inverse_reflection_for(record)
|
||||
@reflection.polymorphic_inverse_of(record.class)
|
||||
reflection.polymorphic_inverse_of(record.class)
|
||||
end
|
||||
|
||||
def target_klass
|
||||
type = @owner[@reflection.foreign_type]
|
||||
type = owner[reflection.foreign_type]
|
||||
type && type.constantize
|
||||
end
|
||||
|
||||
@@ -27,7 +27,7 @@ module ActiveRecord
|
||||
end
|
||||
|
||||
def stale_state
|
||||
[super, @owner[@reflection.foreign_type].to_s]
|
||||
[super, owner[reflection.foreign_type].to_s]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -47,7 +47,7 @@ module ActiveRecord
|
||||
end
|
||||
|
||||
def find(*args)
|
||||
if @reflection.options[:finder_sql]
|
||||
if reflection.options[:finder_sql]
|
||||
find_by_scan(*args)
|
||||
else
|
||||
scoped.find(*args)
|
||||
@@ -67,7 +67,7 @@ module ActiveRecord
|
||||
end
|
||||
|
||||
def create(attributes = {}, &block)
|
||||
unless @owner.persisted?
|
||||
unless owner.persisted?
|
||||
raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
|
||||
end
|
||||
|
||||
@@ -84,13 +84,13 @@ module ActiveRecord
|
||||
# Since << flattens its argument list and inserts each record, +push+ and +concat+ behave identically.
|
||||
def concat(*records)
|
||||
result = true
|
||||
load_target if @owner.new_record?
|
||||
load_target if owner.new_record?
|
||||
|
||||
transaction do
|
||||
records.flatten.each do |record|
|
||||
raise_on_type_mismatch(record)
|
||||
add_to_target(record) do |r|
|
||||
result &&= insert_record(record) unless @owner.new_record?
|
||||
result &&= insert_record(record) unless owner.new_record?
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -108,7 +108,7 @@ module ActiveRecord
|
||||
# # same effect as calling Book.transaction
|
||||
# end
|
||||
def transaction(*args)
|
||||
@reflection.klass.transaction(*args) do
|
||||
reflection.klass.transaction(*args) do
|
||||
yield
|
||||
end
|
||||
end
|
||||
@@ -148,23 +148,23 @@ module ActiveRecord
|
||||
def count(column_name = nil, options = {})
|
||||
column_name, options = nil, column_name if column_name.is_a?(Hash)
|
||||
|
||||
if @reflection.options[:counter_sql] || @reflection.options[:finder_sql]
|
||||
if reflection.options[:counter_sql] || reflection.options[:finder_sql]
|
||||
unless options.blank?
|
||||
raise ArgumentError, "If finder_sql/counter_sql is used then options cannot be passed"
|
||||
end
|
||||
|
||||
@reflection.klass.count_by_sql(custom_counter_sql)
|
||||
reflection.klass.count_by_sql(custom_counter_sql)
|
||||
else
|
||||
if @reflection.options[:uniq]
|
||||
if reflection.options[:uniq]
|
||||
# This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
|
||||
column_name ||= @reflection.klass.primary_key
|
||||
column_name ||= reflection.klass.primary_key
|
||||
options.merge!(:distinct => true)
|
||||
end
|
||||
|
||||
value = scoped.count(column_name, options)
|
||||
|
||||
limit = @reflection.options[:limit]
|
||||
offset = @reflection.options[:offset]
|
||||
limit = reflection.options[:limit]
|
||||
offset = reflection.options[:offset]
|
||||
|
||||
if limit || offset
|
||||
[ [value - offset.to_i, 0].max, limit.to_i ].min
|
||||
@@ -182,7 +182,7 @@ module ActiveRecord
|
||||
# are actually removed from the database, that depends precisely on
|
||||
# +delete_records+. They are in any case removed from the collection.
|
||||
def delete(*records)
|
||||
delete_or_destroy(records, @reflection.options[:dependent])
|
||||
delete_or_destroy(records, reflection.options[:dependent])
|
||||
end
|
||||
|
||||
# Destroy +records+ and remove them from this association calling
|
||||
@@ -206,12 +206,12 @@ module ActiveRecord
|
||||
# This method is abstract in the sense that it relies on
|
||||
# +count_records+, which is a method descendants have to provide.
|
||||
def size
|
||||
if @owner.new_record? || (loaded? && !@reflection.options[:uniq])
|
||||
@target.size
|
||||
elsif !loaded? && @reflection.options[:group]
|
||||
if owner.new_record? || (loaded? && !reflection.options[:uniq])
|
||||
target.size
|
||||
elsif !loaded? && reflection.options[:group]
|
||||
load_target.size
|
||||
elsif !loaded? && !@reflection.options[:uniq] && @target.is_a?(Array)
|
||||
unsaved_records = @target.select { |r| r.new_record? }
|
||||
elsif !loaded? && !reflection.options[:uniq] && target.is_a?(Array)
|
||||
unsaved_records = target.select { |r| r.new_record? }
|
||||
unsaved_records.size + count_records
|
||||
else
|
||||
count_records
|
||||
@@ -265,23 +265,23 @@ module ActiveRecord
|
||||
original_target = load_target.dup
|
||||
|
||||
transaction do
|
||||
delete(@target - other_array)
|
||||
delete(target - other_array)
|
||||
|
||||
unless concat(other_array - @target)
|
||||
unless concat(other_array - target)
|
||||
@target = original_target
|
||||
raise RecordNotSaved, "Failed to replace #{@reflection.name} because one or more of the " \
|
||||
raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \
|
||||
"new records could not be saved."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def include?(record)
|
||||
if record.is_a?(@reflection.klass)
|
||||
if record.is_a?(reflection.klass)
|
||||
if record.new_record?
|
||||
include_in_memory?(record)
|
||||
else
|
||||
load_target if @reflection.options[:finder_sql]
|
||||
loaded? ? @target.include?(record) : scoped.exists?(record)
|
||||
load_target if reflection.options[:finder_sql]
|
||||
loaded? ? target.include?(record) : scoped.exists?(record)
|
||||
end
|
||||
else
|
||||
false
|
||||
@@ -293,7 +293,7 @@ module ActiveRecord
|
||||
end
|
||||
|
||||
def association_scope
|
||||
options = @reflection.options.slice(:order, :limit, :joins, :group, :having, :offset)
|
||||
options = reflection.options.slice(:order, :limit, :joins, :group, :having, :offset)
|
||||
super.apply_finder_options(options)
|
||||
end
|
||||
|
||||
@@ -307,7 +307,7 @@ module ActiveRecord
|
||||
reset
|
||||
end
|
||||
|
||||
@target = merge_target_lists(targets, @target)
|
||||
@target = merge_target_lists(targets, target)
|
||||
end
|
||||
|
||||
loaded!
|
||||
@@ -319,7 +319,7 @@ module ActiveRecord
|
||||
callback(:before_add, record)
|
||||
yield(record) if block_given?
|
||||
|
||||
if @reflection.options[:uniq] && index = @target.index(record)
|
||||
if reflection.options[:uniq] && index = @target.index(record)
|
||||
@target[index] = record
|
||||
else
|
||||
@target << record
|
||||
@@ -339,31 +339,31 @@ module ActiveRecord
|
||||
end
|
||||
|
||||
def uniq_select_value
|
||||
@reflection.options[:uniq] && "DISTINCT #{@reflection.quoted_table_name}.*"
|
||||
reflection.options[:uniq] && "DISTINCT #{reflection.quoted_table_name}.*"
|
||||
end
|
||||
|
||||
def custom_counter_sql
|
||||
if @reflection.options[:counter_sql]
|
||||
interpolate(@reflection.options[:counter_sql])
|
||||
if reflection.options[:counter_sql]
|
||||
interpolate(reflection.options[:counter_sql])
|
||||
else
|
||||
# replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
|
||||
interpolate(@reflection.options[:finder_sql]).sub(/SELECT\b(\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
|
||||
interpolate(reflection.options[:finder_sql]).sub(/SELECT\b(\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
|
||||
end
|
||||
end
|
||||
|
||||
def custom_finder_sql
|
||||
interpolate(@reflection.options[:finder_sql])
|
||||
interpolate(reflection.options[:finder_sql])
|
||||
end
|
||||
|
||||
def find_target
|
||||
records =
|
||||
if @reflection.options[:finder_sql]
|
||||
@reflection.klass.find_by_sql(custom_finder_sql)
|
||||
if reflection.options[:finder_sql]
|
||||
reflection.klass.find_by_sql(custom_finder_sql)
|
||||
else
|
||||
find(:all)
|
||||
end
|
||||
|
||||
records = @reflection.options[:uniq] ? uniq(records) : records
|
||||
records = reflection.options[:uniq] ? uniq(records) : records
|
||||
records.each { |record| set_inverse_instance(record) }
|
||||
records
|
||||
end
|
||||
@@ -408,7 +408,7 @@ module ActiveRecord
|
||||
end
|
||||
|
||||
def build_record(attributes)
|
||||
@reflection.build_association(scoped.scope_for_create.merge(attributes))
|
||||
reflection.build_association(scoped.scope_for_create.merge(attributes))
|
||||
end
|
||||
|
||||
def delete_or_destroy(records, method)
|
||||
@@ -420,7 +420,7 @@ module ActiveRecord
|
||||
records.each { |record| callback(:before_remove, record) }
|
||||
|
||||
delete_records(existing_records, method) if existing_records.any?
|
||||
records.each { |record| @target.delete(record) }
|
||||
records.each { |record| target.delete(record) }
|
||||
|
||||
records.each { |record| callback(:after_remove, record) }
|
||||
end
|
||||
@@ -436,18 +436,18 @@ module ActiveRecord
|
||||
callbacks_for(method).each do |callback|
|
||||
case callback
|
||||
when Symbol
|
||||
@owner.send(callback, record)
|
||||
owner.send(callback, record)
|
||||
when Proc
|
||||
callback.call(@owner, record)
|
||||
callback.call(owner, record)
|
||||
else
|
||||
callback.send(method, @owner, record)
|
||||
callback.send(method, owner, record)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def callbacks_for(callback_name)
|
||||
full_callback_name = "#{callback_name}_for_#{@reflection.name}"
|
||||
@owner.class.send(full_callback_name.to_sym) || []
|
||||
full_callback_name = "#{callback_name}_for_#{reflection.name}"
|
||||
owner.class.send(full_callback_name.to_sym) || []
|
||||
end
|
||||
|
||||
# Should we deal with assoc.first or assoc.last by issuing an independent query to
|
||||
@@ -466,21 +466,21 @@ module ActiveRecord
|
||||
true
|
||||
else
|
||||
!(loaded? ||
|
||||
@owner.new_record? ||
|
||||
@reflection.options[:finder_sql] ||
|
||||
@target.any? { |record| record.new_record? || record.changed? } ||
|
||||
owner.new_record? ||
|
||||
reflection.options[:finder_sql] ||
|
||||
target.any? { |record| record.new_record? || record.changed? } ||
|
||||
args.first.kind_of?(Integer))
|
||||
end
|
||||
end
|
||||
|
||||
def include_in_memory?(record)
|
||||
if @reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
|
||||
@owner.send(@reflection.through_reflection.name).any? { |source|
|
||||
target = source.send(@reflection.source_reflection.name)
|
||||
if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
|
||||
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)
|
||||
} || target.include?(record)
|
||||
else
|
||||
@target.include?(record)
|
||||
target.include?(record)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -12,15 +12,15 @@ module ActiveRecord
|
||||
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))
|
||||
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
|
||||
join_table[reflection.foreign_key] => owner.id,
|
||||
join_table[reflection.association_foreign_key] => record.id
|
||||
)
|
||||
|
||||
@owner.connection.insert stmt.to_sql
|
||||
owner.connection.insert stmt.to_sql
|
||||
end
|
||||
|
||||
record
|
||||
@@ -37,23 +37,23 @@ module ActiveRecord
|
||||
end
|
||||
|
||||
def delete_records(records, method)
|
||||
if sql = @reflection.options[:delete_sql]
|
||||
records.each { |record| @owner.connection.delete(interpolate(sql, record)) }
|
||||
if sql = reflection.options[:delete_sql]
|
||||
records.each { |record| owner.connection.delete(interpolate(sql, record)) }
|
||||
else
|
||||
relation = join_table
|
||||
stmt = relation.where(relation[@reflection.foreign_key].eq(@owner.id).
|
||||
and(relation[@reflection.association_foreign_key].in(records.map { |x| x.id }.compact))
|
||||
stmt = relation.where(relation[reflection.foreign_key].eq(owner.id).
|
||||
and(relation[reflection.association_foreign_key].in(records.map { |x| x.id }.compact))
|
||||
).compile_delete
|
||||
@owner.connection.delete stmt.to_sql
|
||||
owner.connection.delete stmt.to_sql
|
||||
end
|
||||
end
|
||||
|
||||
def construct_joins
|
||||
right = join_table
|
||||
left = @reflection.klass.arel_table
|
||||
left = reflection.klass.arel_table
|
||||
|
||||
condition = left[@reflection.klass.primary_key].eq(
|
||||
right[@reflection.association_foreign_key])
|
||||
condition = left[reflection.klass.primary_key].eq(
|
||||
right[reflection.association_foreign_key])
|
||||
|
||||
right.create_join(right, right.create_on(condition))
|
||||
end
|
||||
@@ -63,7 +63,7 @@ module ActiveRecord
|
||||
end
|
||||
|
||||
def select_value
|
||||
super || @reflection.klass.arel_table[Arel.star]
|
||||
super || reflection.klass.arel_table[Arel.star]
|
||||
end
|
||||
|
||||
def invertible_for?(record)
|
||||
|
||||
@@ -28,9 +28,9 @@ module ActiveRecord
|
||||
# the loaded flag is set to true as well.
|
||||
def count_records
|
||||
count = if has_cached_counter?
|
||||
@owner.send(:read_attribute, cached_counter_attribute_name)
|
||||
elsif @reflection.options[:counter_sql] || @reflection.options[:finder_sql]
|
||||
@reflection.klass.count_by_sql(custom_counter_sql)
|
||||
owner.send(:read_attribute, cached_counter_attribute_name)
|
||||
elsif reflection.options[:counter_sql] || reflection.options[:finder_sql]
|
||||
reflection.klass.count_by_sql(custom_counter_sql)
|
||||
else
|
||||
scoped.count
|
||||
end
|
||||
@@ -40,23 +40,23 @@ module ActiveRecord
|
||||
# documented side-effect of the method that may avoid an extra SELECT.
|
||||
@target ||= [] and loaded! if count == 0
|
||||
|
||||
[@reflection.options[:limit], count].compact.min
|
||||
[reflection.options[:limit], count].compact.min
|
||||
end
|
||||
|
||||
def has_cached_counter?(reflection = @reflection)
|
||||
@owner.attribute_present?(cached_counter_attribute_name(reflection))
|
||||
def has_cached_counter?(reflection = reflection)
|
||||
owner.attribute_present?(cached_counter_attribute_name(reflection))
|
||||
end
|
||||
|
||||
def cached_counter_attribute_name(reflection = @reflection)
|
||||
def cached_counter_attribute_name(reflection = reflection)
|
||||
"#{reflection.name}_count"
|
||||
end
|
||||
|
||||
def update_counter(difference, reflection = @reflection)
|
||||
def update_counter(difference, reflection = reflection)
|
||||
if has_cached_counter?(reflection)
|
||||
counter = cached_counter_attribute_name(reflection)
|
||||
@owner.class.update_counters(@owner.id, counter => difference)
|
||||
@owner[counter] += difference
|
||||
@owner.changed_attributes.delete(counter) # eww
|
||||
owner.class.update_counters(owner.id, counter => difference)
|
||||
owner[counter] += difference
|
||||
owner.changed_attributes.delete(counter) # eww
|
||||
end
|
||||
end
|
||||
|
||||
@@ -64,13 +64,13 @@ module ActiveRecord
|
||||
#
|
||||
# * 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 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_updates_counter_cache?(reflection = @reflection)
|
||||
def inverse_updates_counter_cache?(reflection = reflection)
|
||||
counter_name = cached_counter_attribute_name(reflection)
|
||||
reflection.klass.reflect_on_all_associations(:belongs_to).any? { |inverse_reflection|
|
||||
inverse_reflection.counter_cache_column == counter_name
|
||||
@@ -83,13 +83,13 @@ module ActiveRecord
|
||||
records.each { |r| r.destroy }
|
||||
update_counter(-records.length) unless inverse_updates_counter_cache?
|
||||
else
|
||||
keys = records.map { |r| r[@reflection.association_primary_key] }
|
||||
scope = scoped.where(@reflection.association_primary_key => keys)
|
||||
keys = records.map { |r| r[reflection.association_primary_key] }
|
||||
scope = scoped.where(reflection.association_primary_key => keys)
|
||||
|
||||
if method == :delete_all
|
||||
update_counter(-scope.delete_all)
|
||||
else
|
||||
update_counter(-scope.update_all(@reflection.foreign_key => nil))
|
||||
update_counter(-scope.update_all(reflection.foreign_key => nil))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -14,16 +14,16 @@ module ActiveRecord
|
||||
# SELECT query if you use #length.
|
||||
def size
|
||||
if has_cached_counter?
|
||||
@owner.send(:read_attribute, cached_counter_attribute_name)
|
||||
owner.send(:read_attribute, cached_counter_attribute_name)
|
||||
elsif loaded?
|
||||
@target.size
|
||||
target.size
|
||||
else
|
||||
count
|
||||
end
|
||||
end
|
||||
|
||||
def concat(*records)
|
||||
unless @owner.new_record?
|
||||
unless owner.new_record?
|
||||
records.flatten.each do |record|
|
||||
raise_on_type_mismatch(record)
|
||||
record.save! if record.new_record?
|
||||
@@ -43,7 +43,7 @@ module ActiveRecord
|
||||
private
|
||||
|
||||
def through_record(record)
|
||||
through_association = @owner.association(@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|
|
||||
@@ -52,7 +52,7 @@ module ActiveRecord
|
||||
|
||||
unless through_record
|
||||
through_record = through_association.build(attributes)
|
||||
through_record.send("#{@reflection.source_reflection.name}=", record)
|
||||
through_record.send("#{reflection.source_reflection.name}=", record)
|
||||
end
|
||||
|
||||
through_record
|
||||
@@ -61,7 +61,7 @@ module ActiveRecord
|
||||
def build_record(attributes)
|
||||
record = super(attributes)
|
||||
|
||||
inverse = @reflection.source_reflection.inverse_of
|
||||
inverse = reflection.source_reflection.inverse_of
|
||||
if inverse
|
||||
if inverse.macro == :has_many
|
||||
record.send(inverse.name) << through_record(record)
|
||||
@@ -74,7 +74,7 @@ module ActiveRecord
|
||||
end
|
||||
|
||||
def target_reflection_has_associated_record?
|
||||
if @reflection.through_reflection.macro == :belongs_to && @owner[@reflection.through_reflection.foreign_key].blank?
|
||||
if reflection.through_reflection.macro == :belongs_to && owner[reflection.through_reflection.foreign_key].blank?
|
||||
false
|
||||
else
|
||||
true
|
||||
@@ -84,7 +84,7 @@ module ActiveRecord
|
||||
def update_through_counter?(method)
|
||||
case method
|
||||
when :destroy
|
||||
!inverse_updates_counter_cache?(@reflection.through_reflection)
|
||||
!inverse_updates_counter_cache?(reflection.through_reflection)
|
||||
when :nullify
|
||||
false
|
||||
else
|
||||
@@ -93,29 +93,29 @@ module ActiveRecord
|
||||
end
|
||||
|
||||
def delete_records(records, method)
|
||||
through = @owner.association(@reflection.through_reflection.name)
|
||||
through = owner.association(reflection.through_reflection.name)
|
||||
scope = through.scoped.where(construct_join_attributes(*records))
|
||||
|
||||
case method
|
||||
when :destroy
|
||||
count = scope.destroy_all.length
|
||||
when :nullify
|
||||
count = scope.update_all(@reflection.source_reflection.foreign_key => nil)
|
||||
count = scope.update_all(reflection.source_reflection.foreign_key => nil)
|
||||
else
|
||||
count = scope.delete_all
|
||||
end
|
||||
|
||||
delete_through_records(through, records)
|
||||
|
||||
if @reflection.through_reflection.macro == :has_many && update_through_counter?(method)
|
||||
update_counter(-count, @reflection.through_reflection)
|
||||
if reflection.through_reflection.macro == :has_many && update_through_counter?(method)
|
||||
update_counter(-count, reflection.through_reflection)
|
||||
end
|
||||
|
||||
update_counter(-count)
|
||||
end
|
||||
|
||||
def delete_through_records(through, records)
|
||||
if @reflection.through_reflection.macro == :has_many
|
||||
if reflection.through_reflection.macro == :has_many
|
||||
records.each do |record|
|
||||
through.target.delete(through_record(record))
|
||||
end
|
||||
|
||||
@@ -6,19 +6,19 @@ module ActiveRecord
|
||||
record = check_record(record)
|
||||
load_target
|
||||
|
||||
@reflection.klass.transaction do
|
||||
if @target && @target != record
|
||||
remove_target!(@reflection.options[:dependent])
|
||||
reflection.klass.transaction do
|
||||
if target && target != record
|
||||
remove_target!(reflection.options[:dependent])
|
||||
end
|
||||
|
||||
if record
|
||||
set_inverse_instance(record)
|
||||
set_owner_attributes(record)
|
||||
|
||||
if @owner.persisted? && save && !record.save
|
||||
if owner.persisted? && save && !record.save
|
||||
nullify_owner_attributes(record)
|
||||
set_owner_attributes(@target)
|
||||
raise RecordNotSaved, "Failed to save the new associated #{@reflection.name}."
|
||||
set_owner_attributes(target)
|
||||
raise RecordNotSaved, "Failed to save the new associated #{reflection.name}."
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -29,7 +29,7 @@ module ActiveRecord
|
||||
protected
|
||||
|
||||
def association_scope
|
||||
super.order(@reflection.options[:order])
|
||||
super.order(reflection.options[:order])
|
||||
end
|
||||
|
||||
private
|
||||
@@ -46,20 +46,20 @@ module ActiveRecord
|
||||
|
||||
def remove_target!(method)
|
||||
if [:delete, :destroy].include?(method)
|
||||
@target.send(method)
|
||||
target.send(method)
|
||||
else
|
||||
nullify_owner_attributes(@target)
|
||||
nullify_owner_attributes(target)
|
||||
|
||||
if @target.persisted? && @owner.persisted? && !@target.save
|
||||
set_owner_attributes(@target)
|
||||
raise RecordNotSaved, "Failed to remove the existing associated #{@reflection.name}. " +
|
||||
if target.persisted? && owner.persisted? && !target.save
|
||||
set_owner_attributes(target)
|
||||
raise RecordNotSaved, "Failed to remove the existing associated #{reflection.name}. " +
|
||||
"The record failed to save when after its foreign key was set to nil."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def nullify_owner_attributes(record)
|
||||
record[@reflection.foreign_key] = nil
|
||||
record[reflection.foreign_key] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -12,7 +12,7 @@ module ActiveRecord
|
||||
private
|
||||
|
||||
def create_through_record(record)
|
||||
through_proxy = @owner.association(@reflection.through_reflection.name)
|
||||
through_proxy = owner.association(reflection.through_reflection.name)
|
||||
through_record = through_proxy.send(:load_target)
|
||||
|
||||
if through_record && !record
|
||||
@@ -22,7 +22,7 @@ module ActiveRecord
|
||||
|
||||
if through_record
|
||||
through_record.update_attributes(attributes)
|
||||
elsif @owner.new_record?
|
||||
elsif owner.new_record?
|
||||
through_proxy.build(attributes)
|
||||
else
|
||||
through_proxy.create(attributes)
|
||||
|
||||
@@ -36,7 +36,7 @@ module ActiveRecord
|
||||
|
||||
def new_record(method, attributes)
|
||||
attributes = scoped.scope_for_create.merge(attributes || {})
|
||||
record = @reflection.send("#{method}_association", attributes)
|
||||
record = reflection.send("#{method}_association", attributes)
|
||||
set_new_record(record)
|
||||
record
|
||||
end
|
||||
|
||||
@@ -6,14 +6,14 @@ module ActiveRecord
|
||||
protected
|
||||
|
||||
def target_scope
|
||||
super.merge(@reflection.through_reflection.klass.scoped)
|
||||
super.merge(reflection.through_reflection.klass.scoped)
|
||||
end
|
||||
|
||||
def association_scope
|
||||
scope = super.joins(construct_joins)
|
||||
scope = add_conditions(scope)
|
||||
unless @reflection.options[:include]
|
||||
scope = scope.includes(@reflection.source_reflection.options[:include])
|
||||
unless reflection.options[:include]
|
||||
scope = scope.includes(reflection.source_reflection.options[:include])
|
||||
end
|
||||
scope
|
||||
end
|
||||
@@ -29,40 +29,40 @@ module ActiveRecord
|
||||
end
|
||||
|
||||
def aliased_through_table
|
||||
name = @reflection.through_reflection.table_name
|
||||
name = reflection.through_reflection.table_name
|
||||
|
||||
@reflection.table_name == name ?
|
||||
@reflection.through_reflection.klass.arel_table.alias(name + "_join") :
|
||||
@reflection.through_reflection.klass.arel_table
|
||||
reflection.table_name == name ?
|
||||
reflection.through_reflection.klass.arel_table.alias(name + "_join") :
|
||||
reflection.through_reflection.klass.arel_table
|
||||
end
|
||||
|
||||
def construct_owner_conditions
|
||||
super(aliased_through_table, @reflection.through_reflection)
|
||||
super(aliased_through_table, reflection.through_reflection)
|
||||
end
|
||||
|
||||
def construct_joins
|
||||
right = aliased_through_table
|
||||
left = @reflection.klass.arel_table
|
||||
left = reflection.klass.arel_table
|
||||
|
||||
conditions = []
|
||||
|
||||
if @reflection.source_reflection.macro == :belongs_to
|
||||
reflection_primary_key = @reflection.source_reflection.association_primary_key
|
||||
source_primary_key = @reflection.source_reflection.foreign_key
|
||||
if reflection.source_reflection.macro == :belongs_to
|
||||
reflection_primary_key = reflection.source_reflection.association_primary_key
|
||||
source_primary_key = reflection.source_reflection.foreign_key
|
||||
|
||||
if @reflection.options[:source_type]
|
||||
column = @reflection.source_reflection.foreign_type
|
||||
if reflection.options[:source_type]
|
||||
column = reflection.source_reflection.foreign_type
|
||||
conditions <<
|
||||
right[column].eq(@reflection.options[:source_type])
|
||||
right[column].eq(reflection.options[:source_type])
|
||||
end
|
||||
else
|
||||
reflection_primary_key = @reflection.source_reflection.foreign_key
|
||||
source_primary_key = @reflection.source_reflection.active_record_primary_key
|
||||
reflection_primary_key = reflection.source_reflection.foreign_key
|
||||
source_primary_key = reflection.source_reflection.active_record_primary_key
|
||||
|
||||
if @reflection.source_reflection.options[:as]
|
||||
column = "#{@reflection.source_reflection.options[:as]}_type"
|
||||
if reflection.source_reflection.options[:as]
|
||||
column = "#{reflection.source_reflection.options[:as]}_type"
|
||||
conditions <<
|
||||
left[column].eq(@reflection.through_reflection.klass.name)
|
||||
left[column].eq(reflection.through_reflection.klass.name)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -87,19 +87,19 @@ module ActiveRecord
|
||||
# situation it is more natural for the user to just create or modify their join records
|
||||
# directly as required.
|
||||
def construct_join_attributes(*records)
|
||||
if @reflection.source_reflection.macro != :belongs_to
|
||||
raise HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(@owner, @reflection)
|
||||
if reflection.source_reflection.macro != :belongs_to
|
||||
raise HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection)
|
||||
end
|
||||
|
||||
join_attributes = {
|
||||
@reflection.source_reflection.foreign_key =>
|
||||
reflection.source_reflection.foreign_key =>
|
||||
records.map { |record|
|
||||
record.send(@reflection.source_reflection.association_primary_key)
|
||||
record.send(reflection.source_reflection.association_primary_key)
|
||||
}
|
||||
}
|
||||
|
||||
if @reflection.options[:source_type]
|
||||
join_attributes[@reflection.source_reflection.foreign_type] =
|
||||
if reflection.options[:source_type]
|
||||
join_attributes[reflection.source_reflection.foreign_type] =
|
||||
records.map { |record| record.class.base_class.name }
|
||||
end
|
||||
|
||||
@@ -115,18 +115,18 @@ module ActiveRecord
|
||||
# has a different meaning to scope.where(x).where(y) - the first version might
|
||||
# perform some substitution if x is a string.
|
||||
def add_conditions(scope)
|
||||
unless @reflection.through_reflection.klass.descends_from_active_record?
|
||||
scope = scope.where(@reflection.through_reflection.klass.send(:type_condition))
|
||||
unless reflection.through_reflection.klass.descends_from_active_record?
|
||||
scope = scope.where(reflection.through_reflection.klass.send(:type_condition))
|
||||
end
|
||||
|
||||
scope = scope.where(interpolate(@reflection.source_reflection.options[:conditions]))
|
||||
scope = scope.where(interpolate(reflection.source_reflection.options[:conditions]))
|
||||
scope.where(through_conditions)
|
||||
end
|
||||
|
||||
# If there is a hash of conditions then we make sure the keys are scoped to the
|
||||
# through table name if left ambiguous.
|
||||
def through_conditions
|
||||
conditions = interpolate(@reflection.through_reflection.options[:conditions])
|
||||
conditions = interpolate(reflection.through_reflection.options[:conditions])
|
||||
|
||||
if conditions.is_a?(Hash)
|
||||
Hash[conditions.map { |key, value|
|
||||
@@ -142,14 +142,14 @@ module ActiveRecord
|
||||
end
|
||||
|
||||
def stale_state
|
||||
if @reflection.through_reflection.macro == :belongs_to
|
||||
@owner[@reflection.through_reflection.foreign_key].to_s
|
||||
if reflection.through_reflection.macro == :belongs_to
|
||||
owner[reflection.through_reflection.foreign_key].to_s
|
||||
end
|
||||
end
|
||||
|
||||
def foreign_key_present?
|
||||
@reflection.through_reflection.macro == :belongs_to &&
|
||||
!@owner[@reflection.through_reflection.foreign_key].nil?
|
||||
reflection.through_reflection.macro == :belongs_to &&
|
||||
!owner[reflection.through_reflection.foreign_key].nil?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user