【问题标题】:Rails 5.1 API - how to permit params for nested JSON object's attributesRails 5.1 API - 如何允许嵌套 JSON 对象属性的参数
【发布时间】:2017-10-20 16:42:46
【问题描述】:

关于这个主题至少有 10 个问题,但没有一个回答这个特定问题。许多问题与我没有的 this 之类的 Rails 表单有关,或者与更复杂的 json 结构有关,例如 thisthis


编辑关于接受的答案以及为什么这不是完全重复的答案

@CarlosRoque 答案中的链接问题最初看起来是同一个问题,但它只解决了这个特定问题的 Rails 方面。

如果您阅读了所有 cmets,您将看到多次尝试更改 template_params 方法以重命名或用“template_items_attributes”替换嵌套属性“template_items”。这是必要的,因为 Rails accepts_nested_attributes_for 要求在名称后附加“_attributes”,否则无法看到。

如果您检查该答案中的猴子补丁代码以修复 wrap_parameters 以使其适用于嵌套属性,您仍然会遇到问题,即它实际上找不到“template_items”(嵌套对象),因为它没有后缀“_attributes”。

因此,为了完全解决这个问题,还必须修改客户端以将嵌套对象作为“template_items_attributes”发送。对于 JS 客户端,这可以通过在对象上实现 toJSON() 方法以在序列化期间对其进行修改来完成(例如 here)。但请注意,当您反序列化 JSON 时,您需要手动创建该对象的实例以使 toJSON() 工作(解释了为什么 here)。


我有一个简单的 has_many / belongs_to:

型号:

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

客户端发送过来的json是这样的:

{
  "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"
    }
  ]
}

有时,每个 template_item 中都会有一个 :id 和一个 :content 属性(例如,在它们被创建并且用户开始编辑它们之后)。

templates_controllertemplate_params 方法如下所示:

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

如果这是一个 Rails 表单,那么该行将是:

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

用于将嵌套的子对象保存为父模板更新操作的一部分。

我尝试更改嵌套参数名称:

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

我可以看到他们仍然不允许:

{  
      "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      >
   ]
}

我也尝试过合并:

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

同样的问题。

那么我怎样才能确保它们被允许并包含在 template_params 中而不只是做 .permit! (即。我不想盲目地允许一切)?

控制器更新方法:

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

如果我从客户端将参数中的“template_items_attributes”而不是“template_items”发送到Rails,然后像这样执行推荐的template_params:

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

它仍然没有为模板创建新的孩子!

有了这个,我输出前后的参数,像这样:

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

这里是这个场景的日志——Rails 仍然完全忽略了嵌入式数组。请注意,在 HERE 之前的 params 显示为 allowed: false ,然后是 template_params 不再包含子“template_items_attributes”,并被标记为 allowed: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", "rmcsharry+owner@gmail.com"], ["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)

【问题讨论】:

  • 您也可以发布您拥有的控制器操作吗?
  • 感谢您添加控制器。看来问题可能是您没有optional:true。 Rails 5 强制要求父级。 belongs_to : template, optional: true 。当您尝试此操作时,我还建议您在定义强参数时使用 If this is a Rails form then that line would be 下的 sn-p。看看这些编辑是否能让你正确保存模型。
  • 父级始终存在 - 这不是创建操作。在创建操作中,您只能创建父模板,不能创建子项。但在更新操作中,您可以同时更新父级及其子级。我会尝试一下,因为我变得非常疯狂,在过去 4 小时内尝试了 100 万件事情。
  • 另外,你“不应该”在你的行动中需要@template.template_items = template_params[:template_items_attributes]这一行。当您定义接受嵌套属性时,rails 所做的部分工作。 @template.update(template_params) 还应该读取嵌套属性并进行相应更新。看看这个博客:devopsdiarist.uk/rails-5-nested-attributes 展示了一个非常简单的示例,但与您的确切场景有关。
  • 好点,我补充说,在我添加 Accept_nested_attributes 之前尝试看看我是否可以强制手动创建项目。

标签: ruby-on-rails json parameters


【解决方案1】:

我认为您忘记了 params.require(:template).permit( ... 是一种返回值的方法,当您稍后调用 params 对其进行修改时,您只是在修改尚未被允许的参数。您要做的是交换执行参数操作的顺序。

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

更新:wrap_parameters 是罪魁祸首,因为它没有在包装参数中包含嵌套参数。这解决了问题

更新:这个答案实现了不同的解决方案 Rails 4 Not Updating Nested Attributes Via JSON

这是一个在 github 上的长期开放请求!!疯狂的 https://github.com/rails/rails/pull/19254

更新:最终在 AR 6 中合并 https://github.com/rails/rails/commit/62b7ad46c0f3ff24980956daadba46ccb2568445

【讨论】:

  • 谢谢,我试过了,它们仍然被标记为允许:false。
  • 再看一遍,这应该是正确的答案。为什么在这样做之后 Rails 将参数标记为 false?
  • 是否所有参数都标记为假?包括模板参数?那么这可能是因为一切都需要进入模板:
  • 我已更改我的答案以考虑到这一点。
  • 所以我刚刚测试了这个想法。我把 template_params 方法恢复到一开始的样子。从客户端发送了一个请求,该子节点在参数中已经称为“template_items_attributes”,并且在更新操作中它不再存在! Rails 正在删除整个数组...可能是因为没有 id?
【解决方案2】:

问题 您的问题出在您的 update 操作中,您试图保存尚未构建的 @template 上的关联。因为散列中没有“id”,所以更新函数会忽略它们。

解决方案 是对传入update 操作的关联哈希数组进行迭代,并在@template 上构建它们,然后在@template 上调用update

这里是sudo代码(没试过,所以不要复制粘贴):

模型

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

强参数定义

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

更新操作

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

【讨论】:

  • 谢谢。我会尝试你的代码,但它不起作用,因为根本问题是accepts_nested_attributes_for 添加了一个“attribute_writer”,它要求孩子的参数以单词“_attributes”结尾。我的请求在 json 中没有。无论我如何尝试在 template_params 方法中将其添加到“tempalte_items”,Rails 都会不断丢弃子属性或总是将它们标记为 false! :(
  • 仅供参考,可选:true 仅在创建子项之前确保“模板必须存在”,所以这肯定不是问题,因为它们将始终存在于我的场景中,即更新现有模板但同时创造新的孩子。
  • 对,在你的情况下你不应该需要optional: true,因为你总是先创建模板。
  • 我完全理解你的回答。所以我现在尝试了您的代码(进行了更改)。 template_params.template_items.each -> NoMethodError(#<:parameters:0x007fc082fef1b0> 的未定义方法 `template_items')。我还尝试像以前一样将该参数数组重命名为 template_items_attributes,但它总是被标记为 false 或者只是被许可代码删除。那么更新如何工作呢?它永远不会得到孩子。
  • 例如。在更新中,当我尝试这个时: template_params[:template_items_attributes].each 它会抛出 Forbidden Attributes 错误。
【解决方案3】:
 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

【讨论】:

  • 发布代码没有解释不是很有帮助。此外,这也不起作用,因为它们需要是“template_items_attributes”才能到达这部分代码 -> 所以你在方法中看不到“template_items”,Rails 已经删除了它们。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多