What is the best way to convert all controller params from camelCase to snake_case in Rails?

When you’ve completed the steps below, camelCase param names submitted via JSON requests will be changed to snake_case.

For example, a JSON request param named passwordConfirmation would be accessed in a controller as params[:password_confirmation]

Create an initializer at config/initializers/json_param_key_transform.rb. This file is going to change the parameter parsing behaviour for JSON requests only (JSON requests must have the request header Content-Type: application/json).

Find your Rails version and choose the appropriate section below (find your Rails version in Gemfile.lock):

For Rails 5 and 6

For Rails 5 and 6, to convert camel-case param keys to snake-case, put this in the initializer:

# File: config/initializers/json_param_key_transform.rb
# Transform JSON request param keys from JSON-conventional camelCase to
# Rails-conventional snake_case:
ActionDispatch::Request.parameter_parsers[:json] = lambda { |raw_post|
  # Modified from action_dispatch/http/parameters.rb
  data = ActiveSupport::JSON.decode(raw_post)

  # Transform camelCase param keys to snake_case
  if data.is_a?(Array)
    data.map { |item| item.deep_transform_keys!(&:underscore) }
  else
    data.deep_transform_keys!(&:underscore)
  end

  # Return data
  data.is_a?(Hash) ? data : { '_json': data }
}

For Rails 4.2 (and maybe earlier versions)

For Rails 4.2 (and maybe earlier versions), to convert camel-case param keys to snake-case, put this in the initializer:

# File: config/initializers/json_param_key_transform.rb
# Transform JSON request param keys from JSON-conventional camelCase to
# Rails-conventional snake_case:
Rails.application.config.middleware.swap(
  ::ActionDispatch::ParamsParser, ::ActionDispatch::ParamsParser,
  ::Mime::JSON => Proc.new { |raw_post|

    # Borrowed from action_dispatch/middleware/params_parser.rb except for
    # data.deep_transform_keys!(&:underscore) :
    data = ::ActiveSupport::JSON.decode(raw_post)
    data = {:_json => data} unless data.is_a?(::Hash)
    data = ::ActionDispatch::Request::Utils.deep_munge(data)
    
    # Transform camelCase param keys to snake_case:
    data.deep_transform_keys!(&:underscore)

    data.with_indifferent_access
  }
)

Final step for all Rails versions

Restart rails server.


Example with camelCase to snake_case in rails console

2.3.1 :001 > params = ActionController::Parameters.new({"firstName"=>"john", "lastName"=>"doe", "email"=>"[email protected]"})
=> <ActionController::Parameters {"firstName"=>"john", "lastName"=>"doe", "email"=>"[email protected]"} permitted: false>

2.3.1 :002 > params.transform_keys(&:underscore)
=> <ActionController::Parameters {"first_name"=>"john", "last_name"=>"doe", "email"=>"[email protected]"} permitted: false>

source:

http://api.rubyonrails.org/classes/ActionController/Parameters.html#method-i-transform_keys http://apidock.com/rails/String/underscore

UPDATE:

If you have nested attributes and Rails 6 you can do:

ActionController::Parameters convert to hash and then do deep transform:

params.permit!.to_h.deep_transform_keys { |key| key.to_s.underscore } params.permit!.to_h.deep_transform_values { |value| value.to_s.underscore }

Please see:

http://apidock.com/rails/v6.0.0/Hash/deep_transform_values http://apidock.com/rails/v6.0.0/Hash/deep_transform_keys


In Rails 6.1 will be added deep_transform_keys to ActionController::Parameters so it enables you to make it as simple as:

class ApplicationController < ActionController::Base
  before_action :underscore_params!

  private

  def underscore_params!
    params.deep_transform_keys!(&:underscore)
  end
end

Edit

At the moment you can backport as follows:

module DeepTransformKeys
  def deep_transform_keys!(&block)
    @parameters.deep_transform_keys!(&block)
    self
  end
end

ActionController::Parameters.include(DeepTransformKeys)

ActiveSupport already provides a String#snakecase method. All you have to do is install a filter that does a deep iteration through the params hash and replaces the keys with key.snakecase.

before_filter :deep_snake_case_params!

def deep_snake_case_params!(val = params)
  case val
  when Array
    val.map {|v| deep_snake_case_params! v }
  when Hash
    val.keys.each do |k, v = val[k]|
      val.delete k
      val[k.snakecase] = deep_snake_case_params!(v)
    end
    val
  else
    val
  end
end