1
0
Fork 0
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:
Aaron Patterson 2011-01-01 11:43:53 -08:00
commit 5c7fd87665
26 changed files with 261 additions and 256 deletions

View file

@ -205,7 +205,7 @@ module ActiveRecord
# FIXME: options[:select] is always nil in the tests. Do we really
# need it?
options[:select] || left[Arel.star],
right[reflection.primary_key_name].as(
right[reflection.foreign_key].as(
Arel.sql('the_parent_record_id'))
]
@ -220,7 +220,7 @@ module ActiveRecord
all_associated_records = associated_records(ids) do |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|
ast.and cond
end
@ -241,7 +241,7 @@ module ActiveRecord
unless through_records.empty?
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
through_records.first.class.preload_associations(through_records, source)
if through_reflection.macro == :belongs_to
@ -255,7 +255,7 @@ module ActiveRecord
end
end
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
@ -263,8 +263,8 @@ module ActiveRecord
return if records.first.send(reflection.name).loaded?
options = reflection.options
primary_key_name = reflection.through_reflection_primary_key_name
id_to_record_map, ids = construct_id_map(records, primary_key_name || reflection.options[:primary_key])
foreign_key = reflection.through_reflection_foreign_key
id_to_record_map, ids = construct_id_map(records, foreign_key || reflection.options[:primary_key])
records.each {|record| record.send(reflection.name).loaded}
if options[:through]
@ -281,7 +281,7 @@ module ActiveRecord
else
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
@ -319,7 +319,7 @@ module ActiveRecord
def preload_belongs_to_association(records, reflection, preload_options={})
return if records.first.send("loaded_#{reflection.name}?")
options = reflection.options
primary_key_name = reflection.primary_key_name
foreign_key = reflection.foreign_key
klasses_and_ids = {}
@ -330,7 +330,7 @@ module ActiveRecord
# to their parent_records
records.each do |record|
if klass = record.send(polymorph_type)
klass_id = record.send(primary_key_name)
klass_id = record.send(foreign_key)
if klass_id
id_map = klasses_and_ids[klass.constantize] ||= {}
(id_map[klass_id.to_s] ||= []) << record
@ -339,7 +339,7 @@ module ActiveRecord
end
else
id_map = records.group_by do |record|
key = record.send(primary_key_name)
key = record.send(foreign_key)
key && key.to_s
end
id_map.delete nil
@ -369,7 +369,7 @@ module ActiveRecord
conditions = []
key = reflection.primary_key_name
key = reflection.foreign_key
if interface = reflection.options[:as]
key = "#{interface}_id"

View file

@ -1612,16 +1612,14 @@ module ActiveRecord
# - 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
# option is set to :restrict
#
# The +extra_conditions+ parameter, which is not used within the main
# Active Record codebase, is meant to allow plugins to define extra
# finder conditions.
def configure_dependency_for_has_many(reflection, extra_conditions = nil)
if reflection.options.include?(:dependent)
def configure_dependency_for_has_many(reflection)
if reflection.options[:dependent]
method_name = "has_many_dependent_for_#{reflection.name}"
case reflection.options[:dependent]
when :destroy
method_name = "has_many_dependent_destroy_for_#{reflection.name}".to_sym
define_method(method_name) do
when :destroy, :delete_all, :nullify
define_method(method_name) do
if reflection.options[:dependent] == :destroy
send(reflection.name).each do |o|
# 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
@ -1633,35 +1631,21 @@ module ActiveRecord
o.destroy
end
end
before_destroy method_name
when :delete_all
before_destroy do |record|
self.class.send(:delete_all_has_many_dependencies,
record,
reflection.name,
reflection.klass,
reflection.dependent_conditions(record, self.class, extra_conditions))
# AssociationProxy#delete_all looks at the :dependent option and acts accordingly
send(reflection.name).delete_all
end
when :restrict
define_method(method_name) do
unless send(reflection.name).empty?
raise DeleteRestrictionError.new(reflection)
end
when :nullify
before_destroy do |record|
self.class.send(:nullify_has_many_dependencies,
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
else
raise ArgumentError, "The :dependent option expects either :destroy, :delete_all, :nullify or :restrict (#{reflection.options[:dependent].inspect})"
end
before_destroy method_name
end
end
@ -1686,7 +1670,7 @@ module ActiveRecord
class_eval <<-eoruby, __FILE__, __LINE__ + 1
def #{method_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
eoruby
when :restrict
@ -1724,14 +1708,6 @@ module ActiveRecord
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
@@valid_keys_for_has_many_association = [
: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)
if reflection.association_foreign_key == reflection.primary_key_name
if reflection.association_foreign_key == reflection.foreign_key
raise HasAndBelongsToManyAssociationForeignKeyNeeded.new(reflection)
end

View file

@ -31,10 +31,6 @@ module ActiveRecord
end
end
def scoped
with_scope(@scope) { @reflection.klass.scoped }
end
def find(*args)
options = args.extract_options!
@ -488,7 +484,7 @@ module ActiveRecord
ensure_owner_is_persisted!
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)
end
end

View file

@ -8,7 +8,7 @@ module ActiveRecord
#
# AssociationProxy
# BelongsToAssociation
# BelongsToPolymorphicAssociation
# BelongsToPolymorphicAssociation
# AssociationCollection + HasAssociation
# HasAndBelongsToManyAssociation
# HasManyAssociation
@ -116,6 +116,7 @@ module ActiveRecord
# Reloads the \target and returns +self+ on success.
def reload
reset
construct_scope
load_target
self unless @target.nil?
end
@ -166,6 +167,10 @@ module ActiveRecord
end
end
def scoped
with_scope(@scope) { target_klass.scoped }
end
protected
def interpolate_sql(sql, record = nil)
@owner.send(:interpolate_sql, sql, record)
@ -192,15 +197,19 @@ module ActiveRecord
# Forwards +with_scope+ to the reflection.
def with_scope(*args, &block)
@reflection.klass.send :with_scope, *args, &block
target_klass.send :with_scope, *args, &block
end
# Construct the scope used for find/create queries on the target
def construct_scope
@scope = {
:find => construct_find_scope,
:create => construct_create_scope
}
if target_klass
@scope = {
:find => construct_find_scope,
:create => construct_create_scope
}
else
@scope = nil
end
end
# Implemented by subclasses
@ -214,7 +223,7 @@ module ActiveRecord
end
def aliased_table
@reflection.klass.arel_table
target_klass.arel_table
end
# Set the inverse association, if possible
@ -224,6 +233,12 @@ module ActiveRecord
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
# Forwards any missing method call to the \target.
def method_missing(method, *args)
@ -254,7 +269,7 @@ module ActiveRecord
def load_target
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
end
@ -282,11 +297,6 @@ module ActiveRecord
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
# The record parameter is necessary to support polymorphic inverses as we must check for
# the association in the specific class of the record.

View file

@ -11,29 +11,16 @@ module ActiveRecord
end
def replace(record)
counter_cache_name = @reflection.counter_cache_column
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
record = record.target if AssociationProxy === record
raise_on_type_mismatch(record) unless record.nil?
update_counters(record)
replace_keys(record)
set_inverse_instance(record)
@target = record
@updated = true if record
loaded
record
end
@ -44,8 +31,8 @@ module ActiveRecord
def stale_target?
if @target && @target.persisted?
target_id = @target.send(@reflection.association_primary_key).to_s
foreign_key = @owner.send(@reflection.primary_key_name).to_s
target_id = @target[@reflection.association_primary_key].to_s
foreign_key = @owner[@reflection.foreign_key].to_s
target_id != foreign_key
else
@ -54,31 +41,55 @@ module ActiveRecord
end
private
def find_target
find_method = if @reflection.options[:primary_key]
"find_by_#{@reflection.options[:primary_key]}"
else
"find"
end
def update_counters(record)
counter_cache_name = @reflection.counter_cache_column
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
@reflection.klass.send(find_method,
@owner[@reflection.primary_key_name],
options
) if @owner[@reflection.primary_key_name]
if foreign_key_present
target_klass.decrement_counter(counter_cache_name, target_id)
end
end
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
set_inverse_instance(the_target)
the_target
end
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
def foreign_key_present
!@owner[@reflection.primary_key_name].nil?
!@owner[@reflection.foreign_key].nil?
end
# 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
end
def record_id(record)
record.send(@reflection.options[:primary_key] || :id)
end
def previous_record_id
@previous_record_id ||= if @reflection.options[:primary_key]
previous_record = @owner.send(@reflection.name)
previous_record.nil? ? nil : previous_record.id
else
@owner[@reflection.primary_key_name]
end
def target_id
if @reflection.options[:primary_key]
@owner.send(@reflection.name).try(:id)
else
@owner[@reflection.foreign_key]
end
end
end
end

View file

@ -1,32 +1,11 @@
module ActiveRecord
# = Active Record Belongs To Polymorphic Association
module Associations
class BelongsToPolymorphicAssociation < AssociationProxy #: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
class BelongsToPolymorphicAssociation < BelongsToAssociation #:nodoc:
def stale_target?
if @target && @target.persisted?
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
foreign_type = @owner.send(@reflection.options[:foreign_type]).to_s
@ -38,43 +17,26 @@ module ActiveRecord
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)
@reflection.polymorphic_inverse_of(record.class)
end
def invertible_for?(record)
inverse = inverse_reflection_for(record)
inverse && inverse.macro == :has_one
def target_klass
type = @owner[@reflection.options[:foreign_type]]
type && type.constantize
end
def construct_find_scope
{ :conditions => conditions }
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
def raise_on_type_mismatch(record)
# A polymorphic association cannot have a type mismatch, by definition
end
end
end

View file

@ -193,11 +193,11 @@ module ActiveRecord
first_key = second_key = nil
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
else
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
jt_conditions <<
@ -231,7 +231,7 @@ module ActiveRecord
join_table[reflection.source_reflection.options[:foreign_type]].
eq(reflection.options[:source_type])
else
second_key = source_reflection.primary_key_name
second_key = source_reflection.foreign_key
end
end
@ -262,7 +262,7 @@ module ActiveRecord
end
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
join_target_table(

View file

@ -40,7 +40,7 @@ module ActiveRecord
attributes = columns.map do |column|
name = column.name
value = case name.to_s
when @reflection.primary_key_name.to_s
when @reflection.foreign_key.to_s
@owner.id
when @reflection.association_foreign_key.to_s
record.id
@ -64,7 +64,7 @@ module ActiveRecord
records.each { |record| @owner.connection.delete(interpolate_sql(sql, record)) }
else
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))
).compile_delete
@owner.connection.delete stmt.to_sql

View file

@ -14,9 +14,9 @@ module ActiveRecord
def construct_owner_attributes(reflection = @reflection)
attributes = {}
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
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]
attributes["#{reflection.options[:as]}_type"] = @owner.class.base_class.name

View file

@ -7,14 +7,6 @@ module ActiveRecord
# is provided by its child HasManyThroughAssociation.
class HasManyAssociation < AssociationCollection #:nodoc:
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.
#
# If the association has a counter cache it gets that value. Otherwise
@ -66,7 +58,7 @@ module ActiveRecord
when :delete_all
@reflection.klass.delete(records.map { |r| r.id })
else
updates = { @reflection.primary_key_name => nil }
updates = { @reflection.foreign_key => nil }
conditions = { @reflection.association_primary_key => records.map { |r| r.id } }
with_scope(@scope) do

View file

@ -31,7 +31,7 @@ module ActiveRecord
protected
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
else
true

View file

@ -38,11 +38,11 @@ module ActiveRecord
@target.destroy if @target.persisted?
@owner.clear_association_cache
when :nullify
@target[@reflection.primary_key_name] = nil
@target[@reflection.foreign_key] = nil
@target.save if @owner.persisted? && @target.persisted?
end
else
@target[@reflection.primary_key_name] = nil
@target[@reflection.foreign_key] = nil
@target.save if @owner.persisted? && @target.persisted?
end
end
@ -65,15 +65,6 @@ module ActiveRecord
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
def find_target
options = @reflection.options.dup.slice(:select, :order, :include, :readonly)
@ -105,7 +96,7 @@ module ActiveRecord
if replace_existing
replace(record, true)
else
record[@reflection.primary_key_name] = @owner.id if @owner.persisted?
record[@reflection.foreign_key] = @owner.id if @owner.persisted?
self.target = record
set_inverse_instance(record)
end

View file

@ -13,7 +13,7 @@ module ActiveRecord
def stale_target?
if @target && @reflection.through_reflection.macro == :belongs_to && defined?(@through_foreign_key)
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
else
@ -69,14 +69,14 @@ module ActiveRecord
if @reflection.source_reflection.macro == :belongs_to
reflection_primary_key = @reflection.source_reflection.options[: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]
column = @reflection.source_reflection.options[:foreign_type]
conditions <<
right[column].eq(@reflection.options[:source_type])
end
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] ||
@reflection.through_reflection.klass.primary_key
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)
join_attributes = {
@reflection.source_reflection.primary_key_name =>
@reflection.source_reflection.foreign_key =>
associate.send(@reflection.source_reflection.association_primary_key)
}
@ -158,10 +158,8 @@ module ActiveRecord
alias_method :sql_conditions, :conditions
def update_stale_state
construct_scope if stale_target?
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

View file

@ -343,8 +343,8 @@ module ActiveRecord
association.destroy
else
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)
association[reflection.primary_key_name] = key
if autosave != false && (new_record? || association.new_record? || association[reflection.foreign_key] != key || autosave)
association[reflection.foreign_key] = key
saved = association.save(:validate => !autosave)
raise ActiveRecord::Rollback if !saved && autosave
saved
@ -367,7 +367,7 @@ module ActiveRecord
if association.updated?
association_id = association.send(reflection.options[:primary_key] || :id)
self[reflection.primary_key_name] = association_id
self[reflection.foreign_key] = association_id
end
saved if autosave

View file

@ -874,7 +874,12 @@ module ActiveRecord #:nodoc:
def relation #:nodoc:
@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
# Finder methods must instantiate through this method to work with the
@ -914,10 +919,9 @@ module ActiveRecord #:nodoc:
def type_condition
sti_column = arel_table[inheritance_column.to_sym]
condition = sti_column.eq(sti_name)
descendants.each { |subclass| condition = condition.or(sti_column.eq(subclass.sti_name)) }
sti_names = ([self] + descendants).map { |model| model.sti_name }
condition
sti_column.in(sti_names)
end
# Guesses the table name, but does not decorate it with prefix and suffix information.

View file

@ -634,7 +634,7 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
targets.each do |target|
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) },
nil, @connection)
end

View file

@ -196,8 +196,8 @@ module ActiveRecord
@quoted_table_name ||= klass.quoted_table_name
end
def primary_key_name
@primary_key_name ||= options[:foreign_key] || derive_primary_key_name
def foreign_key
@foreign_key ||= options[:foreign_key] || derive_foreign_key
end
def primary_key_column
@ -251,7 +251,7 @@ module ActiveRecord
false
end
def through_reflection_primary_key_name
def through_reflection_foreign_key
end
def source_reflection
@ -298,17 +298,6 @@ module ActiveRecord
!options[:validate].nil? ? options[:validate] : (options[:autosave] == true || macro == :has_many)
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.
def belongs_to?
macro == :belongs_to
@ -321,7 +310,7 @@ module ActiveRecord
class_name
end
def derive_primary_key_name
def derive_foreign_key
if belongs_to?
"#{name}_id"
elsif options[:as]
@ -403,11 +392,11 @@ module ActiveRecord
end
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
def through_reflection_primary_key_name
through_reflection.primary_key_name if through_reflection.belongs_to?
def through_reflection_foreign_key
through_reflection.foreign_key if through_reflection.belongs_to?
end
private

View file

@ -211,7 +211,7 @@ module ActiveRecord
group_attr = @group_values
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
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_columns = group_aliases.zip(group_fields).map { |aliaz,field|
[aliaz, column_for(field)]

View file

@ -46,13 +46,17 @@ module ActiveRecord
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")
merged_relation.send(:"#{method}_value=", value) unless value.nil?
end
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
merged_relation.send :apply_modules, r.extensions

View file

@ -198,16 +198,23 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal 1, Post.find(p.id).comments.size
end
def test_belongs_to_with_primary_key_counter_with_assigning_nil
debate = Topic.create("title" => "debate")
reply = Reply.create("title" => "blah!", "content" => "world around!", "parent_title" => "debate")
def test_belongs_to_with_primary_key_counter
debate = Topic.create("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, Topic.find(debate.id).send(:read_attribute, "replies_count")
assert_equal 1, debate.reload.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
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
def test_belongs_to_counter_with_reassigning
@ -419,6 +426,18 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_nil sponsor.sponsorable_id
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
essay = Essay.new
saved_writer = Author.create(:name => "David")
@ -537,4 +556,27 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert proxy.stale_target?
assert_equal companies(:first_firm), sponsor.sponsorable
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

View file

@ -3,6 +3,7 @@ require 'models/post'
require 'models/author'
require 'models/project'
require 'models/developer'
require 'models/company'
class AssociationCallbacksTest < ActiveRecord::TestCase
fixtures :posts, :authors, :projects, :developers
@ -81,6 +82,14 @@ class AssociationCallbacksTest < ActiveRecord::TestCase
assert_equal callback_log, jack.post_log
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
david = developers(:david)
ar = projects(:active_record)

View file

@ -101,9 +101,10 @@ class LifecycleTest < ActiveRecord::TestCase
fixtures :topics, :developers, :minimalistics
def test_before_destroy
original_count = Topic.count
(topic_to_be_destroyed = Topic.find(1)).destroy
assert_equal original_count - (1 + topic_to_be_destroyed.replies.size), Topic.count
topic = Topic.find(1)
assert_difference 'Topic.count', -(1 + topic.replies.size) do
topic.destroy
end
end
def test_auto_observer

View file

@ -8,6 +8,8 @@ require 'models/ship'
require 'models/pirate'
require 'models/price_estimate'
require 'models/tagging'
require 'models/author'
require 'models/post'
class ReflectionTest < ActiveRecord::TestCase
include ActiveRecord::Reflection
@ -127,11 +129,11 @@ class ReflectionTest < ActiveRecord::TestCase
def test_belongs_to_inferred_foreign_key_from_assoc_name
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"
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"
assert_equal "xyzzy_id", Company.reflect_on_association(:baz).primary_key_name
assert_equal "xyzzy_id", Company.reflect_on_association(:baz).foreign_key
end
def test_association_reflection_in_modules

View file

@ -485,4 +485,11 @@ class DefaultScopingTest < ActiveRecord::TestCase
posts_offset_limit = Post.offset(2).limit(3)
assert_equal posts_limit_offset, posts_offset_limit
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

View file

@ -38,7 +38,9 @@ end
class Firm < Company
has_many :clients, :order => "id", :dependent => :destroy, :counter_sql =>
"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 :clients_sorted_desc, :class_name => "Client", :order => "id DESC"
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_many :accounts
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
class DependentFirm < Company

View file

@ -143,6 +143,7 @@ ActiveRecord::Schema.define do
t.text :body, :null => false
end
t.string :type
t.integer :taggings_count, :default => 0
end
create_table :companies, :force => true do |t|