Rails "ActionController::Parameters.to_h" can now receive a block


Rails ActionController::Parameters are a convenient way to pass data from a request to a controller action. Every request made to a controller action with parameters should be handled with utmost caution. ActionController::Parameters is a convenient way to do that.

With Rails 7, we can now convert an ActionController::Parameters object to a standard Hash using the .to_h method. This is great news for developers who want to customize the way their data is passed to the controller. This also means we can change the behavior of the parameters without having to change anything on the client code.

Let’s assume we have scaffolded a Product model with product_name, product_price, and product_image as attributes.

class ProductsController < ApplicationController
  def create
    @product = Product.new(product_params)
    respond_to do |format|
      # respond to HTML, JSON
    end
  end
  private

    # Only allow a list of trusted parameters through.
      def product_params
        params.require(:product).permit(:product_name, :product_price, :product_image)
      end
end

Now, if the frontend app sends an appropriate request with appropriate parameters, we should be able to create a new product.

{
  "product": {
    "product_name": "Ruby Programming 101",
    "product_price": "15.00"
    "product_image": "http://link-to-image-url/image.jpg"
  }
}

But, what if the client’s request body has different key/value pairs?

{
  "product": {
    "name": "Ruby Programming 101",
    "price": "15.00"
    "image": "http://link-to-image-url/image.jpg"
  }
}

Before

Before Rails 7, the way to solve this would be to change strong params keys and then modify the keys one by one inside the controller.

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|
      # respond to HTML, JSON
    end
  end

  private

    # Only allow a list of trusted parameters through.
    def product_params
      params.require(:product).permit(:name, :price, :image)
    end
end

There are a lot of manual updates that are needed in this case which does not feel like a good practice.

After

Instead, with Rails 7, we can use the to_h method on the parameters object to convert it to a standard Hash and then make changes to the keys inside the controller. This allows us to handle parameters more intelligently without requiring a lot of manual updates. The to_h method doesn’t change the behavior of the parameters object at all, so there is no need to modify it in any way.

class ProductsController < ApplicationController
  def create
    @product = Product.new(product_params)

    respond_to do |format|
      # respond to HTML, JSON
    end
  end

  private

    # Only allow a list of trusted parameters through.
    def product_params
      params.require(:product)
            .permit(:name, :price, :image)
            .to_h { |key, value| [:"product_#{key}", value] }
    end
end

This solution looks and feels a lot like the Rails way. Clean code with easy to understand logic!

The above example is just one way of using the .to_h method with ActionController::Parameters, we can also modify the values of the params hash to be.

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"}

Check out the PR for more details.

Join Our Newsletter