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

Rails преобразует пустые массивы в nils в params запроса

У меня есть модель Backbone в моем приложении, которая не является типичным плоским объектом, это большой вложенный объект, и мы храним вложенные части в столбцах TEXT в базе данных MySQL.

Я хотел обрабатывать кодирование/декодирование JSON в Rails API, так что извне это выглядит так, что вы можете POST/GET этот один большой вложенный объект JSON, даже если его части хранятся как сжатый текст JSON.

Однако я столкнулся с проблемой, когда Rails волшебным образом преобразует пустые массивы в значения nil. Например, если я ПОЧТАЮ это:

{
  name: "foo",
  surname: "bar",
  nested_json: {
    complicated: []
  }
}

Контроллер My Rails видит это:

{
  :name => "foo",
  :surname => "bar",
  :nested_json => {
    :complicated => nil
  }
}

И поэтому мои данные JSON были изменены.

Кто-нибудь сталкивался с этой проблемой раньше? Почему Rails меняет мои данные POST?

UPDATE

Вот где они это делают:

https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/http/request.rb#L288

И вот почему они это делают:

https://github.com/rails/rails/pull/8862

Итак, теперь вопрос в том, как лучше всего справиться с этим в моей вложенной ситуации API JSON?

4b9b3361

Ответ 1

После долгих поисков я обнаружил, что вы начинаете с Rails 4.1, вы можете полностью пропустить функцию "deep_munge", используя

config.action_dispatch.perform_deep_munge = false

Я не мог найти никакой документации, но вы можете просмотреть введение этой опции здесь: https://github.com/rails/rails/commit/e8572cf2f94872d81e7145da31d55c6e1b074247

Существует риск для безопасности, описанный здесь: https://groups.google.com/forum/#!topic/rubyonrails-security/t1WFuuQyavI

Ответ 2

Похоже, это известная недавно выпущенная проблема: https://github.com/rails/rails/issues/8832

Если вы знаете, где будет пустой массив, вы всегда можете params[:...][:...] ||= [] в переднем фильтре.

В качестве альтернативы вы можете изменить модель BackBone на метод JSON, явно привязывая значение nested_json с помощью JSON.stringify() до того, как оно будет опубликовано, и вручную отрисует его обратно с помощью JSON.parse в файле before_filter.

Уродливо, но это сработает.

Ответ 3

Вы можете повторно проанализировать параметры самостоятельно, например:

class ApiController
  before_filter :fix_json_params

  [...]

  protected

  def fix_json_params
    if request.content_type == "application/json"
      @reparsed_params = JSON.parse(request.body.string).with_indifferent_access
    end
  end

  private

  def params
    @reparsed_params || super
  end
end

Это работает путем поиска запросов с типом содержимого JSON, повторного разбора тела запроса и последующего перехвата метода params для возврата повторно проанализированных параметров, если они существуют.

Ответ 4

У меня возникла аналогичная проблема.

Исправлено его, посылая пустую строку как часть массива.

В идеале ваши параметры должны нравиться

{
  name: "foo",
  surname: "bar",
  nested_json: {
    complicated: [""]
  }
}

Поэтому вместо отправки пустого массива я всегда передаю ("") в свой запрос, чтобы обойти процесс глубокого перебора.

Ответ 5

Здесь (я считаю) разумное решение, которое не требует повторного анализа тела необработанного запроса. Это может не сработать, если ваш клиент является POSTing данными формы, но в моем случае я ПОЧТИ JSON.

в application_controller.rb:

  # replace nil child params with empty list so updates occur correctly
  def fix_empty_child_params resource, attrs
    attrs.each do |attr|
      params[resource][attr] = [] if params[resource].include? attr and params[resource][attr].nil?
    end
  end

Затем в вашем контроллере....

before_action :fix_empty_child_params, only: [:update]

def fix_empty_child_params
  super :user, [:child_ids, :foobar_ids]
end

Я столкнулся с этим и в моей ситуации, если ресурс POSTed содержит либо child_ids: [], либо child_ids: nil, я хочу, чтобы это обновление означало "удалить всех детей". Если клиент намеревается не обновлять список child_ids, он не должен быть отправлен в тело POST, и в этом случае params[:resource].include? attr будет false, а параметры запроса не будут изменены.

Ответ 6

Я столкнулся с аналогичной проблемой и выяснил, что передача массива с пустой строкой будет корректно обработана Rails, как упоминалось выше. Если вы столкнулись с этим при отправке формы, вы можете включить пустое скрытое поле, которое соответствует параметру array:

<input type="hidden" name="model[attribute_ids][]"/>

Когда фактический параметр пуст, контроллер всегда будет видеть массив с пустой строкой, тем самым сохраняя отправку без сохранения.

Ответ 7

Здесь можно обойти эту проблему.

def fix_nils obj

  # fixes an issue where rails turns [] into nil in json data passed to params

  case obj
    when nil
      return []
    when Array
      return obj.collect { |x| nils_to_empty_arrays x }
    when Hash
      newobj = {}
      obj.each do |k,v|
        newobj[k] = nils_to_empty_arrays v
      end
      return newobj
    else
      return obj
  end

end

А потом просто

fixed_params = fix_nils params

который работает до тех пор, пока у вас нет нулей в ваших параметрах по назначению.