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
Related
I have one model validation like below
validates :value, presence: true, allow_blank: false, uniqueness: { scope: [:account_id, :provider] }
I want to add one more condition of case_sensitive inside uniqueness like below
validates :value, presence: true, allow_blank: false, uniqueness: { scope: [:account_id, :provider], case_sensitive: :is_email? }
def is_email?
provider != email
end
In short, it should not validate case_sensitive when email provider is not email, But currently, it's not working it is expecting true or false only not any method or any conditions.
How can I achieve this in rails? I already wrote custom validation because it was not working.
UPDATE
if I add another validation like below
validates_uniqueness_of :value, case_sensitive: false, if: -> { provider == 'email' }
It's giving me same error for twice :value=>["has already been taken", "has already been taken"]
In the specific case of case_sensitive, the value passed to the option will always be compared against its truthy value.
As you can see in the class UniquenessValidator, when the relation is built, it uses the options passed to check if the value of case_sensitive is truthy (not false nor nil), if so, it takes the elsif branch of the condition:
def build_relation(klass, attribute, value)
...
if !options.key?(:case_sensitive) || bind.nil?
klass.connection.default_uniqueness_comparison(attr, bind, klass)
elsif options[:case_sensitive] # <--------------------------------- sadly, this returns true for :is_email?
klass.connection.case_sensitive_comparison(attr, bind)
else
# will use SQL LOWER function before comparison, unless it detects a case insensitive collation
klass.connection.case_insensitive_comparison(attr, bind)
end
...
end
As you're passing the method name is_email? to case_sensitive, which is in fact a symbol, the condition takes that branch.
tl;dr;. You must always use true or false with case_sensitive.
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.
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
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.
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] }