Short version of my question
In Rails ActiveRecord, if I have a Boolean field and I assign it something like " abc
" or 2
, then it gets immediately cast to false
. The value 1
is cast to true
, and nil
remains as nil
. Why is this the case? Where can I find Rails documentation (or Ruby documentation) that explains this behavior?
Long version of my question
I am having difficulty understanding how Rails handles attempts to assign values to a Boolean
field in a Rails model. For example, let's say I have a Website
model that has a String
field called :domain
and a Boolean
field called :can_ssl
.
My migration looks like this:
class CreateWebsites < ActiveRecord::Migration
def change
create_table :websites do |t|
t.string :domain
t.boolean :can_ssl, :default => false
t.timestamps
end
end
end
In my model file, I add some validation rules, so it looks like this:
class Website < ActiveRecord::Base
validates :domain, :presence => true
validates :can_ssl, :inclusion => { :in => [true, false] }
end
Simple enough. Based on what I've done, I'm expecting that :can_ssl
can only be set to the values true
or false
, and nothing else. Anything else would result in valid?
being false
.
But once I start playing around in the console, I notice that, as early as the actual assignment statement, the value I provide is being recast to true
or false
(or nil
). What are the rules governing how a value gets cast as a Boolean
?
Examples from the console:
w = Website.new
w.domain = 'stackoverflow.com'
w.can_ssl = true
w.can_ssl # => true
w.valid? # => true
w.can_ssl = nil
w.can_ssl # => nil
w.valid? # => false (so far so good)
w.can_ssl = 'abc'
w.can_ssl # => false (How did 'abc' become the value false?)
w.valid? # => true
w.can_ssl = 1
w.can_ssl # => true (I guess it makes sense that 1 casts to true)
w.valid? # => true
w.can_ssl = 2
w.can_ssl # => false (But anything other than 1 casts to false?)
w.valid? # => true
So, based on what I've done so far, I think I can conclude the following:
1
or true
to a Boolean
field, the value will immediately get cast as true
and then assigned. nil
to a Boolean
field, the field is actually assigned nil
. String
or any number that is not 1
), then the value will immediately get cast as false
. Am I understanding that correctly? Am I missing anything?
I'm having difficulty finding documentation the Boolean
field type in Rails which can give me clarification on this.
This is done in the bowels of ActiveRecord: specifically with
ActiveRecord::ConnectionAdapters::Column.value_to_boolean
at least in my version of rails (it may be somewhere slightly different in more recent versions).
Here's the source in my version
# File activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb, line 144
144: def value_to_boolean(value)
145: if value.is_a?(String) && value.blank?
146: nil
147: else
148: TRUE_VALUES.include?(value)
149: end
150: end
where TRUE_VALUES is defined as
#activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb:10:
TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE'].to_set
1, 0, "f" and "t" are there because of popular DBMS's like MySql & PostgreSql, which store bools as 0/1 and "f"/"t" respectively.
It's worth noting the difference between this and what passes an "if" test in Ruby/Rails, ie values that are "truthy" or "falsy" ("falsy" values will fail an if test, "truthy" values will pass it).
In Ruby, nil
and false
are "falsy" and literally anything else (including 0, empty arrays, empty strings, empty hashes etc) is "truthy". So, there's a massive disparity between what is deemed truthy/falsy in ruby and what is saved as true/false to a boolean database column.
The behavior of boolean columns changed with two commits:
The new rule is very simple. See the code below:
module ActiveRecord
module Type
class Boolean < Value # :nodoc:
def type
:boolean
end
private
def cast_value(value)
if value == ''
nil
else
!ConnectionAdapters::Column::FALSE_VALUES.include?(value)
end
end
end
end
end
The constant ConnectionAdapters::Column::FALSE_VALUES
is defined as follows:
[false, 0, '0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF'].to_set
If a value is not an empty string and is not among them, it will be cast to true
.
This change will become effective on the Rails 5.0.
This is a Ruby thing rather than a Rails thing. Boolean is "truthy". There's a pretty useful article on it here. https://gist.github.com/jfarmer/2647362
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.