web-dev-qa-db-fra.com

Rails convertit les tableaux vides en nils dans les paramètres de la requête

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?

59
Karolis

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

41
a10s

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.

10
latentflip

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.

8
Grandpa

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.

2
thom_nic

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.

2
waqar mirza

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.

1
jean-baptiste