RailsのStrong Parametersを調べる

railsを触っていたらStrong Parametersというものが出てきたので調べてみました。
初心者ですので間違っている部分ありましたらご指摘いただければと思います。:eyes:

# この記事で言いたいこと
* Strong ParametersはDBに入れる値を制限することで、不正なパラメータの入力を防ぐ仕組みであること

# Strong Parametersの概要
Rails4系から追加された仕組みです。
Rails3系まではMassAssignmentを使っていたのだが、そこに脆弱性があったためStrong Parametersが導入されたとのことです。
簡単に言うとDBへ入れたり更新したりするパラメータを制限してくれる仕組みです。

MassAssignment脆弱性にまつわるニュース↓
https://www.infoq.com/jp/news/2012/03/GitHub-Compromised
## 基本的な使い方

例）```
params.require(:user).permit(:name, :email, :password)
```
①requireでPOSTで受け取る値のキーを設定
②permitで許可するカラムを設定
  ここではname, email, password属性の値だけDBに入れるのを許可
  adminカラムとか付随してても無視します。

ちなみにstrong parametersを使ってないとrailsさん:angry:にエラーを吐かれるので注意
![スクリーンショット 2017-08-25 11.32.24.png](https://qiita-image-store.s3.amazonaws.com/0/156024/04516e71-5cda-a878-213d-f9362bb6eb9d.png)


# requireとpermitを軽く読み解く
https://github.com/rails/strong_parameters/blob/master/lib/action_controller/parameters.rb
を少し見てみました。
## require
```ruby:parameters.rb
def require(key)
  self[key].presence || raise(ActionController::ParameterMissing.new(key))
end
```
presenceはrailsのObject#presenceメソッドです。
値が存在してればselfを返して、値が無ければfalseを返すようです。
requireメソッドでは、値がなければエラーを返すようになっていました。
また「[]」は以下のようにオーバーライドされており、ハッシュをパラメータに変換する機能を持たせてあります。

```ruby:parameters.rb
def [](key)
      convert_hashes_to_parameters(key, super)
end
```

## permit
```ruby:parameters.rb
def permit(*filters)
  params = self.class.new

  filters.flatten.each do |filter|
    case filter
    when Symbol, String
      permitted_scalar_filter(params, filter)
    when Hash then
      hash_filter(params, filter)
    end
  end

  unpermitted_parameters!(params) if self.class.action_on_unpermitted_parameters

  params.permit!
end
```
こちらは実際にDBに受け渡されるパラメータを一つずつフィルターにかける作業をしているようです。
またpermitに指定されたものがハッシュだった場合も、分解されそのパラメータをフィルターに書けます。
そのフィルターではパラメータの型が許可できるものかどうかを検証してくれています。
以下のFilterlingの部分に許可するパラメータの型が設定されています。

```ruby:parameters.rb
PERMITTED_SCALAR_TYPES = [
  String,
  Symbol,
  NilClass,
  Numeric,
  TrueClass,
  FalseClass,
  Date,
  Time,
  # DateTimes are Dates, we document the type but avoid the redundant check.
  StringIO,
  IO,
  ActionDispatch::Http::UploadedFile,
  Rack::Test::UploadedFile,
      ]
```
また、permitで指定していないパラメータはこちらで処理されます。

```ruby:parameters.rb
def unpermitted_parameters!(params)  
  return unless self.class.action_on_unpermitted_parameters
        
  unpermitted_keys = unpermitted_keys(params)

  if unpermitted_keys.any?  
    case self.class.action_on_unpermitted_parameters  
    when :log
    name = "unpermitted_parameters.action_controller"
      ActiveSupport::Notifications.instrument(name, :keys => unpermitted_keys)
    when :raise  
      raise ActionController::UnpermittedParameters.new(unpermitted_keys)  
    end  
  end  
end  
  
def unpermitted_keys(params)  
  self.keys - params.keys - NEVER_UNPERMITTED_PARAMS
end
```
ログを取るかエラーを出すようになっています。
デフォルトではログが出るようになっており、
エラーを出力するためには別途設定する必要があります（後述）。
NEVER_UNPERMITTED_PARAMSにはrailsによって生成されるパラメータが入っており、それは除外されるようになっています。
# その他のstrong parametersの使い方
## ネストしたパラメータを使う時
```ruby
params.require(:user).permit(:name, :email, recommend_book: [:user_id, :comment])
```
入れ子になったパラメータをPOSTするときはStrong Parameters側も設定しないといけないです。

## 指定したキーがないときにエラーを出さないようにする
```ruby
params.fetch(:user, {}).permit(:name, :email, :password)
```
userパラメータがなかったときはActionController::ParameterMissingのエラーが起きるようになっています。
上記の設定をしてあるとuserパラメータの代わりに{}がデフォルト値として評価されるようになります。

## 違うパラメータが送られてきた時にエラーを出力する
各環境のconfigfileに設定します。
出力したい環境に応じて設定を記述するファイルは変えて下さい。

```ruby:config/environments/development.rb
config.action_controller.action_on_unpermitted_parameters = :raise
```
ActionController::UnpermittedParametersエラーが出力されるようになります。

### 参考
----
* http://b1840943.hatenablog.com/entry/2016/01/26/201316
* http://morizyun.github.io/ruby/rails-controller-strong-parameters.html
* http://o.inchiki.jp/obbr/181