Update 2
Incidentally, as I was typing Update 1 @muffinista posted the same conclusion I came to in Update 1 and I just wanted to add clarification that I am not sniping his answer and updating the question randomly. After thinking it through some more after having posted the question, I realized what the issue was. While @muffinista posted the cause, his solution was inadequate – that’s why I am leaving Update 1 intact – because I found a better solution and it makes sense for others to get the full context.
Update 1
So I figured what is causing this error, an infinite loop.
I am trying to save the client record within an after_save callback on the Client record. So it keeps trying to save the client record and executing the after_save callback which tries to save the client_record.
How can I achieve what I want, i.e. updating this client.weighted_score attribute whenever the record is updated without jumping into this loop?
Original Question
I have this callback:
after_save :calculate_weighted_score, :if => Proc.new { |c| c.score.present? }
def calculate_weighted_score
# Sum products of weight & scores of each attribute
client = self
weight = Weight.first
score = self.score
client.weighted_score = (weight.firm_size * score.firm_size) + (weight.priority_level * score.priority_level) +
(weight.inflection_point * score.inflection_point) + (weight.personal_priority * score.personal_priority) +
(weight.sales_priority * score.sales_priority) + (weight.sales_team_priority * score.sales_team_priority) +
(weight.days_since_contact * score.days_since_contact) + (weight.does_client_vote * score.does_client_vote) +
(weight.did_client_vote_for_us * score.did_client_vote_for_us) + (weight.days_until_next_vote * score.days_until_next_vote) +
(weight.does_client_vote_ii * score.does_client_vote_ii) + (weight.did_client_vote_ii_for_us * score.did_client_vote_ii_for_us) +
(weight.days_until_vote_ii * score.days_until_vote_ii)
client.o
# self.save
# client.update_attributes(:weighted_score => weighted_score)
end
This is an example of the state of a Client record before this callback is run:
#<Client:0x007fe00dbcea90> {
:id => 10,
:name => "Manta-Jar Gale",
:email => "mj@gmail.com",
:phone => 8769876435,
:firm_id => 1,
:created_at => Fri, 23 Nov 2012 23:50:09 UTC +00:00,
:updated_at => Tue, 27 Nov 2012 17:50:01 UTC +00:00,
:user_id => 1,
:personal_priority => true,
:last_contact => Sat, 08 Jan 2011,
:vote => true,
:vote_for_user => false,
:next_vote => Thu, 02 Jan 2014,
:vote_ii => true,
:vote_ii_for_us => true,
:next_vote_ii => Mon, 01 Jul 2013,
:weighted_score => nil,
:firm_size => 100.0
}
Notice the weighted_score => nil attribute.
After the callback, this same record looks like this:
#<Client:0x007fe00dbcea90> {
:id => 10,
:name => "Manta-Jar Gale",
:email => "mj@gmail.com",
:phone => 8769876435,
:firm_id => 1,
:created_at => Fri, 23 Nov 2012 23:50:09 UTC +00:00,
:updated_at => Tue, 27 Nov 2012 17:50:01 UTC +00:00,
:user_id => 1,
:personal_priority => true,
:last_contact => Sat, 08 Jan 2011,
:vote => true,
:vote_for_user => false,
:next_vote => Thu, 02 Jan 2014,
:vote_ii => true,
:vote_ii_for_us => true,
:next_vote_ii => Mon, 01 Jul 2013,
:weighted_score => 9808,
:firm_size => 100.0
}
Notice the weighted_score => 9808 attribute.
So I know that the callback calculate_weighted_score is being run, and the entire callback seems to be correct up until the assignment of the client.weighted_score. The issue is, the log shows no UPDATE db transaction for that attribute:
Started PUT "/clients/10" for 127.0.0.1 at 2012-11-27 12:50:01 -0500
Processing by ClientsController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"J172LuZQ=", "client"=>{"name"=>"Manta-Jar Gale", "email"=>"mj@gmail.com", "phone"=>"8769876435", "firm_id"=>"1", "topic_ids"=>["1", "2", "9"], "personal_priority"=>"1", "last_contact"=>"2011-01-08", "vote"=>"1", "vote_for_user"=>"0", "next_vote"=>"2014-01-02", "vote_ii"=>"1", "vote_ii_for_us"=>"1"}, "commit"=>"Update Client", "id"=>"10"}
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT 1
Client Load (0.2ms) SELECT "clients".* FROM "clients" WHERE "clients"."user_id" = 1 AND "clients"."id" = ? LIMIT 1 [["id", "10"]]
Topic Load (0.3ms) SELECT "topics".* FROM "topics"
(0.1ms) begin transaction
Topic Load (0.2ms) SELECT "topics".* FROM "topics" WHERE "topics"."id" IN (1, 2, 9)
Topic Load (0.1ms) SELECT "topics".* FROM "topics" INNER JOIN "clients_topics" ON "topics"."id" = "clients_topics"."topic_id" WHERE "clients_topics"."client_id" = 10
(0.5ms) UPDATE "clients" SET "name" = 'Manta-Jar Gale', "updated_at" = '2012-11-27 17:50:01.856893' WHERE "clients"."id" = 10
Firm Load (0.2ms) SELECT "firms".* FROM "firms" WHERE "firms"."id" = 1 LIMIT 1
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT 1
Score Load (0.2ms) SELECT "scores".* FROM "scores" WHERE "scores"."client_id" = 10 LIMIT 1
SalesTeam Load (0.1ms) SELECT "sales_teams".* FROM "sales_teams" WHERE "sales_teams"."id" = 2 LIMIT 1
PriorityLevel Load (0.1ms) SELECT "priority_levels".* FROM "priority_levels" WHERE "priority_levels"."id" = 1 LIMIT 1
CACHE (0.0ms) SELECT "scores".* FROM "scores" WHERE "scores"."client_id" = 10 LIMIT 1
Max Load (0.2ms) SELECT "maxes".* FROM "maxes" WHERE "maxes"."user_id" = 1 LIMIT 1
CACHE (0.0ms) SELECT "scores".* FROM "scores" WHERE "scores"."client_id" = 10 LIMIT 1
Weight Load (0.2ms) SELECT "weights".* FROM "weights" LIMIT 1
(3.2ms) commit transaction
Redirected to http://localhost:3000/clients
Completed 302 Found in 796ms (ActiveRecord: 26.4ms)
The only UPDATE transaction is for the edit to the name I made to test the callback.
I know there is no UPDATE transaction, because technically I am not saving the record.
But when I try to do any of the commented out statements – i.e. client.save or client.update_attributes(....) I get a Stack Level Too Deep Error.
What is causing this and how can I save this record?
As indicated by @muffinista (and specified in the update to the question itself), the issue here is that the callback is trying to save the record which is calling the callback which is trying to save the record…creating a
Stack Overflow(stole that joke from @ivan on this similar question).It seems the best answer is to use
update_column– the docs can be seen here.That does the same thing that
update_attributesdoes, but doesn’t do validation or callbacks – which is fine for what I want in this particular instance.Of special note, the format of
update_columnis not the same asupdate_attributes.What you do is:
Where
:weighted_scoreis my column name, andweighted_scoreis the local variable I set in the code example above that did my calculation for me.For what it’s worth, I got this answer from this SO answer.