web-dev-qa-db-fra.com

Quel est le meilleur moyen de convertir tous les paramètres du contrôleur de camelCase en snake_case dans Rails?

Comme vous le savez déjà, la convention de nommage JSON préconise l’utilisation de camelSpace et Rails préconise l’utilisation de snake_case pour les noms de paramètres.

Quel est le meilleur moyen de convertir tous les paramètres de la requête en snake_case dans un contrôleur Rails?

À partir de ceci:

{
  ...
  "firstName": "John",
  "lastName": "Smith",
  "moreInfo":
  {
    "mealType": 2,
    "mealSize": 4,
    ...
  }
}

pour ça:

{
  ...
  "first_name": "John",
  "last_name": "Smith",
  "more_info":
  {
    "meal_type": 2,
    "meal_size": 4,
    ...
  }
}

Je vous remercie

26
a.s.t.r.o

Une fois que vous avez terminé les étapes ci-dessous, les noms de paramètres camelCase soumis via des demandes JSON seront remplacés par snake_case.

Par exemple, un paramètre de requête JSON nommé passwordConfirmation serait accessible dans un contrôleur en tant que params[:password_confirmation]

Créez un initialiseur à config/initializers/json_param_key_transform.rb. Ce fichier va modifier le comportement d'analyse des paramètres pour les demandes JSON uniquement (les demandes JSON doivent avoir l'en-tête de demande Content-Type: application/json).

Recherchez votre version de Rails et choisissez la section appropriée ci-dessous (recherchez votre version de Rails dans Gemfile.lock):

Pour Rails 5 uniquement

Pour Rails 5, pour convertir les clés de paramètres camel-case en snake-case, placez-le dans l'initialiseur:

# 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] = -> (raw_post) {
  # Modified from action_dispatch/http/parameters.rb
  data = ActiveSupport::JSON.decode(raw_post)
  data = {:_json => data} unless data.is_a?(Hash)

  # Transform camelCase param keys to snake_case:
  data.deep_transform_keys!(&:underscore)
}

Pour Rails 4.2 (et peut-être des versions antérieures)

Pour Rails 4.2 (et peut-être des versions antérieures), pour convertir les clés de paramètres camel-case en snake-case, placez-le dans l'initialiseur:

# 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
  }
)

Dernière étape pour toutes les versions de Rails

Redémarrez Rails server.

49
Eliot Sykes

ActiveSupport fournit déjà une méthode String # snakecase. Tout ce que vous avez à faire est d'installer un filtre qui effectue une itération approfondie à travers le hachage params et remplace les clés par 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
8
Chris Heald

En fusionnant la réponse de Sebastian Hoitz avec ce Gist , je pourrais le faire fonctionner sur Rails 4.2, paramètres puissants ET paramètres encapsulés avec la méthode wrap_parameters .

Je ne pouvais pas le faire fonctionner avec un before_filter, probablement parce que le wrapping des paramètres est fait avant le filtrage.

Dans config/initializers/wrap_parameters.rb:

# Convert json parameters, sent from Javascript UI, from camelCase to snake_case.
# This bridges the gap between javascript and Ruby naming conventions.
module ActionController
  module ParamsNormalizer
    extend ActiveSupport::Concern

    def process_action(*args)
      deep_underscore_params!(request.parameters)
      super
    end

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

# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
ActiveSupport.on_load(:action_controller) do
  wrap_parameters format: [:json] if respond_to?(:wrap_parameters)
  # Include the above defined concern
  include ::ActionController::ParamsNormalizer
end
8
patatepartie

Exemple avec camelCase to snake_case dans la console Rails

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>

la source:

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

6
Hubert Olender

Solution pour Rails 5

before_action :underscore_params!

def underscore_params!
  underscore_hash = -> (hash) do
    hash.transform_keys!(&:underscore)
    hash.each do |key, value|
      if value.is_a?(ActionController::Parameters)
        underscore_hash.call(value)
      elsif value.is_a?(Array)
        value.each do |item|
          next unless item.is_a?(ActionController::Parameters)
          underscore_hash.call(item)
        end
      end
    end
  end
  underscore_hash.call(params)
end
4
Artur Babagulyyev

Une autre solution Rails 5.1 qui tire parti de la solution de Sebastian Hoitz ci-dessus. Pour clarifier pourquoi nous devons faire cela: dans R5.1 deep_transform_keys! n'est plus une méthode disponible pour nous, car les paramètres n'héritent plus de HashWithIndifferentAccess. Et surmonte le problème mentionné par Eliot Sykes où l’initialiseur ne fonctionne que pour les types application/json mime. Cependant, cela ajoute une surcharge à toutes les demandes. (J'aimerais bien voir des initialiseurs pour ActionDispatch::Request.parameter_parsers[:multipart_form]) cependant, puisque l'initialiseur est un meilleur endroit pour le faire, IMO.

before_action :normalize_key!

 def normalize_keys!(val = params)
  if val.class == Array
    val.map { |v| normalize_keys! v }
  else
    if val.respond_to?(:keys)
      val.keys.each do |k|
        current_key_value = val[k]
        val.delete k
        val[k.to_s.underscore] = normalize_keys!(current_key_value)
      end
    end
    val
  end
  val
end
2
Mike

Je voulais utiliser la version de Chris Healds, mais depuis que j'utilise Rails 4, j'ai strong_parameters activé, donc j'ai du changer un peu.

Ceci est la version que je suis venu avec:

before_filter :deep_underscore_params!


def deep_underscore_params!(val = request.parameters)
  case val
  when Array
    val.map { |v| deep_underscore_params!(v) }
  when Hash
    val.keys.each do |k, v = val[k]|
      val.delete k
      val[k.underscore] = deep_underscore_params!(v)
    end

    params = val
  else
    val
  end
end
2
Sebastian Hoitz

Vous pouvez créer un filtre qui s'exécute avant tout appel du contrôleur et lui appliquer les instructions suivantes:

# 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)

Vous devez avoir à considérer que la procédure ci-dessus imposera une petite surcharge à chaque appel Rails. 

Je ne suis pas convaincu que cela soit nécessaire, si ce n'est que pour s’inscrire dans une convention.

0
Thiago Lewin

vous pouvez essayer ceci:

class ApplicationController < ActionController::API
  include ControllerHelper
  before_action :deep_underscore_params!

  def deep_underscore_params!(app_params = params)
    app_params.transform_keys!(&:underscore)
    app_params.each do |key, value|
      deep_underscore_params!(value) if value.instance_of?(ActionController::Parameters)
    end
    app_params.reject! { |k, v| v.blank? }
  end
end
0
lianjie zhu

Nous convertissons nos clés JSON API Rails de snake_case à camelCase. Nous devons faire la conversion de manière incrémentielle, c’est-à-dire que certaines API fonctionnent avec snake_case, tandis que d’autres passent à camelCase.

Notre solution est que nous 

  • créer la méthode ActionController::Parameters#deep_snakeize
  • créer la méthode ApplicationController#snakeize_params 
  • définissez before_action :snakeize_params uniquement pour les actions du contrôleur qui gèrent les demandes entrantes à l'aide des touches camelCase.

Vous pouvez essayer vochicong/Rails-json-api pour un exemple d'application Rails pleinement opérationnel.

# File: config/initializers/params_snakeizer.rb
# Transform JSON request param keys from JSON-conventional camelCase to
# Rails-conventional snake_case
module ActionController
  # Modified from action_controller/metal/strong_parameters.rb
  class Parameters
    def deep_snakeize!
      @parameters.deep_transform_keys!(&:underscore)
      self
    end
  end
end

# File: app/controllers/application_controller.rb
class ApplicationController < ActionController::API
  protected

    # Snakeize JSON API request params
    def snakeize_params
      params.deep_snakeize!
    end
end

class UsersController < ApplicationController
  before_action :snakeize_params, only: [:create]

  # POST /users
  def create
    @user = User.new(user_params)

    if @user.save
      render :show, status: :created, location: @user
    else
      render json: @user.errors, status: :unprocessable_entity
    end
  end
end
0
vochicong

la réponse de tlewin n'a pas fonctionné pour moi dans Rails 3. Il semble que l'opérateur params = supprime les futures opérations. très étrange. Quoi qu'il en soit, ce qui suit fonctionne pour moi, car il utilise uniquement les opérateurs [] = et delete:

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
jakeonfire