Reputation: 629
In my rails model I have a series of hard-coded questions (loaded into the DB using YAML). In order to display these to the user I have a SaleQualifier model - this has_many questions
and has_one answer
.
In the SalesOpportunity show action (where the SaleQualifier will be displayed from). I create a new SaleQualifier and build the associated question:
def show
@sales_opportunity = SalesOpportunity.includes(:company, :user, :timeline_events, :sale_contacts, :swots, :sale_competitors).find(params[:id])
@sale_qualifier = SaleQualifier.new(sales_opportunity_id: params[@sales_opportunity.id])
@answer = @sale_qualifier.answers.build
#if the question_id is blank it is the first question in the list
if @sale_qualifier.question_id.blank?
@question = Question.find_by_id(@sale_qualifier.next_question_id)
else
@question = Question.find_by_id(@sale_qualifier.question_id)
end
end
The Answer model is set up to ensure that once it becomes saved and permanently tied to this SaleQualifier it will fire an after_save
callback to update the SaleQualifier (adding a has_answer
tag, finding the appropriate next_question_id
etc).
class Answer < ActiveRecord::Base
validates :answer_text, presence: true
belongs_to :sale_qualifier
after_save :update_sale_qualifier
def update_sale_qualifier
sale_qualifier.update_next_question
sale_qualifier.save
end
end
The sale_qualifier.save
line is what is causing me the problem. Rather than purely saving the record, it saves the record AND calls the update_next_question action again. I don't know why, as there's no after_save callback in my SaleQualifier model:
class SaleQualifier < ActiveRecord::Base
has_one :answer, :inverse_of => :sale_qualifier, dependent: :destroy
accepts_nested_attributes_for :answer
belongs_to :sales_opportunity
validates :sales_opportunity_id, presence: true
def update_next_question
#find the question that this SaleQualifier is associated with
question = Question.find_by_id(self.question_id)
#get its answer_type as this defines whether we need to look at the next_question_id_no or not
ans_type = question.answer_type
if ans_type == 'Text Field' || ans_type == 'Datetime' || ans_type == 'Integer' || ans_type == 'Boolean' && self.answer.answer_text == 'True'
self.next_question_id = question.next_question_id_yes
#if the answer_type is a boolean and the answer is no/false, then use the next_question_id_no to route the question
elsif ans_type == 'Boolean' && self.answer.answer_text == 'False'
self.next_question_id = question.next_question_id_no
end
#mark the question as answered, in case we need to iterate over the same quesiton in future
self.has_answer = true
#create a new SaleQualifier with the question set as the next question, unless it already exists and hasn't been answered yet or unless the next question id leads us to a result (i.e. no questions left in the tree)
SaleQualifier.create(question_id: self.next_question_id, sales_opportunity_id: self.sales_opportunity_id) unless Question.exists?(:id => self.next_question_id) || SaleQualifier.find_by(question_id: self.next_question_id) && SaleQualifier.find_by(question_id: self.next_question_id).has_answer == false
end
end
Can anyone tell me why the update_next_question
method is calling itself in a loop every time I save my SaleQualifier?
Upvotes: 0
Views: 127
Reputation: 27747
So we figured out that there's a one-time feedback loop involved by the fact that Answer
calls save
on a SaleQualifer
and SaleQualifier
contains accepts_nested_attributes_for :answer
.... so if you have a SaleQualifer
that contains attributes for an Answer
then you save the answer, it will use the after_save
callback, then call save, which saves the SaleQualifer
, which calls save on the answer
... which calls the after_save
callback again.
So yeah, changing that to an after_create hook fixes it by breaking the cycle on the sale_qualifier.save
line.
Upvotes: 1