I have an (I think) relatively straightforward has_many :through
relationship with a join table:
class User < ActiveRecord::Base
has_many :user_following_thing_relationships
has_many :things, :through => :user_following_thing_relationships
end
class Thing < ActiveRecord::Base
has_many :user_following_thing_relationships
has_many :followers, :through => :user_following_thing_relationships, :source => :user
end
class UserFollowingThingRelationship < ActiveRecord::Base
belongs_to :thing
belongs_to :user
end
And these rspec tests (I know these are not necessarily good tests, these are just to illustrate what's happening):
describe Thing do
before(:each) do
@user = User.create!(:name => "Fred")
@thing = Thing.create!(:name => "Foo")
@user.things << @thing
end
it "should have created a relationship" do
UserFollowingThingRelationship.first.user.should == @user
UserFollowingThingRelationship.first.thing.should == @thing
end
it "should have followers" do
@thing.followers.should == [@user]
end
end
This works fine UNTIL I add an after_save
to the Thing
model that references its followers
. That is, if I do
class Thing < ActiveRecord::Base
after_save :do_stuff
has_many :user_following_thing_relationships
has_many :followers, :through => :user_following_thing_relationships, :source => :user
def do_stuff
followers.each { |f| puts "I'm followed by #{f.name}" }
end
end
Then the second test fails - i.e., the relationship is still added to the join table, but @thing.followers
returns an empty array. Furthermore, that part of the callback never gets called (as if followers
is empty within the model). If I add a puts "HI"
in the callback before the followers.each
line, the "HI" shows up on stdout, so I know the callback is being called. If I comment out the followers.each
line, then the tests pass again.
If I do this all through the console, it works fine. I.e., I can do
>> t = Thing.create!(:name => "Foo")
>> t.followers # []
>> u = User.create!(:name => "Bar")
>> u.things << t
>> t.followers # [u]
>> t.save # just to be super duper sure that the callback is triggered
>> t.followers # still [u]
Why is this failing in rspec? Am I doing something horribly wrong?
Update
Everything works if I manually define Thing#followers
as
def followers
user_following_thing_relationships.all.map{ |r| r.user }
end
This leads me to believe that perhaps I am defining my has_many :through
with :source
incorrectly?
Update
I've created a minimal example project and put it on github: https://github.com/dantswain/RspecHasMany
Another Update
Thanks a ton to @PeterNixey and @kikuchiyo for their suggestions below. The final answer turned out to be a combination of both answers and I wish I could split credit between them. I've updated the github project with what I think is the cleanest solution and pushed the changes: https://github.com/dantswain/RspecHasMany
I would still love it if someone could give me a really solid explanation of what is going on here. The most troubling bit for me is why, in the initial problem statement, everything (except the operation of the callback itself) would work if I commented out the reference to followers
.