Rails 5.1 API - 如何为嵌套的JSON对象属性

时间:2017-10-20 16:42:47

标签: ruby-on-rails json parameters

关于此主题至少有10个问题,但没有一个问题可以回答这个问题。许多问题与我没有的this之类的Rails表格有关,或与thisthis更复杂的json结构有关。

关于已接受的答案的EDIT以及为什么这不完全重复

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

如果您阅读了所有评论,您会看到多次尝试更改template_params方法,以重命名或替换嵌套属性" template_items"使用" template_items_attributes"。这是必要的,因为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属性(例如,在创建它们并且用户开始编辑它们之后)。

template_params的{​​{1}}方法如下所示:

templates_controller

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

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

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

我尝试更改嵌套的param名称:

   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中而不仅仅是执行.permit! (即,我不想盲目地允许一切)?

控制器更新方法:

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

UDPATE

如果我从客户端发送&#34; template_items_attributes&#34;而不是&#34; template_items&#34;在Rails的参数内部,然后执行建议的template_params,如下所示:

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

它仍然不会为模板创建新的子项!

有了这个,我会在之前和之后输出参数,如下所示:

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

这是来自这个场景的日志--Rails是STILL完全忽略了嵌入式阵列。请注意,在HERE之前的params显示允许:false,然后template_params不再包含子项&#34; template_items_attributes&#34;并标记为允许:true。

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

3 个答案:

答案 0 :(得分:5)

我认为你忘记了params.require(:template).permit(...是一个返回值的方法,当你调用params以后修改它时你只修改了尚未被允许的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

答案 1 :(得分:1)

<强>问题 您的问题出在update操作中,您尝试保存尚未构建的@template上的关联。因为没有&#39; ids&#39;使用散列进入,更新函数只是忽略它们。

解决方案 是迭代进入update操作的关联哈希数组,并在@template上调用update之前在@template上构建它们。

这里是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

答案 2 :(得分:0)

 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