Validation not working on Ranges? - ruby-on-rails

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

Related

How can I apply certain validations to objects created at a specific time?

I have the following validation in my model:
validates :price, :numericality => { :greater_than => 0 }, :unless => :free?
How can I only apply this validation to objects created after the June 6, 2014?
The object has a created_at attribute that's formatted like so: 2014-06-19 17:44:27
You can use a lambda in the unless/if option of the validates:
validates :price, :numericality => { :greater_than => 0 }, if: -> { self.created_at > Date.parse('2014-06-19').beginning_of_day }
Also, make sure that the TimeZone is properly handled.

ActiveRecord validation for nil

I am trying to write an active record validation that allows any string but does not allow nil.
The problem with validates_presences_of is that it returns false for "" or " " which I want to consider valid.
I have also tried to do validates_length_of :foo, :minimum => 0 which did not work
I have also tried t o do validates_length_of :foo, :minimum => 0, :unless => :nil? which also did not work. Both of these allowed for nil values to be set and the validation still returns true.
Am i missing something here? I feel like it shouldnt be this hard to simply validate that the element is not nil.
validate :blank_but_not_nil
def blank_but_not_nil
if self.foo.nil?
errors.add :foo, 'cannot be nil'
end
end
Can you try:
validates_length_of :foo, :minimum => 0, :allow_nil => false
For example:
User < ActiveRecord::Base
validates_length_of :name, :minimum => 0, :allow_nil => false
end
> u=User.new
> u.valid? #=> false #u.name is nil
> u.name=""
> u.valid? #=> true

validates_presence_of if condition on rails 3.2 and mongoid + simple_form

I want validate presence of these 2 attributes :shipping_cost and :shipping_cost_anywhere if the attribute :shipping is equal to true. and If
I have this in my model but not working fine for me:
validates_presence_of :shipping_cost, :shipping_cost_anywhere, :allow_blank => "true" if :shipping == "true"
this is my :shipping attribute:
field :shipping, :type => Boolean, :default => "false"
How can I do it?
Thank you!
Edited.
I'm using mongoid and simple_form gems
validates_presence_of :shipping_costs_anywhere, :if => :should_be_filled_in?
def should_be_filled_in?
shipping_costs_anywhere == "value"
end
The method will return true or false when it's called in the statement.
No need to put colon in front of shipping_costs_anywhere.
The fix for me to this question is the next code:
validates :shipping_cost, :shipping_cost_anywhere, :presence => true, :if => :shipping?
Thank you to all for your help but any answer has worked for me. thanks!
Stumbled across this today and thought I'd freshen the answer. As others mentioned, you can put the logic in a function. However, you can also just throw it in a proc.
validates_presence_of :shipping_costs_anywhere, :if => Proc.new { |o|
o.shipping_costs_anywhere == "value"}
http://guides.rubyonrails.org/active_record_validations.html#using-a-symbol-with-if-and-unless
The validates is now preferred over validates_presences_of etc. As hyperjas mentioned you can do this:
validates :shipping_cost,
:shipping_cost_anywhere,
:presence => true, :if => :shipping?
However, that conditionalizes the entire validation for both :shipping_cost and :shipping_cost_anywhere. For better maintainability, I prefer a separate validate declaration for each attribute.
More importantly, you will likely run into situations where you have multiple validations with different conditions (like one for presence and another for length, format or value). You can do that like this:
validates :shipping_cost,
presence: { if: :shipping? },
numericality: { greater_than: 100, if: :heavy? }
You can also let rails evaluate a ruby string.
validates :shipping_cost,
presence: { if: "shipping?" },
numericality: { greater_than: 100, if: "shipping? and heavy?" }
And finally, optionally add separate custom messages:
validates :shipping_cost,
presence: { if: "shipping?", message: 'You forgot the shipping cost.' },
numericality: { greater_than: 100, if: "shipping? and heavy?", message: 'Shipping heavy items is $100 minimum.' }
And so on. Hope that helps.
I can't test it, but I think the syntax is more like:
validates_presence_of :shipping_cost, :shipping_cost_anywhere, :allow_blank => "true", :if => "shipping.nil?"
See:
http://guides.rubyonrails.org/active_record_validations_callbacks.html#conditional-validation
Here is my code working for me.Call method on if condition rather than comparing
validates :prefix, :allow_blank => true, :uniqueness => { :case_sensitive => true } ,:if => :trunk_group_is_originating?
def trunk_group_is_originating?
if self.direction == "originating"
true
else
false
end
end
Hope it helps you.......

working with hash keys and values

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.

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