エラーメッセージの国際化(3)

2009/01/17

前回に引き続き、サンプルアプリケーション asagao のエラーメッセージを国際化していきましょう。

member.rb に次のような記述があります。

  # 値の検証
  def validate
    if member_number and !Member.positive_integer?(member_number)
      errors.add(:member_number, 'は1以上の整数で記入してください。')
    end
    
    if !email.blank? and !email.well_formed_as_email_address?
      errors.add(:email, 'の書式が正しくありません。')
    end
    
    if birthday and !birthday.is_valid?
      errors.add(:birthday, 'が存在しない日付です。')
    end
    
    if member_image and member_image.data.size > 65535
      errors.add(:uploaded_image,
        'のサイズが大き過ぎます(最大64KB)。')
    end
  end

Errors#add を使ってエラーを登録する場合に、どのようにエラーメッセージを指定するのがスマートでしょうか。

これが、今回のテーマです。


Errors#add の API は Rails 2.2 で少し変更になりました。

Rails 2.1.2 のソースコードが、これです。

    def add(attribute, msg = @@default_error_messages[:invalid])
      @errors[attribute.to_s] = [] if @errors[attribute.to_s].nil?
      @errors[attribute.to_s] << msg
    end

第 2 引数にはエラーメッセージを文字列で指定しますが、省略すると @@default_error_messages[:invalid] の値(上書きしていなければ 'is invalid')になりました。

Rails 2.2.2 ではこうなりました。

    def add(attribute, message = nil, options = {})
      message ||= :invalid
      message = generate_message(attribute, message, options) if message.is_a?(Symbol)
      @errors[attribute.to_s] ||= []
      @errors[attribute.to_s] << message
    end

第 2 引数にシンボルを指定すると、翻訳ファイルによって変換されます。


まず、先ほどの member.rb を次のように修正します。

  # 値の検証
  def validate
    if member_number and !Member.positive_integer?(member_number)
      errors.add(:member_number, :positive_integer)
    end
    
    if !email.blank? and !email.well_formed_as_email_address?
      errors.add(:email, :invalid_format)
    end
    
    if birthday and !birthday.is_valid?
      errors.add(:birthday, :invalid_date)
    end
    
    if member_image and member_image.data.size > 65535
      errors.add(:uploaded_image, :too_large)
    end
  end

次に、config/locales/activerecord_en.yml を次のように修正します。

en:
  activerecord:
    attributes:
      member:
        member_number: Member number
        player: Player
        family_name: Family name
        given_name: Given name
        furigana: Furigana
        email: E-mail address
        phone: Phone number
        birthday: Birthday
        sex: Sex
        remarks: Remarks
        login_name: Login name
        password: Password
        administrator: Administrator
        uploaded_image: Uploaded Image
      blog_entry:
        heading: Heading
        body1: Body 1
        body2: Body 2
        note: Note
        blog_date: Date
      group:
        name: Name
        remarks: Remarks
      article:
        place: Place
        heading: Heading
        body: Body
        note: Note
        released_at: Released at
        expired_at: Expired at
    errors:
      messages:
        positive_integer: "must be a positive integer"
        invalid_format: "has an invalid format"
        invalid_date: "is an invalid date"
      models:
        member:
          attributes:
            uploaded_image:
              too_large: "is too large (maximum is 64KB)"

続いて、config/locales/activerecord_ja.yml を次のように修正します。

ja:
  activerecord:
    attributes:
      member:
        member_number: 背番号
        player: 選手登録
        family_name: 名前(姓)
        given_name: 名前(名)
        furigana: ふりがな
        email: メールアドレス
        phone: 電話番号
        birthday: 生年月日
        sex: 性別
        remarks: 備考
        login_name: ログイン名
        password: パスワード
        administrator: サイト管理者
        uploaded_image: 画像'
      blog_entry:
        heading: 見出し
        body1: 本文
        body2: 続き
        note: 注
        blog_date: 日付
      group:
        name: 名称
        remarks: 備考
      article:
        place: 掲載場所
        heading: 見出し
        body: 本文
        note: 注
        released_at: 掲載開始日時
        expired_at: 掲載終了日時
    errors:
      messages:
        inclusion: "は一覧にありません。"
        exclusion: "は予約されています。"
        invalid: "が不正な値です。"
        confirmation: "が一致しません。"
        accepted: "を承諾してください。"
        empty: "が記入されていません。"
        blank: "が記入されていません。"
        too_long: "は{{count}}文字以内で記入してください。"
        too_short: "は{{count}}文字以上で記入してください。"
        wrong_length: "は{{count}}文字で記入してください。"
        taken: "はすでに使用されています。"
        not_a_number: "は数値で入力してください。"
        greater_than: "は{{count}}より大きい値を指定してください。"
        greater_than_or_equal_to: "は{{count}}以上の値を指定してください。"
        equal_to: "は{{count}}を指定してください。"
        less_than: "は{{count}}より小さい値を指定してください。"
        less_than_or_equal_to: "は{{count}}以下の値を指定してください。"
        odd: "は奇数を指定してください。"
        even: "は偶数を指定してください。"
        positive_integer: "は1以上の整数で記入してください。"
        invalid_format: "の書式が正しくありません。"
        invalid_date: "が存在しない日付です。"
      models:
        member:
          taken: が他の方と重なっています。
          attributes:
            password:
              confirmation: が確認用パスワードと一致しません。
            uploaded_image:
              too_large: "のサイズが大き過ぎます(最大64KB)。"

Errors#add だけでなく、validates_format_of などのメソッドでも :message オプションにシンボルが指定できます。

member.rb に次のような記述があります。

  validates_format_of :phone,
    :with => /^\d+(-\d+)*$/,
    :message => 'の書式が不正です。',
    :if => Proc.new {|member| !member.phone.blank? }

Rails 2.2 では次のように書けます。

  validates_format_of :phone,
    :with => /^\d+(-\d+)*$/,
    :message => :invalid_format,
    :if => Proc.new {|member| !member.phone.blank? }

rake test でテストが通ることを確認し、ブラウザで目視によるテストを行います。

すると、ここでバグが発見されました。

ログイン中のユーザーが自分自身のアカウントの情報を不正な値に変更しようとすると、NoMethodError が発生してしまうのです。

機能テストがカバーしていない部分が存在したため、スクリプトによるテストでは発見できなかったバグです。

このような場合は、すぐに直すのではなく、バグが再現できるように機能テストを修正してから、アプリケーション本体を修正するのが定石です。

test/functional/accounts_controller_test.rb を開いてください。

update アクションをテストしているコードを注意深く見ていくと、何と test_update2 が 2 つ存在しています!

つまり 1 つ目の test_update2 は上書きされてしまって実行されていなかったのです!

誠にお恥ずかしい限りです。

2 つ目の test_update2test_update3 に修正して機能テストを実行すると、正しくテストが失敗します。

その上で、app/views/accounts/_errors.rhtml の 13 行目を次のように修正します。

<span class="attributeWithErrors"><%= h(Member.human_attribute_name(attrib.to_s)) %></span> <%= h(msg) %>

attribattrib.to_s に変えてください。

今回の教訓は「テストを書け。されど過信するな。」ということになりましょうか。