I'm trying to write a validation that checks if one of the nested attributes that belong to a model contains a certain value.
In this case I have a Questions model that contains many Answers. I need a validation that checks to see if at least one of the questions has a correct answer marked.
This is an app for creating tests. The question has several answers but not all of them are the correct one.
Here's my Question model:
class Question < ApplicationRecord
belongs_to :examination
has_many :answers, dependent: :destroy
has_many :responses
accepts_nested_attributes_for :answers, allow_destroy: true, :reject_if => :all_blank
validates_presence_of :body
validates_presence_of :question_type
validate :has_correct_ans?
private
def has_correct_ans?
errors.add(:correct, "You must select at least one correct answer") unless
self.answers.exists?(correct: true)
end
end
Here's the Answer model
class Answer < ApplicationRecord
belongs_to :question
has_many :responses, dependent: :destroy
end
I attempted to write a method called "has_correct_ans?" to check if any of the answers contain the correct attribute. This fails every time though. I assume that's because prior to saving the data doesn't exist in the database. From testing in the console that command works fine on existing data.
i.e. Question.find.answers.exists?(correct: true)
will return true for questions in which one of the answers has the correct attribute.
Id really like this to work as a validation. I just don't know how to access the nested attribute prior to save.
This is what the params look like:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"dOf8H1Wqark3TZAGgX6kaY5Yt4kYKm1FNbCnNi4BlVTTQV9PijlkA1bNS8Qi8DwLLxV6FkWzNbmiT6X+7Vr6Xg==", "question"=>{"body"=>"gfdgdfs", "question_type"=>"Multiple Choice", "points"=>"1", "answers_attributes"=>{"0"=>{"correct"=>"true", "body"=>"dggf", "_destroy"=>"0"}}}, "commit"=>"Submit", "examination_id"=>"12"}
I also tried to do this in the controller using the params. Here's what my create function looks like:
class QuestionsController < ApplicationController
def create
@exam = Examination.find(params[:examination_id])
@question = @exam.questions.build(question_params)
ans_params = params[:question][:answers_attributes]
@correct_ans = false
ans_params.each do |k, v|
if @correct_ans == false
@correct_ans = v.has_key?(:correct)
end
end
if @exam.questions.count > 0
@question.position = @exam.questions.count + 1
else
@question.position = 1
end
if @correct_ans == true && @question.save
redirect_to @exam, notice: "question created successfully"
elsif @question.save
flash[:error] = "You need a correct answer"
render :new
else
render :new
end
end
That doesn't actually work either. It still saves even though there is no correct answer. I don't want to do it in the controller anyway. It would work much better as a validation.
I'm sure I'm missing something obvious here. Can anyone help me out?
I got it!
Thanks, Ashik. I had no idea that you can use methods like that, even though the data is not in the database.
I tried your idea. It didn't return the right result but was really close.
answers.map{ |x| x[:correct] == true }.size == 0?
Returns an array of the correct attributes for all the Answers records. Like this:
[false, false, true]
I changed the method a little bit to this:
answers.map{ |x| x[:correct]}.include? true
Which returns true if any of the records contain a correct. I tried it in my validation method and it works perfectly.
Here's the updated Question model.
class Question < ApplicationRecord
belongs_to :examination
has_many :answers, dependent: :destroy
has_many :responses
accepts_nested_attributes_for :answers, allow_destroy: true, :reject_if => :all_blank
validates_presence_of :body
validates_presence_of :question_type
validate :has_correct_ans?
private
def has_correct_ans?
errors.add(:correct, "You must select at least one correct answer") unless
answers.map{ |x| x[:correct]}.include? true
end
end