web-dev-qa-db-fra.com

API Rails 5.1 - Comment autoriser les paramètres pour les attributs d'objet JSON imbriqués

Il y a au moins 10 questions sur ce sujet mais aucune d’entre elles ne répond à ce problème particulier. Beaucoup de questions concernent des formes Rails comme this , que je n'ai pas encore, ou à des structures plus compliquées, comme this ou this .


EDIT concernant la réponse acceptée et la raison pour laquelle il ne s'agit pas d'un duplicata exact

La question liée dans la réponse de @CarlosRoque semble initialement être le même problème mais cela ne résout que le côté Rails de ce problème particulier.

Si vous lisez tous les commentaires, vous constaterez que plusieurs tentatives de modification de la méthode template_params ont été effectuées pour RENAME ou pour REMPLACER l'attribut imbriqué "template_items" avec "template_items_attributes". Cela est nécessaire car Rails accepts_nested_attributes_for requiert que "_attributes" soit ajouté au nom, sinon il ne peut pas le voir.

Si vous examinez le code du correctif de singe dans cette réponse pour corriger les paramètres wrap_parameters afin qu'il fonctionne pour les attributs imbriqués, le problème persiste: il ne trouvera pas réellement "template_items" (l'objet imbriqué) car il n'a pas le suffixe "_attributes". ".

Par conséquent, pour résoudre complètement ce problème, le client devait également être modifié pour envoyer l'objet imbriqué sous la forme "template_items_attributes". Pour les clients JS, cela peut être fait en implémentant une méthode toJSON () sur l'objet pour le modifier lors de la sérialisation (exemple ici ). Sachez toutefois que lorsque vous désérialiserez le JSON, vous devrez créer manuellement une instance de cet objet pour que toJSON () fonctionne (a expliqué pourquoi ici ).


J'ai un simple has_many/appartient_to:

Des modèles:

class Template < ApplicationRecord
  belongs_to :account
  has_many :template_items
  accepts_nested_attributes_for :template_items, allow_destroy: true
end


class TemplateItem < ApplicationRecord  
  belongs_to :template
  validates_presence_of :template

  enum item_type: {item: 0, heading: 1} 
end

Le json envoyé par le client ressemble à ceci:

{
  "id": "55e27eb7-1151-439d-87b7-2eba07f3e1f7",
  "account_id": "a61151b8-deed-4efa-8cad-da1b143196c9",
  "name": "Test",
  "info": "INFO1234",
  "title": "TITLE1",
  "template_items": [
    {
      "is_completed": false,
      "item_type": "item"
    },
    {
      "is_completed": false,
      "item_type": "heading"
    }
  ]
}

Parfois, il y aura un attribut :id et un attribut :content dans chaque template_item (par exemple, une fois qu'ils auront été créés et que l'utilisateur commencera à les éditer).

La méthode template_params du templates_controller ressemble à ceci:

   params.require(:template).permit(
      :id, :account_id, :name, :title, :info, 
      template_items: [:id, :is_completed, :content, :item_type]
  )

S'il s'agissait d'un formulaire Rails, cette ligne serait:

   params.require(:template).permit(
      :id, :account_id, :name, :title, :info, 
      template_items_attributes: [:id, :is_completed, :content, :item_type]
  )

pour enregistrer les objets enfants imbriqués dans le cadre de l'action de mise à jour du modèle parent.

J'ai essayé de changer le nom du paramètre imbriqué:

def template_params
  params.require(:template).permit(:id, :account_id, :name, :title, :info, template_items: [:id, :is_completed, :content, :item_type])
  params[:template_items_attributes] = params.delete(:template_items) if params[:template_items]
  Rails.logger.info params
end

et je peux voir qu'ils ne sont toujours pas autorisés:

{  
      "template"   =><ActionController::Parameters   {  
      "id"      =>"55e27eb7-1151-439d-87b7-2eba07f3e1f7",
      "account_id"      =>"a61151b8-deed-4efa-8cad-da1b143196c9",
      "name"      =>"Test",
      "info"      =>"INFO1234",
      "title"      =>"TITLE1",
   }   permitted:false   >,
   "template_items_attributes"   =>   [  
      <ActionController::Parameters      {  
         "is_completed"         =>false,
         "item_type"         =>"item"
      }      permitted:false      >,
      <ActionController::Parameters      {  
         "is_completed"         =>false,
         "item_type"         =>"item"
      }      permitted:false      >
   ]
}

J'ai aussi essayé de fusionner:

template_params.merge! ({template_items_attributes:
params[:template_items]}) if params[:template_items].present?

Même problème.

Alors, comment puis-je m'assurer qu'ils sont autorisés et inclus dans template_params SANS que faire .permit! (ie. Je ne veux pas tout permettre aveuglément)?

La méthode de mise à jour du contrôleur:

def update
    Rails.logger.info "*******HERE*******"
    Rails.logger.info template_params
    @template.template_items = template_params[:template_items_attributes]

    if @template.update(template_params)
      render json: @template
    else
      render json: ErrorSerializer.serialize(@template.errors), status: :unprocessable_entity
    end
  end

UDPATE

Si j'envoie depuis le client "template_items_attributes" au lieu de "template_items" dans les paramètres de Rails, puis effectuez les templates_params recommandés comme ceci:

    def template_params
      params.require(:template).permit(:id, :account_id, :name, :title, :info, template_items_attributes: [:id, :is_completed, :content, :item_type])
    end

cela ne crée toujours pas de nouveaux enfants pour le modèle!

Avec ceci en place, je produis les paramètres avant et après, comme ceci:

def update
    Rails.logger.info params
    Rails.logger.info "*******HERE*******"    
    Rails.logger.info template_params

    if @template.update(template_params)
      render json: @template
    else
      render json: ErrorSerializer.serialize(@template.errors), status: :unprocessable_entity
    end
  end

Et voici le journal de ce scénario - Rails ignore TOUJOURS complètement le tableau incorporé. Remarquez que param, juste avant ICI, indique autorisé: false, puis template_params ne contient plus les enfants "template_items_attributes" et est marqué comme autorisé: true.

I, [2017-10-20T21:52:39.886104 #28142]  INFO -- : Processing by Api::TemplatesController#update as JSON
I, [2017-10-20T21:52:39.886254 #28142]  INFO -- :   Parameters: {"id"=>"55e27eb7-1151-439d-87b7-2eba07f3e1f7", "account_id"=>"a61151b8-deed-4efa-8cad-da1b143196c9", "name"=>"Test", "info"=>"INFO12345", "title"=>"TITLE1", "created_at"=>"2017-10-14T19:30:41.450Z", "updated_at"=>"2017-10-20T17:48:24.909Z", "template_items_attributes"=>[{"is_completed"=>false, "item_type"=>"item"}], "template"=>{"id"=>"55e27eb7-1151-439d-87b7-2eba07f3e1f7", "account_id"=>"a61151b8-deed-4efa-8cad-da1b143196c9", "name"=>"Test", "info"=>"INFO12345", "title"=>"TITLE1", "created_at"=>"2017-10-14T19:30:41.450Z", "updated_at"=>"2017-10-20T17:48:24.909Z"}}
D, [2017-10-20T21:52:39.903011 #28142] DEBUG -- :   User Load (7.7ms)  SELECT  "users".* FROM "users" WHERE "users"."uid" = $1 LIMIT $2  [["uid", "[email protected]"], ["LIMIT", 1]]
D, [2017-10-20T21:52:40.072148 #28142] DEBUG -- :   Template Load (1.4ms)  SELECT  "templates".* FROM "templates" WHERE "templates"."id" = $1 ORDER BY name ASC LIMIT $2  [["id", "55e27eb7-1151-439d-87b7-2eba07f3e1f7"], ["LIMIT", 1]]
I, [2017-10-20T21:52:40.083727 #28142]  INFO -- : <ActionController::Parameters {"id"=>"55e27eb7-1151-439d-87b7-2eba07f3e1f7", "account_id"=>"a61151b8-deed-4efa-8cad-da1b143196c9", "name"=>"Test", "info"=>"INFO12345", "title"=>"TITLE1", "created_at"=>"2017-10-14T19:30:41.450Z", "updated_at"=>"2017-10-20T17:48:24.909Z", "template_items_attributes"=>[{"is_completed"=>false, "item_type"=>"item"}], "controller"=>"api/templates", "action"=>"update", "template"=>{"id"=>"55e27eb7-1151-439d-87b7-2eba07f3e1f7", "account_id"=>"a61151b8-deed-4efa-8cad-da1b143196c9", "name"=>"Test", "info"=>"INFO12345", "title"=>"TITLE1", "created_at"=>"2017-10-14T19:30:41.450Z", "updated_at"=>"2017-10-20T17:48:24.909Z"}} permitted: false>
I, [2017-10-20T21:52:40.083870 #28142]  INFO -- : *******HERE*******
D, [2017-10-20T21:52:40.084550 #28142] DEBUG -- : Unpermitted parameters: :created_at, :updated_at
I, [2017-10-20T21:52:40.084607 #28142]  INFO -- : <ActionController::Parameters {"id"=>"55e27eb7-1151-439d-87b7-2eba07f3e1f7", "account_id"=>"a61151b8-deed-4efa-8cad-da1b143196c9", "name"=>"Test", "title"=>"TITLE1", "info"=>"INFO12345"} permitted: true>
D, [2017-10-20T21:52:40.084923 #28142] DEBUG -- : Unpermitted parameters: :created_at, :updated_at
D, [2017-10-20T21:52:40.085375 #28142] DEBUG -- :    (0.2ms)  BEGIN
D, [2017-10-20T21:52:40.114015 #28142] DEBUG -- :   Account Load (1.2ms)  SELECT  "accounts".* FROM "accounts" WHERE "accounts"."id" = $1 LIMIT $2  [["id", "a61151b8-deed-4efa-8cad-da1b143196c9"], ["LIMIT", 1]]
D, [2017-10-20T21:52:40.131895 #28142] DEBUG -- :   Template Exists (0.8ms)  SELECT  1 AS one FROM "templates" WHERE "templates"."name" = $1 AND ("templates"."id" != $2) AND "templates"."account_id" = 'a61151b8-deed-4efa-8cad-da1b143196c9' LIMIT $3  [["name", "Test"], ["id", "55e27eb7-1151-439d-87b7-2eba07f3e1f7"], ["LIMIT", 1]]
D, [2017-10-20T21:52:40.133754 #28142] DEBUG -- :    (0.3ms)  COMMIT
D, [2017-10-20T21:52:40.137763 #28142] DEBUG -- :   CACHE Account Load (0.0ms)  SELECT  "accounts".* FROM "accounts" WHERE "accounts"."id" = $1 LIMIT $2  [["id", "a61151b8-deed-4efa-8cad-da1b143196c9"], ["LIMIT", 1]]
D, [2017-10-20T21:52:40.138714 #28142] DEBUG -- :    (0.2ms)  BEGIN
D, [2017-10-20T21:52:40.141293 #28142] DEBUG -- :   User Load (1.3ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 FOR UPDATE  [["id", "88de3be9-6d18-4687-ab80-d50f78638ca9"], ["LIMIT", 1]]
D, [2017-10-20T21:52:40.235163 #28142] DEBUG -- :   Account Load (0.7ms)  SELECT  "accounts".* FROM "accounts" WHERE "accounts"."id" = $1 LIMIT $2  [["id", "a61151b8-deed-4efa-8cad-da1b143196c9"], ["LIMIT", 1]]
D, [2017-10-20T21:52:40.240997 #28142] DEBUG -- :   SQL (1.4ms)  UPDATE "users" SET "tokens" = $1, "updated_at" = $2 WHERE "users"."id" = $3  [["tokens", "{\"ryyymFZ7fpH50rMKArjZ2Q\":{\"token\":\"$2a$10$4jkgRe4LBPxJ8fQUOKCSausUi7DbIUD0bE.7ZRoOuTHrRuX6CaWOe\",\"expiry\":1509293414,\"last_token\":\"$2a$10$cpI.mz81JFjQT0J9acCCl.NdrEatI5l17GtrwrAfwyhyN3xRExcaC\",\"updated_at\":\"2017-10-15T17:10:16.996+02:00\"},\"Y2y0maUT5WYSfH6VZeORag\":{\"token\":\"$2a$10$8KERiIwlc3rX.Mdu.CW6wOMLDbVyB2PFCaBIlw7/LUxC3ITpYTISW\",\"expiry\":1509293475,\"last_token\":\"$2a$10$r6Xw6798T1P7UZlTbEaXoeBCl9oK2fMs72ppAtars8Ai/kaE6nE66\",\"updated_at\":\"2017-10-15T17:11:18.066+02:00\"},\"9Cy48CPVj3WhFkEBPUZQ1Q\":{\"token\":\"$2a$10$Qy4JOD8.jIcPhf93MqFCIelnVaA/ssE31w5DlL8MShDuMROsLSNuS\",\"expiry\":1509293942,\"last_token\":\"$2a$10$e6sxklrHRRD1C15Ix/MqQOfACuCMznmzUjF296cpO1ypWVvJ.JFJK\",\"updated_at\":\"2017-10-15T17:19:05.200+02:00\"},\"O5iufW0Gacqs9sIfJ9705w\":{\"token\":\"$2a$10$EkDf7.y3lY9D36lAwNHBGuct97M6/HGDvnrUsD72c8zCsfVd8y9c2\",\"expiry\":1509482450,\"last_token\":\"$2a$10$S0kHEvKxom2Qgdy0r.q0aeTSlSBFkqU4XZeY91n3RkkYkQykmmGVi\",\"updated_at\":\"2017-10-17T21:40:50.300+02:00\"},\"ETOadoEtoxcz6rR6Ced_dA\":{\"token\":\"$2a$10$8t01bWv/PsVojs3cazuSg..FWa9SZwq1/PUDfuN1S4yBxnMFv2zre\",\"expiry\":1509742360,\"last_token\":\"$2a$10$hveuajISXDOjHLm9EkVzvOd3pwKkqE1rQnIFBoojf0vgMLXV2EvVe\",\"updated_at\":\"2017-10-20T21:52:40.233+02:00\"}}"], ["updated_at", "2017-10-20 19:52:40.236607"], ["id", "88de3be9-6d18-4687-ab80-d50f78638ca9"]]
D, [2017-10-20T21:52:40.243960 #28142] DEBUG -- :    (1.3ms)  COMMIT
I, [2017-10-20T21:52:40.244504 #28142]  INFO -- : Completed 200 OK in 358ms (Views: 1.0ms | ActiveRecord: 37.7ms)
9
rmcsharry

Je pense que vous oubliez que params.require (: template) .permit (... est une méthode qui renvoie une valeur et lorsque vous appelez des paramètres pour la modifier ultérieurement, vous ne modifiez que les paramètres non encore autorisés. Ce que vous devez faire est d’échanger l’ordre de la manipulation des paramètres.

def template_params
  params[:template][:template_items_attributes] = params[:template_items_attributes]
  params.require(:template).permit(:id, :account_id, :name, :title, :info, template_items_attributes: [:id, :is_completed, :content, :item_type])
end

UPDATE: wrap_parameters était le coupable car il n'incluait pas de paramètres imbriqués dans les paramètres encapsulés. cela corrige le problème

UPDATE: cette réponse implémente une solution différente Rails 4 ne mettant pas à jour les attributs imbriqués via JSON

C'est une demande ouverte depuis longtemps dans github !! crazy https://github.com/Rails/rails/pull/19254

5
Carlos Roque

Problème Votre problème réside dans votre action update et vous essayez de sauvegarder les associations sur @template qui n’ont pas encore été créées. Comme il n'y a pas d '«identifiant» entrant avec le hachage, la fonction de mise à jour les ignore.

La solution Consiste à itérer sur le tableau de hachages d’association entrant dans l’action update et à les construire sur le @template avant d’appeler update sur le @template.

Voici le code Sudo (je ne l'ai pas essayé, donc ne copiez pas coller): 

des modèles

class Template < ApplicationRecord
  belongs_to :account
  has_many :template_items
  accepts_nested_attributes_for :template_items, allow_destroy: true
end


class TemplateItem < ApplicationRecord  
  belongs_to :template, optional:true  # <------ CHANGE
  validates_presence_of :template

  enum item_type: {item: 0, heading: 1} 
end

Définition forte params

params.require(:template).permit(
      :id, :account_id, :name, :title, :info, 
      template_items_attributes: [:id, :is_completed, :content, :item_type]
  )

Action de mise à jour

def update

    template_params.template_items.each do |item_hash| # <------ CHANGE
        @template.template_items.build(item_hash)
    end

    if @template.update(template_params)
      render json: @template
    else
      render json: ErrorSerializer.serialize(@template.errors), status: :unprocessable_entity
    end
 end
1
Shaunak
 def  template_params 
    template_params = params.require(:template).permit(:id, :account_id, :name,:title, :info, template_items: [:id, :is_completed, :content, :item_type])
    template_params[:template_items_attributes] = template_params.delete :template_items      
    template_params.permit!
end
0
Sathibabu P