LoginSignup
0
1

More than 5 years have passed since last update.

Rails4.1から4.2へのアップデートでハマったActiveRecordの謎挙動

Posted at

一言でまとめると、integerなどの数値系のカラムに空文字を代入しないこと
以下、平成も終わるというのに未だにRails4.1で疲弊している男のつぶやきです。


こんな感じの商品テーブルがあるとします。

create_table :products do |t|
  t.string :name, null: false, default: ''
  t.integer :price, null: false, default: 0
  t.timestamps null: false
end

default: 0 が設定されているのでnewするとpriceは0です。

product = Product.new
product.price # => 0

ここで空文字を代入してみます.

product = Product.new
product.price = ''
product.price # => nil

ふむふむ、空文字はnilとして評価されて、デフォルト値の0から変更されている(ように見えます)。
null: falseなのでこのままでは保存できないのかなと思いきや...

product.price # => nil
product.changes #=> {} ???
product.save #=> true ????
product.price # => 0 ?????

一瞬priceがnilになっていたのに、changesが更新されておらず、priceはデフォルトの0のままで保存されるというのが、今回紹介したい謎挙動です。
Rails4.2にアップデートした時に、このバグのような挙動を前提にしていたコードが動かなくなってハマりました。

Rails4.2以降だと、普通にchangesが更新されます。

product = Product.new
product.price = ''
product.price # => nil ここまでは同じ
product.changes # => {"price"=>[0, nil]} ちゃんと0→nilに更新されている
product.save # => null: falseなのでちゃんとraiseする

空文字以外の文字列はto_iされた結果が入るので問題無いようでした。

product = Product.new
product.price = '1'
product.price # => 1
product.changes # => {"price"=>[0, 1]}

integerだけでなく、decimalやfloatでも同様の挙動を示します。
今回はwrite_attributeをoverrideして、数値系カラムに空文字を代入している箇所をログ出力し、コードを修正してから再度アップデートにチャレンジすることになりました。

class ActiveRecord::Base
  def write_attribute(attr_name, value)
    column = self.class.columns.find { |c| c.name.to_s == attr_name.to_s }
    if column && column.type.in?(%i[integer decimal float]) && value == ''
      Rails.logger.warn "Deprecated: assigning blank string into #{column.type} column. #{caller.first}"
    end
    super
  end
end

callerメソッドって初めて使いましたが便利ですね。

5.2への道のりは長い...

0
1
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
0
1