Rails convertit les tableaux vides en nils dans les parameters de la requête

J’ai un modèle Backbone dans mon application qui n’est pas un object plat typique, c’est un gros object nested et nous stockons les parties nestedes dans des colonnes TEXT dans une firebase database MySQL.

Je voulais gérer l’encodage / décodage JSON dans l’API Rails afin que de l’extérieur, il semble que vous puissiez POST / GET cet object JSON nested de grande taille, même si certaines parties sont stockées en tant que texte JSON stratifié.

Cependant, j’ai rencontré un problème où Rails convertit comme par magie des tableaux vides en valeurs nil . Par exemple, si je poste ceci:

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

Mon contrôleur Rails voit cela:

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

Et donc mes données JSON ont été modifiées.

Quelqu’un at-il déjà rencontré ce problème? Pourquoi Rails modifierait-il mes données POST?

METTRE À JOUR

Voici où ils le font:

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

Et voici ~ pourquoi ils le font:

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

Alors maintenant, la question est de savoir comment gérer au mieux cette situation dans ma situation d’API JSON nestede?

Après beaucoup de recherches, j’ai découvert que vous commencez dans Rails 4.1, vous pouvez ignorer complètement la “fonctionnalité” deep_munge en utilisant

 config.action_dispatch.perform_deep_munge = false 

Je n’ai pas trouvé de documentation, mais vous pouvez voir l’introduction de cette option ici: https://github.com/rails/rails/commit/e8572cf2f94872d81e7145da31d55c6e1b074247

Il existe un risque de sécurité possible, documenté ici: https://groups.google.com/forum/#!topic/rubyonrails-security/t1WFuuQyavI

On dirait que c’est un problème connu et récemment introduit: https://github.com/rails/rails/issues/8832

Si vous savez où le tableau vide sera, vous pouvez toujours params[:...][:...] ||= [] dans un filtre avant.

Vous pouvez également modifier la méthode JSON de votre modèle BackBone, en explicitant explicitement la valeur nested_json à l’aide de JSON.ssortingngify() avant de la JSON.ssortingngify() et en la analysant manuellement à l’aide de JSON.parse dans un before_filter.

Moche, mais ça va marcher.

Vous pouvez ré-parsingr les parameters vous-même, comme ceci:

 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.ssortingng).with_indifferent_access end end private def params @reparsed_params || super end end 

Cela fonctionne en recherchant les requêtes avec un type de contenu JSON, en analysant à nouveau le corps de la requête, puis en interceptant la méthode params pour renvoyer les parameters ré-analysés s’ils existent.

J’ai rencontré un problème similaire.

Correction du problème en envoyant une chaîne vide dans le tableau.

Donc, idéalement, vos parameters devraient aimer

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

Donc, au lieu d’envoyer un tableau vide, je passe toujours (“”) dans ma requête pour contourner le processus de fusion profonde.

Voici (je crois) une solution raisonnable qui n’implique pas une nouvelle parsing du corps de la requête brute. Cela ne fonctionnera peut-être pas si votre client envoie des données de formulaire POST, mais dans mon cas, je lance un JSON.

dans 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 

Alors dans votre contrôleur ….

 before_action :fix_empty_child_params, only: [:update] def fix_empty_child_params super :user, [:child_ids, :foobar_ids] end 

J’ai rencontré ceci et dans ma situation, si une ressource POSTed contient soit child_ids: [] ou child_ids: nil je veux que cette mise à jour signifie “supprimer tous les enfants”. Si le client a l’intention de ne pas mettre à jour la liste child_ids il ne devrait pas être envoyé dans le corps POST, auquel cas params[:resource].include? attr params[:resource].include? attr sera false et les parameters de la requête seront inchangés.

J’ai rencontré un problème similaire et j’ai découvert que le passage d’un tableau avec une chaîne vide serait traité correctement par Rails, comme mentionné ci-dessus. Si vous rencontrez ce problème lors de la soumission d’un formulaire, vous souhaiterez peut-être inclure un champ masqué vide correspondant au paramètre du tableau:

  

Lorsque le paramètre réel est vide, le contrôleur verra toujours un tableau avec une chaîne vide, conservant ainsi la soumission sans état.

Voici un moyen de contourner ce problème.

 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 

Et puis juste faire

 fixed_params = fix_nils params 

qui fonctionne aussi longtemps que vous n’avez pas volontairement de nils dans vos parameters.