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
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, 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 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
}
)
Redémarrez Rails server
.
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
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
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
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
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
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
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.
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
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
ActionController::Parameters#deep_snakeize
ApplicationController#snakeize_params
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
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