概要
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する。
<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)
オブジェクトを指定してそれに応じたフォームを作成する。
<%= 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の中で用いる。
<%= 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属性」については通常通り更新される。