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

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

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

* [Rails4のMass Assignment脆弱性対策のStrong Parametersについて](http://wada811.blogspot.com/2013/08/strong-parameters-mass-assignment-vulnerability-countermeasure-in-rails4.html)
* [strong_parametersについて](http://qiita.com/kmats@github/items/1deb95ca12fa8520671f)
* [7. StrongParameters -TECHSCORE](http://www.techscore.com/tech/Ruby/rails-4.0/strong-parameters/)

### StrongParametersで例外時の挙動

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


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

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

```rb
Unpermitted parameters: utf8, authenticity_token, commit
```

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

```rb: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に対応したコードです。 
**ActiveAdmin**は ***[Inherited_resources](https://github.com/josevalim/inherited_resources)*** というCRUDの処理に特化したコントローラを簡単に実装できるGemに依存しているので、 **permitted_params** というメソッドの中身をコントローラで定義することでStrongParametersに対応させることができるようです。active_adminでは 「 **controller do** 」 ブロックに定義することで対応させます。

[【参考】 active_admin本家 permitted_paramsメソッドを生成するGeneratorのコード](https://github.com/gregbell/active_admin/commit/b5cff823ee39756d2fb543244c056f03689ca319)
[【参考】inherited_resources の permitted_params を定義しているコードinherited_resources/base_helpers.rb](https://github.com/josevalim/inherited_resources/blob/1538e195a2b68be67763aa5509404b2efc42789b/lib/inherited_resources/base_helpers.rb#L319-L330)

しかし、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に入っているだけ。

```rb:全体で使うparamsの初期化
hash_params =  {
	:name=>"John",
	:admin=>true,
	:form=>{:email=>"hoge@hoge.com", :pass=>"hogehoge"}
}
# ActionController::Parametersクラスを作る
params = ActionController::Parameters.new(hash_params)  
```

```rb:①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となる。
```

```rb:②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したいのにこれだと、なんか違う気もする。
```

```ruby:③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するという方法が一番使い回しがきくなーという感じですねー。ま～適宜用途にあった使いかたをしていくのがいいとは思いますが、、

*******

## 実際の実装例

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

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

```rb: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
```

### ２．通常のRailsのコントローラの場合

Modelが`User`で `email` と `password` を持っている場合を想定して書きます。

#### ２-１． `InheritedResources::Base` を継承している時
`rails generate scaffold`コマンドで生成した時は`inherited_resources`を継承したコントローラクラスになるので、以下のように`permitted_params`メソッドを定義します。

```rb: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メソッドなど)は内部で実装されているため特に定義しなくても動作する。

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

```rb: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
```

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

## 参考

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


## おまけ

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

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

#### おまけ２ Unpermitted parametersのログを色付けする
`colorize_unpermitted_parameters` というGemをインストールするだけ。Gemfileに以下を定義して`bundle install`

```Gemfile
gem 'colorize_unpermitted_parameters'
```

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