Skip to content

Commit

Permalink
Set inverse associations when using scopes
Browse files Browse the repository at this point in the history
Currently using scopes through a has_many association will not set
the inverse association, e.g:

  product = Product.first
  product.variants.enabled.first.product.equal? product
  => false

This commit fixes it by copying the association into the scope and
then attempting to set the inverse association when the records are
loaded.
  • Loading branch information
pixeltrix committed Aug 26, 2012
1 parent ea84b0c commit 9f607fb
Show file tree
Hide file tree
Showing 6 changed files with 35 additions and 5 deletions.
Expand Up @@ -15,6 +15,7 @@ def initialize(association)

def scope
scope = klass.unscoped
scope.association = @association
scope.merge! eval_scope(klass, reflection.scope) if reflection.scope
add_constraints(scope)
end
Expand Down
Expand Up @@ -35,8 +35,8 @@ module Associations
# instantiation of the actual post records.
class CollectionProxy < Relation
def initialize(association) #:nodoc:
@association = association
super association.klass, association.klass.arel_table
@association = association
merge! association.scope
end

Expand Down Expand Up @@ -896,7 +896,7 @@ def ==(other)
end

# Returns a new array of objects from the collection. If the collection
# hasn't been loaded, it fetches the records from the database.
# hasn't been loaded, it fetches the records from the database.
#
# class Person < ActiveRecord::Base
# has_many :pets
Expand Down
7 changes: 6 additions & 1 deletion activerecord/lib/active_record/relation.rb
Expand Up @@ -17,7 +17,7 @@ class Relation
include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation

attr_reader :table, :klass, :loaded
attr_accessor :default_scoped
attr_accessor :default_scoped, :association
alias :model :klass
alias :loaded? :loaded
alias :default_scoped? :default_scoped
Expand All @@ -29,6 +29,7 @@ def initialize(klass, table, values = {})
@implicit_readonly = nil
@loaded = false
@default_scoped = false
@association = nil
end

def insert(values)
Expand Down Expand Up @@ -572,6 +573,10 @@ def exec_queries
@records = default_scoped.to_a
end

if association
@records.each { |record| association.set_inverse_instance(record) }
end

@loaded = true
@records
end
Expand Down
4 changes: 4 additions & 0 deletions activerecord/lib/active_record/relation/merger.rb
Expand Up @@ -35,6 +35,10 @@ def initialize(relation, other)
other = other.with_default_scope
end

if other.association
relation.association = other.association
end

@relation = relation
@values = other.values
end
Expand Down
20 changes: 18 additions & 2 deletions activerecord/test/cases/associations/inverse_associations_test.rb
Expand Up @@ -261,8 +261,24 @@ def test_parent_instance_should_be_shared_with_replaced_via_accessor_children

def test_parent_instance_should_be_shared_with_first_and_last_child
man = Man.first
assert man.interests.first.man.equal? man
assert man.interests.last.man.equal? man
assert man.interests.first.man.equal?(man), "Man should be the same instance when using association.first"
assert man.interests.last.man.equal?(man), "Man should be the same instance when using association.last"
end

def test_parent_instance_should_be_shared_with_all_children
man = Man.first
assert man.interests.to_a.all?{ |interest| interest.man.equal?(man) }, "Man should be the same instance when using association.to_a"
end

def test_parent_instance_should_be_shared_with_first_and_last_child_when_using_scope
man = Man.first
assert man.interests.by_topic.first.man.equal?(man), "Man should be the same instance when using association.scope.first"
assert man.interests.by_topic.last.man.equal?(man), "Man should be the same instance when using association.scope.last"
end

def test_parent_instance_should_be_shared_with_all_children_when_using_scope
man = Man.first
assert man.interests.by_topic.to_a.all?{ |interest| interest.man.equal?(man) }, "Man should be the same instance when using association.scope.to_a"
end

def test_trying_to_use_inverses_that_dont_exist_should_raise_an_error
Expand Down
4 changes: 4 additions & 0 deletions activerecord/test/models/interest.rb
Expand Up @@ -2,4 +2,8 @@ class Interest < ActiveRecord::Base
belongs_to :man, :inverse_of => :interests
belongs_to :polymorphic_man, :polymorphic => true, :inverse_of => :polymorphic_interests
belongs_to :zine, :inverse_of => :interests

def self.by_topic
order(arel_table[:topic])
end
end

0 comments on commit 9f607fb

Please sign in to comment.