Help us understand the problem. What is going on with this article?

Strong Parameters について調べてみた(ActiveAdminの対応も含む)

Rails4系からデフォルトで組み込まれたStrongParametersについていろいろ調べてみたのでまとめてみました。

StrongParametersについて概要

StrongParameters とは Mass Assignment 脆弱性対策のための機能です。おおまかに言うと、Rails3系ではModelでattr_accessibleを使って制御していたものとは違い、Controller側で受け取ったパラメータを制御するものです。
Mass Assignemt対策やStrong Parametersについての詳細は以下がとても参考になります。ありがとうございます。

StrongParametersで例外時の挙動

  • controller側に渡されたparamsにpermitの設定がないところで params["user"]とかでモデルに new, updateすると ActiveModel::ForbiddenAttributesError の例外が発生する。
  • paramsにpermit の 設定を一つでも入れた上で、permitしていない paramsをnew, update「Unpermitted parameters: name」の例外が発生する。

今回StrongParametersについて調べるきっかけは

Rails4のアプリケーションを作ろうとしている時に、管理機能ツールとして ActiveAdmin の初期化したあとに 「admin_user」というModelを更新するテストしていた時にStrongParameterで以下のエラーが出ていたことです。

Unpermitted parameters: utf8, authenticity_token, commit

ActiveAdminを初期化したときはapp/admi/admin_user.rbが生成され、その中に以下のStrongParameterに対応したコードができます。

app/admi/admin_user.rb
ActiveAdmin.register AdminUser do
  index do
    column :email
    column :current_sign_in_at
    column :last_sign_in_at
    column :sign_in_count
    default_actions
  end

  filter :email

  form do |f|
    f.inputs "Admin Details" do
      f.input :email
      f.input :password
      f.input :password_confirmation
    end
    f.actions
  end
  controller do
    def permitted_params
      params.permit admin_user: [:email, :password, :password_confirmation]
    end
  end
end

permitted_paramsメソッドがStrongParametersに対応したコードです。
ActiveAdminInherited_resources というCRUDの処理に特化したコントローラを簡単に実装できるGemに依存しているので、 permitted_params というメソッドの中身をコントローラで定義することでStrongParametersに対応させることができるようです。active_adminでは 「 controller do 」 ブロックに定義することで対応させます。

【参考】 active_admin本家 permitted_paramsメソッドを生成するGeneratorのコード
【参考】inherited_resources の permitted_params を定義しているコードinherited_resources/base_helpers.rb

しかし、ActiveAdmin初期化時に生成された permitted_params メソッドの中身では、上述したように Unpermitted parameters が発生してしまいます。

ここからはその部分を確認してみます。
Unpermitted parameters がなぜ発生するのか、ActionController::Parameters クラスの挙動を色々見て行きます。

ActionController::Parameters をREPL(irb や pry)を使って確認

まずはparamsとして ActionController::Parameters のクラスを作る
:form以下のハッシュデータ(:email, :admin) が実際のformからPostされたデータと仮定して、Mass Assignment したい(permitする対象にしたい)という状況を作る。
:name:admin は 付加情報としてparamsに入っているだけ。

全体で使うparamsの初期化
hash_params =  {
    :name=>"John",
    :admin=>true,
    :form=>{:email=>"hoge@hoge.com", :pass=>"hogehoge"}
}
# ActionController::Parametersクラスを作る
params = ActionController::Parameters.new(hash_params)  
①params.permit()を使って、form以下をpermitさせる
permit1 = params.permit(form: [:email, :pass]) #「Unpermitted parameters: name, admin」が発生
permit1.permitted? #=> true
puts permit1 #=> {"form"=>{"email"=>"hoge@hoge.com", "pass"=>"hogehoge"}}

# これだとparamsの「:form=>{:email, :pass}」がpermit1に入り、permitted?もtrueになるが、、「permit()」メソッドを実行したタイミングでparamsで「:form」と同階層にある「:name」「:admin」がUnpermitted parametersとなる。
②params.permit()を使って、paramsのすべてをpermitさせる
# ①でpermitした:formに加えて:name, :adminもpermitさせると
permit2 = params.permit(:name, :admin, form: [:email, :pass])  # 「Unpermitted parameters」は発生しない
permit2.permitted? #=> true
puts permit2 #=> {"name"=>"John", "admin"=>true, "form"=>{"email"=>"hoge@hoge.com", "pass"=>"hogehoge"}}

# 当然ながらpermit2にはparams全てのデータが入り、permitted?もtrueになる。「Unpermitted parameter」は発生しない。:form以下のみpermitしたいのにこれだと、なんか違う気もする。
③requireで対象を指定してpermitする。「params.require().permit()」
# requireメソッドでparams内のハッシュキーを特定して、それ以下のみをpemitさせる方法
permit3 = params.require(:form).permit(:email,:pass) # 「Unpermitted parameters」は発生しない
permit3.permitted? #=> true
puts permit3 #=> {"email"=>"hoge@hoge.com", "pass"=>"hogehoge"}

# Unpermitted parametersは発生せず目的の「:form」のみがpermit3に入り、permitted?もtrueになる。
# また、以下のようにすると「Unpermitted parameters」は発生する
permit4 = params.require(:form).permit(:email) #=> Unpermitted parameters: pass
# :form=> :emailと同階層の:passを指定していないパターン。:passについてUnpermitted parametersが発生

以上から、strong_parametersでpermitする場合は、requireしてpermitするという方法が一番使い回しがきくなーという感じですねー。ま~適宜用途にあった使いかたをしていくのがいいとは思いますが、、


実際の実装例

実際の実装例というか自分の設定している定義方法を示します。

1.ActiveAdmin Controllerの場合

冒頭にactive_admin:installで自動生成されたadmin_user.rbの controller do ブロックの中を書き換えます。

app/admin/admin_user.rb
ActiveAdmin.register AdminUser do
…省略
  controller do
    def permitted_params
       {admin_user: params.require(:admin_user).permit(:email, :password, :password_confirmation)}
    end
  end
…省略
end

2.通常のRailsのコントローラの場合

ModelがUseremailpassword を持っている場合を想定して書きます。

2-1. InheritedResources::Base を継承している時

rails generate scaffoldコマンドで生成した時はinherited_resourcesを継承したコントローラクラスになるので、以下のようにpermitted_paramsメソッドを定義します。

app/controllers/users_controller.rb
class UsersController < InheritedResources::Base
  private
    def permitted_params
      {:user => params.require(:user).permit(:email, :password)}
      # params.permit!  #<=すべてのparamを許可するとき
    end
end

inherited_resources を使っているときは特に CRUDに関するメソッド(create, updateメソッドなど)は内部で実装されているため特に定義しなくても動作する。

2−2. ApplicationController を継承している時

rails generate controllerrails generate scaffold_controller コマンドで生成した時はApplicationControllerを継承したコントローラクラスになるので以下のように指定します。
privateなuser_paramsメソッドで独自にpermitするカラムを指定する

app/controllers/users_controller.rb
class UsersController < ApplicationController
…省略
  def create
    @user = User.new(user_params)

    respond_to do |format|
      if @user.save
        format.html { redirect_to @user, notice: 'User was successfully created.' }
        format.json { render action: 'show', status: :created, location: @user }
      else
        format.html { render action: 'new' }
        format.json { render json: @user.errors, status: :unprocessable_entity }
      end
    end
  end

  def update
    respond_to do |format|
      if @user.update(user_params)
        format.html { redirect_to @user, notice: 'User was successfully updated.' }
        format.json { head :no_content }
      else
        format.html { render action: 'edit' }
        format.json { render json: @user.errors, status: :unprocessable_entity }
      end
    end
  end
…省略
  private
    def user_params
      params.require(:user).permit(:email, :password)
    end
…省略
end

2.通常のRailsのコントローラの場合 の時は個人的には ApplicationController を継承した 2−2 の方法が使いやすいかな〜と思っています。InheritedResources::Base を継承した 2−1 の方法はコード量は少ないけどコントローラの処理が隠蔽されすぎていてわかりにくくなるので。

参考

InheritedResourcesを使ったStrongParametersの定義方法は正規のドキュメントに書かれていました。
3つの方法があるようです。私は2番めのrequireを使ったHashで返す方法を利用しています。
https://github.com/josevalim/inherited_resources#strong-parameters

おまけ

おまけ1 Unpermitted parametersの時の振る舞いを設定する

StrongParametersの Unpermitted Parameters が発生した時はデフォルトではLogに書きだされる設定になっているが例外を吐くようにすることもできる。application.rb に設定する。

config/application.rb
…省略
    # StrongParameters unpermitted parameters behavior 
    config.action_controller.action_on_unpermitted_parameters = :raise # :raise か :log で設定する
…省略

おまけ2 Unpermitted parametersのログを色付けする

colorize_unpermitted_parameters というGemをインストールするだけ。Gemfileに以下を定義してbundle install

gem 'colorize_unpermitted_parameters'

以上、長くなったけど、自分の備忘録として。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした