aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/associations
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record/associations')
-rw-r--r--activerecord/lib/active_record/associations/association_collection.rb19
-rw-r--r--activerecord/lib/active_record/associations/association_proxy.rb38
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb58
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb62
-rw-r--r--activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb70
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb105
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb80
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb27
8 files changed, 244 insertions, 215 deletions
diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb
index 7ee567e0b4..a66248ff2e 100644
--- a/activerecord/lib/active_record/associations/association_collection.rb
+++ b/activerecord/lib/active_record/associations/association_collection.rb
@@ -18,6 +18,7 @@ module ActiveRecord
def <<(*records)
result = true
load_target
+
@owner.transaction do
flatten_deeper(records).each do |record|
raise_on_type_mismatch(record)
@@ -28,7 +29,7 @@ module ActiveRecord
end
end
- result and self
+ result && self
end
alias_method :push, :<<
@@ -60,11 +61,13 @@ module ActiveRecord
# Removes all records from this association. Returns +self+ so method calls may be chained.
def clear
return self if length.zero? # forces load_target if hasn't happened already
- if @options[:exclusively_dependent]
+
+ if @reflection.options[:exclusively_dependent]
destroy_all
else
delete_all
end
+
self
end
@@ -124,14 +127,6 @@ module ActiveRecord
end
private
- def raise_on_type_mismatch(record)
- raise ActiveRecord::AssociationTypeMismatch, "#{@association_class} expected, got #{record.class}" unless record.is_a?(@association_class)
- end
-
- def target_obsolete?
- false
- end
-
# Array#flatten has problems with recursive arrays. Going one level deeper solves the majority of the problems.
def flatten_deeper(array)
array.collect { |element| element.respond_to?(:flatten) ? element.flatten : element }.flatten
@@ -155,8 +150,8 @@ module ActiveRecord
end
def callbacks_for(callback_name)
- full_callback_name = "#{callback_name.to_s}_for_#{@association_name.to_s}"
- @owner.class.read_inheritable_attribute(full_callback_name.to_sym) or []
+ full_callback_name = "#{callback_name}_for_#{@reflection.name}"
+ @owner.class.read_inheritable_attribute(full_callback_name.to_sym) || []
end
end
diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb
index 8a7d925d0d..75f9184aa2 100644
--- a/activerecord/lib/active_record/associations/association_proxy.rb
+++ b/activerecord/lib/active_record/associations/association_proxy.rb
@@ -5,15 +5,9 @@ module ActiveRecord
alias_method :proxy_extend, :extend
instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?|^proxy_respond_to\?|^proxy_extend|^send)/ }
- def initialize(owner, association_name, association_class_name, association_class_primary_key_name, options)
- @owner = owner
- @options = options
- @association_name = association_name
- @association_class = eval(association_class_name, nil, __FILE__, __LINE__)
- @association_class_primary_key_name = association_class_primary_key_name
-
- proxy_extend(options[:extend]) if options[:extend]
-
+ def initialize(owner, reflection)
+ @owner, @reflection = owner, reflection
+ proxy_extend(reflection.options[:extend]) if reflection.options[:extend]
reset
end
@@ -28,6 +22,11 @@ module ActiveRecord
other === @target
end
+ def reset
+ @target = nil
+ @loaded = false
+ end
+
def reload
reset
load_target
@@ -45,14 +44,14 @@ module ActiveRecord
@target
end
- def target=(t)
- @target = t
- @loaded = true
+ def target=(target)
+ @target = target
+ loaded
end
protected
def dependent?
- @options[:dependent] || false
+ @reflection.options[:dependent] || false
end
def quoted_record_ids(records)
@@ -68,7 +67,7 @@ module ActiveRecord
end
def sanitize_sql(sql)
- @association_class.send(:sanitize_sql, sql)
+ @reflection.klass.send(:sanitize_sql, sql)
end
def extract_options_from_args!(args)
@@ -84,13 +83,14 @@ module ActiveRecord
def load_target
if !@owner.new_record? || foreign_key_present
begin
- @target = find_target if not loaded?
+ @target = find_target if !loaded?
rescue ActiveRecord::RecordNotFound
reset
end
end
- @loaded = true if @target
- @target
+
+ loaded if target
+ target
end
# Can be overwritten by associations that might have the foreign key available for an association without
@@ -100,7 +100,9 @@ module ActiveRecord
end
def raise_on_type_mismatch(record)
- raise ActiveRecord::AssociationTypeMismatch, "#{@association_class} expected, got #{record.class}" unless record.is_a?(@association_class)
+ unless record.is_a?(@reflection.klass)
+ raise ActiveRecord::AssociationTypeMismatch, "#{@reflection.class_name} expected, got #{record.class}"
+ end
end
end
end
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index 39d128aef1..804a7ebf21 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -1,41 +1,27 @@
module ActiveRecord
module Associations
class BelongsToAssociation < AssociationProxy #:nodoc:
- def initialize(owner, association_name, association_class_name, association_class_primary_key_name, options)
- super
- construct_sql
- end
-
- def reset
- @target = nil
- @loaded = false
- end
-
def create(attributes = {})
- record = @association_class.create(attributes)
- replace(record, true)
- record
+ replace(@reflection.klass.create(attributes))
end
def build(attributes = {})
- record = @association_class.new(attributes)
- replace(record, true)
- record
+ replace(@reflection.klass.new(attributes))
end
- def replace(obj, dont_save = false)
- if obj.nil?
- @target = @owner[@association_class_primary_key_name] = nil
+ def replace(record)
+ if record.nil?
+ @target = @owner[@reflection.primary_key_name] = nil
else
- raise_on_type_mismatch(obj) unless obj.nil?
+ raise_on_type_mismatch(record)
- @target = (AssociationProxy === obj ? obj.target : obj)
- @owner[@association_class_primary_key_name] = obj.id unless obj.new_record?
+ @target = (AssociationProxy === record ? record.target : record)
+ @owner[@reflection.primary_key_name] = record.id unless record.new_record?
@updated = true
end
- @loaded = true
- return (@target.nil? ? nil : self)
+ loaded
+ record
end
def updated?
@@ -44,27 +30,15 @@ module ActiveRecord
private
def find_target
- if @options[:conditions]
- @association_class.find(
- @owner[@association_class_primary_key_name],
- :conditions => interpolate_sql(@options[:conditions]),
- :include => @options[:include]
- )
- else
- @association_class.find(@owner[@association_class_primary_key_name], :include => @options[:include])
- end
+ @reflection.klass.find(
+ @owner[@reflection.primary_key_name],
+ :conditions => @reflection.options[:conditions] ? interpolate_sql(@reflection.options[:conditions]) : nil,
+ :include => @reflection.options[:include]
+ )
end
def foreign_key_present
- !@owner[@association_class_primary_key_name].nil?
- end
-
- def target_obsolete?
- @owner[@association_class_primary_key_name] != @target.id
- end
-
- def construct_sql
- @finder_sql = "#{@association_class.table_name}.#{@association_class.primary_key} = #{@owner.id}"
+ !@owner[@reflection.primary_key_name].nil?
end
end
end
diff --git a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
index e2a7f1a58e..6c2b9b89fd 100644
--- a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
@@ -1,69 +1,49 @@
module ActiveRecord
module Associations
- class BelongsToPolymorphicAssociation < BelongsToAssociation #:nodoc:
- def initialize(owner, association_name, association_class_name, association_class_primary_key_name, options)
- @owner = owner
- @options = options
- @association_name = association_name
- @association_class_primary_key_name = association_class_primary_key_name
-
- proxy_extend(options[:extend]) if options[:extend]
-
- reset
- end
-
- def create(attributes = {})
- raise ActiveRecord::ActiveRecordError, "Can't create an abstract polymorphic object"
- end
-
- def build(attributes = {})
- raise ActiveRecord::ActiveRecordError, "Can't build an abstract polymorphic object"
- end
-
- def replace(obj, dont_save = false)
- if obj.nil?
- @target = @owner[@association_class_primary_key_name] = @owner[@options[:foreign_type]] = nil
+ 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 === obj ? obj.target : obj)
+ @target = (AssociationProxy === record ? record.target : record)
- unless obj.new_record?
- @owner[@association_class_primary_key_name] = obj.id
- @owner[@options[:foreign_type]] = ActiveRecord::Base.send(:class_name_of_active_record_descendant, obj.class).to_s
+ unless record.new_record?
+ @owner[@reflection.primary_key_name] = record.id
+ @owner[@reflection.options[:foreign_type]] = ActiveRecord::Base.send(:class_name_of_active_record_descendant, record.class).to_s
end
@updated = true
end
- @loaded = true
+ loaded
+ record
+ end
- return (@target.nil? ? nil : self)
+ def updated?
+ @updated
end
-
+
private
def find_target
return nil if association_class.nil?
- if @options[:conditions]
+ if @reflection.options[:conditions]
association_class.find(
- @owner[@association_class_primary_key_name],
- :conditions => interpolate_sql(@options[:conditions]),
- :include => @options[:include]
+ @owner[@reflection.primary_key_name],
+ :conditions => interpolate_sql(@reflection.options[:conditions]),
+ :include => @reflection.options[:include]
)
else
- association_class.find(@owner[@association_class_primary_key_name], :include => @options[:include])
+ association_class.find(@owner[@reflection.primary_key_name], :include => @reflection.options[:include])
end
end
def foreign_key_present
- !@owner[@association_class_primary_key_name].nil?
+ !@owner[@reflection.primary_key_name].nil?
end
- def target_obsolete?
- @owner[@association_class_primary_key_name] != @target.id
- end
-
def association_class
- @owner[@options[:foreign_type]] ? @owner[@options[:foreign_type]].constantize : nil
+ @owner[@reflection.options[:foreign_type]] ? @owner[@reflection.options[:foreign_type]].constantize : nil
end
end
end
diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
index 4bfa759666..417b0905f4 100644
--- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
@@ -1,20 +1,14 @@
module ActiveRecord
module Associations
class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc:
- def initialize(owner, association_name, association_class_name, association_class_primary_key_name, options)
+ def initialize(owner, reflection)
super
-
- @association_foreign_key = options[:association_foreign_key] || association_class_name.foreign_key
- @association_table_name = options[:table_name] || @association_class.table_name
- @join_table = options[:join_table]
- @order = options[:order]
-
construct_sql
end
def build(attributes = {})
load_target
- record = @association_class.new(attributes)
+ record = @reflection.klass.new(attributes)
@target << record
record
end
@@ -27,7 +21,7 @@ module ActiveRecord
options = Base.send(:extract_options_from_args!, args)
# If using a custom finder_sql, scan the entire collection.
- if @options[:finder_sql]
+ if @reflection.options[:finder_sql]
expects_array = args.first.kind_of?(Array)
ids = args.flatten.compact.uniq
@@ -40,60 +34,64 @@ module ActiveRecord
end
else
conditions = "#{@finder_sql}"
+
if sanitized_conditions = sanitize_sql(options[:conditions])
conditions << " AND (#{sanitized_conditions})"
end
+
options[:conditions] = conditions
options[:joins] = @join_sql
options[:readonly] ||= false
- if options[:order] && @options[:order]
- options[:order] = "#{options[:order]}, #{@options[:order]}"
- elsif @options[:order]
- options[:order] = @options[:order]
+ if options[:order] && @reflection.options[:order]
+ options[:order] = "#{options[:order]}, #{@reflection.options[:order]}"
+ elsif @reflection.options[:order]
+ options[:order] = @reflection.options[:order]
end
# Pass through args exactly as we received them.
args << options
- @association_class.find(*args)
+ @reflection.klass.find(*args)
end
end
def push_with_attributes(record, join_attributes = {})
raise_on_type_mismatch(record)
join_attributes.each { |key, value| record[key.to_s] = value }
+
callback(:before_add, record)
insert_record(record) unless @owner.new_record?
@target << record
callback(:after_add, record)
+
self
end
alias :concat_with_attributes :push_with_attributes
def size
- @options[:uniq] ? count_records : super
+ @reflection.options[:uniq] ? count_records : super
end
protected
def method_missing(method, *args, &block)
- if @target.respond_to?(method) || (!@association_class.respond_to?(method) && Class.respond_to?(method))
+ if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
super
else
- @association_class.with_scope(:find => { :conditions => @finder_sql, :joins => @join_sql, :readonly => false }) do
- @association_class.send(method, *args, &block)
+ @reflection.klass.with_scope(:find => { :conditions => @finder_sql, :joins => @join_sql, :readonly => false }) do
+ @reflection.klass.send(method, *args, &block)
end
end
end
def find_target
- if @options[:finder_sql]
- records = @association_class.find_by_sql(@finder_sql)
+ if @reflection.options[:finder_sql]
+ records = @reflection.klass.find_by_sql(@finder_sql)
else
- records = find(:all, :include => @options[:include])
+ records = find(:all, :include => @reflection.options[:include])
end
- @options[:uniq] ? uniq(records) : records
+ @reflection.options[:uniq] ? uniq(records) : records
end
def count_records
@@ -105,16 +103,16 @@ module ActiveRecord
return false unless record.save
end
- if @options[:insert_sql]
- @owner.connection.execute(interpolate_sql(@options[:insert_sql], record))
+ if @reflection.options[:insert_sql]
+ @owner.connection.execute(interpolate_sql(@reflection.options[:insert_sql], record))
else
- columns = @owner.connection.columns(@join_table, "#{@join_table} Columns")
+ columns = @owner.connection.columns(@reflection.options[:join_table], "#{@reflection.options[:join_table]} Columns")
attributes = columns.inject({}) do |attributes, column|
case column.name
- when @association_class_primary_key_name
+ when @reflection.primary_key_name
attributes[column.name] = @owner.quoted_id
- when @association_foreign_key
+ when @reflection.association_foreign_key
attributes[column.name] = record.quoted_id
else
if record.attributes.has_key?(column.name)
@@ -126,7 +124,7 @@ module ActiveRecord
end
sql =
- "INSERT INTO #{@join_table} (#{@owner.send(:quoted_column_names, attributes).join(', ')}) " +
+ "INSERT INTO #{@reflection.options[:join_table]} (#{@owner.send(:quoted_column_names, attributes).join(', ')}) " +
"VALUES (#{attributes.values.join(', ')})"
@owner.connection.execute(sql)
@@ -136,26 +134,26 @@ module ActiveRecord
end
def delete_records(records)
- if sql = @options[:delete_sql]
+ if sql = @reflection.options[:delete_sql]
records.each { |record| @owner.connection.execute(interpolate_sql(sql, record)) }
else
ids = quoted_record_ids(records)
- sql = "DELETE FROM #{@join_table} WHERE #{@association_class_primary_key_name} = #{@owner.quoted_id} AND #{@association_foreign_key} IN (#{ids})"
+ sql = "DELETE FROM #{@reflection.options[:join_table]} WHERE #{@reflection.primary_key_name} = #{@owner.quoted_id} AND #{@reflection.association_foreign_key} IN (#{ids})"
@owner.connection.execute(sql)
end
end
def construct_sql
- interpolate_sql_options!(@options, :finder_sql)
+ interpolate_sql_options!(@reflection.options, :finder_sql)
- if @options[:finder_sql]
- @finder_sql = @options[:finder_sql]
+ if @reflection.options[:finder_sql]
+ @finder_sql = @reflection.options[:finder_sql]
else
- @finder_sql = "#{@join_table}.#{@association_class_primary_key_name} = #{@owner.quoted_id} "
- @finder_sql << " AND (#{interpolate_sql(@options[:conditions])})" if @options[:conditions]
+ @finder_sql = "#{@reflection.options[:join_table]}.#{@reflection.primary_key_name} = #{@owner.quoted_id} "
+ @finder_sql << " AND (#{interpolate_sql(@reflection.options[:conditions])})" if @reflection.options[:conditions]
end
- @join_sql = "JOIN #{@join_table} ON #{@association_class.table_name}.#{@association_class.primary_key} = #{@join_table}.#{@association_foreign_key}"
+ @join_sql = "JOIN #{@reflection.options[:join_table]} ON #{@reflection.klass.table_name}.#{@reflection.klass.primary_key} = #{@reflection.options[:join_table]}.#{@reflection.association_foreign_key}"
end
end
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 465e1f8c72..f4a08420b7 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -1,10 +1,9 @@
module ActiveRecord
module Associations
class HasManyAssociation < AssociationCollection #:nodoc:
- def initialize(owner, association_name, association_class_name, association_class_primary_key_name, options)
+ def initialize(owner, reflection)
super
- @conditions = sanitize_sql(options[:conditions])
-
+ @conditions = sanitize_sql(reflection.options[:conditions])
construct_sql
end
@@ -13,8 +12,8 @@ module ActiveRecord
attributes.collect { |attr| create(attr) }
else
load_target
- record = @association_class.new(attributes)
- record[@association_class_primary_key_name] = @owner.id unless @owner.new_record?
+ record = @reflection.klass.new(attributes)
+ record[@reflection.primary_key_name] = @owner.id unless @owner.new_record?
@target << record
record
end
@@ -22,13 +21,13 @@ module ActiveRecord
# DEPRECATED.
def find_all(runtime_conditions = nil, orderings = nil, limit = nil, joins = nil)
- if @options[:finder_sql]
- @association_class.find_by_sql(@finder_sql)
+ if @reflection.options[:finder_sql]
+ @reflection.klass.find_by_sql(@finder_sql)
else
conditions = @finder_sql
conditions += " AND (#{sanitize_sql(runtime_conditions)})" if runtime_conditions
- orderings ||= @options[:order]
- @association_class.find_all(conditions, orderings, limit, joins)
+ orderings ||= @reflection.options[:order]
+ @reflection.klass.find_all(conditions, orderings, limit, joins)
end
end
@@ -39,14 +38,14 @@ module ActiveRecord
# Count the number of associated records. All arguments are optional.
def count(runtime_conditions = nil)
- if @options[:counter_sql]
- @association_class.count_by_sql(@counter_sql)
- elsif @options[:finder_sql]
- @association_class.count_by_sql(@finder_sql)
+ if @reflection.options[:counter_sql]
+ @reflection.klass.count_by_sql(@counter_sql)
+ elsif @reflection.options[:finder_sql]
+ @reflection.klass.count_by_sql(@finder_sql)
else
sql = @finder_sql
sql += " AND (#{sanitize_sql(runtime_conditions)})" if runtime_conditions
- @association_class.count(sql)
+ @reflection.klass.count(sql)
end
end
@@ -54,7 +53,7 @@ module ActiveRecord
options = Base.send(:extract_options_from_args!, args)
# If using a custom finder_sql, scan the entire collection.
- if @options[:finder_sql]
+ if @reflection.options[:finder_sql]
expects_array = args.first.kind_of?(Array)
ids = args.flatten.compact.uniq
@@ -72,49 +71,49 @@ module ActiveRecord
end
options[:conditions] = conditions
- if options[:order] && @options[:order]
- options[:order] = "#{options[:order]}, #{@options[:order]}"
- elsif @options[:order]
- options[:order] = @options[:order]
+ if options[:order] && @reflection.options[:order]
+ options[:order] = "#{options[:order]}, #{@reflection.options[:order]}"
+ elsif @reflection.options[:order]
+ options[:order] = @reflection.options[:order]
end
# Pass through args exactly as we received them.
args << options
- @association_class.find(*args)
+ @reflection.klass.find(*args)
end
end
protected
def method_missing(method, *args, &block)
- if @target.respond_to?(method) || (!@association_class.respond_to?(method) && Class.respond_to?(method))
+ if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
super
else
- @association_class.with_scope(
+ @reflection.klass.with_scope(
:find => {
:conditions => @finder_sql,
:joins => @join_sql,
:readonly => false
},
:create => {
- @association_class_primary_key_name => @owner.id
+ @reflection.primary_key_name => @owner.id
}
) do
- @association_class.send(method, *args, &block)
+ @reflection.klass.send(method, *args, &block)
end
end
end
def find_target
- if @options[:finder_sql]
- @association_class.find_by_sql(@finder_sql)
+ if @reflection.options[:finder_sql]
+ @reflection.klass.find_by_sql(@finder_sql)
else
- @association_class.find(:all,
+ @reflection.klass.find(:all,
:conditions => @finder_sql,
- :order => @options[:order],
- :limit => @options[:limit],
- :joins => @options[:joins],
- :include => @options[:include],
- :group => @options[:group]
+ :order => @reflection.options[:order],
+ :limit => @reflection.options[:limit],
+ :joins => @reflection.options[:joins],
+ :include => @reflection.options[:include],
+ :group => @reflection.options[:group]
)
end
end
@@ -122,10 +121,10 @@ module ActiveRecord
def count_records
count = if has_cached_counter?
@owner.send(:read_attribute, cached_counter_attribute_name)
- elsif @options[:counter_sql]
- @association_class.count_by_sql(@counter_sql)
+ elsif @reflection.options[:counter_sql]
+ @reflection.klass.count_by_sql(@counter_sql)
else
- @association_class.count(@counter_sql)
+ @reflection.klass.count(@counter_sql)
end
@target = [] and loaded if count == 0
@@ -138,22 +137,22 @@ module ActiveRecord
end
def cached_counter_attribute_name
- "#{@association_name}_count"
+ "#{@reflection.name}_count"
end
def insert_record(record)
- record[@association_class_primary_key_name] = @owner.id
+ record[@reflection.primary_key_name] = @owner.id
record.save
end
def delete_records(records)
- if @options[:dependent]
+ if @reflection.options[:dependent]
records.each { |r| r.destroy }
else
ids = quoted_record_ids(records)
- @association_class.update_all(
- "#{@association_class_primary_key_name} = NULL",
- "#{@association_class_primary_key_name} = #{@owner.quoted_id} AND #{@association_class.primary_key} IN (#{ids})"
+ @reflection.klass.update_all(
+ "#{@reflection.primary_key_name} = NULL",
+ "#{@reflection.primary_key_name} = #{@owner.quoted_id} AND #{@reflection.klass.primary_key} IN (#{ids})"
)
end
end
@@ -164,25 +163,25 @@ module ActiveRecord
def construct_sql
case
- when @options[:as]
+ when @reflection.options[:finder_sql]
+ @finder_sql = interpolate_sql(@reflection.options[:finder_sql])
+
+ when @reflection.options[:as]
@finder_sql =
- "#{@association_class.table_name}.#{@options[:as]}_id = #{@owner.quoted_id} AND " +
- "#{@association_class.table_name}.#{@options[:as]}_type = '#{ActiveRecord::Base.send(:class_name_of_active_record_descendant, @owner.class).to_s}'"
+ "#{@reflection.klass.table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " +
+ "#{@reflection.klass.table_name}.#{@reflection.options[:as]}_type = '#{ActiveRecord::Base.send(:class_name_of_active_record_descendant, @owner.class).to_s}'"
@finder_sql << " AND (#{interpolate_sql(@conditions)})" if @conditions
- when @options[:finder_sql]
- @finder_sql = interpolate_sql(@options[:finder_sql])
-
else
- @finder_sql = "#{@association_class.table_name}.#{@association_class_primary_key_name} = #{@owner.quoted_id}"
+ @finder_sql = "#{@reflection.klass.table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
@finder_sql << " AND (#{interpolate_sql(@conditions)})" if @conditions
end
- if @options[:counter_sql]
- @counter_sql = interpolate_sql(@options[:counter_sql])
- elsif @options[:finder_sql]
- @options[:counter_sql] = @options[:finder_sql].gsub(/SELECT (.*) FROM/i, "SELECT COUNT(*) FROM")
- @counter_sql = interpolate_sql(@options[:counter_sql])
+ if @reflection.options[:counter_sql]
+ @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
+ elsif @reflection.options[:finder_sql]
+ @reflection.options[:counter_sql] = @reflection.options[:finder_sql].gsub(/SELECT (.*) FROM/i, "SELECT COUNT(*) FROM")
+ @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
else
@counter_sql = @finder_sql
end
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
new file mode 100644
index 0000000000..ca4496b32e
--- /dev/null
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -0,0 +1,80 @@
+module ActiveRecord
+ module Associations
+ class HasManyThroughAssociation < AssociationProxy #:nodoc:
+ def find(*args)
+ options = Base.send(:extract_options_from_args!, args)
+
+ conditions = "#{@finder_sql}"
+ if sanitized_conditions = sanitize_sql(options[:conditions])
+ conditions << " AND (#{sanitized_conditions})"
+ end
+ options[:conditions] = conditions
+
+ if options[:order] && @reflection.options[:order]
+ options[:order] = "#{options[:order]}, #{@reflection.options[:order]}"
+ elsif @reflection.options[:order]
+ options[:order] = @reflection.options[:order]
+ end
+
+ # Pass through args exactly as we received them.
+ args << options
+ @reflection.klass.find(*args)
+ end
+
+ def reset
+ @target = []
+ @loaded = false
+ end
+
+ protected
+ def method_missing(method, *args, &block)
+ if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
+ super
+ else
+ @reflection.klass.with_scope(construct_scope) { @reflection.klass.send(method, *args, &block) }
+ end
+ end
+
+ def find_target
+ @reflection.klass.find(:all,
+ :conditions => construct_conditions,
+ :from => construct_from,
+ :order => @reflection.options[:order],
+ :limit => @reflection.options[:limit],
+ :joins => @reflection.options[:joins],
+ :group => @reflection.options[:group]
+ )
+ end
+
+ def construct_conditions
+ through_reflection = @owner.class.reflections[@reflection.options[:through]]
+
+ if through_reflection.options[:as]
+ conditions =
+ "#{@reflection.table_name}.#{@reflection.klass.primary_key} = #{through_reflection.table_name}.#{@reflection.klass.to_s.foreign_key} " +
+ "AND #{through_reflection.table_name}.#{through_reflection.options[:as]}_id = #{@owner.quoted_id} " +
+ "AND #{through_reflection.table_name}.#{through_reflection.options[:as]}_type = '#{ActiveRecord::Base.send(:class_name_of_active_record_descendant, @owner.class).to_s}'"
+ else
+ conditions =
+ "#{@reflection.klass.table_name}.#{@reflection.klass.primary_key} = #{through_reflection.table_name}.#{@reflection.klass.to_s.foreign_key} " +
+ "AND #{through_reflection.table_name}.#{@owner.to_s.foreign_key} = #{@owner.quoted_id}"
+ end
+
+ conditions << " AND (#{interpolate_sql(sanitize_sql(@reflection.options[:conditions]))})" if @reflection.options[:conditions]
+
+ return conditions
+ end
+
+ def construct_from
+ "#{@reflection.table_name}, #{@owner.class.reflections[@reflection.options[:through]].table_name}"
+ end
+
+ def construct_scope
+ {
+ :find => { :conditions => construct_conditions },
+ :create => { @reflection.primary_key_name => @owner.id }
+ }
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index 8f7857ebea..17483305fc 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -1,9 +1,8 @@
module ActiveRecord
module Associations
class HasOneAssociation < BelongsToAssociation #:nodoc:
- def initialize(owner, association_name, association_class_name, association_class_primary_key_name, options)
+ def initialize(owner, reflection)
super
-
construct_sql
end
@@ -14,12 +13,12 @@ module ActiveRecord
end
def build(attributes = {}, replace_existing = true)
- record = @association_class.new(attributes)
+ record = @reflection.klass.new(attributes)
if replace_existing
replace(record, true)
else
- record[@association_class_primary_key_name] = @owner.id unless @owner.new_record?
+ record[@reflection.primary_key_name] = @owner.id unless @owner.new_record?
self.target = record
end
@@ -28,12 +27,13 @@ module ActiveRecord
def replace(obj, dont_save = false)
load_target
+
unless @target.nil?
if dependent? && !dont_save && @target != obj
@target.destroy unless @target.new_record?
@owner.clear_association_cache
else
- @target[@association_class_primary_key_name] = nil
+ @target[@reflection.primary_key_name] = nil
@target.save unless @owner.new_record?
end
end
@@ -43,11 +43,12 @@ module ActiveRecord
else
raise_on_type_mismatch(obj)
- obj[@association_class_primary_key_name] = @owner.id unless @owner.new_record?
+ obj[@reflection.primary_key_name] = @owner.id unless @owner.new_record?
@target = (AssociationProxy === obj ? obj.target : obj)
end
@loaded = true
+
unless @owner.new_record? or obj.nil? or dont_save
return (obj.save ? self : false)
else
@@ -57,16 +58,16 @@ module ActiveRecord
private
def find_target
- @association_class.find(:first, :conditions => @finder_sql, :order => @options[:order], :include => @options[:include])
- end
-
- def target_obsolete?
- false
+ @reflection.klass.find(:first,
+ :conditions => @finder_sql,
+ :order => @reflection.options[:order],
+ :include => @reflection.options[:include]
+ )
end
def construct_sql
- @finder_sql = "#{@association_class.table_name}.#{@association_class_primary_key_name} = #{@owner.quoted_id}"
- @finder_sql << " AND (#{sanitize_sql(@options[:conditions])})" if @options[:conditions]
+ @finder_sql = "#{@reflection.table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
+ @finder_sql << " AND (#{sanitize_sql(@reflection.options[:conditions])})" if @reflection.options[:conditions]
@finder_sql
end
end