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

后端 未结 14 822
灰色年华
灰色年华 2020-12-23 00:02

As you already know, JSON naming convention advocates the use of camelSpace and the Rails advocates the use of snake_case for parameter names.

What is the best way t

相关标签:
14条回答
  • 2020-12-23 00:12

    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
    
    0 讨论(0)
  • 2020-12-23 00:14

    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.

    0 讨论(0)
  • 2020-12-23 00:15

    You can create a filter that runs before any controller call and apply the following instructions to it:

    # transform camel case string into snake case
    snake_string  = Proc.new {|s| s.gsub(/([a-z])([A-Z])/) {|t| "#{$1}_#{$2.downcase}"}} 
    
    # transform all hash keys into snake case
    snake_hash    = Proc.new do |hash| 
      hash.inject({}) do |memo, item|
        key, value = item
    
        key = case key
              when String
                snake_string.call(key)
              when Symbol
                snake_string.call(key.to_s).to_sym
              else 
                key
              end    
    
        memo[key] = value.instance_of?(Hash) ? snake_hash.call(value) : value
        memo
      end
    end
    
    params = snake_hash.call(params)
    

    You must have to consider the above procedure will impose a small overhead on every Rails call.

    I am not convinced this is necessary, if it is just to fit in a convention.

    0 讨论(0)
  • 2020-12-23 00:16

    Riffing on Eliot Sykes's answer above, I think we can do a bit better in the Rails5 case. I don't love overwriting that function entirely, since that code could change. So instead I suggest using function composition:

    # 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] = (
      # Compose the original parser with a transformation
      ActionDispatch::Request.parameter_parsers[:json] >>
        # Transform camelCase param keys to snake_case
        ->(data) {
          data.deep_transform_keys(&:underscore)
        }
    )
    
    0 讨论(0)
  • 2020-12-23 00:24

    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)
    
    0 讨论(0)
  • 2020-12-23 00:28

    tlewin's answer didn't work for me in Rails 3. it seems the params' = operator renders future operations on it void. very strange. anyways the following works for me, as it only uses the []= and delete operators:

    before_filter :underscore_param_keys
    def underscore_param_keys
      snake_hash = ->(hash) {
        # copying the hash for iteration so we are not altering and iterating over the same object
        hash.to_a.each do |key, value|
          hash.delete key
          hash[key.to_s.underscore] = value
          snake_hash.call(value) if value.is_a? Hash
          value.each { |item| snake_hash.call(item) if item.is_a? Hash } if value.is_a? Array
        end
      }
      snake_hash.call(params)
    end
    
    0 讨论(0)
提交回复
热议问题