Skip to content

Commit

Permalink
Merge pull request #10886 from wangjohn/chnges_for_automatic_inverse_…
Browse files Browse the repository at this point in the history
…associations

Documentation and cleanup of automatic discovery of inverse associations
  • Loading branch information
jonleighton committed Jun 9, 2013
2 parents 8b82387 + d6b03a3 commit ae6e6d9
Show file tree
Hide file tree
Showing 11 changed files with 45 additions and 19 deletions.
21 changes: 21 additions & 0 deletions activerecord/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,24 @@
* Rails now automatically detects inverse associations. If you do not set the
`:inverse_of` option on the association, then Active Record will guess the
inverse association based on heuristics.

Note that automatic inverse detection only works on `has_many`, `has_one`,
and `belongs_to` associations. Extra options on the associations will
also prevent the association's inverse from being found automatically.

The automatic guessing of the inverse association uses a heuristic based
on the name of the class, so it may not work for all associations,
especially the ones with non-standard names.

You can turn off the automatic detection of inverse associations by setting
the `:inverse_of` option to `false` like so:

class Taggable < ActiveRecord::Base
belongs_to :tag, inverse_of: false
end

*John Wang*

* Fix `add_column` with `array` option when using PostgreSQL. Fixes #10432

*Adam Anderson*
Expand Down
11 changes: 6 additions & 5 deletions activerecord/lib/active_record/associations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -586,9 +586,10 @@ def association_instance_set(name, association)
# belongs_to :tag, inverse_of: :taggings
# end
#
# If you do not set the +:inverse_of+ record, the association will do its
# best to match itself up with the correct inverse. Automatic +:inverse_of+
# detection only works on +has_many+, +has_one+, and +belongs_to+ associations.
# If you do not set the <tt>:inverse_of</tt> record, the association will
# do its best to match itself up with the correct inverse. Automatic
# inverse detection only works on <tt>has_many</tt>, <tt>has_one</tt>, and
# <tt>belongs_to</tt> associations.
#
# Extra options on the associations, as defined in the
# <tt>AssociationReflection::INVALID_AUTOMATIC_INVERSE_OPTIONS</tt> constant, will
Expand All @@ -599,10 +600,10 @@ def association_instance_set(name, association)
# especially the ones with non-standard names.
#
# You can turn off the automatic detection of inverse associations by setting
# the +:automatic_inverse_of+ option to +false+ like so:
# the <tt>:inverse_of</tt> option to <tt>false</tt> like so:
#
# class Taggable < ActiveRecord::Base
# belongs_to :tag, automatic_inverse_of: false
# belongs_to :tag, inverse_of: false
# end
#
# == Nested \Associations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ def macro
end

def valid_options
super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :automatic_inverse_of, :counter_cache]
super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache]
end

def valid_dependent_options
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
module ActiveRecord::Associations::Builder
class SingularAssociation < Association #:nodoc:
def valid_options
super + [:remote, :dependent, :counter_cache, :primary_key, :inverse_of, :automatic_inverse_of]
super + [:remote, :dependent, :counter_cache, :primary_key, :inverse_of]
end

def constructable?
Expand Down
4 changes: 4 additions & 0 deletions activerecord/lib/active_record/nested_attributes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,10 @@ class TooManyRecords < ActiveRecordError
# validates_presence_of :member
# end
#
# Note that if you do not specify the <tt>inverse_of</tt> option, then
# Active Record will try to automatically guess the inverse association
# based on heuristics.
#
# For one-to-one nested associations, if you build the new (in-memory)
# child object yourself before assignment, then this module will not
# overwrite it, e.g.:
Expand Down
14 changes: 7 additions & 7 deletions activerecord/lib/active_record/reflection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -313,8 +313,7 @@ def clear_inverse_of_cache!
# and prevents this object from finding the inverse association
# automatically in the future.
def remove_automatic_inverse_of!
@automatic_inverse_of = nil
options[:automatic_inverse_of] = false
@automatic_inverse_of = false
end

def polymorphic_inverse_of(associated_class)
Expand Down Expand Up @@ -449,15 +448,16 @@ def valid_inverse_reflection?(reflection)

# Checks to see if the reflection doesn't have any options that prevent
# us from being able to guess the inverse automatically. First, the
# +automatic_inverse_of+ option cannot be set to false. Second, we must
# have +has_many+, +has_one+, +belongs_to+ associations. Third, we must
# not have options such as +:polymorphic+ or +:foreign_key+ which prevent us
# from correctly guessing the inverse association.
# <tt>inverse_of</tt> option cannot be set to false. Second, we must
# have <tt>has_many</tt>, <tt>has_one</tt>, <tt>belongs_to</tt> associations.
# Third, we must not have options such as <tt>:polymorphic</tt> or
# <tt>:foreign_key</tt> which prevent us from correctly guessing the
# inverse association.
#
# Anything with a scope can additionally ruin our attempt at finding an
# inverse, so we exclude reflections with scopes.
def can_find_inverse_of_automatically?(reflection)
reflection.options[:automatic_inverse_of] != false &&
reflection.options[:inverse_of] != false &&
VALID_AUTOMATIC_INVERSE_MACROS.include?(reflection.macro) &&
!INVALID_AUTOMATIC_INVERSE_OPTIONS.any? { |opt| reflection.options[opt] } &&
!reflection.scope
Expand Down
2 changes: 1 addition & 1 deletion activerecord/test/models/club.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
class Club < ActiveRecord::Base
has_one :membership
has_many :memberships, :automatic_inverse_of => false
has_many :memberships, :inverse_of => false
has_many :members, :through => :memberships
has_many :current_memberships
has_one :sponsor
Expand Down
2 changes: 1 addition & 1 deletion activerecord/test/models/interest.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
class Interest < ActiveRecord::Base
belongs_to :man, :inverse_of => :interests, :automatic_inverse_of => false
belongs_to :man, :inverse_of => :interests
belongs_to :polymorphic_man, :polymorphic => true, :inverse_of => :polymorphic_interests
belongs_to :zine, :inverse_of => :interests
end
2 changes: 1 addition & 1 deletion activerecord/test/models/man.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
class Man < ActiveRecord::Base
has_one :face, :inverse_of => :man
has_one :polymorphic_face, :class_name => 'Face', :as => :polymorphic_man, :inverse_of => :polymorphic_man
has_many :interests, :inverse_of => :man, :automatic_inverse_of => false
has_many :interests, :inverse_of => :man
has_many :polymorphic_interests, :class_name => 'Interest', :as => :polymorphic_man, :inverse_of => :polymorphic_man
# These are "broken" inverse_of associations for the purposes of testing
has_one :dirty_face, :class_name => 'Face', :inverse_of => :dirty_man
Expand Down
2 changes: 1 addition & 1 deletion activerecord/test/models/member.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class Member < ActiveRecord::Base
has_one :hairy_club, -> { where :clubs => {:name => "Moustache and Eyebrow Fancier Club"} }, :through => :membership, :source => :club
has_one :sponsor, :as => :sponsorable
has_one :sponsor_club, :through => :sponsor
has_one :member_detail, :automatic_inverse_of => false
has_one :member_detail, :inverse_of => false
has_one :organization, :through => :member_detail
belongs_to :member_type

Expand Down
2 changes: 1 addition & 1 deletion activerecord/test/models/member_detail.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
class MemberDetail < ActiveRecord::Base
belongs_to :member, :automatic_inverse_of => false
belongs_to :member, :inverse_of => false
belongs_to :organization
has_one :member_type, :through => :member

Expand Down

0 comments on commit ae6e6d9

Please sign in to comment.