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?
Ответы
Ответ 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
который работает до тех пор, пока у вас нет нулей в ваших параметрах по назначению.