Railsでモデル名.newしたときのBoolean型の変換について調べた

# 前提

調査に使用したRailsとActiveModelのバージョンは以下の通り。

```ruby:rails console
irb(main):101:0> Rails.version
=> "7.0.4.2"
irb(main):102:0> ActiveModel.version
=> Gem::Version.new("7.0.4.2")
```

# 調べたことと結論

RailsでBoolean型の属性を持つモデルのインスタンスを生成するときに、以下のように適当な値を入力すると `false` になったり `true` になったりしたのでどこで定義されてるのか調べてみた。

```ruby:rails console
irb(main):001:0> Book
=> Book(id: integer, is_e_book: boolean, created_at: datetime, updated_at: datetime)
irb(main):002:0> Book.new(is_e_book: true).is_e_book
=> true
irb(main):003:0> Book.new(is_e_book: 0).is_e_book
=> false
irb(main):004:0> Book.new(is_e_book: 10).is_e_book
=> true
irb(main):005:0> Book.new(is_e_book: :true).is_e_book
=> true
irb(main):006:0> Book.new(is_e_book: "").is_e_book
=> nil
irb(main):007:0> Book.new(is_e_book: nil).is_e_book
=> nil
irb(main):008:0> Book.new(is_e_book: "FALSE").is_e_book
=> false
irb(main):009:0> Book.new(is_e_book: "False").is_e_book
=> true
```
`ActiveModel::Type::Boolean`の中には`FALSE_VALUES`が定義されている。

https://github.com/rails/rails/blob/5df0f0da7cd682e491933337dcfee937fa34dc3c/activemodel/lib/active_model/type/boolean.rb#L13-L22

```ruby:rails/activemodel/lib/active_model/type/boolean.rb#L13-L22
  FALSE_VALUES = [
    false, 0,
    "0", :"0",
    "f", :f,
    "F", :F,
    "false", :false,
    "FALSE", :FALSE,
    "off", :off,
    "OFF", :OFF,
  ].to_set.freeze
```

この中に含まれている値でBoolean値を指定した場合には`false`になる。
ただし、空文字列または`nil`を指定した場合には`nil`になる。

## 実験

### falseへの変換

```ruby:rails console
irb(main):010:0> ActiveModel::Type::Boolean::FALSE_VALUES
=> #<Set: {false, 0, "0", :"0", "f", :f, "F", :F, "false", :false, "FALSE", :FALSE, "off", :off, "OFF", :OFF}>                              
irb(main):011:1* ActiveModel::Type::Boolean::FALSE_VALUES.each do |f|
irb(main):012:1*   book = Book.new(is_e_book: f)
irb(main):013:1*   puts "#{book.object_id} is_e_book => #{book.is_e_book}"
irb(main):014:0> end
1775500 is_e_book => false
1775520 is_e_book => false                              
1775540 is_e_book => false                     
1775560 is_e_book => false                    
1775580 is_e_book => false                    
1775600 is_e_book => false                     
1775620 is_e_book => false        
1775640 is_e_book => false        
1775660 is_e_book => false        
1775680 is_e_book => false       
1775700 is_e_book => false       
1775720 is_e_book => false        
1775740 is_e_book => false       
1775760 is_e_book => false
1775780 is_e_book => false
1775800 is_e_book => false
```

[上記の実行例](#調べたことと結論)で`"False"`を指定しても`true`になってしまったのは`ActiveModel::Type::Boolean::FALSE_VALUES` に含まれていないから、ということだった。

```ruby:rails console
irb(main):008:0> Book.new(is_e_book: "FALSE").is_e_book
=> false
irb(main):009:0> Book.new(is_e_book: "False").is_e_book
=> true
```

### nilへの変換

[ActiveModel::Type::Boolean#L8-L11](https://github.com/rails/rails/blob/5df0f0da7cd682e491933337dcfee937fa34dc3c/activemodel/lib/active_model/type/boolean.rb#L8-L11) のコメントにも書かれているが、空文字列も`nil`に変換される。

```ruby:rails console
irb(main):006:0> Book.new(is_e_book: "").is_e_book
=> nil
irb(main):007:0> Book.new(is_e_book: nil).is_e_book
=> nil
```

`ActiveModel::Type::Value#cast`では`nil`以外をキャストするように定義されている。

https://github.com/rails/rails/blob/5df0f0da7cd682e491933337dcfee937fa34dc3c/activemodel/lib/active_model/type/value.rb#L55-L57

```ruby:ActiveModel::Type::Value#L55-L57
def cast(value)
    cast_value(value) unless value.nil?
end
```

また、`ActiveModel::Type::Value`を継承している`ActiveModel::Type::Boolean`では`cast_value`をオーバーライドしていて、空文字列でもnilを返すようになっていた。

https://github.com/rails/rails/blob/5df0f0da7cd682e491933337dcfee937fa34dc3c/activemodel/lib/active_model/type/boolean.rb#L37-L43

```ruby:ActiveModel::Type::Boolean#L36-L43
 private
    def cast_value(value)
      if value == ""
        nil
      else
        !FALSE_VALUES.include?(value)
      end
    end
```

よって、空文字列または`nil`を指定した場合は`nil`が返る。

```ruby:rails console
irb(main):110:0> ActiveModel::Type::Boolean.new.cast(nil)
=> nil
irb(main):111:0> ActiveModel::Type::Boolean.new.cast("")
=> nil
```

# 調査にあたって参考にした記事

[Ruby 3.2 のIRBの新機能](https://qiita.com/k0kubun/items/4600e52a1a245495dfaa#ruby-32-%E3%81%AEirb%E3%81%AE%E6%96%B0%E6%A9%9F%E8%83%BD)
[debug.gemを使ったことがない方に向けて、いくつかの機能を紹介する](https://qiita.com/yuskubo/items/111a87e2402de8e12914#next-n--step-s--continue-c)