2010/10/31

Rails3 にて ActiveRecord で数値列の形式バリデーションが機能しない

今日 Rails3 でハマったところ。

環境
・Ruby : 1.8.7
・Rails : 3.0.0
・DB : PostgreSQL
・OS : Ubuntu 10.04

手っ取り早く試すために scaffold で
% rails g scaffold products name:string price:integer

こんなマイグレーションになる。
class CreateProducts < ActiveRecord::Migration
  def self.up
    create_table :products do |t|
      t.string :name
      t.integer :price
 
      t.timestamps
    end
  end

  def self.down
    drop_table :products
  end
end
バリデーションを設定する
class Product < ActiveRecord::Base
  validates :price, :presence => true, :format => { :with => /\d+/ } # 数値だけ入力可
end
これで、ブラウザから http://localhost:3000/products にアクセスし、新規にデータを作成する。 その際、name : 'test', price : aaaa と不正な値を入力すると、当然 Invalid price にな・・・・・・らない!!! なんと price が 0 で登録が成功してしまう。 実際 rails c として下記を実行するとよくわかる。
irb(main):001:0> p = Product.new
=> #<Product id: nil, name: nil, price: nil, created_at: nil, updated_at: nil>
irb(main):002:0> p.price = 'aaaa'
=> "aaaa"
irb(main):003:0> puts p.price
0
=> nil
ちなみに 100aaa を price に代入すると price は 100 になる。数値じゃない文字は無視してるのか? フォームで数値の形式バリデーションを行いたいなら ActiveModel を使うしかないのか?
class ProductForm
  extend ActiveModel::Naming
  include ActiveModel::Conversion
  include ActiveModel::Validations

  attr_accessor :name, :price
  validates :price, :presence => true, :format => { :with => /\d+/ }
end
こんな感じのフォーム用モデルを作成すれば、price に aaaa を入力して検証するとエラーになる。

というわけで、ちゃんとした web アプリを作るなら、scaffold のように ActiveRecord のインスタンスを form_for に使うわけにはいかないだろう。
ActiveModel で便利になったんじゃなくて、ActiveModel が必須になっただけなんじゃないだろうか。

しばらく Rails に触ってなかったので、これが Rails3 からの挙動かどうかはわからない。
確か 2.0.2 とかのころは違ったと思うんだけど。
DB にはいろいろな型を利用できるが、Web から入力されたデータは全て文字列。
当然どこかで変換する必要はある。
そして「正規表現は文字列を対象にするものなので、数値列の検証に利用すべきでない」というのも理解できる。
どこまで自動的に内部で変換するのか?いつ変換するのか?あたりの問題なのかな?
ともかく文字列を数値に変換してくれるのはありがたいが、数値にならない文字を切り捨てるのはやりすぎだと思う。
これは ActiveRecord がより DB のレイヤに近づいたということなんだろうか。

[追記]
無視してるというか to_i の仕様ですね。

0 件のコメント :

コメントを投稿