I have (I think) a relatively direct relation of has_many :through to the 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 are rspec tests (I know that these are not necessarily good tests, this is just to illustrate what happens):
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 after_save to the Thing model, which 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 is performed - i.e. the relation is still added to the join table, but @thing.followers returns an empty array. Also, this part of the callback is never called (as if followers empty in the model). If I add puts "HI" in the callback before the followers.each line, the message "HI" appears on the screen, so I know that the calling call is being called. If I comment on the line followers.each , the tests will pass again.
If I do this through the console, it works fine. Ie I can do
>> t = Thing.create!(:name => "Foo") >> t.followers
Why does this not work in rspec? Am I doing something terribly wrong?
Update
Everything works if I manually define Thing#followers as
def followers user_following_thing_relationships.all.map{ |r| r.user } end
This makes me think that maybe I am defining my has_many :through with :source ?
Update
I created a minimal example project and put it on github: https://github.com/dantswain/RspecHasMany
Another update
Thanks for the ton @PeterNixey and @kikuchiyo for their suggestions below. The final answer turned out to be a combination of both answers, and I would like to split the loan between them. I updated the github project with what, in my opinion, is the cleanest solution and pushed the changes: https://github.com/dantswain/RspecHasMany
I would still like it if someone could give me a really convincing explanation of what is happening here. The most unpleasant bit for me is why in the original formulation of the task everything (except the operation of the callback itself) will work if I comment on the link to followers .