mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Merge remote branch 'jonleighton/association_fixes' into fuuu
* jonleighton/association_fixes: Rename AssociationReflection#primary_key_name to foreign_key, since the options key which it relates to is :foreign_key Support for :counter_cache on polymorphic belongs_to Refactor BelongsToAssociation to allow BelongsToPolymorphicAssociation to inherit from it Specify the STI type condition using SQL IN rather than a whole load of ORs. Required a fix to ActiveRecord::Relation#merge for properly merging create_with_value. This also fixes a situation where the type condition was appearing twice in the resultant SQL query. Verify that when has_many associated objects are destroyed via :dependent => :destroy, when the parent is destroyed, the callbacks are run Get rid of extra_conditions param from configure_dependency_for_has_many. I can't see a particularly plausible argument for this being used by plugins, and if they really want they can just redefine the callback or whatever. Note also that before my recent commit the extra_conditions param was completely ignored for :dependent => :destroy. And owner_quoted_id can go too Now we can drop-kick AssociationReflection#dependent_conditions into oblivion. Refactor configure_dependency_for_has_many to use AssociationCollection#delete_all. It was necessary to change test_before_destroy in lifecycle_test.rb so that it checks topic.replies.size *before* doing the destroy, as afterwards it will now (correctly) be 0.
This commit is contained in:
commit
5c7fd87665
26 changed files with 261 additions and 256 deletions
|
@ -205,7 +205,7 @@ module ActiveRecord
|
||||||
# FIXME: options[:select] is always nil in the tests. Do we really
|
# FIXME: options[:select] is always nil in the tests. Do we really
|
||||||
# need it?
|
# need it?
|
||||||
options[:select] || left[Arel.star],
|
options[:select] || left[Arel.star],
|
||||||
right[reflection.primary_key_name].as(
|
right[reflection.foreign_key].as(
|
||||||
Arel.sql('the_parent_record_id'))
|
Arel.sql('the_parent_record_id'))
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -220,7 +220,7 @@ module ActiveRecord
|
||||||
|
|
||||||
all_associated_records = associated_records(ids) do |some_ids|
|
all_associated_records = associated_records(ids) do |some_ids|
|
||||||
method = in_or_equal(some_ids)
|
method = in_or_equal(some_ids)
|
||||||
conditions = right[reflection.primary_key_name].send(*method)
|
conditions = right[reflection.foreign_key].send(*method)
|
||||||
conditions = custom_conditions.inject(conditions) do |ast, cond|
|
conditions = custom_conditions.inject(conditions) do |ast, cond|
|
||||||
ast.and cond
|
ast.and cond
|
||||||
end
|
end
|
||||||
|
@ -241,7 +241,7 @@ module ActiveRecord
|
||||||
|
|
||||||
unless through_records.empty?
|
unless through_records.empty?
|
||||||
through_reflection = reflections[options[:through]]
|
through_reflection = reflections[options[:through]]
|
||||||
through_primary_key = through_reflection.primary_key_name
|
through_primary_key = through_reflection.foreign_key
|
||||||
source = reflection.source_reflection.name
|
source = reflection.source_reflection.name
|
||||||
through_records.first.class.preload_associations(through_records, source)
|
through_records.first.class.preload_associations(through_records, source)
|
||||||
if through_reflection.macro == :belongs_to
|
if through_reflection.macro == :belongs_to
|
||||||
|
@ -255,7 +255,7 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
set_association_single_records(id_to_record_map, reflection.name, find_associated_records(ids, reflection, preload_options), reflection.primary_key_name)
|
set_association_single_records(id_to_record_map, reflection.name, find_associated_records(ids, reflection, preload_options), reflection.foreign_key)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -263,8 +263,8 @@ module ActiveRecord
|
||||||
return if records.first.send(reflection.name).loaded?
|
return if records.first.send(reflection.name).loaded?
|
||||||
options = reflection.options
|
options = reflection.options
|
||||||
|
|
||||||
primary_key_name = reflection.through_reflection_primary_key_name
|
foreign_key = reflection.through_reflection_foreign_key
|
||||||
id_to_record_map, ids = construct_id_map(records, primary_key_name || reflection.options[:primary_key])
|
id_to_record_map, ids = construct_id_map(records, foreign_key || reflection.options[:primary_key])
|
||||||
records.each {|record| record.send(reflection.name).loaded}
|
records.each {|record| record.send(reflection.name).loaded}
|
||||||
|
|
||||||
if options[:through]
|
if options[:through]
|
||||||
|
@ -281,7 +281,7 @@ module ActiveRecord
|
||||||
|
|
||||||
else
|
else
|
||||||
set_association_collection_records(id_to_record_map, reflection.name, find_associated_records(ids, reflection, preload_options),
|
set_association_collection_records(id_to_record_map, reflection.name, find_associated_records(ids, reflection, preload_options),
|
||||||
reflection.primary_key_name)
|
reflection.foreign_key)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -319,7 +319,7 @@ module ActiveRecord
|
||||||
def preload_belongs_to_association(records, reflection, preload_options={})
|
def preload_belongs_to_association(records, reflection, preload_options={})
|
||||||
return if records.first.send("loaded_#{reflection.name}?")
|
return if records.first.send("loaded_#{reflection.name}?")
|
||||||
options = reflection.options
|
options = reflection.options
|
||||||
primary_key_name = reflection.primary_key_name
|
foreign_key = reflection.foreign_key
|
||||||
|
|
||||||
klasses_and_ids = {}
|
klasses_and_ids = {}
|
||||||
|
|
||||||
|
@ -330,7 +330,7 @@ module ActiveRecord
|
||||||
# to their parent_records
|
# to their parent_records
|
||||||
records.each do |record|
|
records.each do |record|
|
||||||
if klass = record.send(polymorph_type)
|
if klass = record.send(polymorph_type)
|
||||||
klass_id = record.send(primary_key_name)
|
klass_id = record.send(foreign_key)
|
||||||
if klass_id
|
if klass_id
|
||||||
id_map = klasses_and_ids[klass.constantize] ||= {}
|
id_map = klasses_and_ids[klass.constantize] ||= {}
|
||||||
(id_map[klass_id.to_s] ||= []) << record
|
(id_map[klass_id.to_s] ||= []) << record
|
||||||
|
@ -339,7 +339,7 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
id_map = records.group_by do |record|
|
id_map = records.group_by do |record|
|
||||||
key = record.send(primary_key_name)
|
key = record.send(foreign_key)
|
||||||
key && key.to_s
|
key && key.to_s
|
||||||
end
|
end
|
||||||
id_map.delete nil
|
id_map.delete nil
|
||||||
|
@ -369,7 +369,7 @@ module ActiveRecord
|
||||||
|
|
||||||
conditions = []
|
conditions = []
|
||||||
|
|
||||||
key = reflection.primary_key_name
|
key = reflection.foreign_key
|
||||||
|
|
||||||
if interface = reflection.options[:as]
|
if interface = reflection.options[:as]
|
||||||
key = "#{interface}_id"
|
key = "#{interface}_id"
|
||||||
|
|
|
@ -1612,16 +1612,14 @@ module ActiveRecord
|
||||||
# - set the foreign key to NULL if the option is set to :nullify
|
# - set the foreign key to NULL if the option is set to :nullify
|
||||||
# - do not delete the parent record if there is any child record if the
|
# - do not delete the parent record if there is any child record if the
|
||||||
# option is set to :restrict
|
# option is set to :restrict
|
||||||
#
|
def configure_dependency_for_has_many(reflection)
|
||||||
# The +extra_conditions+ parameter, which is not used within the main
|
if reflection.options[:dependent]
|
||||||
# Active Record codebase, is meant to allow plugins to define extra
|
method_name = "has_many_dependent_for_#{reflection.name}"
|
||||||
# finder conditions.
|
|
||||||
def configure_dependency_for_has_many(reflection, extra_conditions = nil)
|
|
||||||
if reflection.options.include?(:dependent)
|
|
||||||
case reflection.options[:dependent]
|
case reflection.options[:dependent]
|
||||||
when :destroy
|
when :destroy, :delete_all, :nullify
|
||||||
method_name = "has_many_dependent_destroy_for_#{reflection.name}".to_sym
|
define_method(method_name) do
|
||||||
define_method(method_name) do
|
if reflection.options[:dependent] == :destroy
|
||||||
send(reflection.name).each do |o|
|
send(reflection.name).each do |o|
|
||||||
# No point in executing the counter update since we're going to destroy the parent anyway
|
# No point in executing the counter update since we're going to destroy the parent anyway
|
||||||
counter_method = ('belongs_to_counter_cache_before_destroy_for_' + self.class.name.downcase).to_sym
|
counter_method = ('belongs_to_counter_cache_before_destroy_for_' + self.class.name.downcase).to_sym
|
||||||
|
@ -1633,35 +1631,21 @@ module ActiveRecord
|
||||||
o.destroy
|
o.destroy
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
before_destroy method_name
|
|
||||||
when :delete_all
|
# AssociationProxy#delete_all looks at the :dependent option and acts accordingly
|
||||||
before_destroy do |record|
|
send(reflection.name).delete_all
|
||||||
self.class.send(:delete_all_has_many_dependencies,
|
end
|
||||||
record,
|
when :restrict
|
||||||
reflection.name,
|
define_method(method_name) do
|
||||||
reflection.klass,
|
unless send(reflection.name).empty?
|
||||||
reflection.dependent_conditions(record, self.class, extra_conditions))
|
raise DeleteRestrictionError.new(reflection)
|
||||||
end
|
end
|
||||||
when :nullify
|
end
|
||||||
before_destroy do |record|
|
else
|
||||||
self.class.send(:nullify_has_many_dependencies,
|
raise ArgumentError, "The :dependent option expects either :destroy, :delete_all, :nullify or :restrict (#{reflection.options[:dependent].inspect})"
|
||||||
record,
|
|
||||||
reflection.name,
|
|
||||||
reflection.klass,
|
|
||||||
reflection.primary_key_name,
|
|
||||||
reflection.dependent_conditions(record, self.class, extra_conditions))
|
|
||||||
end
|
|
||||||
when :restrict
|
|
||||||
method_name = "has_many_dependent_restrict_for_#{reflection.name}".to_sym
|
|
||||||
define_method(method_name) do
|
|
||||||
unless send(reflection.name).empty?
|
|
||||||
raise DeleteRestrictionError.new(reflection)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
before_destroy method_name
|
|
||||||
else
|
|
||||||
raise ArgumentError, "The :dependent option expects either :destroy, :delete_all, :nullify or :restrict (#{reflection.options[:dependent].inspect})"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
before_destroy method_name
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1686,7 +1670,7 @@ module ActiveRecord
|
||||||
class_eval <<-eoruby, __FILE__, __LINE__ + 1
|
class_eval <<-eoruby, __FILE__, __LINE__ + 1
|
||||||
def #{method_name}
|
def #{method_name}
|
||||||
association = #{reflection.name}
|
association = #{reflection.name}
|
||||||
association.update_attribute(#{reflection.primary_key_name.inspect}, nil) if association
|
association.update_attribute(#{reflection.foreign_key.inspect}, nil) if association
|
||||||
end
|
end
|
||||||
eoruby
|
eoruby
|
||||||
when :restrict
|
when :restrict
|
||||||
|
@ -1724,14 +1708,6 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete_all_has_many_dependencies(record, reflection_name, association_class, dependent_conditions)
|
|
||||||
association_class.delete_all(dependent_conditions)
|
|
||||||
end
|
|
||||||
|
|
||||||
def nullify_has_many_dependencies(record, reflection_name, association_class, primary_key_name, dependent_conditions)
|
|
||||||
association_class.update_all("#{primary_key_name} = NULL", dependent_conditions)
|
|
||||||
end
|
|
||||||
|
|
||||||
mattr_accessor :valid_keys_for_has_many_association
|
mattr_accessor :valid_keys_for_has_many_association
|
||||||
@@valid_keys_for_has_many_association = [
|
@@valid_keys_for_has_many_association = [
|
||||||
:class_name, :table_name, :foreign_key, :primary_key,
|
:class_name, :table_name, :foreign_key, :primary_key,
|
||||||
|
@ -1806,7 +1782,7 @@ module ActiveRecord
|
||||||
|
|
||||||
reflection = create_reflection(:has_and_belongs_to_many, association_id, options, self)
|
reflection = create_reflection(:has_and_belongs_to_many, association_id, options, self)
|
||||||
|
|
||||||
if reflection.association_foreign_key == reflection.primary_key_name
|
if reflection.association_foreign_key == reflection.foreign_key
|
||||||
raise HasAndBelongsToManyAssociationForeignKeyNeeded.new(reflection)
|
raise HasAndBelongsToManyAssociationForeignKeyNeeded.new(reflection)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -31,10 +31,6 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def scoped
|
|
||||||
with_scope(@scope) { @reflection.klass.scoped }
|
|
||||||
end
|
|
||||||
|
|
||||||
def find(*args)
|
def find(*args)
|
||||||
options = args.extract_options!
|
options = args.extract_options!
|
||||||
|
|
||||||
|
@ -488,7 +484,7 @@ module ActiveRecord
|
||||||
ensure_owner_is_persisted!
|
ensure_owner_is_persisted!
|
||||||
|
|
||||||
transaction do
|
transaction do
|
||||||
with_scope(:create => @scope[:create].merge(scoped.where_values_hash)) do
|
with_scope(:create => @scope[:create].merge(scoped.scope_for_create)) do
|
||||||
build_record(attrs, &block)
|
build_record(attrs, &block)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,7 +8,7 @@ module ActiveRecord
|
||||||
#
|
#
|
||||||
# AssociationProxy
|
# AssociationProxy
|
||||||
# BelongsToAssociation
|
# BelongsToAssociation
|
||||||
# BelongsToPolymorphicAssociation
|
# BelongsToPolymorphicAssociation
|
||||||
# AssociationCollection + HasAssociation
|
# AssociationCollection + HasAssociation
|
||||||
# HasAndBelongsToManyAssociation
|
# HasAndBelongsToManyAssociation
|
||||||
# HasManyAssociation
|
# HasManyAssociation
|
||||||
|
@ -116,6 +116,7 @@ module ActiveRecord
|
||||||
# Reloads the \target and returns +self+ on success.
|
# Reloads the \target and returns +self+ on success.
|
||||||
def reload
|
def reload
|
||||||
reset
|
reset
|
||||||
|
construct_scope
|
||||||
load_target
|
load_target
|
||||||
self unless @target.nil?
|
self unless @target.nil?
|
||||||
end
|
end
|
||||||
|
@ -166,6 +167,10 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def scoped
|
||||||
|
with_scope(@scope) { target_klass.scoped }
|
||||||
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
def interpolate_sql(sql, record = nil)
|
def interpolate_sql(sql, record = nil)
|
||||||
@owner.send(:interpolate_sql, sql, record)
|
@owner.send(:interpolate_sql, sql, record)
|
||||||
|
@ -192,15 +197,19 @@ module ActiveRecord
|
||||||
|
|
||||||
# Forwards +with_scope+ to the reflection.
|
# Forwards +with_scope+ to the reflection.
|
||||||
def with_scope(*args, &block)
|
def with_scope(*args, &block)
|
||||||
@reflection.klass.send :with_scope, *args, &block
|
target_klass.send :with_scope, *args, &block
|
||||||
end
|
end
|
||||||
|
|
||||||
# Construct the scope used for find/create queries on the target
|
# Construct the scope used for find/create queries on the target
|
||||||
def construct_scope
|
def construct_scope
|
||||||
@scope = {
|
if target_klass
|
||||||
:find => construct_find_scope,
|
@scope = {
|
||||||
:create => construct_create_scope
|
:find => construct_find_scope,
|
||||||
}
|
:create => construct_create_scope
|
||||||
|
}
|
||||||
|
else
|
||||||
|
@scope = nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Implemented by subclasses
|
# Implemented by subclasses
|
||||||
|
@ -214,7 +223,7 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def aliased_table
|
def aliased_table
|
||||||
@reflection.klass.arel_table
|
target_klass.arel_table
|
||||||
end
|
end
|
||||||
|
|
||||||
# Set the inverse association, if possible
|
# Set the inverse association, if possible
|
||||||
|
@ -224,6 +233,12 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# This class of the target. belongs_to polymorphic overrides this to look at the
|
||||||
|
# polymorphic_type field on the owner.
|
||||||
|
def target_klass
|
||||||
|
@reflection.klass
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
# Forwards any missing method call to the \target.
|
# Forwards any missing method call to the \target.
|
||||||
def method_missing(method, *args)
|
def method_missing(method, *args)
|
||||||
|
@ -254,7 +269,7 @@ module ActiveRecord
|
||||||
def load_target
|
def load_target
|
||||||
return nil unless defined?(@loaded)
|
return nil unless defined?(@loaded)
|
||||||
|
|
||||||
if !loaded? && (!@owner.new_record? || foreign_key_present)
|
if !loaded? && (!@owner.new_record? || foreign_key_present) && @scope
|
||||||
@target = find_target
|
@target = find_target
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -282,11 +297,6 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns the ID of the owner, quoted if needed.
|
|
||||||
def owner_quoted_id
|
|
||||||
@owner.quoted_id
|
|
||||||
end
|
|
||||||
|
|
||||||
# Can be redefined by subclasses, notably polymorphic belongs_to
|
# Can be redefined by subclasses, notably polymorphic belongs_to
|
||||||
# The record parameter is necessary to support polymorphic inverses as we must check for
|
# The record parameter is necessary to support polymorphic inverses as we must check for
|
||||||
# the association in the specific class of the record.
|
# the association in the specific class of the record.
|
||||||
|
|
|
@ -11,29 +11,16 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def replace(record)
|
def replace(record)
|
||||||
counter_cache_name = @reflection.counter_cache_column
|
record = record.target if AssociationProxy === record
|
||||||
|
raise_on_type_mismatch(record) unless record.nil?
|
||||||
if record.nil?
|
|
||||||
if counter_cache_name && @owner.persisted?
|
|
||||||
@reflection.klass.decrement_counter(counter_cache_name, previous_record_id) if @owner[@reflection.primary_key_name]
|
|
||||||
end
|
|
||||||
|
|
||||||
@target = @owner[@reflection.primary_key_name] = nil
|
|
||||||
else
|
|
||||||
raise_on_type_mismatch(record)
|
|
||||||
|
|
||||||
if counter_cache_name && @owner.persisted? && record.id != @owner[@reflection.primary_key_name]
|
|
||||||
@reflection.klass.increment_counter(counter_cache_name, record.id)
|
|
||||||
@reflection.klass.decrement_counter(counter_cache_name, @owner[@reflection.primary_key_name]) if @owner[@reflection.primary_key_name]
|
|
||||||
end
|
|
||||||
|
|
||||||
@target = (AssociationProxy === record ? record.target : record)
|
|
||||||
@owner[@reflection.primary_key_name] = record_id(record) if record.persisted?
|
|
||||||
@updated = true
|
|
||||||
end
|
|
||||||
|
|
||||||
|
update_counters(record)
|
||||||
|
replace_keys(record)
|
||||||
set_inverse_instance(record)
|
set_inverse_instance(record)
|
||||||
|
|
||||||
|
@target = record
|
||||||
|
@updated = true if record
|
||||||
|
|
||||||
loaded
|
loaded
|
||||||
record
|
record
|
||||||
end
|
end
|
||||||
|
@ -44,8 +31,8 @@ module ActiveRecord
|
||||||
|
|
||||||
def stale_target?
|
def stale_target?
|
||||||
if @target && @target.persisted?
|
if @target && @target.persisted?
|
||||||
target_id = @target.send(@reflection.association_primary_key).to_s
|
target_id = @target[@reflection.association_primary_key].to_s
|
||||||
foreign_key = @owner.send(@reflection.primary_key_name).to_s
|
foreign_key = @owner[@reflection.foreign_key].to_s
|
||||||
|
|
||||||
target_id != foreign_key
|
target_id != foreign_key
|
||||||
else
|
else
|
||||||
|
@ -54,31 +41,55 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def find_target
|
def update_counters(record)
|
||||||
find_method = if @reflection.options[:primary_key]
|
counter_cache_name = @reflection.counter_cache_column
|
||||||
"find_by_#{@reflection.options[:primary_key]}"
|
|
||||||
else
|
|
||||||
"find"
|
|
||||||
end
|
|
||||||
|
|
||||||
options = @reflection.options.dup.slice(:select, :include, :readonly)
|
if counter_cache_name && @owner.persisted? && different_target?(record)
|
||||||
|
if record
|
||||||
|
record.class.increment_counter(counter_cache_name, record.id)
|
||||||
|
end
|
||||||
|
|
||||||
the_target = with_scope(:find => @scope[:find]) do
|
if foreign_key_present
|
||||||
@reflection.klass.send(find_method,
|
target_klass.decrement_counter(counter_cache_name, target_id)
|
||||||
@owner[@reflection.primary_key_name],
|
end
|
||||||
options
|
end
|
||||||
) if @owner[@reflection.primary_key_name]
|
end
|
||||||
|
|
||||||
|
# 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]
|
||||||
|
end
|
||||||
|
|
||||||
|
def replace_keys(record)
|
||||||
|
@owner[@reflection.foreign_key] = record && record[@reflection.association_primary_key]
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_target
|
||||||
|
if foreign_key_present
|
||||||
|
scoped.first.tap { |record| set_inverse_instance(record) }
|
||||||
end
|
end
|
||||||
set_inverse_instance(the_target)
|
|
||||||
the_target
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def construct_find_scope
|
def construct_find_scope
|
||||||
{ :conditions => conditions }
|
{
|
||||||
|
:conditions => construct_conditions,
|
||||||
|
:select => @reflection.options[:select],
|
||||||
|
:include => @reflection.options[:include],
|
||||||
|
:readonly => @reflection.options[:readonly]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def construct_conditions
|
||||||
|
conditions = aliased_table[@reflection.association_primary_key].
|
||||||
|
eq(@owner[@reflection.foreign_key])
|
||||||
|
|
||||||
|
conditions = conditions.and(Arel.sql(sql_conditions)) if sql_conditions
|
||||||
|
conditions
|
||||||
end
|
end
|
||||||
|
|
||||||
def foreign_key_present
|
def foreign_key_present
|
||||||
!@owner[@reflection.primary_key_name].nil?
|
!@owner[@reflection.foreign_key].nil?
|
||||||
end
|
end
|
||||||
|
|
||||||
# NOTE - for now, we're only supporting inverse setting from belongs_to back onto
|
# NOTE - for now, we're only supporting inverse setting from belongs_to back onto
|
||||||
|
@ -88,17 +99,12 @@ module ActiveRecord
|
||||||
inverse && inverse.macro == :has_one
|
inverse && inverse.macro == :has_one
|
||||||
end
|
end
|
||||||
|
|
||||||
def record_id(record)
|
def target_id
|
||||||
record.send(@reflection.options[:primary_key] || :id)
|
if @reflection.options[:primary_key]
|
||||||
end
|
@owner.send(@reflection.name).try(:id)
|
||||||
|
else
|
||||||
def previous_record_id
|
@owner[@reflection.foreign_key]
|
||||||
@previous_record_id ||= if @reflection.options[:primary_key]
|
end
|
||||||
previous_record = @owner.send(@reflection.name)
|
|
||||||
previous_record.nil? ? nil : previous_record.id
|
|
||||||
else
|
|
||||||
@owner[@reflection.primary_key_name]
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,32 +1,11 @@
|
||||||
module ActiveRecord
|
module ActiveRecord
|
||||||
# = Active Record Belongs To Polymorphic Association
|
# = Active Record Belongs To Polymorphic Association
|
||||||
module Associations
|
module Associations
|
||||||
class BelongsToPolymorphicAssociation < AssociationProxy #:nodoc:
|
class BelongsToPolymorphicAssociation < BelongsToAssociation #:nodoc:
|
||||||
def replace(record)
|
|
||||||
if record.nil?
|
|
||||||
@target = @owner[@reflection.primary_key_name] = @owner[@reflection.options[:foreign_type]] = nil
|
|
||||||
else
|
|
||||||
@target = (AssociationProxy === record ? record.target : record)
|
|
||||||
|
|
||||||
@owner[@reflection.primary_key_name] = record_id(record)
|
|
||||||
@owner[@reflection.options[:foreign_type]] = record.class.base_class.name.to_s
|
|
||||||
|
|
||||||
@updated = true
|
|
||||||
end
|
|
||||||
|
|
||||||
set_inverse_instance(record)
|
|
||||||
loaded
|
|
||||||
record
|
|
||||||
end
|
|
||||||
|
|
||||||
def updated?
|
|
||||||
@updated
|
|
||||||
end
|
|
||||||
|
|
||||||
def stale_target?
|
def stale_target?
|
||||||
if @target && @target.persisted?
|
if @target && @target.persisted?
|
||||||
target_id = @target.send(@reflection.association_primary_key).to_s
|
target_id = @target.send(@reflection.association_primary_key).to_s
|
||||||
foreign_key = @owner.send(@reflection.primary_key_name).to_s
|
foreign_key = @owner.send(@reflection.foreign_key).to_s
|
||||||
target_type = @target.class.base_class.name
|
target_type = @target.class.base_class.name
|
||||||
foreign_type = @owner.send(@reflection.options[:foreign_type]).to_s
|
foreign_type = @owner.send(@reflection.options[:foreign_type]).to_s
|
||||||
|
|
||||||
|
@ -38,43 +17,26 @@ module ActiveRecord
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def replace_keys(record)
|
||||||
|
super
|
||||||
|
@owner[@reflection.options[:foreign_type]] = record && record.class.base_class.name
|
||||||
|
end
|
||||||
|
|
||||||
|
def different_target?(record)
|
||||||
|
super || record.class != target_klass
|
||||||
|
end
|
||||||
|
|
||||||
def inverse_reflection_for(record)
|
def inverse_reflection_for(record)
|
||||||
@reflection.polymorphic_inverse_of(record.class)
|
@reflection.polymorphic_inverse_of(record.class)
|
||||||
end
|
end
|
||||||
|
|
||||||
def invertible_for?(record)
|
def target_klass
|
||||||
inverse = inverse_reflection_for(record)
|
type = @owner[@reflection.options[:foreign_type]]
|
||||||
inverse && inverse.macro == :has_one
|
type && type.constantize
|
||||||
end
|
end
|
||||||
|
|
||||||
def construct_find_scope
|
def raise_on_type_mismatch(record)
|
||||||
{ :conditions => conditions }
|
# A polymorphic association cannot have a type mismatch, by definition
|
||||||
end
|
|
||||||
|
|
||||||
def find_target
|
|
||||||
return nil if association_class.nil?
|
|
||||||
|
|
||||||
target = association_class.send(:with_scope, :find => @scope[:find]) do
|
|
||||||
association_class.find(
|
|
||||||
@owner[@reflection.primary_key_name],
|
|
||||||
:select => @reflection.options[:select],
|
|
||||||
:include => @reflection.options[:include]
|
|
||||||
)
|
|
||||||
end
|
|
||||||
set_inverse_instance(target)
|
|
||||||
target
|
|
||||||
end
|
|
||||||
|
|
||||||
def foreign_key_present
|
|
||||||
!@owner[@reflection.primary_key_name].nil?
|
|
||||||
end
|
|
||||||
|
|
||||||
def record_id(record)
|
|
||||||
record.send(@reflection.options[:primary_key] || :id)
|
|
||||||
end
|
|
||||||
|
|
||||||
def association_class
|
|
||||||
@owner[@reflection.options[:foreign_type]] ? @owner[@reflection.options[:foreign_type]].constantize : nil
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -193,11 +193,11 @@ module ActiveRecord
|
||||||
first_key = second_key = nil
|
first_key = second_key = nil
|
||||||
|
|
||||||
if through_reflection.macro == :belongs_to
|
if through_reflection.macro == :belongs_to
|
||||||
jt_primary_key = through_reflection.primary_key_name
|
jt_primary_key = through_reflection.foreign_key
|
||||||
jt_foreign_key = through_reflection.association_primary_key
|
jt_foreign_key = through_reflection.association_primary_key
|
||||||
else
|
else
|
||||||
jt_primary_key = through_reflection.active_record_primary_key
|
jt_primary_key = through_reflection.active_record_primary_key
|
||||||
jt_foreign_key = through_reflection.primary_key_name
|
jt_foreign_key = through_reflection.foreign_key
|
||||||
|
|
||||||
if through_reflection.options[:as] # has_many :through against a polymorphic join
|
if through_reflection.options[:as] # has_many :through against a polymorphic join
|
||||||
jt_conditions <<
|
jt_conditions <<
|
||||||
|
@ -231,7 +231,7 @@ module ActiveRecord
|
||||||
join_table[reflection.source_reflection.options[:foreign_type]].
|
join_table[reflection.source_reflection.options[:foreign_type]].
|
||||||
eq(reflection.options[:source_type])
|
eq(reflection.options[:source_type])
|
||||||
else
|
else
|
||||||
second_key = source_reflection.primary_key_name
|
second_key = source_reflection.foreign_key
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -262,7 +262,7 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def join_belongs_to_to(relation)
|
def join_belongs_to_to(relation)
|
||||||
foreign_key = options[:foreign_key] || reflection.primary_key_name
|
foreign_key = options[:foreign_key] || reflection.foreign_key
|
||||||
primary_key = options[:primary_key] || reflection.klass.primary_key
|
primary_key = options[:primary_key] || reflection.klass.primary_key
|
||||||
|
|
||||||
join_target_table(
|
join_target_table(
|
||||||
|
|
|
@ -40,7 +40,7 @@ module ActiveRecord
|
||||||
attributes = columns.map do |column|
|
attributes = columns.map do |column|
|
||||||
name = column.name
|
name = column.name
|
||||||
value = case name.to_s
|
value = case name.to_s
|
||||||
when @reflection.primary_key_name.to_s
|
when @reflection.foreign_key.to_s
|
||||||
@owner.id
|
@owner.id
|
||||||
when @reflection.association_foreign_key.to_s
|
when @reflection.association_foreign_key.to_s
|
||||||
record.id
|
record.id
|
||||||
|
@ -64,7 +64,7 @@ module ActiveRecord
|
||||||
records.each { |record| @owner.connection.delete(interpolate_sql(sql, record)) }
|
records.each { |record| @owner.connection.delete(interpolate_sql(sql, record)) }
|
||||||
else
|
else
|
||||||
relation = Arel::Table.new(@reflection.options[:join_table])
|
relation = Arel::Table.new(@reflection.options[:join_table])
|
||||||
stmt = relation.where(relation[@reflection.primary_key_name].eq(@owner.id).
|
stmt = relation.where(relation[@reflection.foreign_key].eq(@owner.id).
|
||||||
and(relation[@reflection.association_foreign_key].in(records.map { |x| x.id }.compact))
|
and(relation[@reflection.association_foreign_key].in(records.map { |x| x.id }.compact))
|
||||||
).compile_delete
|
).compile_delete
|
||||||
@owner.connection.delete stmt.to_sql
|
@owner.connection.delete stmt.to_sql
|
||||||
|
|
|
@ -14,9 +14,9 @@ module ActiveRecord
|
||||||
def construct_owner_attributes(reflection = @reflection)
|
def construct_owner_attributes(reflection = @reflection)
|
||||||
attributes = {}
|
attributes = {}
|
||||||
if reflection.macro == :belongs_to
|
if reflection.macro == :belongs_to
|
||||||
attributes[reflection.association_primary_key] = @owner.send(reflection.primary_key_name)
|
attributes[reflection.association_primary_key] = @owner.send(reflection.foreign_key)
|
||||||
else
|
else
|
||||||
attributes[reflection.primary_key_name] = @owner.send(reflection.active_record_primary_key)
|
attributes[reflection.foreign_key] = @owner.send(reflection.active_record_primary_key)
|
||||||
|
|
||||||
if reflection.options[:as]
|
if reflection.options[:as]
|
||||||
attributes["#{reflection.options[:as]}_type"] = @owner.class.base_class.name
|
attributes["#{reflection.options[:as]}_type"] = @owner.class.base_class.name
|
||||||
|
|
|
@ -7,14 +7,6 @@ module ActiveRecord
|
||||||
# is provided by its child HasManyThroughAssociation.
|
# is provided by its child HasManyThroughAssociation.
|
||||||
class HasManyAssociation < AssociationCollection #:nodoc:
|
class HasManyAssociation < AssociationCollection #:nodoc:
|
||||||
protected
|
protected
|
||||||
def owner_quoted_id
|
|
||||||
if @reflection.options[:primary_key]
|
|
||||||
@owner.class.quote_value(@owner.send(@reflection.options[:primary_key]))
|
|
||||||
else
|
|
||||||
@owner.quoted_id
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns the number of records in this collection.
|
# Returns the number of records in this collection.
|
||||||
#
|
#
|
||||||
# If the association has a counter cache it gets that value. Otherwise
|
# If the association has a counter cache it gets that value. Otherwise
|
||||||
|
@ -66,7 +58,7 @@ module ActiveRecord
|
||||||
when :delete_all
|
when :delete_all
|
||||||
@reflection.klass.delete(records.map { |r| r.id })
|
@reflection.klass.delete(records.map { |r| r.id })
|
||||||
else
|
else
|
||||||
updates = { @reflection.primary_key_name => nil }
|
updates = { @reflection.foreign_key => nil }
|
||||||
conditions = { @reflection.association_primary_key => records.map { |r| r.id } }
|
conditions = { @reflection.association_primary_key => records.map { |r| r.id } }
|
||||||
|
|
||||||
with_scope(@scope) do
|
with_scope(@scope) do
|
||||||
|
|
|
@ -31,7 +31,7 @@ module ActiveRecord
|
||||||
|
|
||||||
protected
|
protected
|
||||||
def target_reflection_has_associated_record?
|
def target_reflection_has_associated_record?
|
||||||
if @reflection.through_reflection.macro == :belongs_to && @owner[@reflection.through_reflection.primary_key_name].blank?
|
if @reflection.through_reflection.macro == :belongs_to && @owner[@reflection.through_reflection.foreign_key].blank?
|
||||||
false
|
false
|
||||||
else
|
else
|
||||||
true
|
true
|
||||||
|
|
|
@ -38,11 +38,11 @@ module ActiveRecord
|
||||||
@target.destroy if @target.persisted?
|
@target.destroy if @target.persisted?
|
||||||
@owner.clear_association_cache
|
@owner.clear_association_cache
|
||||||
when :nullify
|
when :nullify
|
||||||
@target[@reflection.primary_key_name] = nil
|
@target[@reflection.foreign_key] = nil
|
||||||
@target.save if @owner.persisted? && @target.persisted?
|
@target.save if @owner.persisted? && @target.persisted?
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
@target[@reflection.primary_key_name] = nil
|
@target[@reflection.foreign_key] = nil
|
||||||
@target.save if @owner.persisted? && @target.persisted?
|
@target.save if @owner.persisted? && @target.persisted?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -65,15 +65,6 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
|
||||||
def owner_quoted_id
|
|
||||||
if @reflection.options[:primary_key]
|
|
||||||
@owner.class.quote_value(@owner.send(@reflection.options[:primary_key]))
|
|
||||||
else
|
|
||||||
@owner.quoted_id
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
def find_target
|
def find_target
|
||||||
options = @reflection.options.dup.slice(:select, :order, :include, :readonly)
|
options = @reflection.options.dup.slice(:select, :order, :include, :readonly)
|
||||||
|
@ -105,7 +96,7 @@ module ActiveRecord
|
||||||
if replace_existing
|
if replace_existing
|
||||||
replace(record, true)
|
replace(record, true)
|
||||||
else
|
else
|
||||||
record[@reflection.primary_key_name] = @owner.id if @owner.persisted?
|
record[@reflection.foreign_key] = @owner.id if @owner.persisted?
|
||||||
self.target = record
|
self.target = record
|
||||||
set_inverse_instance(record)
|
set_inverse_instance(record)
|
||||||
end
|
end
|
||||||
|
|
|
@ -13,7 +13,7 @@ module ActiveRecord
|
||||||
def stale_target?
|
def stale_target?
|
||||||
if @target && @reflection.through_reflection.macro == :belongs_to && defined?(@through_foreign_key)
|
if @target && @reflection.through_reflection.macro == :belongs_to && defined?(@through_foreign_key)
|
||||||
previous_key = @through_foreign_key.to_s
|
previous_key = @through_foreign_key.to_s
|
||||||
current_key = @owner.send(@reflection.through_reflection.primary_key_name).to_s
|
current_key = @owner.send(@reflection.through_reflection.foreign_key).to_s
|
||||||
|
|
||||||
previous_key != current_key
|
previous_key != current_key
|
||||||
else
|
else
|
||||||
|
@ -69,14 +69,14 @@ module ActiveRecord
|
||||||
if @reflection.source_reflection.macro == :belongs_to
|
if @reflection.source_reflection.macro == :belongs_to
|
||||||
reflection_primary_key = @reflection.source_reflection.options[:primary_key] ||
|
reflection_primary_key = @reflection.source_reflection.options[:primary_key] ||
|
||||||
@reflection.klass.primary_key
|
@reflection.klass.primary_key
|
||||||
source_primary_key = @reflection.source_reflection.primary_key_name
|
source_primary_key = @reflection.source_reflection.foreign_key
|
||||||
if @reflection.options[:source_type]
|
if @reflection.options[:source_type]
|
||||||
column = @reflection.source_reflection.options[:foreign_type]
|
column = @reflection.source_reflection.options[:foreign_type]
|
||||||
conditions <<
|
conditions <<
|
||||||
right[column].eq(@reflection.options[:source_type])
|
right[column].eq(@reflection.options[:source_type])
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
reflection_primary_key = @reflection.source_reflection.primary_key_name
|
reflection_primary_key = @reflection.source_reflection.foreign_key
|
||||||
source_primary_key = @reflection.source_reflection.options[:primary_key] ||
|
source_primary_key = @reflection.source_reflection.options[:primary_key] ||
|
||||||
@reflection.through_reflection.klass.primary_key
|
@reflection.through_reflection.klass.primary_key
|
||||||
if @reflection.source_reflection.options[:as]
|
if @reflection.source_reflection.options[:as]
|
||||||
|
@ -100,7 +100,7 @@ module ActiveRecord
|
||||||
raise ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(@owner, @reflection) if [:has_one, :has_many].include?(@reflection.source_reflection.macro)
|
raise ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(@owner, @reflection) if [:has_one, :has_many].include?(@reflection.source_reflection.macro)
|
||||||
|
|
||||||
join_attributes = {
|
join_attributes = {
|
||||||
@reflection.source_reflection.primary_key_name =>
|
@reflection.source_reflection.foreign_key =>
|
||||||
associate.send(@reflection.source_reflection.association_primary_key)
|
associate.send(@reflection.source_reflection.association_primary_key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,10 +158,8 @@ module ActiveRecord
|
||||||
alias_method :sql_conditions, :conditions
|
alias_method :sql_conditions, :conditions
|
||||||
|
|
||||||
def update_stale_state
|
def update_stale_state
|
||||||
construct_scope if stale_target?
|
|
||||||
|
|
||||||
if @reflection.through_reflection.macro == :belongs_to
|
if @reflection.through_reflection.macro == :belongs_to
|
||||||
@through_foreign_key = @owner.send(@reflection.through_reflection.primary_key_name)
|
@through_foreign_key = @owner.send(@reflection.through_reflection.foreign_key)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -343,8 +343,8 @@ module ActiveRecord
|
||||||
association.destroy
|
association.destroy
|
||||||
else
|
else
|
||||||
key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id
|
key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id
|
||||||
if autosave != false && (new_record? || association.new_record? || association[reflection.primary_key_name] != key || autosave)
|
if autosave != false && (new_record? || association.new_record? || association[reflection.foreign_key] != key || autosave)
|
||||||
association[reflection.primary_key_name] = key
|
association[reflection.foreign_key] = key
|
||||||
saved = association.save(:validate => !autosave)
|
saved = association.save(:validate => !autosave)
|
||||||
raise ActiveRecord::Rollback if !saved && autosave
|
raise ActiveRecord::Rollback if !saved && autosave
|
||||||
saved
|
saved
|
||||||
|
@ -367,7 +367,7 @@ module ActiveRecord
|
||||||
|
|
||||||
if association.updated?
|
if association.updated?
|
||||||
association_id = association.send(reflection.options[:primary_key] || :id)
|
association_id = association.send(reflection.options[:primary_key] || :id)
|
||||||
self[reflection.primary_key_name] = association_id
|
self[reflection.foreign_key] = association_id
|
||||||
end
|
end
|
||||||
|
|
||||||
saved if autosave
|
saved if autosave
|
||||||
|
|
|
@ -874,7 +874,12 @@ module ActiveRecord #:nodoc:
|
||||||
|
|
||||||
def relation #:nodoc:
|
def relation #:nodoc:
|
||||||
@relation ||= Relation.new(self, arel_table)
|
@relation ||= Relation.new(self, arel_table)
|
||||||
finder_needs_type_condition? ? @relation.where(type_condition) : @relation
|
|
||||||
|
if finder_needs_type_condition?
|
||||||
|
@relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name)
|
||||||
|
else
|
||||||
|
@relation
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Finder methods must instantiate through this method to work with the
|
# Finder methods must instantiate through this method to work with the
|
||||||
|
@ -914,10 +919,9 @@ module ActiveRecord #:nodoc:
|
||||||
|
|
||||||
def type_condition
|
def type_condition
|
||||||
sti_column = arel_table[inheritance_column.to_sym]
|
sti_column = arel_table[inheritance_column.to_sym]
|
||||||
condition = sti_column.eq(sti_name)
|
sti_names = ([self] + descendants).map { |model| model.sti_name }
|
||||||
descendants.each { |subclass| condition = condition.or(sti_column.eq(subclass.sti_name)) }
|
|
||||||
|
|
||||||
condition
|
sti_column.in(sti_names)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Guesses the table name, but does not decorate it with prefix and suffix information.
|
# Guesses the table name, but does not decorate it with prefix and suffix information.
|
||||||
|
|
|
@ -634,7 +634,7 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
|
||||||
|
|
||||||
targets.each do |target|
|
targets.each do |target|
|
||||||
join_fixtures["#{label}_#{target}"] = Fixture.new(
|
join_fixtures["#{label}_#{target}"] = Fixture.new(
|
||||||
{ association.primary_key_name => row[primary_key_name],
|
{ association.foreign_key => row[primary_key_name],
|
||||||
association.association_foreign_key => Fixtures.identify(target) },
|
association.association_foreign_key => Fixtures.identify(target) },
|
||||||
nil, @connection)
|
nil, @connection)
|
||||||
end
|
end
|
||||||
|
|
|
@ -196,8 +196,8 @@ module ActiveRecord
|
||||||
@quoted_table_name ||= klass.quoted_table_name
|
@quoted_table_name ||= klass.quoted_table_name
|
||||||
end
|
end
|
||||||
|
|
||||||
def primary_key_name
|
def foreign_key
|
||||||
@primary_key_name ||= options[:foreign_key] || derive_primary_key_name
|
@foreign_key ||= options[:foreign_key] || derive_foreign_key
|
||||||
end
|
end
|
||||||
|
|
||||||
def primary_key_column
|
def primary_key_column
|
||||||
|
@ -251,7 +251,7 @@ module ActiveRecord
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
def through_reflection_primary_key_name
|
def through_reflection_foreign_key
|
||||||
end
|
end
|
||||||
|
|
||||||
def source_reflection
|
def source_reflection
|
||||||
|
@ -298,17 +298,6 @@ module ActiveRecord
|
||||||
!options[:validate].nil? ? options[:validate] : (options[:autosave] == true || macro == :has_many)
|
!options[:validate].nil? ? options[:validate] : (options[:autosave] == true || macro == :has_many)
|
||||||
end
|
end
|
||||||
|
|
||||||
def dependent_conditions(record, base_class, extra_conditions)
|
|
||||||
dependent_conditions = []
|
|
||||||
dependent_conditions << "#{primary_key_name} = #{record.send(name).send(:owner_quoted_id)}"
|
|
||||||
dependent_conditions << "#{options[:as]}_type = '#{base_class.name}'" if options[:as]
|
|
||||||
dependent_conditions << klass.send(:sanitize_sql, options[:conditions]) if options[:conditions]
|
|
||||||
dependent_conditions << extra_conditions if extra_conditions
|
|
||||||
dependent_conditions = dependent_conditions.collect {|where| "(#{where})" }.join(" AND ")
|
|
||||||
dependent_conditions = dependent_conditions.gsub('@', '\@')
|
|
||||||
dependent_conditions
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns +true+ if +self+ is a +belongs_to+ reflection.
|
# Returns +true+ if +self+ is a +belongs_to+ reflection.
|
||||||
def belongs_to?
|
def belongs_to?
|
||||||
macro == :belongs_to
|
macro == :belongs_to
|
||||||
|
@ -321,7 +310,7 @@ module ActiveRecord
|
||||||
class_name
|
class_name
|
||||||
end
|
end
|
||||||
|
|
||||||
def derive_primary_key_name
|
def derive_foreign_key
|
||||||
if belongs_to?
|
if belongs_to?
|
||||||
"#{name}_id"
|
"#{name}_id"
|
||||||
elsif options[:as]
|
elsif options[:as]
|
||||||
|
@ -403,11 +392,11 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def through_reflection_primary_key
|
def through_reflection_primary_key
|
||||||
through_reflection.belongs_to? ? through_reflection.klass.primary_key : through_reflection.primary_key_name
|
through_reflection.belongs_to? ? through_reflection.klass.primary_key : through_reflection.foreign_key
|
||||||
end
|
end
|
||||||
|
|
||||||
def through_reflection_primary_key_name
|
def through_reflection_foreign_key
|
||||||
through_reflection.primary_key_name if through_reflection.belongs_to?
|
through_reflection.foreign_key if through_reflection.belongs_to?
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -211,7 +211,7 @@ module ActiveRecord
|
||||||
group_attr = @group_values
|
group_attr = @group_values
|
||||||
association = @klass.reflect_on_association(group_attr.first.to_sym)
|
association = @klass.reflect_on_association(group_attr.first.to_sym)
|
||||||
associated = group_attr.size == 1 && association && association.macro == :belongs_to # only count belongs_to associations
|
associated = group_attr.size == 1 && association && association.macro == :belongs_to # only count belongs_to associations
|
||||||
group_fields = Array(associated ? association.primary_key_name : group_attr)
|
group_fields = Array(associated ? association.foreign_key : group_attr)
|
||||||
group_aliases = group_fields.map { |field| column_alias_for(field) }
|
group_aliases = group_fields.map { |field| column_alias_for(field) }
|
||||||
group_columns = group_aliases.zip(group_fields).map { |aliaz,field|
|
group_columns = group_aliases.zip(group_fields).map { |aliaz,field|
|
||||||
[aliaz, column_for(field)]
|
[aliaz, column_for(field)]
|
||||||
|
|
|
@ -46,13 +46,17 @@ module ActiveRecord
|
||||||
|
|
||||||
merged_relation.where_values = merged_wheres
|
merged_relation.where_values = merged_wheres
|
||||||
|
|
||||||
(Relation::SINGLE_VALUE_METHODS - [:lock]).each do |method|
|
(Relation::SINGLE_VALUE_METHODS - [:lock, :create_with]).each do |method|
|
||||||
value = r.send(:"#{method}_value")
|
value = r.send(:"#{method}_value")
|
||||||
merged_relation.send(:"#{method}_value=", value) unless value.nil?
|
merged_relation.send(:"#{method}_value=", value) unless value.nil?
|
||||||
end
|
end
|
||||||
|
|
||||||
merged_relation.lock_value = r.lock_value unless merged_relation.lock_value
|
merged_relation.lock_value = r.lock_value unless merged_relation.lock_value
|
||||||
|
|
||||||
|
if r.create_with_value
|
||||||
|
merged_relation.create_with_value = (merged_relation.create_with_value || {}).merge(r.create_with_value)
|
||||||
|
end
|
||||||
|
|
||||||
# Apply scope extension modules
|
# Apply scope extension modules
|
||||||
merged_relation.send :apply_modules, r.extensions
|
merged_relation.send :apply_modules, r.extensions
|
||||||
|
|
||||||
|
|
|
@ -198,16 +198,23 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
|
||||||
assert_equal 1, Post.find(p.id).comments.size
|
assert_equal 1, Post.find(p.id).comments.size
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_belongs_to_with_primary_key_counter_with_assigning_nil
|
def test_belongs_to_with_primary_key_counter
|
||||||
debate = Topic.create("title" => "debate")
|
debate = Topic.create("title" => "debate")
|
||||||
reply = Reply.create("title" => "blah!", "content" => "world around!", "parent_title" => "debate")
|
debate2 = Topic.create("title" => "debate2")
|
||||||
|
reply = Reply.create("title" => "blah!", "content" => "world around!", "parent_title" => "debate")
|
||||||
|
|
||||||
assert_equal debate.title, reply.parent_title
|
assert_equal 1, debate.reload.replies_count
|
||||||
assert_equal 1, Topic.find(debate.id).send(:read_attribute, "replies_count")
|
assert_equal 0, debate2.reload.replies_count
|
||||||
|
|
||||||
|
reply.topic_with_primary_key = debate2
|
||||||
|
|
||||||
|
assert_equal 0, debate.reload.replies_count
|
||||||
|
assert_equal 1, debate2.reload.replies_count
|
||||||
|
|
||||||
reply.topic_with_primary_key = nil
|
reply.topic_with_primary_key = nil
|
||||||
|
|
||||||
assert_equal 0, Topic.find(debate.id).send(:read_attribute, "replies_count")
|
assert_equal 0, debate.reload.replies_count
|
||||||
|
assert_equal 0, debate2.reload.replies_count
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_belongs_to_counter_with_reassigning
|
def test_belongs_to_counter_with_reassigning
|
||||||
|
@ -419,6 +426,18 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
|
||||||
assert_nil sponsor.sponsorable_id
|
assert_nil sponsor.sponsorable_id
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_assignment_updates_foreign_id_field_for_new_and_saved_records
|
||||||
|
client = Client.new
|
||||||
|
saved_firm = Firm.create :name => "Saved"
|
||||||
|
new_firm = Firm.new
|
||||||
|
|
||||||
|
client.firm = saved_firm
|
||||||
|
assert_equal saved_firm.id, client.client_of
|
||||||
|
|
||||||
|
client.firm = new_firm
|
||||||
|
assert_nil client.client_of
|
||||||
|
end
|
||||||
|
|
||||||
def test_polymorphic_assignment_with_primary_key_updates_foreign_id_field_for_new_and_saved_records
|
def test_polymorphic_assignment_with_primary_key_updates_foreign_id_field_for_new_and_saved_records
|
||||||
essay = Essay.new
|
essay = Essay.new
|
||||||
saved_writer = Author.create(:name => "David")
|
saved_writer = Author.create(:name => "David")
|
||||||
|
@ -537,4 +556,27 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
|
||||||
assert proxy.stale_target?
|
assert proxy.stale_target?
|
||||||
assert_equal companies(:first_firm), sponsor.sponsorable
|
assert_equal companies(:first_firm), sponsor.sponsorable
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_reloading_association_with_key_change
|
||||||
|
client = companies(:second_client)
|
||||||
|
firm = client.firm # note this is a proxy object
|
||||||
|
|
||||||
|
client.firm = companies(:another_firm)
|
||||||
|
assert_equal companies(:another_firm), firm.reload
|
||||||
|
|
||||||
|
client.client_of = companies(:first_firm).id
|
||||||
|
assert_equal companies(:first_firm), firm.reload
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_polymorphic_counter_cache
|
||||||
|
tagging = taggings(:welcome_general)
|
||||||
|
post = posts(:welcome)
|
||||||
|
comment = comments(:greetings)
|
||||||
|
|
||||||
|
assert_difference 'post.reload.taggings_count', -1 do
|
||||||
|
assert_difference 'comment.reload.taggings_count', +1 do
|
||||||
|
tagging.taggable = comment
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,6 +3,7 @@ require 'models/post'
|
||||||
require 'models/author'
|
require 'models/author'
|
||||||
require 'models/project'
|
require 'models/project'
|
||||||
require 'models/developer'
|
require 'models/developer'
|
||||||
|
require 'models/company'
|
||||||
|
|
||||||
class AssociationCallbacksTest < ActiveRecord::TestCase
|
class AssociationCallbacksTest < ActiveRecord::TestCase
|
||||||
fixtures :posts, :authors, :projects, :developers
|
fixtures :posts, :authors, :projects, :developers
|
||||||
|
@ -81,6 +82,14 @@ class AssociationCallbacksTest < ActiveRecord::TestCase
|
||||||
assert_equal callback_log, jack.post_log
|
assert_equal callback_log, jack.post_log
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_has_many_callbacks_for_destroy_on_parent
|
||||||
|
firm = Firm.create! :name => "Firm"
|
||||||
|
client = firm.clients.create! :name => "Client"
|
||||||
|
firm.destroy
|
||||||
|
|
||||||
|
assert_equal ["before_remove#{client.id}", "after_remove#{client.id}"], firm.log
|
||||||
|
end
|
||||||
|
|
||||||
def test_has_and_belongs_to_many_add_callback
|
def test_has_and_belongs_to_many_add_callback
|
||||||
david = developers(:david)
|
david = developers(:david)
|
||||||
ar = projects(:active_record)
|
ar = projects(:active_record)
|
||||||
|
|
|
@ -101,9 +101,10 @@ class LifecycleTest < ActiveRecord::TestCase
|
||||||
fixtures :topics, :developers, :minimalistics
|
fixtures :topics, :developers, :minimalistics
|
||||||
|
|
||||||
def test_before_destroy
|
def test_before_destroy
|
||||||
original_count = Topic.count
|
topic = Topic.find(1)
|
||||||
(topic_to_be_destroyed = Topic.find(1)).destroy
|
assert_difference 'Topic.count', -(1 + topic.replies.size) do
|
||||||
assert_equal original_count - (1 + topic_to_be_destroyed.replies.size), Topic.count
|
topic.destroy
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_auto_observer
|
def test_auto_observer
|
||||||
|
|
|
@ -8,6 +8,8 @@ require 'models/ship'
|
||||||
require 'models/pirate'
|
require 'models/pirate'
|
||||||
require 'models/price_estimate'
|
require 'models/price_estimate'
|
||||||
require 'models/tagging'
|
require 'models/tagging'
|
||||||
|
require 'models/author'
|
||||||
|
require 'models/post'
|
||||||
|
|
||||||
class ReflectionTest < ActiveRecord::TestCase
|
class ReflectionTest < ActiveRecord::TestCase
|
||||||
include ActiveRecord::Reflection
|
include ActiveRecord::Reflection
|
||||||
|
@ -127,11 +129,11 @@ class ReflectionTest < ActiveRecord::TestCase
|
||||||
|
|
||||||
def test_belongs_to_inferred_foreign_key_from_assoc_name
|
def test_belongs_to_inferred_foreign_key_from_assoc_name
|
||||||
Company.belongs_to :foo
|
Company.belongs_to :foo
|
||||||
assert_equal "foo_id", Company.reflect_on_association(:foo).primary_key_name
|
assert_equal "foo_id", Company.reflect_on_association(:foo).foreign_key
|
||||||
Company.belongs_to :bar, :class_name => "Xyzzy"
|
Company.belongs_to :bar, :class_name => "Xyzzy"
|
||||||
assert_equal "bar_id", Company.reflect_on_association(:bar).primary_key_name
|
assert_equal "bar_id", Company.reflect_on_association(:bar).foreign_key
|
||||||
Company.belongs_to :baz, :class_name => "Xyzzy", :foreign_key => "xyzzy_id"
|
Company.belongs_to :baz, :class_name => "Xyzzy", :foreign_key => "xyzzy_id"
|
||||||
assert_equal "xyzzy_id", Company.reflect_on_association(:baz).primary_key_name
|
assert_equal "xyzzy_id", Company.reflect_on_association(:baz).foreign_key
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_association_reflection_in_modules
|
def test_association_reflection_in_modules
|
||||||
|
|
|
@ -485,4 +485,11 @@ class DefaultScopingTest < ActiveRecord::TestCase
|
||||||
posts_offset_limit = Post.offset(2).limit(3)
|
posts_offset_limit = Post.offset(2).limit(3)
|
||||||
assert_equal posts_limit_offset, posts_offset_limit
|
assert_equal posts_limit_offset, posts_offset_limit
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_create_with_merge
|
||||||
|
aaron = (PoorDeveloperCalledJamis.create_with(:name => 'foo', :salary => 20) &
|
||||||
|
PoorDeveloperCalledJamis.create_with(:name => 'Aaron')).new
|
||||||
|
assert_equal 20, aaron.salary
|
||||||
|
assert_equal 'Aaron', aaron.name
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -38,7 +38,9 @@ end
|
||||||
class Firm < Company
|
class Firm < Company
|
||||||
has_many :clients, :order => "id", :dependent => :destroy, :counter_sql =>
|
has_many :clients, :order => "id", :dependent => :destroy, :counter_sql =>
|
||||||
"SELECT COUNT(*) FROM companies WHERE firm_id = 1 " +
|
"SELECT COUNT(*) FROM companies WHERE firm_id = 1 " +
|
||||||
"AND (#{QUOTED_TYPE} = 'Client' OR #{QUOTED_TYPE} = 'SpecialClient' OR #{QUOTED_TYPE} = 'VerySpecialClient' )"
|
"AND (#{QUOTED_TYPE} = 'Client' OR #{QUOTED_TYPE} = 'SpecialClient' OR #{QUOTED_TYPE} = 'VerySpecialClient' )",
|
||||||
|
:before_remove => :log_before_remove,
|
||||||
|
:after_remove => :log_after_remove
|
||||||
has_many :unsorted_clients, :class_name => "Client"
|
has_many :unsorted_clients, :class_name => "Client"
|
||||||
has_many :clients_sorted_desc, :class_name => "Client", :order => "id DESC"
|
has_many :clients_sorted_desc, :class_name => "Client", :order => "id DESC"
|
||||||
has_many :clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id"
|
has_many :clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id"
|
||||||
|
@ -88,6 +90,19 @@ class Firm < Company
|
||||||
has_one :unautosaved_account, :foreign_key => "firm_id", :class_name => 'Account', :autosave => false
|
has_one :unautosaved_account, :foreign_key => "firm_id", :class_name => 'Account', :autosave => false
|
||||||
has_many :accounts
|
has_many :accounts
|
||||||
has_many :unautosaved_accounts, :foreign_key => "firm_id", :class_name => 'Account', :autosave => false
|
has_many :unautosaved_accounts, :foreign_key => "firm_id", :class_name => 'Account', :autosave => false
|
||||||
|
|
||||||
|
def log
|
||||||
|
@log ||= []
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def log_before_remove(record)
|
||||||
|
log << "before_remove#{record.id}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def log_after_remove(record)
|
||||||
|
log << "after_remove#{record.id}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class DependentFirm < Company
|
class DependentFirm < Company
|
||||||
|
|
|
@ -143,6 +143,7 @@ ActiveRecord::Schema.define do
|
||||||
t.text :body, :null => false
|
t.text :body, :null => false
|
||||||
end
|
end
|
||||||
t.string :type
|
t.string :type
|
||||||
|
t.integer :taggings_count, :default => 0
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table :companies, :force => true do |t|
|
create_table :companies, :force => true do |t|
|
||||||
|
|
Loading…
Reference in a new issue