How to test after_initialize callback of a rails model? - ruby-on-rails

I am using FactoryGirl and Rspec for testing.
The model sets a foreign key after init if it is nil. Therefore it uses data of another association. But how can I test it?
Normally I would use a factory for creation of this object and use a stub_chain for "self.user.main_address.country_id". But with the creation of this object, after initialize will be invoked. I have no chance to stub it.
after_initialize do
if self.country_id.nil?
self.country_id = self.user.main_address.country_id || Country.first.id
end
end
Any idea?

Ideally it's better that you test behavior instead of implementation. Test that the foreign key gets set instead of testing that the method gets called.
Although, if you want to test the after_initialize callback here is a way that works.
obj = Model.allocate
obj.should_receive(:method_here)
obj.send(:initialize)
Allocate puts the object in memory but doesn't call initialize. After you set the expectation, then you can call initialize and catch the method call.

Orlando's method works, and here's another which I'd like to add. (Using new rspec 'expect' syntax)
expect_any_instance_of(Model).to receive(:method_here)
Model.new

Another approach is to refactor your code to allow simple unit tests. As is, your code is approaching the upper end of how much code I'd put in a callback:
after_initialize do
if self.country_id.nil?
self.country_id = self.user.main_address.country_id || Country.first.id
end
end
If it grew much more, I'd extract it to a method and reduce your callback to a method call:
after_initialize :init_country_id
def init_country_id
if self.country_id.nil?
self.country_id = self.user.main_address.country_id || Country.first.id
end
end
The bonus here is that testing init_country_id becomes just another method unit test at this point...nothing fancy about that.
Now that you've got a unit test on the behavior, you can also test that it gets called, if you're in doubt. (Something as simple as after_initialize :init_country_id does not need invocation testing, IMO)
You can use gem shoulda-callback-matchers to test that your callbacks are actually getting triggered as intended:
it { is_expected.to callback(:init_country_id).before(:initialize) }

Related

Rspec expect(instance) to receive method not working as expected

#controller file
def update
#payment = Payment.find_by(reference_id: params[:reference_id])
if #payment.update(update_params)
#payment.do_something
end
end
when trying to spec if do_something method was called, by
expect(#payment).to receive(:do_something)
it says
expected: 1 time with any arguments
received: 0 times with any arguments
do_something is in my Payment Class. It is actually being called, but rspec says not.
Any ideas? thanks in advance
Firstly you need to stub the lines in controller in order to expect some code
before do
allow(Payment).to receive(:find_by).and_return(payment)
allow(payment).to receive(:update).and_return(true)
allow(payment).to receive(:do_something)
end
Also, instance variable in controller won't be directly accessible in rspecs.
So, First create a payment object in rspecs using let and use it before block like I did it in above solution
Your #payment in specs is actually a totally different variable, which is part of specs class, not the controller. I may be wrong, but that is my assumption from the parts of code you post - add specs code for more info.
As of the solution, may use 'stub any instance'
Payment.any_instance.stub(:do_something).and_return(:smthing)
A more complicated approach - using doubles

How to test if method is called in RSpec but do not override the return value

There are already questions similar to this, but they all override the return values to nil unless .and_return is called as well
PROBLEM
I am wondering if there is a way to just check if a method is called using expect_any_instance_of(Object).to receive(:somemethod) and it runs normally without overriding or affecting the return value of .somemethod.
rspec-3.4.0
rails 4.2
Consider the following:
# rspec
it 'gets associated user' do
expect_any_instance_of(Post).to receive(:get_associated_user)
Manager.run_processes
end
# manager.rb
class Manager
def self.run_processes
associated_user = Category.first.posts.first.get_associated_user
associated_user.destroy!
end
end
The spec above although will work because :get_associated_user is called in the run_processes, however it raises NoMethodError: undefined method 'destroy!' for NilClass precisely because I mocked the :get_associated_user for any instance of Post.
I could add a .and_return method like expect_any_instance_of(Post).to receive(:get_associated_user).and_return(User.first) so that it will work without raising that error, but that already is a mocked return value (which might affect the rest of the code below it), and not the correct expected value it should have returned at the time the method is called.
I can however specify .and_return(correct_user) where correct_user is the user that is going to be the same return value as if it has ran normally. However, this will need me to mock every return value in the sequence Category.first.posts.first.get_associated_user just so that it will work normally. The actual problem is a lot more complex than above, therefore stubbing is not really a possible solution in my case.
You can use and_call_original on the fluent interface to "pass
through" the received message to the original method.
https://www.relishapp.com/rspec/rspec-mocks/v/2-14/docs/message-expectations/calling-the-original-method
expect_any_instance_of(Post).to receive(:get_associated_user).and_call_original
However the use of expect_any_instance_of might be telling you that you have a code smell and you should be testing the behavior - not the implementation.
# test what it does - not how it does it.
it 'destroys the associated user' do
expect { Manager.run_processes }.to change(Category.first.posts.first.users, :count).by(-1)
end

how to correctly use stub for testing in rspec

So, I have a method in a class as follow:
def installation_backlog
Api::Dashboards::InstallationsBacklog.new(operational_district_id, officer_id).backlog_tasks
end
And I want to spec it. So, I just wrote an RSpec test to test it as follow:
it "should call a new instance of InstallationsBacklog with the backlog_tasks method" do
expect_any_instance_of(Api::Dashboards::InstallationsBacklog).to receive(:backlog_tasks)
#installation_officer.installation_backlog # #installation_officer is a new instance of the container class.
end
And this is working.
However, I began to wonder if this was a correct way of doing it. Like: am I sure that even if I stub the wrong ( probably inexistent ) method, and test for it, will it pass or fail?
I tried it, it passed
So, if later, the method name gets changed, there is no way for this test to detect that.
So, here is the question: How can I be sure that a RSpec stubbed method is actually existent in a code?
Here's how I'd set it up. Might help..
let(:backlog) {
double('Backlog', backlog_tasks: [])
}
before do
allow(Api::Dashboards::InstallationsBacklog).to receive(:new).
and_return(backlog)
end
it 'instantiates InstallationBacklog' do
expect(Api::Dashboards::InstallationBacklog).to receive(:new).
with(operational_district_id, officer_id)
#installation_officer.installation_backlog
end
it 'calls backlog_tasks on instance of InstallationBacklog' do
expect(backlog).to receive(:backlog_tasks)
#installation_officer.installation_backlog
end

Rails 4: Create object only in a factory method?

Going to simplify a bit here, but assume an app that has Users and UserRecords. A User must have one or more UserRecords. I want to limit the creation of UserRecords to a method in User, namely #create_new_user_record.
In other words, I don't want to allow UserRecord.new or UserRecords.create anywhere else in the application. I need to control the creation of these records, and perform some logic around them (for example, setting the new one current and any others to not current), and I don't want any orphaned UserRecords in the database.
I tried the after_initialize callback and checking if the object is new and raising an error there, but of course I do need to call UserRecord.new in User#create_new_user_record. If I could somehow flag in #create_new_user_record that I am calling new from that method, and pick that up in after_intialize, that would work, but how?
I might be over thinking it. I can certainly create a that method on User, and just 'know' to always call it. But others will eventually work on this app, and I will go away and come back to it as some point.
I suppose I could raise the error and just rescue from it in #create_new_user_record. Then at least, if another develop tries it elsewhere they will find out why I did it when they pursue the error.
Anyway, wondering what the Rails gurus here had to say about it.
super method is what you are looking for. Though you'll need some workaround (maybe simple check for value of option only you know about) to fit your needs
class User < ActiveRecord:Base
def .new(attributes = nil, options = {})
do_your_fancy_stuff
if option[:my_secret_new_method]
super # call AR's .new method and automatically pass all the arguments
end
end
Ok, here's what I did. Feel free to tell me if this is bad idea or, if it's an ok idea, if there's a better way. For what it's worth, this does accomplish my goal.
In the factory method in the User model, I send a custom parameter in the optional options hash defined on the new method in the API. Then I in the UserRecord#new override, I check for this parameter. If it's true, I create and return the object, otherwise I raise in custom error.
In my way of thinking, creating a UserRecord object any other way is an error. And a developer who innocently attempts it would be lead to explanatory comments in the two methods.
One thing that's not clear to me is why I need to leave off the options hash when I call super. Calling super with it causes the ArgumentError I posted in my earlier comment. Calling super without it seems to work fine.
class User < ActiveRecord::Base
...
def create_new_user_record
# do fancy stuff here
user_record = UserRecord.new( { owner_id: self.id, is_current: true }, called_from_factory: true )
user_record.save
end
...
end
class UserRecord < ActiveRecord::Base
...
def UserRecord.new(attributes = nil, options = {})
if options[:called_from_factory] == true
super(attributes)
else
raise UseFactoryError, "You must use factory method (User#create_new_user_record) to create UserRecords"
end
end
...
end

Using the after_save callback to modify the same object without triggering the callback again (recursion)

If I add an after_save callback to an ActiveRecord model, and on that callback I use update_attribute to change the object, the callback is called again, and so a 'stack overflow' occurs (hehe, couldn't resist).
Is it possible to avoid this behavior, maybe disabling the callback during it's execution? Or is there another approach?
Thanks!
One workaround is to set a variable in the class, and check its value in the after_save.
Check it first. (if var)
Assign it to a 'false' value before calling update_attribute.
call update_attribute.
Assign it to a 'true' value.
end
This way, it'll only attempt to save twice. This will likely hit your database twice, which may or may not be desirable.
I have a vague feeling that there's something built in, but this is a fairly foolproof way to prevent a specific point of recursion in just about any application.
I would also recommend looking at the code again, as it's likely that whatever you're doing in the after_save should be done in before_save. There are times that this isn't true, but they're fairly rare.
Could you use the before_save callback instead?
I didn't see this answer, so I thought I'd add it in case it helps anyone searching on this topic. (ScottD's without_callbacks suggestion is close.)
ActiveRecord provides update_without_callbacks for this situation, but it is a private method. Use send to get access to it anyway. Being inside a callback for the object you are saving is exactly the reason to use this.
Also there is another SO thread here that covers this pretty well:
How can I avoid running ActiveRecord callbacks?
Also you can look at the plugin Without_callbacks. It adds a method to AR that lets you skip certain call backs for a given block.
Example:
def your_after_save_func
YourModel.without_callbacks(:your_after_save_func) do
Your updates/changes
end
end
Check out how update_attribute is implemented. Use the send method instead:
send(name.to_s + '=', value)
If you use before_save, you can modify any additional parameters before the save is completed, meaning you won't have to explicitly call save.
This code doesn't even attempt to address threading or concurrency issues, much like Rails proper. If you need that feature, take heed!
Basically, the idea is to keep a count at what level of recursive calls of "save" you are, and only allow after_save when you are exiting the topmost level. You'll want to add in exception handling, too.
def before_save
#attempted_save_level ||= 0
#attempted_save_level += 1
end
def after_save
if (#attempted_save_level == 1)
#fill in logic here
save #fires before_save, incrementing save_level to 2, then after_save, which returns without taking action
#fill in logic here
end
#attempted_save_level -= 1 # reset the "prevent infinite recursion" flag
end
Thanks guys, the problem is that I update other objects too (siblings if you will)... forgot to mention that part...
So before_save is out of the question, because if the save fails all the modifications to the other objects would have to be reverted and that could get messy :)
The trick is just to use #update_column:
Validations are skipped.
Callbacks are skipped.
updated_at/updated_on are not updated.
Additionally, it simply issues a single quick update query to the db.
http://apidock.com/rails/ActiveRecord/Persistence/update_columns
I had this problem too. I need to save an attribute which depends upon the object id. I solved it by using conditional invocation for the callback ...
Class Foo << ActiveRecord::Base
after_save :init_bar_attr, :if => "bar_attr.nil?" # just make sure this is false after the callback runs
def init_bar_attr
self.bar_attr = "my id is: #{self.id}"
# careful now, let's save only if we're sure the triggering condition will fail
self.save if bar_attr
end
Sometimes this is because of not specifying attr_accessible in models. When update_attribute wants to edit the attributes, if finds out they are not accessible and create new objects instead.On saving the new objects, it will get into an unending loop.
I had a need to gsub the path names in a block of text when its record was copied to a different context:
attr_accessor :original_public_path
after_save :replace_public_path, :if => :original_public_path
private
def replace_public_path
self.overview = overview.gsub(original_public_path, public_path)
self.original_public_path = nil
save
end
The key to stop the recursion was to assign the value from the attribute and then set the attribute to nil so that the :if condition isn't met on the subsequent save.
You can use after_save in association with if as follows:
after_save :after_save_callback, if: Proc.new {
//your logic when to call the callback
}
or
after_save :after_save_callback, if: :call_if_condition
def call_if_condition
//condition for when to call the :after_save_callback method
end
call_if_condition is a method. Define the scenario when to call the after_save_callback in that method

Resources