Secret Ninja Blog

Support Engineering Manager してます

Rails4 -> 5のtype_cast_from_userとcastでinvalidな値が来た時の挙動の違い

リクエスト中のパラメータにて、true/falseの値が来ることを期待されているデータがある場合に、Booleanへのキャストをしたときの Rails4/5挙動の差異があるのを知ったので、後学のためにメモ。

Rails5から提供されているActiveRecord::Type::Boolean.new.castだと下記。

irb(main):001:0> require 'active_record'
=> true
irb(main):002:0> ActiveRecord.version
=> #<Gem::Version "5.2.0">
irb(main):003:0> ActiveRecord::Type::Boolean.new.cast(false)
=> false
irb(main):004:0> ActiveRecord::Type::Boolean.new.cast('false')
=> false
irb(main):005:0> ActiveRecord::Type::Boolean.new.cast(true)
=> true
irb(main):006:0> ActiveRecord::Type::Boolean.new.cast('true')
=> true
irb(main):007:0> ActiveRecord::Type::Boolean.new.cast('a')
=> true
irb(main):008:0> ActiveRecord::Type::Boolean.new.cast('\t')
=> true

Rails4で提供されているActiveRecord::Type::Boolean.new.type_cast_from_userだと、true/falseでもないものが来た時はfalseになる。 Rails5からはtrueになる。

irb(main):001:0>  require 'active_record'
=> true
irb(main):002:0> ActiveRecord.version
=> #<Gem::Version "4.2.7.1">
irb(main):003:0> require 'active_support/core_ext'
=> true

irb(main):005:0> ActiveRecord::Type::Boolean.new.type_cast_from_user(false)
=> false
irb(main):006:0> ActiveRecord::Type::Boolean.new.type_cast_from_user('false')
=> false
irb(main):007:0> ActiveRecord::Type::Boolean.new.type_cast_from_user(true)
=> true
irb(main):008:0> ActiveRecord::Type::Boolean.new.type_cast_from_user('true')
=> true
irb(main):009:0> ActiveRecord::Type::Boolean.new.type_cast_from_user('a')
DEPRECATION WARNING: You attempted to assign a value which is not explicitly `true` or `false` ("a") to a boolean column. Currently this value casts to `false`. This will change to match Ruby's semantics, and will cast to `true` in Rails 5. If you would like to maintain the current behavior, you should explicitly handle the values you would like cast to `false`. (called from irb_binding at (irb):9)
=> false
irb(main):010:0> ActiveRecord::Type::Boolean.new.type_cast_from_user('\t')
DEPRECATION WARNING: You attempted to assign a value which is not explicitly `true` or `false` ("\\t") to a boolean column. Currently this value casts to `false`. This will change to match Ruby's semantics, and will cast to `true` in Rails 5. If you would like to maintain the current behavior, you should explicitly handle the values you would like cast to `false`. (called from irb_binding at (irb):10)
=> false

というか、ちゃんとWARNINGもでるのでえらいなあと思う次第です。

上記で何が困るかというと、パラメータのデフォルトの値がtrueなんだけど、実はユーザがinvalidな値を送って来ていた場合に、ユーザから見た時にRails4 -> 5で意図せずfalse -> true変わってしまうように見えてしまう、ということが起こり得る、という話。

こういう挙動の違いを見てると、デベロッパーの人は大変やな・・・と思うので、感謝の気持ちが絶えないですね。