Подтвердить что ты не робот

Каков наилучший способ преобразования всех параметров контроллера из camelCase в snake_case в Rails?

Как вы уже знаете, соглашение об именах JSON защищает использование camelSpace, а Rails защищает использование snake_case для имен параметров.

Каков наилучший способ преобразования всех параметров запроса в snake_case в контроллере rails?

Из этого:

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

:

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

Спасибо

4b9b3361

Ответ 1

Когда вы выполните приведенные ниже шаги, camelCase имена параметров, представленные через запросы JSON, будут изменены на snake_case.

Например, параметр запроса JSON с именем passwordConfirmation будет доступен в контроллере как params[:password_confirmation]

Создайте инициализатор в config/initializers/json_param_key_transform.rb. Этот файл будет изменять поведение разбора параметров только для запросов JSON (запросы JSON должны иметь заголовок запроса Content-Type: application/json).

Найдите версию Rails и выберите соответствующий раздел ниже (найдите версию Rails в Gemfile.lock):

Только для Rails 5

Для Rails 5, чтобы преобразовать ключи params camel-case в змеиный случай, поместите это в инициализатор:

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

Для Rails 4.2 (и, возможно, более ранних версий)

Для Rails 4.2 (и, возможно, более ранних версий), чтобы преобразовать ключи params camel-case в змеиный случай, поместите это в инициализатор:

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

Конечный шаг для всех версий Rails

Перезапустите rails server.

Ответ 2

ActiveSupport уже предоставляет метод snarkecase String #. Все, что вам нужно сделать, это установить фильтр, который выполняет глубокую итерацию с помощью хэша params и заменяет ключи 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

Ответ 3

Объединив ответ Себастьяна Хойца с этот смысл, я мог бы заставить его работать с рельсами 4.2, сильными параметрами и параметрами, обертывающими с помощью wrap_parameters метод тегов.

Я не мог заставить его работать с помощью before_filter, возможно, потому, что перенос параметров выполняется до фильтрации.

В 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

Ответ 4

Пример использования camelCase для snake_case в консоли 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>

источник:

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

Ответ 5

Решение для 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

Ответ 6

Другое решение для рельсов 5.1, которое поросенок отступает от решения Sebastian Hoitz выше. Чтобы уточнить, зачем нам это нужно: в R5.1 deep_transform_keys! больше не является методом, доступным для нас, поскольку params больше не наследуются от HashWithIndifferentAccess. И преодолевает проблему, упомянутую Элиотом Сайкесом, где инициализатор работает только для приложений /json mime. Тем не менее, он добавляет накладные расходы на все запросы. (Мне бы хотелось увидеть некоторые инициализаторы для ActionDispatch::Request.parameter_parsers[:multipart_form]), хотя, поскольку инициализатор - лучшее место для этого, 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

Ответ 7

Я хотел использовать версию Chris Healds, но поскольку я использую Rails 4, у меня есть сильные_параметры, поэтому мне пришлось немного изменить его.

Это версия, с которой я столкнулся:

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

Ответ 8

Мы конвертируем наши ключи JSON Rails API из snake_case в camelCase. Мы должны делать преобразование постепенно, т.е. Некоторые API работают с snake_case, а другие - с помощью camelCase.

Наше решение состоит в том, что мы

  • создать метод ActionController::Parameters#deep_snakeize
  • создать метод ApplicationController#snakeize_params
  • установить before_action :snakeize_params только для действий контроллера, которые обрабатывают входящий запрос с помощью клавиш camelCase.

Вы можете попробовать vochicong/rails-json-api для полностью работающего примера приложения Rails.

# 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

Ответ 9

Я не мог использовать другие предложения здесь напрямую, но это привело меня на правильный путь.

С Rails 5.2, используя версионный API и, следовательно, не может изменить его для всего приложения. Я создал эту проблему, которую затем включил в базовый контроллер моего нового модуля версии API.

module UnderscoreizeParams
  extend ActiveSupport::Concern

  def process_action(*args)
    request.parameters.deep_transform_keys!(&:underscore)
    super
  end
end

тогда в моем API V3 BaseController

class V3::BaseController
  include UnderscoreizeParams
end

наслаждаться.

Ответ 10

Вы можете создать фильтр, который запускается до вызова контроллера, и применить к нему следующие инструкции:

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

Вы должны учитывать, что описанная выше процедура накладывает небольшие накладные расходы на каждый вызов Rails.

Я не уверен, что это необходимо, если оно просто подходит к соглашению.

Ответ 11

Ответ tlewin не работал у меня в Rails 3. Кажется, оператор params = = делает будущие операции на нем недействительными. очень странно. в любом случае для меня работает следующее, поскольку оно использует только операторы [] = и 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

Ответ 12

Вы можете попробовать это:

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

Ответ 13

Обращаясь к ответу Элиота Сайкса выше, я думаю, что мы можем сделать немного лучше в случае с Rails5. Я не люблю полностью переписывать эту функцию, так как этот код может измениться. Поэтому вместо этого я предлагаю использовать композицию функций:

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

Ответ 14

Rails 6 добавил deep_transform_keys в ActionController::Parameters, чтобы вы могли сделать это так просто, как:

class ApplicationController < ActionController::Base
  before_action :underscore_params!

  private

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