API などでクライアント側から渡ってきた "true"、"false"、1、0 などの値を、 Ruby の true や false として扱いたいときがある。
Ruby では false と nil 以外の値はすべて 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
しかしながら、どんな値でも true か false にしてくれるわけではない。
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 に定義されていることがわかる。
# ...
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 を見てみると、真偽値の判定ロジックが書いてある。
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
module ActiveModel
module Type
class Value
#...
def cast(value)
cast_value(value) unless value.nil?
end
#...
end
end
end
結論
処理の流れをまとめると以下のようになる。
-
ActiveRecord::Type::Boolean.new.cast(value)が呼ばれる -
ActiveModel::Type::Boolean.new.cast(value)が呼ばれる -
valueがnilでなければActiveModel::Type::Boolean.new.cast_value(value)が呼ばれる -
valueが空文字ならnil、FALSE_VALUESに含まれている値ならfalse、そうでなければtrueを返す
よって、冒頭で触れたような結果になる。