ActiveRecord boolean validation accepting non-boolean values - ruby-on-rails

I'm trying to validate that an attribute is a boolean, i.e. true or false.
From the Rails guide I expected
validates :new_out_of_stock, inclusion: { in: [true, false] }
to work, but it accepts non-boolean values (e.g. "Hi") as valid:
irb> ip = ItemPrice.new
irb> ip.new_out_of_stock = "Hi"
=> "Hi"
irb> ip.valid?
=> true
irb> ip.errors.messages
=> {}
It correctly rejects nil and accepts true and false.
All the documentation on the Internet that I've read says that this is the correct way to validate a boolean attribute. Why doesn't it work?
Edit: Here's the class definition:
class ItemPrice < ActiveRecord::Base
belongs_to :item, counter_cache: true
validates :new_out_of_stock, inclusion: { in: [true, false] }
end

I think that non-boolean values are coerced to booleans when they're assigned to the attribute, causing them to pass the validation.
See:
irb> ip.new_out_of_stock
=> nil
irb> ip.new_out_of_stock = "hi"
=> "hi"
irb> ip.new_out_of_stock
=> false
and clearly false is a valid boolean value, so the field passes.

It's not accept non-boolean values (e.g. "Hi") as valid, you just need to do this:
irb> ip.new_out_of_stock="Hi"
=> "Hi"
irb> ip.valid?
=> false # so it's not accept "Hi"
irb> ip.errors.full_messages
=> ["New_out_of_stock is not included in the list"]
irb> ip.errors.messages
=> {:new_out_of_stock=>["is not included in the list"]}
irb> ip.errors.messages[:new_out_of_stock]
=> ["is not included in the list"]
UPDATE:
Try this in your model:
validate :require_boolean
def require_boolean
errors.add(:new_out_of_stock, "is not included in the list") unless self.new_out_of_stock.in?([true, false])
end

Try to change the syntax slightly to the following
validates :new_out_of_stock, :inclusion => {:in => [true, false]}
This has worked for me in the past

Related

Proc command is not working on checking the inclusion

I have a field called visit_time with two distinct values. They are "AM" and "PM"
I check the presence of the visit_time by the following validation syntax.
validates_presence_of :visit_time,
message: "visit time is required"
Then I need to check the inclusion validation only if the visit_time is presence, for this I am using the Proc. But it is not working.
validates :visit_time,
:inclusion => { :in => [ 'AM', 'PM'],
:message => "%{value} is not a valid time" },
:if => Proc.new { |o| o.errors.empty? }
Let me know what's wrong on it. Is Proc is not working for inclusion ??? Thanks in advance.
If you want the inclusion validation to run only if it's present, you should change the Proc to this instead:
if: Proc.new { |o| o.visit_time.present? }

Custom validation that boolean field has a boolean and not any other type

Currently, my model and validation is this:
class Suya < ActiveRecord::Base
belongs_to :vendor
validates :meat, presence: true
validates_inclusion_of :spicy, :in => [true, false]
end
The problem is that when I run this test:
test "suya is invalid if spiciness is not a boolean" do
suya = Suya.new(meat: "beef", spicy: 1)
suya1 = Suya.new(meat: "beef", spicy: "some string")
assert suya.invalid?
refute suya1.valid?
end
I get a deprecation warning that says:
DEPRECATION WARNING: You attempted to assign a value which is not
explicitly true or false to a boolean column. Currently this value
casts to false. This will change to match Ruby's semantics, and will
cast to true in Rails 5.
So I think my validation is not doing what I think it should be doing. I think my validation checks the presence of the column value and if it IS or is converted to true or false. So I think my test fixtures both convert to false and therefore pass the test which I don't want. What can I do?
You can use custom validation like:
validate :check_boolean_field
def check_boolean_field
false unless self.spicy.is_a?(Boolean)
end
Rails performs type casting any time you assign a value to an attribute. This is a convenience thing. It's not really your text case's fault, it's just how Rails works. If the attribute is a Boolean it'll convert truthy-looking values (true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON') to true and anything else to false. For example:
suya.spicy = "asdf"
suya.spicy # => false
# Likewise for other attribute types:
# Assuming Suya has an `id` attribute that is an Integer
suya.id = "asdf"
suya.id # => 0 # Because "asdf".to_i # => 0
# Assuming Suya has a `name` attribute that is a String
suya.name = 1
suya.name # => "1" # Because 1.to_s # => "1"
So this is just how rails works. In your test case your values are being typecast into their respective attributes' types via mass-assignment.
You can either test out Rails's typecasting by assigning "some value" to your booleans or you can just use more obvious boolean values like true and false in your test cases.

Rails 4 rspec test failing on numericality only_integer on boolean value?

I have in my model
validates :is_foo, presence: true, numericality: {only_integer:true, less_than_or_equal_to: 1, greater_than_or_equal_to: 0}
in my spec
describe "when is_foo is not an integer" do
before {#user.is_foo = true}
it {should_not be_valid}
end
The above test fails, but since I set is_foo to a boolean and not an integer, should the test pass?
Are booleans considered integers in ruby? Because when I did
true.to_i # error
true == 1 # false
false == 0 # false
The spec is correct. 1 is truthy (the only non-truthy values in Ruby are false and nil), but it is not a boolean. Booleans are not numeric; they are their own type. A numericality validation will validate that the type is actually numeric, and boolean is not numeric.
> true.class
=> TrueClass
> 1.class
=> Fixnum
What you probably want instead, though, is a validation that tests for a boolean value:
validates :is_foo, inclusion => {:in => [true, false]}
I guess ruby/rails substitutes 1 for true to a model class attribute when the attribute expects an integer. So the test failed because it was comparing 1 to an integer.

validate a field in function of the presence of another

I have 2 fields:
attr_accessible :in_home #=> boolean, setted at false by default
mount_uploader :carousel_picture, CarouselUploader#=>an image picture with CarrierWave + Rmagick. Nil by default
attr_accessible :carousel_picture
My question is: I try to validate the fact that when the user checks the "in_home" checkbox, he should have uploaded a "carousel_picture"
validates :in_home, :if => Proc.new { |obj| (obj.in_home && obj.carousel_picture?) == true }, :presence => {:message => "You should upload a carousel picture to set the item in the home page."},
But this validation doesn't work, I can create an object with the in_home checked and the carousel_picture empty.
When I try after in my rails console:
1.9.3p362 :001 > obj = MyObject.all.last
=> #<Gift id: 22, name: "foo", created_at: "2013-04-02 09:13:00", updated_at: "2013-04-02 09:13:00", carousel_picture: nil, in_home: true>
1.9.3p362 :002 > obj.in_home && obj.carousel_picture?
=> false
So I think there is something that I don't understand in how I do a validation of a field. Any help would be useful!
Thank you in advance
Try this,
validates_presence_of :carousel_picture, :if => lambda {|obj| obj.in_home == true}
I was trying to do something similar recently, I think the below code should work for you:
validates :carousel_picture, :presence => { :if => Proc.new { |a| a.in_home? }, message: "You should upload a carousel picture to set the item in the home page." }

In ActiveRecord, validate that an attribute is present but may be empty

I'm trying to set up my model in Rails 3.2.8 such that particular values must be present, but are allowed to be the empty string. Does anyone know how to do this?
The behavior I'm looking for looks like:
#foo.value = "this is a real value"
#foo.valid? # => true
#foo.value = nil
#foo.valid? # => false
#foo.value = ""
#foo.valid? # => true
If I use
validates :foo, :presence => true
then I get what I want for 1 and 2, but not 3. And, helpfully, :allow_blank => true is ignored when validating presence.
I also tried
validates :foo, :length => { :minimum => 0 }, :allow_nil => false
Same behavior: get what I want for 1 and 2, but not 3.
I suppose I could just mark the column as NOT NULL in the database, but then I have to deal with catching the exception; I'd really rather catch this at the validation stage.
Any ideas?
I would try something like this:
validates :foo, presence: true, unless: lambda { |f| f.foo === "" }

Resources