前 基礎Ruby on Rails Chapter11 モデル間の関連付け/ブログ機能の追加(コントローラ/ビュー編)
次 基礎Ruby on Rails Chapter12 アセット・パイプライン
ストロング・パラメータとは
マスアサインメント脆弱性
- 例えば、編集ページを開いて、HTMLソースの
account[member]
をaccount[administrator]
に書き換えて、更新ボタンを押せば管理者に変更できてしまう。これは重大なセキュリティホールで、マスアサインメント脆弱性と呼ばれる。
ストロング・パラメータの有効化
- ストロング・パラメータは、マスアサインメント脆弱性への対策として、Rails4.0から導入された仕組み。
- 現在は学習のためにストロング・パラメータを無効化しているので、有効化する。
config/application.rb(一部)
# false=ストロング・パラメータの有効化
config.action_controller.permit_all_parameters = false
ストロング・パラメータの有効にした状態で更新するとエラーが出る。
ストロング・パラメータの使い方
- assign_attributesの所に、
.permit(:member, :name,・・・
を追加することにより、余計な項目を保存しないようにする。
def update
@member = current_member
@member.assign_attributes(params[:account].permit(:member, :name, :full_name, :sex, :birthday, :email))
if @member.save
redirect_to :account, notice: "アカウント情報を更新しました。"
else
render "edit"
end
end
プライベートメソッドを活用する
- さらに上記のassign_attributesの引数部分をプライベートメソッドにすると、ごちゃごちゃしない。
#プライベートメソッド
private def account_params
params[:account].permit(:member, :name, :full_name, :sex, :birthday, :email)
end
#以下のように呼び出しができる。
@member.assign_attributes(account_params)
requireメソッド
- 上記で、
:account
キーが含まれていない場合、NoMethodErrorが発生してしまう。 - params.require(:account).permitとすれば、
:account
がない時は、ParameterMissingとなる。 - エラーの種類としては、NoMethodErrorはバグなので、ParameterMissingのほうが望ましい。よって、requireを使うべきである。
private def account_params
params.require(:account).permit(:member, :name, :full_name, :sex, :birthday, :email)
end
コントローラの修正
マイアカウントページのストロング・パラメータ対応
- プライベートメソッド
account_params
を作成し、updateのパラメータに入れる。
app/controllers/accounts_controller.rb(一部)
def update
@member = current_member
@member.assign_attributes(account_params)
if @member.save
redirect_to :account, notice: "アカウント情報を更新しました。"
else
render "edit"
end
end
private def account_params
params.require(:account).permit(
:number,
:name,
:full_name,
:sex,
:birthday,
:email
)
end
会員情報のストロング・パラメータ対応
- プライベートメソッド
member_params
を作成し、createとupdateのパラメータに入れる。
app/controllers/members_controller.rb(一部)
def create
@member = Member.new(member_params)
if @member.save
# 保存が成功したらshowにリダイレクトする。フラッシュ値を設定する。
redirect_to @member, notice: "会員を登録しました。"
else
# エラー発生時はnewに戻る
render "new"
end
end
def update
@member = Member.find(params[:id])
@member.assign_attributes(member_params)
if @member.save
# 保存が成功したらshowにリダイレクトする。フラッシュ値を設定する。
redirect_to @member, notice: "会員を更新しました。"
else
# エラー発生時はeditに戻る
render "edit"
end
end
private def member_params
attrs = [
:number,
:name,
full_name,
:sex,
:bitthday,
:email,
:administrator
]
# createの時は、:passwordを追加する
attrs << :password if params[:action] == "create"
params.require(:member).permit(attrs)
end
パスワード変更機能のストロング・パラメータ対応
- プライベートメソッド
account_params
を作成し、updateのパラメータに入れる。 - また、
current_password = params[:account][:current_password]
も、current_password = account_params[:current_password]
にする。
app/controllers/passwords_controller.rb(一部)
def update
@member = current_member
current_password = account_params[:current_password]
if current_password.present?
if @member.authenticate(current_password)
@member.assign_attributes(account_params)
if @member.save
redirect_to :account, notice: "パスワードを変更しました。"
else
render "edit"
end
else
@member.errors.add(:current_password, :wrong)
render "edit"
end
else
@member.errors.add(:current_password, :empty)
render "edit"
end
end
private def account_params
params.require(:account).permit(
:current_password,
:password,
:password_confirmation
)
end
ニュース記事管理機能のストロング・パラメータ対応
- プライベートメソッド
article_params
を作成し、createとupdateのパラメータに入れる。
app/controllers/articles_controller.rb(一部)
def create
@article = Article.new(article_params)
if @article.save
redirect_to @article, notice: "ニュース記事を登録しました。"
else
render "new"
end
end
def update
@article = Article.find(article_params)
@article.assign_attributes(params[:article])
if @article.save
redirect_to @article, notice: "ニュース記事を更新しました。"
else
render "edit"
end
end
private def article_params
params.require(:article).permit(
:title,
:body,
:released_at,
:no_expiration,
:expired_at,
:member_only
)
end
ブログ記事管理機能のストロング・パラメータ対応
- プライベートメソッド
entry_params
を作成し、createとupdateのパラメータに入れる。
app/controllers/entries_controller.rb(一部)
def create
@entry = Entry.new(entry_params)
@entry.author = current_member
if @entry.save
redirect_to @entry, notice: "記事を作成しました。"
else
render "new"
end
end
def update
@entry = current_member.entries.find(params[:id])
@entry.assign_attributes(entry_params)
if @entry.save
redirect_to @entry, notice: "記事を更新しました。"
else
render "edit"
end
end
private def entry_params
params.require(:entry).permit(
:member_id,
:title,
:body,
:posted_at,
:status
)
end
- いくつかの画面を更新したところ、エラーが出ることなく保存できることを確認した。
エラーページのカスタマイズ
Railsの例外を処理する
- Railsアプリケーションの起動中に例外が発生したときは、Railsが自動的に例外を捕捉してエラー表示を行う。
- エラー表示をカスタマイズしたい場合は、コントローラの中でクラスメソッド
rescue_from
メソッドを使用して、自分で例外を処理する。 - rescue_fromの第1引数は、例外のクラス名。withオプションには例外が発生した時に実行するメソッドの名前を指定する。
- 上の3つは、本番モードの時だけ処理している。
- 親子関係にある例外クラスは、親のほうを先に指定する。StandardErrorは親(の親)。
app/controllers/application_controller.rb(一部)
if Rails.env.production? || ENV["RESCUE_EXCEPTIONS"]
rescue_from StandardError, with: :rescue_internal_server_error
rescue_from ActiveRecord::RecordNotFound, with: :rescue_not_found
rescue_from ActionController::ParameterMissing, with: :rescue_bad_request
end
rescue_from LoginRequired, with: :rescue_login_required
rescue_from Forbidden, with: :rescue_forbidden
#(省略)
private def rescue_bad_request(exception)
render "error/bad_request", status: 400, layout: "error", formats: [:html]
end
private def rescue_login_required(exception)
render "error/login_required", status: 403, layout: "error", formats: [:html]
end
private def rescue_forbidden(exception)
render "error/forbidden", status: 403, layout: "error", formats: [:html]
end
private def rescue_not_found(exception)
render "error/not_found", status: 404, layout: "error", formats: [:html]
end
private def rescue_internal_server_error(exception)
render "error/internal_server_error", status: 500, layout: "error", formats: [:html]
end
エラー用テンプレート
テンプレートの作成
app/views/layouts/error.html.erb
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title><%= page_title %></title>
<%= stylesheet_link_tag "application", media: "all" %>
</head>
<body>
<div id="container">
<header>
<%= image_tag("logo.gif", size: "272x48", alt: "Morning Glory") %>
<nav class="menubar">
<a href="/">TOP</a>
</nav>
</header>
<main>
<%= yield %>
</main>
<footer>
<%= render "shared/footer" %>
</footer>
</div>
</body>
</html>
app/views/errors/bad_request.html.erb
<% @page_title = "Bad Request" %>
<h1>400 Bad Request</h1>
<p>リクエストの形式が正しくありません。</p>
app/views/errors/login_required.html.erb
<% @page_title = "ログインが必要です" %>
<h1><%= @page_title %></h1>
<%= render "shared/login_form" %>
app/views/errors/forbidden.html.erb
<% @page_title = "Forbidden" %>
<h1>403 Forbidden</h1>
<p>このページにはアクセスできません。</p>
app/views/errors/not_found.html.erb
<% @page_title = "Not Found" %>
<h1>404 Not Found</h1>
<p>ページが見つかりません。</p>
app/views/errors/internal_server_error.html.erb
<% @page_title = "Internal Server Error" %>
<h1>500 Internal Server Error</h1>
<p>システムエラーのためページが表示できません。</p>
エラーページの確認
- config/environmentsディレクトリのdevelop.rbに、
consider_all_requests_local
という設定項目がある- true・・・エラーをブラウザに表示をする。
- false・・・ブラウザには詳細情報が表示されない。
- falseに設定する。
config/environments/development.rb(一部)
# Show full error reports.
config.consider_all_requests_local = false
- 上のほうで、以下のような部分があり、環境変数
ENV["RESCUE_EXCEPTIONS"]
がtrueであれば、if内のエラー表示がされるようになる。
app/controllers/application_controller.rb(一部)
if Rails.env.production? || ENV["RESCUE_EXCEPTIONS"]
rescue_from StandardError, with: :rescue_internal_server_error
rescue_from ActiveRecord::RecordNotFound, with: :rescue_not_found
rescue_from ActionController::ParameterMissing, with: :rescue_bad_request
end
- 以下のようにしてサーバーを起動すると、環境変数
ENV["RESCUE_EXCEPTIONS"]=1
がセットされて、本番モードと同様にすべての例外が捕捉されるようになる。
$ RESCUE_EXCEPTIONS=1 bin/rails s
-
http://localhost:3000/members
を開くと、ログインフォームが表示される。
-
http://localhost:3000/articles/100
を開くと、404エラーが表示される。
- 他のエラーは出すのが大変なので、top_controllerに、
bad_request
、forbidden
、internal_server_error
メソッドを加えて、呼び出す。
app/controllers/top_controller.rb(一部)
def bad_request
raise ActionController::ParameterMissing, ""
end
def forbidden
raise Forbidden, ""
end
def internal_server_error
raise
end
- ルーティングにも、
bad_request
、forbidden
、internal_server_error
を追加する。
config/routes.rb(一部)
Rails.application.routes.draw do
root "top#index"
get "about" => "top#about", as: "about"
get "bad_request" => "top#bad_request"
get "forbidden" => "top#forbidden"
get "internal_server_error" => "top#internal_server_error"
- それぞれ、
http://localhost:3000/bad_request
、http://localhost:3000/forbidden
、http://localhost:3000/internal_server_error
を呼び出す。
ルーティングエラーの処理
- ルーティングエラーは、ApplicationControllerを書き換えただけでは処理できない。
- Action Dispatchがルーティングエラーを処理する。
- Action Controllerに伝わらない例外を処理する方法として、ErrorControllerのshowアクションを呼ぶように指定する。
config/application.rb(一部)
config.exceptions_app = ->(env) do
ErrorsController.action(:show).call(env)
end
- 新規ファイル、errors_controller.rbを新規作成する。
app/controllers/errors_controller.rb
class ErrorsController < ActionController::Base
layout "error"
def show
case request.env["action_dispatch.exception"]
when ActionController::RoutingError
render "not_found", status: 404, formats: [:html]
else
render "internal_server_error", status: 500, formats: [:html]
end
end
end
-
http://localhost:3000/xxxxxxxxxxxxx
のような変なアドレスにしても適切に処理される。
-
config/environments/development.rb
のconfig.consider_all_requests_local = true
に元に戻して、終了。
まとめ
- ストロング・パラメータを設定することにより、画面に表示されていない項目の更新を防ぐ。
- 様々なエラーを画面に適切に出力することにより、実用的なWebサイトを構築できる。