working with hash keys and values - ruby-on-rails

I have a model QuizAttempt that is going to check the result of a Quiz. I am looking to loop through each submitted answer and check the question id, if the answer supplied is correct. I'm looking for some guidance please...
class QuizAttempt < ActiveRecord::Base
belongs_to :user
belongs_to :quiz
validates_presence_of :quiz, :user
validate :check_result
attr_accessor :questions, :submitted_answers
private
def self.create_for_user_and_answers!(user, answers)
self.new(:user => user).tap do |q|
q.submitted_answers = answers
q.questions = []
answers.each{|k, v| q.questions = q.questions << Question.find(k.gsub(/[^0-9]/i, '').to_i) }
end
end
def check_result
if submitted_answers
unless submitted_answers.keys.length == quiz.easy_questions + quiz.moderate_questions + quiz.hard_questions
self.errors.add(:submitted_answers, "must be provided for each question")
end
else
self.errors.add(:submitted_answers, "must be provided")
end
return false unless self.errors.empty?
score = 0
submitted_answers.each do |answer|
#check the answers and score + 1 if correct
end
self.total_questions = submitted_answers.length
self.passed_questions = score
self.passed = percentage_score >= quiz.pass_percentage
end
public
def percentage_score
(passed_questions / total_questions.to_f * 100).to_i
end
end
The submitted answers are in the form of a (nested?) hash, returned from a form with radio buttons
{"question_1"=>{"answer_3"=>"5"}, "question_2"=>{"answer_2"=>"4"}}
But when I loop through them as in the above QuizAttempt model ie. submitted_answers.each do |answer| i get answer ==
["question_1", {"answer_3"=>"5"}]
And I want to check these answers based on the question model below
class Question < ActiveRecord::Base
belongs_to :quiz
validates :question, :presence => true, :length => {:minimum => 3, :maximum => 254}
validates :answer_1, :answer_2, :answer_3, :answer_4, :presence => true, :length => {:maximum => 254}
validates :rank, :presence => true, :numericality => { :only_integer => true, :greater_than => 0, :less_than => 4 }
validate :only_one_answer_correct
#has boolean values answer_1_correct, answer_2_correct, answer_3_correct, answer_4_correct
def correct_answer_number
(1..4).each{|i| return i if send("answer_#{i}_correct")}
end
end

Would be much simpler if you changed the way the form was structured, however.
answer[1].keys.first.sub("answer_", '').to_i
will give you 3 given your example ["question_1", {"answer_3"=>"5"}] which you can then compare to correct_answer_number in the Question model.
I'm unsure what the value associated with the "answer_x" is? I presumed it wasn't the answer (as in 1,2,3 or 4) as it's 5 and you only have 4 possible answers, so I presumed that 5 was the actual answer to the question and ignored it.

Related

Validation not working on Ranges?

Can someone explain to me why my validation won't get triggered when I submit a string such as "foo" to number?
class Course < ActiveRecord::Base
validates :number, :inclusion => 0..100
end
Only when I change my code to this...
class Course < ActiveRecord::Base
validates :number, :inclusion => 0..100, :numericality => true
end
... the validation gets triggered.
Is this a Rails bug or am I missing something really fundamental here?
I am using Rails 4.2.0 by the way.
It's because rails is converting the string to a number (assuming you've got it persisted as an integer) before doing the validation. If you call to_i on a string you get 0 which is valid for your range.
For example:
> c = Course.new
> c.number = 'hi'
> c.number
=> 0
> c.valid?
=> true
The reason the numericality validators is triggering when you add it is, I think, because it checks the value before any type casting happens:
> c.number = 'hi'
> c.number_before_type_cast
=> 'hi'
Please try this:
validates_numericality_of :number, :only_integer => true,
:greater_than_or_equal_to => 1,
:less_than_or_equal_to => 99,
:message => "can only be number between 1 and 100."
ref: http://apidock.com/rails/ActiveModel/Validations/ClassMethods/validates_numericality_of
This turned out to work best for me:
class Course < ActiveRecord::Base
validates :number, :numericality => { :greater_than_or_equal_to => 0, :less_than_or_equal_to => 100 }
end

Rails validation with if

I am having trouble getting my validation working properly. I am trying to check if the user has checked :noship then :weight must equal 0.
With the code below I get an "Undefined local variable noship" error
snippet from models/package.rb
class Package < ActiveRecord::Base
belongs_to :campaign
validates_presence_of :weight, :campaign
validates :weight, numericality: { equal_to: 0 }, :if => :noship_test?
def noship_test?
noship == true
end
rails_admin do
object_label_method do
:custom_name
end
end
The :noship and :weight values are working and being saved correctly to my database
Since noship belongs to campaign model, you'll need to do something like:
def noship_test?
campaign && campaign.noship
end
However having such a method seems redundant, just pass a lambda to if key:
validates :weight, numericality: { equal_to: 0 }, :if => ->(r) { r.campaign && r.campaign.noship }

Rails: How to set different validation rules on creation and update

I have a text field that can be empty when created, but not when updated.
How can I do that in rails: Different validation rules depending on action?
The idea behind this, is to allow an admin to create a blank issue ticket, to be filled by a user.
Here is my original model (issue.rb):
class Issue < ActiveRecord::Base
attr_accessible :content, :status
validates :content, :presence => true, :length => { :maximum => 2048 }
validates :status, :inclusion => { :in => %w(WAITING REJECTED ON OFF) }
belongs_to :user
end
How can I set :presence => true of :content only when updating, but not when creating?
Thanks in advance.
You can use :on => :create in your validation statement.
Like in this question.

validates_uniqueness_of can't check on unsaved data?

I have a model called Science Subject Choice
class ScienceSubjectChoice < SubjectChoice
belongs_to :subject
belongs_to :subject_preference
validates_associated :subject
validates_associated :subject_preference
#TODO: validation
validates :priority, :presence => true, :numericality => true, :inclusion => {:in => 1..SubjectPreference::MAX_SCIENCE_SUBJECT_CHOICE}
validates_uniqueness_of :subject_id, :scope => :subject_preference_id
validates_uniqueness_of :priority, :scope => :subject_preference_id
end
the uniqueness validator don't work on unsaved data?
How can I solve it?
Solution:
Instead of validating in itself, the parent object should do the validation:
def validate_science_subject_choices_uniqueness
if science_subject_choices.map(&:priority) != science_subject_choices.map(&:priority).uniq
errors[:base] << "Duplicated priority in science subject"
end
end
Validations do not work like that. They are dynamic by nature. If you want database constraints, you have to specify it in your migrations. For instance, a :uniq => true would make sure that a value is unique in your model.

Rails 3: Why integer field is not validated against regex?

Job model has an integer job_price field:
class CreateJobs < ActiveRecord::Migration
def self.up
create_table :jobs do |t|
...
t.integer "job_price"
...
end
end
...
end
I would like to display an error message if user types strings in the job_price field, so I added the following validation:
class Job < ActiveRecord::Base
validates_format_of :job_price, :with => /\A\d{0,10}\z/,
:message => "^Job Price must be valid"
...
end
However, it seems like the validation passes even when I type strings.
Any ideas why ?
Note
I had to add :value => #job.job_price_before_type_cast here:
f.text_field(:job_price, :maxlength => 10,
:value => #job.job_price_before_type_cast)
because, otherwise, if I was typing abc5, for example, and then submit the form, Rails was converted it to 5 (I guess because job_price is defined as integer).
You could ensure it's an integer and in a range:
validates_numericality_of :myfield, :only_integer => true
validates_inclusion_of :myfield, :in => 0..9999999999
Rails 3 way would be:
validates :myfield, :numericality => { only_integer: true }
validates :myfield, :inclusion => { :in => 1..10000 }
ActiveModel does have a built-in validation method for integers.
validates_numericality_of
Hopefully will behave how you want it to.

Resources