aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
authorRick Olson <technoweenie@gmail.com>2008-03-21 18:09:03 +0000
committerRick Olson <technoweenie@gmail.com>2008-03-21 18:09:03 +0000
commit273b21faa911681ed4b6c748676146e0f6eed0a0 (patch)
tree7454ae8ead82fbe980c9f3630289c672f247b232 /activerecord/lib
parentd8f76e66a19435239eec6f65f910133c6059cd60 (diff)
downloadrails-273b21faa911681ed4b6c748676146e0f6eed0a0.tar.gz
rails-273b21faa911681ed4b6c748676146e0f6eed0a0.tar.bz2
rails-273b21faa911681ed4b6c748676146e0f6eed0a0.zip
Add has_one :through support, finally. Closes #4756 [thechrisoshow]
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@9067 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
Diffstat (limited to 'activerecord/lib')
-rw-r--r--activerecord/lib/active_record/association_preload.rb34
-rwxr-xr-xactiverecord/lib/active_record/associations.rb56
-rw-r--r--activerecord/lib/active_record/associations/has_one_through_association.rb28
3 files changed, 98 insertions, 20 deletions
diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb
index cc657dc433..0c31c85bb0 100644
--- a/activerecord/lib/active_record/association_preload.rb
+++ b/activerecord/lib/active_record/association_preload.rb
@@ -48,6 +48,14 @@ module ActiveRecord
association_proxy.target.push(*[associated_record].flatten)
end
end
+
+ def add_preloaded_record_to_collection(parent_records, reflection_name, associated_record)
+ parent_records.each do |parent_record|
+ association_proxy = parent_record.send(reflection_name)
+ association_proxy.loaded
+ association_proxy.target = associated_record
+ end
+ end
def set_association_collection_records(id_to_record_map, reflection_name, associated_records, key)
associated_records.each do |associated_record|
@@ -97,11 +105,27 @@ module ActiveRecord
end
def preload_has_one_association(records, reflection, preload_options={})
- id_to_record_map, ids = construct_id_map(records)
- records.each {|record| record.send("set_#{reflection.name}_target", nil)}
+ id_to_record_map, ids = construct_id_map(records)
+ options = reflection.options
+ if options[:through]
+ records.each {|record| record.send(reflection.name) && record.send(reflection.name).loaded}
+ through_records = preload_through_records(records, reflection, options[:through])
+ through_reflection = reflections[options[:through]]
+ through_primary_key = through_reflection.primary_key_name
+ unless through_records.empty?
+ source = reflection.source_reflection.name
+ through_records.first.class.preload_associations(through_records, source)
+ through_records.compact.each do |through_record|
+ add_preloaded_record_to_collection(id_to_record_map[through_record[through_primary_key].to_i],
+ reflection.name, through_record.send(source))
+ end
+ end
+ else
+ records.each {|record| record.send("set_#{reflection.name}_target", nil)}
- 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.primary_key_name)
+ end
end
def preload_has_many_association(records, reflection, preload_options={})
@@ -126,7 +150,7 @@ module ActiveRecord
reflection.primary_key_name)
end
end
-
+
def preload_through_records(records, reflection, through_association)
through_reflection = reflections[through_association]
through_primary_key = through_reflection.primary_key_name
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index d7d5d9b312..0e07ee4913 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -6,6 +6,7 @@ require 'active_record/associations/has_one_association'
require 'active_record/associations/has_many_association'
require 'active_record/associations/has_many_through_association'
require 'active_record/associations/has_and_belongs_to_many_association'
+require 'active_record/associations/has_one_through_association'
module ActiveRecord
class HasManyThroughAssociationNotFoundError < ActiveRecordError #:nodoc:
@@ -737,6 +738,12 @@ module ActiveRecord
# as the default +foreign_key+.
# * <tt>:include</tt> - specify second-order associations that should be eager loaded when this object is loaded.
# * <tt>:as</tt>: Specifies a polymorphic interface (See <tt>#belongs_to</tt>).
+ # * <tt>:through</tt>: Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt>
+ # are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a
+ # <tt>has_one</tt> or <tt>belongs_to</tt> association on the join model.
+ # * <tt>:source</tt>: Specifies the source association name used by <tt>has_one :through</tt> queries. Only use it if the name cannot be
+ # inferred from the association. <tt>has_one :favorite, :through => :favorites</tt> will look for a
+ # <tt>:favorite</tt> on +Favorite+, unless a <tt>:source</tt> is given.
# * <tt>:readonly</tt> - if set to +true+, the associated object is readonly through the association.
#
# Option examples:
@@ -746,27 +753,34 @@ module ActiveRecord
# has_one :project_manager, :class_name => "Person", :conditions => "role = 'project_manager'"
# has_one :attachment, :as => :attachable
# has_one :boss, :readonly => :true
+ # has_one :club, :through => :membership
+ # has_one :primary_address, :through => :addressables, :conditions => ["addressable.primary = ?", true], :source => :addressable
def has_one(association_id, options = {})
- reflection = create_has_one_reflection(association_id, options)
+ if options[:through]
+ reflection = create_has_one_through_reflection(association_id, options)
+ association_accessor_methods(reflection, ActiveRecord::Associations::HasOneThroughAssociation)
+ else
+ reflection = create_has_one_reflection(association_id, options)
- ivar = "@#{reflection.name}"
+ ivar = "@#{reflection.name}"
- method_name = "has_one_after_save_for_#{reflection.name}".to_sym
- define_method(method_name) do
- association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}")
+ method_name = "has_one_after_save_for_#{reflection.name}".to_sym
+ define_method(method_name) do
+ association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}")
- if !association.nil? && (new_record? || association.new_record? || association["#{reflection.primary_key_name}"] != id)
- association["#{reflection.primary_key_name}"] = id
- association.save(true)
+ if !association.nil? && (new_record? || association.new_record? || association["#{reflection.primary_key_name}"] != id)
+ association["#{reflection.primary_key_name}"] = id
+ association.save(true)
+ end
end
- end
- after_save method_name
+ after_save method_name
- association_accessor_methods(reflection, HasOneAssociation)
- association_constructor_method(:build, reflection, HasOneAssociation)
- association_constructor_method(:create, reflection, HasOneAssociation)
+ association_accessor_methods(reflection, HasOneAssociation)
+ association_constructor_method(:build, reflection, HasOneAssociation)
+ association_constructor_method(:create, reflection, HasOneAssociation)
- configure_dependency_for_has_one(reflection)
+ configure_dependency_for_has_one(reflection)
+ end
end
# Adds the following methods for retrieval and query for a single associated object for which this object holds an id:
@@ -1058,7 +1072,12 @@ module ActiveRecord
association = association_proxy_class.new(self, reflection)
end
- association.replace(new_value)
+ if association_proxy_class == HasOneThroughAssociation
+ association.create_through_record(new_value)
+ self.send(reflection.name, new_value)
+ else
+ association.replace(new_value)
+ end
instance_variable_set(ivar, new_value.nil? ? nil : association)
end
@@ -1300,6 +1319,13 @@ module ActiveRecord
create_reflection(:has_one, association_id, options, self)
end
+
+ def create_has_one_through_reflection(association_id, options)
+ options.assert_valid_keys(
+ :class_name, :foreign_key, :remote, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :through, :source
+ )
+ create_reflection(:has_one, association_id, options, self)
+ end
def create_belongs_to_reflection(association_id, options)
options.assert_valid_keys(
diff --git a/activerecord/lib/active_record/associations/has_one_through_association.rb b/activerecord/lib/active_record/associations/has_one_through_association.rb
new file mode 100644
index 0000000000..ecf9d8208d
--- /dev/null
+++ b/activerecord/lib/active_record/associations/has_one_through_association.rb
@@ -0,0 +1,28 @@
+module ActiveRecord
+ module Associations
+ class HasOneThroughAssociation < ActiveRecord::Associations::HasManyThroughAssociation
+
+ def create_through_record(new_value) #nodoc:
+ klass = @reflection.through_reflection.klass
+
+ current_object = @owner.send(@reflection.through_reflection.name)
+
+ if current_object
+ klass.destroy(current_object)
+ @owner.clear_association_cache
+ end
+
+ @owner.send(@reflection.through_reflection.name, klass.send(:create, construct_join_attributes(new_value)))
+ end
+
+ private
+ def find(*args)
+ super(args.merge(:limit => 1))
+ end
+
+ def find_target
+ super.first
+ end
+ end
+ end
+end