ruby on rails - tutorial - Rails convierte arreglos vacíos en nils en params de la solicitud
rails parse json file (7)
Tengo un modelo Backbone en mi aplicación que no es un objeto plano típico, es un gran objeto anidado y almacenamos las partes anidadas en columnas TEXT en una base de datos MySQL.
Quería manejar la codificación / decodificación JSON en la API de Rails para que desde el exterior parezca que puedes POST / GET este gran objeto JSON anidado incluso si partes de él están almacenadas como texto JSON en cadena.
Sin embargo, me encontré con un problema donde Rails convierte mágicamente matrices vacías a valores nil
. Por ejemplo, si PUBLICO esto:
{
name: "foo",
surname: "bar",
nested_json: {
complicated: []
}
}
El controlador My Rails ve esto:
{
:name => "foo",
:surname => "bar",
:nested_json => {
:complicated => nil
}
}
Y entonces mis datos JSON han sido alterados ...
¿Alguien se ha encontrado con este problema antes? ¿Por qué Rails estaría modificando mis datos de POST?
ACTUALIZAR
Aquí es donde lo hacen:
https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/http/request.rb#L288
Y aquí está ~ por qué lo hacen:
https://github.com/rails/rails/pull/8862
Así que ahora la pregunta es, ¿cómo lidiar mejor con esto en mi situación API JSON anidada?
Aquí está (creo) una solución razonable que no implica volver a analizar el cuerpo de solicitud sin procesar. Puede que esto no funcione si su cliente está PUBLICANDO datos de formulario, pero en mi caso estoy PUBLICANDO JSON.
en 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
Entonces en tu controlador ...
before_action :fix_empty_child_params, only: [:update]
def fix_empty_child_params
super :user, [:child_ids, :foobar_ids]
end
Me encontré con esto y en mi situación, si un recurso POST contiene child_ids: []
o child_ids: nil
, quiero que esa actualización signifique "eliminar a todos los niños". Si el cliente tiene la intención de no actualizar la lista child_ids
entonces no se debe enviar en el cuerpo POST, en cuyo caso params[:resource].include? attr
params[:resource].include? attr
será false
y los parámetros de solicitud no se modificarán.
Aquí hay una forma de evitar este problema.
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
Y luego solo hazlo
fixed_params = fix_nils params
que funciona siempre que no tengas nils en tus params a propósito.
Después de mucha búsqueda, descubrí que comenzando en Rails 4.1 puede omitir la "característica" de deep_munge usando completamente
config.action_dispatch.perform_deep_munge = false
No pude encontrar ninguna documentación, pero puede ver la introducción de esta opción aquí: https://github.com/rails/rails/commit/e8572cf2f94872d81e7145da31d55c6e1b074247
Existe un posible riesgo de seguridad al hacerlo, documentado aquí: https://groups.google.com/forum/#!topic/rubyonrails-security/t1WFuuQyavI
Me encontré con un problema similar y descubrí que al pasar una matriz con una cadena vacía, Rails procesó correctamente, como se mencionó anteriormente. Si se encuentra con esto al enviar un formulario, es posible que desee incluir un campo oculto vacío que coincida con el parámetro de matriz:
<input type="hidden" name="model[attribute_ids][]"/>
Cuando el parámetro real está vacío, el controlador siempre verá una matriz con una cadena vacía, manteniendo así el envío sin estado.
Me encontré con un problema similar.
Se corrigió enviando una cadena vacía como parte de la matriz.
Entonces, idealmente tus params deberían gustar
{
name: "foo",
surname: "bar",
nested_json: {
complicated: [""]
}
}
Entonces, en lugar de enviar matriz vacía, siempre paso ("") en mi solicitud para eludir el proceso de munging profundo.
Parece que se trata de un problema conocido y recientemente introducido: https://github.com/rails/rails/issues/8832
Si sabe dónde estará la matriz vacía, siempre podría params[:...][:...] ||= []
en un filtro anterior.
Alternativamente, puede modificar el modelo de BackBone al método JSON, especificando el valor de JSON.stringify()
utilizando JSON.stringify()
antes de publicarlo y JSON.parse
manualmente de nuevo utilizando JSON.parse
en un before_filter.
Feo, pero funcionará.
Puede volver a analizar los parámetros usted mismo, así:
class ApiController
before_filter :fix_json_params # Rails 4 or earlier
# before_action :fix_json_params # Rails 5
[...]
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
Esto funciona buscando solicitudes con un tipo de contenido JSON, volviendo a analizar el cuerpo de la solicitud y luego interceptando el método params
para devolver los parámetros re-analizados, si es que existen.