26
19

More than 3 years have passed since last update.

Railsのストロングパラメータ(Strong Parameters)を一括で処理する書き方

Last updated at Posted at 2019-08-07

Railsのリファクタリング記事です。
RailsでStrong Parametersを書くときに入力項目が多いページだと記述が多くなって「なんだかなぁ…」と思ったので、スッキリさせてみました。

テーブルからカラム名を引っ張ってゴニョゴニョして出力しています。
結論をすぐに知りたい方は後半のStrong Parametersをスッキリさせてみたへどうぞ。

Strong Parametersってなんだっけ?

Strong Parametersを使ってない人はいないと信じたいのですが、確認も含めて簡単に説明しておきます。
Strong ParametersはRails4系から追加されたセキュリティを向上させるための仕組みです。
こちらで指定したパラメータ以外は受け取らないようにして、攻撃者による意図しないコードの実行を防止するセキュリティ対策。

実際にどうやって使うのか。
例えば、送信された値からユーザーを作成するようなケースでは以下のように利用されます。

app/controllers/users_controller.rb
class UsersController < ApplicationController
  def create
    user = User.new(user_params)
  end

  private

  def user_params
    params.require(:user).permit(:name, :email)
  end
end

これが基本系で、送信されてくるパラメータに:userというキーが必須です。
:userはハッシュ型の値を持っていて、このハッシュには:name:emailのキーのみを許可するという設定になりました。

モデルによって記述量が増える問題

通常、上記のような使い方をしますが、テーブルのカラム数が多くなると記述する値が増えてしまいます。
そんなにたくさんの項目を「1つのStrong Parametersに持たせる設計」にするべきかは別の問題としてありますが…
それは一旦置いておくとして、カラム分を追加していくと以下のような感じになると思います。

app/controllers/users_controller.rb
def user_params
  params.require(:user).permit(:screen_name, :name, :kana, :nickname, :tel, :email, :address, :building, :gender, :birth_day)
end

このように、たくさん書かないといけません。面倒。
一行で記述すると読みづらいので、僕は今まで下記のように書いていました。

app/controllers/users_controller.rb
def user_params
  params.require(:user).permit(
    :screen_name, :name, :kana, :nickname,
    :tel, :email, :address, :building,
    :gender, :birth_day,
  )
end

1つのアプリケーションで入力があるモデルは複数になることが多いと思います。
モデルが増えていくと、同様にモデルごとに大量のStrong Parametersを書くことになってしまい非常に手間です。

今回はこれを便利な感じにリファクタリングしてスッキリさせてみました。

Strong Parametersをスッキリさせてみた

上記のようにStrong Parametersに記述する値はモデルのテーブル名と一致することが多いと思います。(一致していないとRailに乗っていないような…)

これを利用して、モデルのテーブル名からcolumn_namesを使って一括で出力します。
column_namesはActiveRecord::Baseに用意されているメソッドです。

column_namesで出力されるのは文字列型の配列なので、どのモデルからでもシンボル型の配列で取得できる共通のクラスメソッドを用意しておきます。
ついでにリストから除外したいカラム名を配列で渡せるような引数(reject)も用意します。

app/models/concerns/common_module.rb
module CommonModule
  extend ActiveSupport::Concern

  module ClassMethods
    # カラム名のシンボル型の配列
    def column_symbolized_names(reject=[])
      column_names.delete_if {|n| n.in?(reject)}.map(&:to_sym)
    end
  end
end

このModuleを共通メソッドとしてモデルで読み込みます。

app/models/user.rb
class User < ApplicationRecord
  # 共通設定の読み込み
  include CommonModule
end

あとは用意しておいたクラスメソッドでカラム名をそのままStrong Prametersに格納します。

app/controllers/users_controller.rb
def user_params
  columns = User.column_symbolized_names
  params.require(:user).permit(*columns)
end

(注意)
通常の方法であればupdated_atcreated_atはStrong Parametersには含めなくてもデフォルト値を入れてくれるので、記述は不要なのですが、同様にrejectに含めてしまうと、保存時にMySQLからField 'created_at' doesn't have a default valueと怒られてしまいました。
Railsの仕様なのでしょうか…理由がよくわからなかったのですが、ご存知の方がいたら教えてください:bow:

ただし、この方法でもStrong Parametersに不要なカラムや、配列・連想配列を持ちたいパラメータには対応しないので、そこだけはカスタマイズします。
例えば、配列を保持するtagsパラメータが必要だとしたら…

app/controllers/users_controller.rb
def user_params
  reject = %w(tags)  # 出力する配列からimagesを除外しておく
  columns = User.column_symbolized_names(reject).push(tags: [])
  params.require(:user).permit(*columns)  # 出力された配列にPush
end

tagsカラムを一度rejectして、最後にcolumnsに追加するだけ。

メタプログラミングを駆使すればもう少し共通化できそうな気もしますが、柔軟性も担保する場合はこのぐらいの共通化、簡略化が妥当かなと思います。

上記のように共通化することで、様々なケースに柔軟に対応可能ほぼほぼコピペで使いまわせちゃうStrong Parametersを作ることができました。

26
19
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
19