LoginSignup
27
17

More than 3 years have passed since last update.

ActiveRecord::Type::Boolean#cast の返り値が nil となるケースについて

Last updated at Posted at 2019-12-06

API などでクライアント側から渡ってきた "true""false"10 などの値を、 Ruby の truefalse として扱いたいときがある。
Ruby では falsenil 以外の値はすべて true として扱われるため、!! を先頭につけたとしても実現できない。

!!nil #=> false
!!0 #=> true
!!'false' #=> true

そのようなときには ActiveRecord::Type::Boolean#cast を使う。

ActiveRecord::Type::Boolean.new.cast "true" #=> true
ActiveRecord::Type::Boolean.new.cast 0      #=> false

しかしながら、どんな値でも truefalse にしてくれるわけではない。

ActiveRecord::Type::Boolean.new.cast nil #=> nil
ActiveRecord::Type::Boolean.new.cast ""  #=> nil

結論を先に書くと、

  • nil になるもの => nil, ""
  • false になるもの => false, 0, "0", :"0", "f", :f, "F", :F, "false", :false, "FALSE", :FALSE, "off", :off, "OFF", :OFF
  • true になるもの => 上記以外すべて

となる。ロジックの定義箇所は以下を参照。

定義箇所

rails/activerecord/lib/active_record/type.rb を見てみると、実態は rails/activemodel/lib/active_model/type/boolean.rb に定義されていることがわかる。

rails/activerecord/lib/active_record/type.rb
#...

module ActiveRecord
  module Type
    @registry = AdapterSpecificRegistry.new

    class << self
      attr_accessor :registry # :nodoc:

      #...

      def register(type_name, klass = nil, **options, &block)
        registry.register(type_name, klass, **options, &block)
      end

      #...
    end

    #...    

    Boolean = ActiveModel::Type::Boolean

    #...

    register(:boolean, Type::Boolean, override: false)

    #...
  end
end

rails/activemodel/lib/active_model/type/boolean.rb を見てみると、真偽値の判定ロジックが書いてある。

rails/activemodel/lib/active_model/type/boolean.rb
module ActiveModel
  module Type
    #...

    class Boolean < Value
      FALSE_VALUES = [
        false, 0,
        "0", :"0",
        "f", :f,
        "F", :F,
        "false", :false,
        "FALSE", :FALSE,
        "off", :off,
        "OFF", :OFF,
      ].to_set.freeze

      #...

      private
        def cast_value(value)
          if value == ""
            nil
          else
            !FALSE_VALUES.include?(value)
          end
        end
    end
  end
end
rails/activemodel/lib/active_model/type/value.rb
module ActiveModel
  module Type
    class Value
      #...

      def cast(value)
        cast_value(value) unless value.nil?
      end

      #...
    end
  end
end

結論

処理の流れをまとめると以下のようになる。

  1. ActiveRecord::Type::Boolean.new.cast(value) が呼ばれる
  2. ActiveModel::Type::Boolean.new.cast(value) が呼ばれる
  3. valuenil でなければ ActiveModel::Type::Boolean.new.cast_value(value) が呼ばれる
  4. value が空文字なら nilFALSE_VALUES に含まれている値なら false、そうでなければ true を返す

よって、冒頭で触れたような結果になる。

27
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
27
17