J'ai un modèle Backbone dans mon application qui n'est pas un objet plat typique, c'est un grand objet imbriqué et nous stockons les pièces imbriquées dans des colonnes TEXT dans une base de données MySQL.
Je voulais gérer l'encodage/décodage JSON dans Rails API de sorte que de l'extérieur, il semble que vous puissiez POST/GET ce seul grand objet JSON imbriqué même si des parties de celui-ci sont stockées sous forme de texte JSON stratifié .
Cependant, j'ai rencontré un problème où Rails convertit comme par magie des tableaux vides en valeurs de nil
. Par exemple, si I POST ceci:
{
name: "foo",
surname: "bar",
nested_json: {
complicated: []
}
}
Mon Rails voit ceci:
{
:name => "foo",
:surname => "bar",
:nested_json => {
:complicated => nil
}
}
Et donc mes données JSON ont été modifiées ..
Quelqu'un a-t-il déjà rencontré ce problème? Pourquoi Rails modifierait-il mes données POST?
[~ # ~] mise à 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 cela dans ma situation d'API JSON imbriquée?
Après beaucoup de recherches, j'ai découvert que vous commençant 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 trouvé aucune 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
Il semble que ce soit un problème connu récemment introduit: https://github.com/Rails/rails/issues/8832
Si vous savez où sera le tableau vide, vous pouvez toujours params[:...][:...] ||= []
Dans un filtre avant.
Alternativement, vous pouvez modifier la méthode JSON de votre modèle BackBone, en chaîne explicitement la valeur nested_json en utilisant JSON.stringify()
avant qu'il ne soit publié et en l'analysant manuellement en utilisant JSON.parse
Dans un filtre before_filter.
Laid, mais ça marchera.
Vous pouvez ré-analyser les paramètres par 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.string).with_indifferent_access
end
end
private
def params
@reparsed_params || super
end
end
Cela fonctionne en recherchant les demandes avec un type de contenu JSON, en analysant à nouveau le corps de la demande, puis en interceptant la méthode params
pour renvoyer les paramètres analysés s'ils existent.
Voici (je crois) une solution raisonnable qui n'implique pas une nouvelle analyse du corps de la demande brute. Cela peut ne pas fonctionner si votre client POSTE des données de formulaire mais dans mon cas, je POSTE du 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
Ensuite, dans votre contrôleur ....
before_action :fix_empty_child_params, only: [:update]
def fix_empty_child_params
super :user, [:child_ids, :foobar_ids]
end
Je suis tombé sur cela 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 le child_ids
alors il ne doit pas être envoyé dans le corps POST, auquel cas params[:resource].include? attr
sera false
et les paramètres de la demande seront inchangés.
J'ai rencontré un problème similaire.
Corrigé en envoyant une chaîne vide dans le cadre du tableau.
Donc, idéalement, vos params devraient aimer
{
name: "foo",
surname: "bar",
nested_json: {
complicated: [""]
}
}
Donc, au lieu d'envoyer un tableau vide, je passe toujours ("") dans ma demande pour contourner le processus de munging profond.
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 cela lors de la soumission d'un formulaire, vous souhaiterez peut-être inclure un champ masqué vide qui correspond au paramètre du tableau:
<input type="hidden" name="model[attribute_ids][]"/>
Lorsque le paramètre réel est vide, le contrôleur verra toujours un tableau avec une chaîne vide, gardant ainsi la soumission sans état.