[Rails4.0] フォームの基本とStrongParametersを理解する



## 概要

Ruby on Rails Tutorialのエッセンスを自分なりに整理9

[Rails] 基本的なユーザ管理用Modelをテスト駆動で実装してみる
http://qiita.com/kidachi_/items/99f2c90788bd931ea3ee
の続き。

Ruby on Rails Tutorial（chapter7）
http://railstutorial.jp/chapters/sign-up?version=4.0#code-after_save_tests


## 分かること

- フォームの生成方法
- フォームを介してやりとりされるデータの形
- Strong Parametersとは何か

## 基本的なフォーム

まず、ユーザ登録を行うフォームを見てみる。

### UsersController

二つのメソッドを準備。

- new（GET）
 
 ユーザ登録ページを生成
- create（POST）

 newページで入力された値を受け取って実際にユーザを登録


```rb
class UsersController < ApplicationController
  def new
    @user = User.new
  end
  def create
    @user = User.new(params[:user])
    if @user.save      
      redirect_to @user    # @userを解析し、'/users/:id'にリダイレクト
    else
      render 'new'
    end
  end
end
```

### View（app/views/users/new.html.erb）

- ユーザからの入力を受け取り、createメソッドにPOSTする。

```rb:erb
<div class="row">
  <div class="span6 offset3">
    <%= form_for(@user) do |f| %>

      <%= f.label :name %>
      <%= f.text_field :name %>

      <%= f.label :email %>
      <%= f.text_field :email %>

      <%= f.label :password %>
      <%= f.password_field :password %>

      <%= f.label :password_confirmation, "Confirmation" %>
      <%= f.password_field :password_confirmation %>

      <%= f.submit "Create my account", class: "btn btn-large btn-primary" %>
    <% end %>
  </div>
</div>
```

### 生成されるhtml

```html
<form accept-charset="UTF-8" action="/users" class="new_user"
      id="new_user" method="post">

  <label for="user_name">Name</label>
  <input id="user_name" name="user[name]" type="text" />

  <label for="user_email">Email</label>
  <input id="user_email" name="user[email]" type="text" />

  <label for="user_password">Password</label>
  <input id="user_password" name="user[password]" type="password" />

  <label for="user_password_confirmation">Confirmation</label>
  <input id="user_password_confirmation"
         name="user[password_confirmation]" type="password" />

  <input class="btn btn-large btn-primary" name="commit" type="submit"
         value="Create my account" />
</form>
```

## 上記内で用いられている機能

### form_for(obj)

オブジェクトを指定してそれに応じたフォームを作成する。

```rb:erb
    <%= form_for(@user) do |f| %>
      ~
    <% end %>
```
@userは中身が空であることから、新規作成のメソッド（create）が必要
→Railsが`method="post"`のformを自動生成してくれている。

```html
<form action="/users" class="new_user" id="new_user" method="post">
```


### f.Formヘルパー

form_forの中で用いる。

```rb:erb
<%= f.label :name %>
<%= f.text_field :name %>
```
このerbは以下のHTMLを生成する。

```html
<label for="user_name">Name</label>
<input id="user_name" name="user[name]" type="text" />
```
"user[email]"という名前は、userハッシュのemail属性を指す。

### createメソッドに渡される値

input属性のname値をもとに、params変数経由で初期化用のハッシュが構成される。

```rb
"user" => { "name" => "Foo Bar",
            "email" => "foo@example.com",
            "password" => "[FILTERED]",
            "password_confirmation" => "[FILTERED]"
          }
```

これらのハッシュはUser.newの引数に必要な属性と一致する。

つまり、以下の二つの記述はほぼ等価であるということ。

```rb
@user = User.new(params[:user])
```

```rb
@user = User.new(name: "Foo Bar", email: "foo@example.com",
                 password: "foo", password_confirmation: "bar")
```

こうして、（適切な値が渡されていれば）`@user.save`にて新規レコードが登録される。

```rb
  def create
    @user = User.new(params[:user])
    if @user.save      
      redirect_to @user    # @userを解析し、'/users/:id'にリダイレクト
    else
      render 'new'
    end
  end
```

## Strong Parameters


上記でformを利用してparamsハッシュを準備したが、
これら全てをそのまま初期化するという行為はセキュリティ上危険。

### リスク例

例えば、もしデータモデルの一属性にadmin属性（管理権限）を持たせていた場合、
`admin=’1’`という値を`params[:user]`の一部に紛れ込ませれば、
管理権限を奪うことが出来てしまう。

### curlによる不正なPOSTサンプル

```
$ curl http://localhost:3000/users -X POST \
-d "user[name]=invalid" \
-d "user[email]=invalid@example.com" \
-d "user[password]=foobar" \
-d "user[password_confirmation]=foobar" \
-d "user[admin]=1" 
```

※実際はこれを通すためには一部手を加える必要有り（Rails4.0の場合）
詳しくは以下等参照。

Rails 4.0だとCSRFトークンでエラーになる
http://qiita.com/naoty_k/items/b40b13735fd7f06f8cb7


### 対策

要は<strong>ユーザーが送信したデータをまるごとUser.newに渡してしまうこと</strong>が危ない。

これを防ぐ為に、Rails 4.0ではStrong Parametersを使用する。
（以前のバージョンではattr_accessible。以下参照）

Railsのattr_accessible設定について
http://yoshifumisato.jeez.jp/wordpress/post/rails/882


### 記述方法

```rb
params.require(:user).permit(:name, :email, :password, :password_confirmation)
```

今回の例に照らして言うと、これで

- paramsハッシュは:user属性を必須とする
- 名前、メールアドレス、パスワード、パスワードの確認の属性のみ許可する

ことを実現出来る。


privateメソッドとして定義し、createの中から呼び出す。

```rb
class UsersController < ApplicationController
  def create
    @user = User.new(user_params)
    if @user.save
      redirect_to @user
    else
      render 'new'
    end
  end

  private

    def user_params
      params.require(:user).permit(:name, :email, :password,
                                   :password_confirmation)
    end
end
```

これで、許可した4属性以外がPOSTされた場合はその値を無効化してくれる。

※エラーを吐く訳ではないことに注意。
例えば「許可した4属性＋不正な1属性」がPOSTされた場合、
「許可した4属性」については通常通り更新される。