简体   繁体   中英

How are values cast when assigning to a Boolean field in Rails ActiveRecord?

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:

  • When assigning the value 1 or true to a Boolean field, the value will immediately get cast as true and then assigned.
  • When assigning nil to a Boolean field, the field is actually assigned nil .
  • When assigning anything else (such as a 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM