事象
以下の単体テストが失敗してしまう。
it 'priceが半角数字でなければ保存できないこと' do
@item.price = '1000'
@item.valid?
expect(@item.errors.full_messages).to include('Price Half-width characters.')
end
結論
以下2点を実施することで解決
①price_before_type_castに対してバリデーションを設定
②price_before_type_castがpriceとして、エラーメッセージに表示されるように設定
price_before_type_castとは、暗黙的型変換が行われる前のpriceの値が格納された項目です。
ActiveRecordには「カラム名_before_type_cast」という項目が標準で用意されております。
つまり現在、priceには数値「0」、
price_before_type_castには文字列「1000」が格納されていることになります。
本題に戻ります
①price_before_type_castに対してバリデーションを設定
class Item < ApplicationRecord
# 価格は半角数字のみ
validates :price_before_type_cast,
format: { with: /\A[0-9]+\z/, message: 'Half-width characters.' }
end
しかし、このままではエラーメッセージが「Price before type cast Half-width characters.」と表示されてしまい、単体テストはまたエラーとなります。Webページにも同じエラーメッセージが表示されてしまうため、困ります。
そこで②を実施します。
②price_before_type_castがpriceとして、エラーメッセージに表示されるように設定
module アプリ名
class Application < Rails::Application
# 以下2行を追記
config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.yml').to_s]
config.active_model.i18n_customize_full_message = true
end
end
en:
activerecord:
attributes:
item:
price_before_type_cast: Price
これでprice_before_type_castのバリデーションエラーはpriceとして表示されるようになります。
単体テストも無事に通りました🎉
#原因
integer型の項目「price」への文字列「1000」代入時、
暗黙的型変換が行われ、priceには数値「0」が代入されたため。
↓暗黙的型変換が行われる様子をコンソールで確認↓
irb(main):002:0> @item.price = '1000'
=> "1000"
irb(main):003:0> @item.price
=> 0
文字列「1000」を代入した後のpriceの値は数値「0」となります。
これはRails 5から追加された「ActiveRecord Attributes API」という機能によるものです。
暗黙的型変換は裏で以下のようなセッターが動いているイメージです。
to_iしているところがポイントです。
# セッター
def price=(price)
@price = price.to_i
end
【参考リンク】
「ActiveRecord Attributes API」の仕組みについては、以下の記事を参考にさせていただきました。
https://www.wantedly.com/companies/wantedly/post_articles/31132