aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
authorMurray Steele <muz@h-lame.com>2009-05-01 16:01:13 +0100
committerJeremy Kemper <jeremy@bitsweat.net>2009-05-04 15:27:39 -0700
commitccea98389abbf150b886c9f964b1def47f00f237 (patch)
treed1d23dec77f509af84a5b5363818606db5f575a6 /activerecord/lib
parenteb201e64c0b68aee6d0715d44cf48178204c4133 (diff)
downloadrails-ccea98389abbf150b886c9f964b1def47f00f237.tar.gz
rails-ccea98389abbf150b886c9f964b1def47f00f237.tar.bz2
rails-ccea98389abbf150b886c9f964b1def47f00f237.zip
Providing support for :inverse_of as an option to associations.
You can now add an :inverse_of option to has_one, has_many and belongs_to associations. This is best described with an example: class Man < ActiveRecord::Base has_one :face, :inverse_of => :man end class Face < ActiveRecord::Base belongs_to :man, :inverse_of => :face end m = Man.first f = m.face Without :inverse_of m and f.man would be different instances of the same object (f.man being pulled from the database again). With these new :inverse_of options m and f.man are the same in memory instance. Currently :inverse_of supports has_one and has_many (but not the :through variants) associations. It also supplies inverse support for belongs_to associations where the inverse is a has_one and it's not a polymorphic. Signed-off-by: Murray Steele <muz@h-lame.com> Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
Diffstat (limited to 'activerecord/lib')
-rwxr-xr-xactiverecord/lib/active_record/associations.rb12
-rw-r--r--activerecord/lib/active_record/associations/association_collection.rb8
-rw-r--r--activerecord/lib/active_record/associations/association_proxy.rb14
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb12
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb5
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb10
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb11
-rw-r--r--activerecord/lib/active_record/reflection.rb21
8 files changed, 81 insertions, 12 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 2115878e32..0952b087d1 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1,4 +1,10 @@
module ActiveRecord
+ class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc:
+ def initialize(reflection)
+ super("Could not find the inverse association for #{reflection.name} (#{reflection.options[:inverse_of].inspect} in #{reflection.class_name})")
+ end
+ end
+
class HasManyThroughAssociationNotFoundError < ActiveRecordError #:nodoc:
def initialize(owner_class_name, reflection)
super("Could not find the association #{reflection.options[:through].inspect} in model #{owner_class_name}")
@@ -1488,7 +1494,7 @@ module ActiveRecord
:finder_sql, :counter_sql,
:before_add, :after_add, :before_remove, :after_remove,
:extend, :readonly,
- :validate
+ :validate, :inverse_of
]
def create_has_many_reflection(association_id, options, &extension)
@@ -1502,7 +1508,7 @@ module ActiveRecord
@@valid_keys_for_has_one_association = [
:class_name, :foreign_key, :remote, :select, :conditions, :order,
:include, :dependent, :counter_cache, :extend, :as, :readonly,
- :validate, :primary_key
+ :validate, :primary_key, :inverse_of
]
def create_has_one_reflection(association_id, options)
@@ -1521,7 +1527,7 @@ module ActiveRecord
@@valid_keys_for_belongs_to_association = [
:class_name, :foreign_key, :foreign_type, :remote, :select, :conditions,
:include, :dependent, :counter_cache, :extend, :polymorphic, :readonly,
- :validate, :touch
+ :validate, :touch, :inverse_of
]
def create_belongs_to_reflection(association_id, options)
diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb
index 3aef1b21e9..26987dde97 100644
--- a/activerecord/lib/active_record/associations/association_collection.rb
+++ b/activerecord/lib/active_record/associations/association_collection.rb
@@ -399,11 +399,14 @@ module ActiveRecord
find(:all)
end
- @reflection.options[:uniq] ? uniq(records) : records
+ records = @reflection.options[:uniq] ? uniq(records) : records
+ records.each do |record|
+ set_inverse_instance(record, @owner)
+ end
+ records
end
private
-
def create_record(attrs)
attrs.update(@reflection.options[:conditions]) if @reflection.options[:conditions].is_a?(Hash)
ensure_owner_is_not_new
@@ -433,6 +436,7 @@ module ActiveRecord
@target ||= [] unless loaded?
@target << record unless @reflection.options[:uniq] && @target.include?(record)
callback(:after_add, record)
+ set_inverse_instance(record, @owner)
record
end
diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb
index 241b9bfee0..e36b04ea95 100644
--- a/activerecord/lib/active_record/associations/association_proxy.rb
+++ b/activerecord/lib/active_record/associations/association_proxy.rb
@@ -53,6 +53,7 @@ module ActiveRecord
def initialize(owner, reflection)
@owner, @reflection = owner, reflection
+ reflection.check_validity!
Array(reflection.options[:extend]).each { |ext| proxy_extend(ext) }
reset
end
@@ -274,6 +275,19 @@ module ActiveRecord
def owner_quoted_id
@owner.quoted_id
end
+
+ def set_inverse_instance(record, instance)
+ return if record.nil? || !we_can_set_the_inverse_on_this?(record)
+ inverse_relationship = @reflection.inverse_of
+ unless inverse_relationship.nil?
+ record.send(:"set_#{inverse_relationship.name}_target", instance)
+ end
+ end
+
+ # Override in subclasses
+ def we_can_set_the_inverse_on_this?(record)
+ false
+ 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 f05c6be075..c88575048a 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -31,6 +31,8 @@ module ActiveRecord
@updated = true
end
+ set_inverse_instance(record, @owner)
+
loaded
record
end
@@ -41,18 +43,26 @@ module ActiveRecord
private
def find_target
- @reflection.klass.find(
+ the_target = @reflection.klass.find(
@owner[@reflection.primary_key_name],
:select => @reflection.options[:select],
:conditions => conditions,
:include => @reflection.options[:include],
:readonly => @reflection.options[:readonly]
)
+ set_inverse_instance(the_target, @owner)
+ the_target
end
def foreign_key_present
!@owner[@reflection.primary_key_name].nil?
end
+
+ # NOTE - for now, we're only supporting inverse setting from belongs_to back onto
+ # has_one associations.
+ def we_can_set_the_inverse_on_this?(record)
+ @reflection.has_inverse? && @reflection.inverse_of.macro == :has_one
+ end
end
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 a2cbabfe0c..73dd50dd07 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -116,6 +116,11 @@ module ActiveRecord
:create => create_scoping
}
end
+
+ def we_can_set_the_inverse_on_this?(record)
+ inverse = @reflection.inverse_of
+ return !inverse.nil?
+ end
end
end
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
index 1c091e7d5a..2dca84b911 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -1,11 +1,6 @@
module ActiveRecord
module Associations
class HasManyThroughAssociation < HasManyAssociation #:nodoc:
- def initialize(owner, reflection)
- reflection.check_validity!
- super
- end
-
alias_method :new, :build
def create!(attrs = nil)
@@ -251,6 +246,11 @@ module ActiveRecord
def cached_counter_attribute_name
"#{@reflection.name}_count"
end
+
+ # NOTE - not sure that we can actually cope with inverses here
+ def we_can_set_the_inverse_on_this?(record)
+ false
+ 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 1464227bb0..4908005d2e 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -74,13 +74,15 @@ module ActiveRecord
private
def find_target
- @reflection.klass.find(:first,
+ the_target = @reflection.klass.find(:first,
:conditions => @finder_sql,
:select => @reflection.options[:select],
:order => @reflection.options[:order],
:include => @reflection.options[:include],
:readonly => @reflection.options[:readonly]
)
+ set_inverse_instance(the_target, @owner)
+ the_target
end
def construct_sql
@@ -117,8 +119,15 @@ module ActiveRecord
self.target = record
end
+ set_inverse_instance(record, @owner)
+
record
end
+
+ def we_can_set_the_inverse_on_this?(record)
+ inverse = @reflection.inverse_of
+ return !inverse.nil?
+ end
end
end
end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 2d4c1d5507..ec0175497d 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -212,6 +212,13 @@ module ActiveRecord
end
def check_validity!
+ check_validity_of_inverse!
+ end
+
+ def check_validity_of_inverse!
+ if has_inverse? && inverse_of.nil?
+ raise InverseOfAssociationNotFoundError.new(self)
+ end
end
def through_reflection
@@ -225,6 +232,18 @@ module ActiveRecord
nil
end
+ def has_inverse?
+ !@options[:inverse_of].nil?
+ end
+
+ def inverse_of
+ if has_inverse?
+ @inverse_of ||= klass.reflect_on_association(options[:inverse_of])
+ else
+ nil
+ end
+ end
+
private
def derive_class_name
class_name = name.to_s.camelize
@@ -300,6 +319,8 @@ module ActiveRecord
unless [:belongs_to, :has_many].include?(source_reflection.macro) && source_reflection.options[:through].nil?
raise HasManyThroughSourceAssociationMacroError.new(self)
end
+
+ check_validity_of_inverse!
end
def through_reflection_primary_key