Rails 7: ActionController::Parameters.to_hにブロックを渡せるようになった(翻訳)
RailsのActionController::Parametersは、リクエストのデータをコントローラのアクションに渡すときに便利な方法です。コントローラのアクションにパラメータ付きのリクエストを行うときは常に最大限の注意が必要ですが、そのときに有用なのがActionController::Parametersです。
Rails 7から、ActionController::Parametersオブジェクトをto_hメソッドで標準のHashに変換できるようになりました。これは、データをコントローラに渡す方法をカスタマイズしたい開発者にとって朗報です。また、これによってクライアントコードを一切変更せずにパラメータの振る舞いを変更できるようになります。
Productモデルをscaffoldで作成し、product_name、product_price、product_imageという属性を持たせたとしましょう。
class ProductsController < ApplicationController
  def create
    @product = Product.new(product_params)
    respond_to do |format|
      # HTMLとJSONでレスポンスを返す
    end
  end
  private
    # 信頼できるパラメータリストのみ受け取りを許可する
    def product_params
      params.require(:product).permit(:product_name, :product_price, :product_image)
    end
end
これで、フロントエンドアプリが適切なリクエストを適切なパラメータ付きで送信すれば、以下のように新しいproductが作成できるようになるはずです。
{
  "product": {
    "product_name": "Ruby Programming 101",
    "product_price": "15.00"
    "product_image": "http://link-to-image-url/image.jpg"
  }
}
しかしクライアントリクエストのbodyにあるキーバリューペアが以下のように異なっている場合はどうすればよいでしょうか。
{
  "product": {
    "name": "Ruby Programming 101",
    "price": "15.00"
    "image": "http://link-to-image-url/image.jpg"
  }
}
改修前
Rails 7より前は、strong parametersのキーを変更してから、コントローラ内でキーの値を1つずつ変更する方法で解決したりしていたでしょう。
class ProductsController < ApplicationController
  def create
    params = product_params
    @product = Product.new
    @product.product_name = params[:product][:name]
    @product.product_price = params[:product][:price]
    @product.product_image = params[:product][:image]
    respond_to do |format|
      # HTMLとJSONでレスポンスを返す
    end
  end
  private
    # 信頼できるパラメータリストのみ受け取りを許可する
    def product_params
      params.require(:product).permit(:name, :price, :image)
    end
end
この方法だと手動更新が多数必要になるのが残念です。
改修後
Rails 7では、パラメータオブジェクトでto_hを呼び出して標準のハッシュに置き換えてからキーをコントローラ内で変更できるようになりました。これにより、多くの手動更新を必要とせずにパラメータをインテリジェントに扱えるようになります。to_hはパラメータオブジェクトの振る舞いを一切変更しないので、パラメータオブジェクトの変更は不要です。
class ProductsController < ApplicationController
  def create
    @product = Product.new(product_params)
    respond_to do |format|
      # HTMLとJSONでレスポンスを返す
    end
  end
  private
    # 信頼できるパラメータリストのみ受け取りを許可する
    def product_params
      params.require(:product)
            .permit(:name, :price, :image)
            .to_h { |key, value| [:"product_#{key}", value] }
    end
end
このソリューションはコードが明快でロジックも理解しやすいので、よりRails wayらしく感じられます。
上の例はActionController::Parametersのto_hの活用法のひとつに過ぎません。paramsハッシュの値を以下のように変更することも可能です。
params = ActionController::Parameters.new(language: "Ruby", framework: "Ruby on Rails", version: "7.0.1")
params.to_h { |key, value| [key, "#{value == "Ruby" ? "Best Programming language on Earth" : value}"] }
#=> {"language"=>"Best Programming language on Earth", "framework"=>"Ruby on Rails", "version"=>"7.0.1"}
編集部注
上のサンプルコードはpermitが省略されているのでそのままではActionController::UnfilteredParametersエラーになります。
参考: Rails API to_h -- ActionController::Parameters
詳しくは#44756を参照してください。
      
概要
元サイトの許諾を得て翻訳・公開いたします。
#44756は、現時点ではmainブランチにのみ含まれており、7-0-stableブランチには含まれていません。
参考: 週刊Railsウォッチ20220328
ActionController::Parameters.to_hにブロックを渡せるようになった