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

More than 1 year has passed since last update.

概要

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ページで入力された値を受け取って実際にユーザを登録

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する。
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

<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)

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

erb
    <%= form_for(@user) do |f| %>
      ~
    <% end %>

@userは中身が空であることから、新規作成のメソッド(create)が必要
→Railsがmethod="post"のformを自動生成してくれている。

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

f.Formヘルパー

form_forの中で用いる。

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

このerbは以下のHTMLを生成する。

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

"user[email]"という名前は、userハッシュのemail属性を指す。

createメソッドに渡される値

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

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

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

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

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

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

  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

対策

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

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

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

記述方法

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

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

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

ことを実現出来る。

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

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属性」については通常通り更新される。

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.