Rails

さくっとStrongParametersへ移行するbe_strongというgemをつくりました

Rails

ハンドメイドマーケット minne開発チームの@monochromeganeです。

今回はRailsプロジェクトのStrongParameters移行を手軽に行うためにつくったbe_strongというgemを紹介します。

はじめに

私の所属するminne開発チームでは技術的な課題がサービスの発展を妨げることのないよう、継続的に改善を行っています。このあたりについては、以前minne技術戦略発表会 - 15万人の作家を支えるサービス開発の裏側でお話させていただきました。Railsで開発されているminneでは、最新のバージョンに追従する、そして提供される新しい機能への乗り換えもその一環として行っています。

みなさま御存知のとおり、Rails4ではMass assignment対策としてこれまで提供されていたモデル層でのattr_accessible/attr_protectedに代わり、コントローラー層でStrongParametersを用いるようになりました。

minneでは移行措置として提供されているProtected Attributes gemをしばらく使っていましたが、最終的にattr_accessible/attr_protectedStrongParametersのコードが両方存在する状態になってしまいました。このため、従来の機能を置き換える際に本筋とは関係ないStrongParametersへの移行も入ることになり、開発速度に影響が出ることが想定されたため年末大掃除として全箇所の移行を行うこととしました。

be_strong gem

今回、StrongParametersへの移行にあたっては機械的に置換できるところは機械的にやってしまいたいと考え、be_strongという移行用のgemを作成しました。

本gemを追加し、下記のrakeタスクを実行することで標準的なattr_accessible/attr_protectedを使っているコードをStrongParametersを使ったコードに移行できます。

$ bundle exec rake be_strong:convert

これによりモデルとコントローラーのコードが下記のように変更されます。

Before

# Model
class Author < ActiveRecord::Base
  attr_accessible :name, :age
end

# Controller
class AuthorsController < ApplicationController
  def create
    Author.new(params[:author])
    ...
  end
end

After

# Strong Model
class Author < ActiveRecord::Base
end

# Strong Controller
class AuthorsController < ApplicationController
  def create
    Author.new(author_params)
    ...
  end

  private

  def author_params
    params.require(:author).permit(:name, :age)
  end
end

be_strong gem がやること、やらないこと

ご覧いただいた通り、be_strong gemは

  • コントローラーにStrongParametersを利用するprivateメソッドの追加
  • Mass assignment利用箇所への上記メソッドの適用
  • 不要になったモデル側のattr_accessible/attr_protectedの削除

といった処理を全モデルとコントローラーのコードに対して適用します。特に難しいことをしていないものについては大部分を置換できるはずです。

しかしながら対象の検出と置換は単純な正規表現によって行っており、以下のような複雑なケースにおいては置換を行うことができません。

  • paramsのキーに該当するモデルが存在しない場合
  • paramsを直接Mass assignmentのメソッドに渡していない場合
    • 変数に一度代入している
    • 変換などを行うため一度メソッドを経由している

また、現状のattr_accessible/attr_protectedの設定値からpermitの引数を生成するため、コントローラー単位では不要となり得るパラメーターについても一旦追加されます。こういった不要なパラメーターの判断やメソッド内の処理の変更については内容を確認して個別に修正する必要があります。

Synvert

SynvertStrongParameters snippetでも同様のことが行えますが、

  • StrongParametersを利用するメソッドは必ずprivateメソッドとして定義したい
  • コントローラー名から類推されるモデル名と異なるモデルが利用されている場合にも置換対象としたい
    • HogeController内のFugaモデルに対するMass assignment
    • Hoge::FugaController内のFugaモデルに対するMass assignment

といったパターンについても対応したかったこと、そしてStrongParametersに特化したものであればそんなに手間をかけずに作れるであろうという判断から、今回は自作の運びとなりました。

移行の手順

minneのプロジェクトでは下記のような流れで移行を行いました。

1. be_strong gemの追加

Gemfileに以下を追加してbundle installします。

gem 'be_strong'

2. 移行用rakeタスクの実行

$ bundle exec rake be_strong:convert

3. Protected Attributes gemの削除

Gemfileから以下の行を削除してbundle install

gem 'protected_attributes'

4. 許可されていないパラメーターを検知できるようにする

config/application.rbに下記を追加して、permitで許可されていないパラメーターが渡された場合に例外を発生させ、検知しやすいようにします。

config.action_controller.action_on_unpermitted_parameters = :raise

5. テストの実行

この状態でプロジェクトのテストを実行し、be_strong gemで対応できなかった箇所を把握します。

6. be_strong gemで対応できなかった箇所の修正

テストを繰り返しながら、前述したbe_strong gemで対応できないケースの修正や、コントローラー単位で許可するパラメーターを精査して不要なものを除去していく作業を行います。CIが通って、動作確認が済んだらリリースです。

minneの場合、40箇所程度残っていたattr_accessible/attr_protected関連のコードを朝活の範囲内でさくっと移行を行うことができました。

最後に

今回の修正で依存gemを減らしただけでなく、Mass assignmentの問題を状況に応じたコントローラー側で解決できるようになりました。こういった改善はサービスを利用する方々には直接影響のないものですが、継続することこそがサービス全体の価値提供のスピードを保つことに繋がると考えています。

StrongParametersについては提供から時間が経っていることもあり、移行済みのプロジェクトも多いと思いますが、まだ移行されていないプロジェクトで使ってもらえたらうれしいなあと思ってgemにしてみました。年末の大掃除も兼ねて依存gemを減らしてみてはいかがでしょうか。