Rspec has_many validation: via and after_save

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 # [] >> 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 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 .

+4
source share
3 answers

I had similar problems in the past that were resolved by reloading the association (rather than the parent).

thing.followers this work if you reload thing.followers in RSpec?

 it "should have followers" do @thing.followers.reload @thing.followers.should == [@user] end 

EDIT

If (as you mentioned) you have problems with broken callbacks, you can perform this reboot in the object itself:

 class Thing < ActiveRecord::Base after_save { followers.reload} after_save :do_stuff ... end 

or

 class Thing < ActiveRecord::Base ... def do_stuff followers.reload ... end end 

I do not know why RSpec has problems with not reloading associations, but I myself encountered the same problems.

Edit 2

Although @dantswain confirmed that followers.reload helped fix some of the issues that he still hasn't fixed.

To do this, it was necessary to fix the solution from @kikuchiyo, which required a save call after making callbacks in Thing :

 describe Thing do before :each do ... @user.things << @thing @thing.run_callbacks(:save) end ... end 

Final sentence

I believe this is due to the use of << in the has_many_through operation. I don't see that << should actually fire your after_save event:

Your current code is:

 describe Thing do before(:each) do @user = User.create!(:name => "Fred") @thing = Thing.create!(:name => "Foo") @user.things << @thing end end class Thing < ActiveRecord::Base after_save :do_stuff ... def do_stuff followers.each { |f| puts "I'm followed by #{f.name}" } end end 

and the problem is that the do_stuff call do_stuff not called. I think this is the right behavior.

Go through RSpec:

 describe Thing do before(:each) do @user = User.create!(:name => "Fred") # user is created and saved @thing = Thing.create!(:name => "Foo") # thing is created and saved @user.things << @thing # user_thing_relationship is created and saved # no call is made to @user.save since nothing is updated on the user end end 

The problem is that the third step does not actually require the Thing object to be saved - it's just creating an entry in the connection table.

If you want @user to really call save, you could get the effect you want, for example:

 describe Thing do before(:each) do @thing = Thing.create!(:name => "Foo") # thing is created and saved @user = User.create!(:name => "Fred") # user is created BUT NOT SAVED @user.things << @thing # user_thing_relationship is created and saved # @user.save is also called as part of the addition end end 

You may also find that the after_save actually on the wrong object and that you would prefer to use it instead of the relation object. Finally, if the callback really belongs to the user and you need to start it after creating the relationship, you can use touch to update the user when creating a new relationship.

+7
source

UPDATED ANSWER ** This skips rspec without interruption, starts callbacks to save (after the after_save callback has completed), and checks that @ thing. The number remains blank before trying to access its elements. (

 describe Thing do before :each do @user = User.create(:name => "Fred"); @thing = Thing.new(:name => 'Foo') @user.things << @thing @thing.run_callbacks(:save) end it "should have created a relationship" do @thing.followers.should == [@user] puts @thing.followers.inspect end end class Thing < ActiveRecord::Base after_save :some_function has_many :user_following_thing_relationships has_many :followers, :through => :user_following_thing_relationships, :source => :user def some_function the_followers = followers unless the_followers.empty? puts "accessing followers here: the_followers = #{the_followers.inspect}..." end end end 

ORIGINAL RESPONSE **

I managed to get the work to work with the after_save callback until I referenced the followers inside the do_stuff body / block. Do you need to reference followers in a real method that you call from after_save ?

Updated code to complete the callback. Now the model can remain as needed, we show @ thing. After we expected, we can actually set the do_stuff / some_function functionality through after_save in another specification.

I clicked a copy of the code here: https://github.com/kikuchiyo/RspecHasMany

And the thing transfer specification * code below:

 # thing_spec.rb require 'spec_helper' describe Thing do before :each do Thing.any_instance.stub(:some_function) { puts 'stubbed out...' } Thing.any_instance.should_receive(:some_function).once @thing = Thing.create(:name => "Foo"); @user = User.create(:name => "Fred"); @user.things << @thing end it "should have created a relationship" do @thing.followers.should == [@user] puts @thing.followers.inspect end end # thing.rb class Thing < ActiveRecord::Base after_save :some_function has_many :user_following_thing_relationships has_many :followers, :through => :user_following_thing_relationships, :source => :user def some_function # well, lets me do this, but I cannot use @x without breaking the spec... @x = followers puts 'testing puts hear shows up in standard output' x ||= 1 puts "testing variable setting and getting here: #{x} == 1\n\t also shows up in standard output" begin # If no stubbing, this causes rspec to fail... puts "accessing followers here: @x = #{@x.inspect}..." rescue puts "and this is but this is never seen." end end end 
+2
source

I assume that you need to reload the Thing instance by doing @thing.reload (I am sure there is a way to avoid this, but this may cause your test to pass first and then you can figure out where you are. "Wrong "

A few questions:

I do not see you @thing.save in your specification. Are you doing this, as in the console example?

Why are you calling t.save and not u.save in your console test, assuming you press t on u ? Saving u should initiate saving to t , getting the desired end result, and I think it would be β€œmore sensible" if you really work on u , not t .

+1
source

Source: https://habr.com/ru/post/1391679/


All Articles