This question already has answers here:
Rails: Validating inclusion of a boolean fails tests
(2 answers)
Closed 8 years ago.
I need to validate boolean fields for a Rails ActiveRecord object. I used the common solution to this problem:
validates :boolean_field, :inclusion => { :in => [true, false] }
But although this solutions solves the problem with validating false values, it still allows to save strings to the boolean_field:
my_object.boolean_field = "string"
my_object.save # => true
my_object.boolean_field # => false
Why does this happen if I specified the inclusion to [true, false]? And more importantly, how do I validate only true and false values?
P.S. If it is important to the question, I'm using PostgreSQL.
You can validate by checking datatype, you can use is_a? and pass class name of the datatype as a parameter
pry(main)> a = "aaaaa"
=> "aaaaa"
pry(main)> a.is_a?(Boolean)
=> false
Since it was a string it returned false, now try for boolean
pry(main)> b = true
=> true
pry(main)> b.is_a?(Boolean)
=> true
Add a custom validation by using this logic
validate :check_boolean_field
def check_boolean_field
errors.add(...) unless boolean_field.is_a?(Boolean)
end
Related
I have a model wish contains the bellow method called by before_validation :
def set_to_false
self.confirme ||= false
self.deny ||= false
self.favoris ||= false
self.code_valid ||= false
end
When I run my tests, I got the deprecation message
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. If you would like to maintain the current
behavior, you should explicitly handle the values you would like cast
to false. (called from cast_value at
./Ruby2.1.0/lib/ruby/gems/2.1.0/gems/activerecord-4.2.1/lib/active_record/type/boolean.rb:17)
I understand I have to cast but I couldn't find a simple and smart way to do it. Any help to remove this deprecation would be great.
Here's a simple booleanification trick that I use often, double negation:
before_validation :booleanify
def booleanify
self.confirm = !!confirm
self.deny = !!deny
...
end
In case you are not familiar with this trick, it'll convert all values to their boolean equivalents, according to ruby rules (nil and false become false, everything else becomes true)
'foo' # => "foo"
!'foo' # => false
!!'foo' # => true
!nil # => true
!!nil # => false
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.
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 === "" }
I'm using Shoulda and Rspec for testing.
When I try this in my test spec it keeps passing when I haven't done the validations in the model:
it { should ensure_inclusion_of(:private).in_array(%w[true false]) }
The attribute is a boolean that is either true or false:
validates_inclusion_of :private, :in => [true, false]
How would I write this correctly?
True and False are not strings so don't use %w.
it { should ensure_inclusion_of(:private).in_array([true, false]) }
Update - 10th Apr 2014
This validation will not work in current versions of Shoulda and, as per this recent commit, it will not be fixed but will instead raise an exception.
As any value assigned to a boolean field will be cast to either true (set by true, 1, '1', 't', 'T', 'true', 'TRUE') or false (set by anything else) my preferred approach for testing a boolean field is as follows:
For a boolean that allows nulls in the database - no test is required, any possible value will be valid
For a boolean that does not allow nulls in the database - use it { should_not allow_value(nil).for(:field) which will pass when the validates :field, inclusion: { in: [true,false] } is set on the model
Does rails have a validator like validates_numericality_of for boolean or do I need to roll my own?
Since Rails 3, you can do:
validates :field, inclusion: { in: [ true, false ] }
I believe for a boolean field you will need to do something like:
validates_inclusion_of :field_name, :in => [true, false]
From an older version of the API: "This is due to the way Object#blank? handles boolean values. false.blank? # => true"
I'm not sure if this will still be fine for Rails 3 though, hope that helped!
When I apply this, I get:
Warning from shoulda-matchers:
You are using validate_inclusion_of to assert that a boolean column
allows boolean values and disallows non-boolean ones. Be aware that it
is not possible to fully test this, as boolean columns will
automatically convert non-boolean values to boolean ones. Hence, you
should consider removing this test.
You can use the shorter version:
validates :field, inclusion: [true, false]
Extra thought. When dealing with enums, I like to use a constant too:
KINDS = %w(opening appointment).freeze
enum kind: KINDS
validates :kind, inclusion: KINDS
Answer according to Rails Docs 5.2.3
This helper (presence) validates that the specified attributes are not empty. It uses the blank? method to check if the value is either nil or a blank string, that is, a string that is either empty or consists of whitespace.
Since false.blank? is true, if you want to validate the presence of a boolean field you should use one of the following validations:
validates :boolean_field_name, inclusion: { in: [true, false] }