基礎Ruby on Rails Chapter7 バリデーションと国際化

前 [基礎Ruby on Rails Chapter6 レコードの作成、更新、削除](https://qiita.com/tseno/items/5b3f7ea85e38b115ae30)
次 [基礎Ruby on Rails Chapter8 単数リソース](https://qiita.com/tseno/items/9633572a7e0f38d1ca56)

## バリデーション

### バリデーションとエラー情報

- saveメソッドを呼び出すと、バリデーションを実行する。
- Memberモデルに、validatesメソッドを加えてみる。

```rb
class Member < ApplicationRecord
  validates :number, presence: true
```

- member.number=nilにして、保存してみると、saveメソッドがfalseを返している。
- `member.errors.messages`で、エラーの中身が見ることができる。`{:number=>["can't be blank"]}`
- `member.errors.empty?`で、エラーオブジェクトの有無が確認できる。
- `member.errors[:number]`で、属性ごとのエラー情報を取り出すことができる。
- `member.valid?`は、エラーなし？という意味で、エラーがある場合、falseを返す。
- `member.invalid?`は、エラーあり？という意味で、エラーがある場合、trueを返す。

```
irb(main):001:0> member = Member.first
  Member Load (0.4ms)  SELECT  "members".* FROM "members" ORDER BY "members"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<Member id: 1, number: 10, name: "Taro", full_name: "佐藤 太郎", email: "Taro@example.com", birthday: "1981-12-01", sex: 1, administrator: true, created_at: "2018-09-21 14:01:43", updated_at: "2018-09-21 14:01:43">
irb(main):002:0> member.number = nil
=> nil
irb(main):003:0> member.save
   (0.0ms)  begin transaction
   (0.1ms)  rollback transaction
=> false
irb(main):004:0> member.errors.messages
=> {:number=>["can't be blank"]}
irb(main):005:0> member.errors.empty?
=> false
irb(main):006:0> member.errors[:number]
=> ["can't be blank"]
irb(main):007:0>
```

### validatesメソッドの書き方

- validatesメソッドの引数には、シンボルでモデルの属性名を指定し、ハッシュでバリデーションの種類:trueを並べる。属性名、バリデーションともに複数並べられる。
- バリデーションの種類:{オプション：オプションの値}とすると、バリデーションごとにオプションを指定できる。

```rb
validates :number, :name, presence: true
validates :name, length: { maximum: 20 }
```

### 会員情報の検証

```rb:app/models/member.rb(一部)
class Member < ApplicationRecord
  # numberのバリデート。nil,空,全角/半角空白禁止
  validates :number, presence: true,
    numericality: {
      # 整数のみ
      only_integer: true,
      # 0以上
      greater_than: 0,
      # 100未満
      less_than: 100,
      # presenceとの重複を避けるため
      allow_blank: true
    },
    # 重複禁止
    uniqueness: true
  # nameのバリデート。nil,空,全角/半角空白禁止
  validates :name, presence: true,
    # アルファベットから始まり、英数字のみ
    format: { with: /\A[A-Za-z][A-Za-z0-9]*\z/, allow_blank: true },
    # 2文字以上20文字以下
    length: { minimum: 2, maximum: 20, allow_blank: true },
    # 重複禁止
    uniqueness: { case_sensitive: false }
  # full_nameのバリデート。nil,空,全角/半角空白禁止。20文字以下
  validates :full_name, presence: true, length: { maximum: 20 }
```

### メールアドレスのチェック

- メールアドレスのチェックは複雑なので、既存のGemパッケージを利用する。


```rb:Gemfile
gem 'email_validator', '~> 1.6'
```

- ctrl-cでRailsサーバを止めて、bundle installコマンドを実行する。サーバを起動しなおす。

```sh
$ bundle install
$ bin/rails s
```

```rb:app/models/member.rb(一部)
  # emailのバリデート
  validates :email, email: { allow_blank: true }
```
## エラーメッセージの表示

- エラー表示のテンプレートを作成する。
- full_messagesは、エラーを出した属性＋メッセージの配列を返す。

```erb:app/views/shared/_errors.html.erb
<% if obj.errors.present? %>
  <div id="errors">
    <h3>エラーがあります。</h3>
    <ul>
      <% obj.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
    </ul>
  </div>
<% end %>
```

- 上記の部分テンプレートを表示する部分を、先頭に追加する。
- `obj: @member`で、_errors.html.erbのローカル変数objがモデルオブジェクトを参照するようにする。

```erb:app/views/members/_form.html.erb（一部）
<%= render "shared/errors", obj: @member %>
```

- エラー表示用のCSSファイルを追加する。
- エラーを起こした入力欄は、class属性が`field_with_errors`のdivタグで囲まれる。

```css:app/assets/stylesheets:errors.css
/* エラー表示 */
div#errors {
  background-color: #fee;
  border: 1px solid #ecc;
  padding: 15px;
  margin: 0 0 1em;
}

div#errors h2 {
  font-size: 90%;
  color: red;
  margin: 0 0 4px;
  border: none;
}

div#errors ul {
  margin: 0;
  padding-left: 1.5em;
}

div.field_with_errors {
  background-color: #fcc;
  padding: 2px;
}
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/88686/78e274d9-f47f-8a6a-b964-d6b5718d7932.png)

## Railsの国際化機能

- Railsの国際化機能は、I18nというクラスにロケールを設定し、ロケールに合わせてYAML形式のロケールテキストから文字列を読み込んで表示する。
- ロケールを設定するには、コントローラでロケールを表す文字列やシンボルを設定する。

```rb
I18n.locale = "ja"
```

- 指定がなければデフォルトのロケール「en」が使われる。
- application.rbにデフォルトのロケールを設定する。

```rb:config/application.rb
config.i18n.default_locale = :ja
```

## エラーメッセージの日本語化

### Gemパッケージrails-l18n

- Gemパッケージ「rails-i18n」は、さまざまな言語のためのエラーメッセージや日付の書式などを集めたもの。
- Gemfileに追加する。

```rb:Gemfile
gem 'rails-i18n', '~> 5.1'
```

- ターミナルでrails-i18nをインストールする。

```sh
$ bundle install
```

### モデルの属性名の日本語化

- 日本語テキストを記述する、ja.ymlを作成する。

```yml:config/locales/ja.yml
ja:
  activerecord:
    models:
      member: 会員情報
    attributes:
      member:
        number: 背番号
        name: ユーザー名
        full_name: 氏名
        sex: 性別
        sex_1: 男
        sex_2: 女
        birthday: 誕生日
        email: メールアドレス
        administrator: 管理者
```

- _form.html.erbの`form.label :number, "背番号"`のように、日本語がある部分を取る。ja.ymlを利用する。
- human_attribute_nameは、モデルの属性名をロケールテキストから取り出す。

```erb:app/views/members/_form.html.erb
<%= render "shared/errors", obj: @member %>

<table class="attr">
  <tr>
    <th><%= form.label :number %></th>
    <td><%= form.text_field :number, size: 8 %></td>
  </tr>
  <tr>
    <th><%= form.label :name %></th>
    <td><%= form.text_field :name %></td>
  </tr>
  <tr>
    <th><%= form.label :full_name %></th>
    <td><%= form.text_field :full_name %></td>
  </tr>
  <tr>
    <th>性別</th>
    <td>
      <%= form.radio_button :sex, 1 %>
      <%= form.label :sex_1 %>
      <%= form.radio_button :sex, 2 %>
      <%= form.label :sex_2 %>
    </td>
  </tr>
  <tr>
    <th>
      <%= form.label :birthday, for: "member_birthday_li" %>
    </th>
    <td>
      <%= form.date_select :birthday, start_year: 1940, end_year: Time.current.year, use_month_numbers: true %>
    </td>
  </tr>
  <tr>
    <th><%= form.label :email %></th>
    <td><%= form.text_field :email %></td>
  </tr>
  <tr>
    <th><%= Member.human_attribute_name(:administrator) %></th>
    <td>
      <%= form.check_box :administrator %>
      <%= form.label :administrator %>
    </td>
  </tr>
</table>
```

- 日本語になったことを確認する。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/88686/43dded1c-fb46-35df-18f0-d455c0d02ac3.png)

### エラーメッセージのカスタマイズ

- 「は不正な値です。」のテキストを変えたければ、ja:→activerecord→errors→messagesの下に、「エラー名：文字列」を記述する。

```yml:config/locale/ja.yml（一部）
ja:
  activerecord:
(略)
    errors:
      messages:
        invalid: "の書式が正しくありません。"
```

- 新しいエラーメッセージを追加するには、`invalid_member_name`のように、新たな文字列で作成する。

```yml:config/locale/ja.yml（一部）
ja:
  activerecord:
(略)
    errors:
      messages:
        invalid_member_name: "は半角英数字で入力してください。"
```

- 上記で追加したエラーメッセージ`:invalid_member_name`を追加する。

```rb:app/models/member.rb（一部）
  # nameのバリデート。nil,空,全角/半角空白禁止
  validates :name, presence: true,
    # アルファベットから始まり、英数字のみ
    format: { with: /\A[A-Za-z][A-Za-z0-9]*\z/, allow_blank: true, message: :invalid_member_name },
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/88686/5b81b242-c10c-7af9-7460-aac14bcf0819.png)


### YAML

- yamlファイルの読み込み方法

```rb
require "yaml"

animals = YAML.load(File.new("members.yml"))
members.each do |key, val|
  puts "#{val['number']}\t#{val['name']}"
end
```

## 国際化機能の使い方

### テキスト

- 日本語用のテキストを用意する

```yml
ja:
  messages:
    hello: "こんにちは"
```

- ロケールの設定の文字列を取り出す。

```rb
s = I18n.t("messages.hello")
```

- コントローラやテンプレートの中では、I18nを付けずに、tメソッドが使える。

```erb
<%= t("messages.hello") %>
```

- テキストの中に値を埋め込む。

```yml
ja:
  messages:
    hello: "%{name}こんにちは"
```

- 引数に、ハッシュを追加すれば、「Taroさんこんにちは」というテキストになる。

```erb
<%= t("messages.hello", name: "Taro") %>
```

### 日付と時刻

- lメソッドの引数には、DateクラスやTimeクラス、ActiveSupport::TimeWithZoneクラスのオブジェクトを渡す。

```rb
s = I18n.l(Time.current)
```

- コントローラやテンプレートの中では、I18nを付けずに、lメソッドが使える。

```erb
<%= l(Time.current) %>
```

- lメソッドの第2引数には、formatオプションを指定できる。
- :longは「2018年05月05日(土) 17時06分56秒 +0900」のような文字列になる。

```erb
<%= l(Time.current, format: :long) %>
```

- 自分で日付フォーマット作成したい場合、以下のように書式を登録する。

```yml:condig/locales/ja.yml（一部）
ja:
  time:
(略)
    formats:
      default:
        medium: "%Y年%m月%d日(%a) %H:%M"
```

- formatオプションで指定すれば、上記フォーマットのようになる。

```erb
<%= l(Time.current, format: :medium) %>
```


参考
[改訂4版 基礎 Ruby on Rails (IMPRESS KISO SERIES)](https://www.amazon.co.jp/%E6%94%B9%E8%A8%824%E7%89%88-Ruby-Rails-IMPRESS-KISO/dp/429500460X/ref=tmm_pap_swatch_0?_encoding=UTF8&qid=1536666358&sr=1-1)