【问题标题】:Rails 4 Not Updating Nested Attributes Via JSONRails 4 不通过 JSON 更新嵌套属性
【发布时间】:2013-11-03 16:07:22
【问题描述】:

我已经搜索了相关问题,但通过从我的 AngularJS 前端返回的 JSON 更新 rails 4 中的嵌套属性仍然存在问题。

问题:下面的代码概述了从 AngularJS 传递到我的 Rails4 应用程序中的候选模型的 JSON。 Candidate 模型有很多 Works,我正在尝试通过 Candidate 模型更新 Works 模型。由于某种原因,Works 模型无法更新,我希望有人能指出我所缺少的。感谢您的帮助。


这是候选人的 AngularJS 前端中的 json:

{"id"=>"13", "nickname"=>"New Candidate", "works_attributes"=>[
{"title"=>"Financial Analyst", "description"=>"I did things"},
{"title"=>"Accountant", "description"=>"I did more things"}]}

Rails 然后通过添加候选标头将此 JSON 转换为以下内容,但不包括候选标头下的嵌套属性,并且无法通过候选模型更新works_attributes

{"id"=>"13", "nickname"=>"New Candidate", "works_attributes"=>[
{"title"=>"Financial Analyst", "description"=>"I did things"},
{"title"=>"Accountant", "description"=>"I did more things"}],
"candidate"=>{"id"=>"13", "nickname"=>"New Candidate"}}

candidate_controller.rb 包含一个简单的更新:

class CandidatesController < ApplicationController

    before_filter :authenticate_user!

  respond_to :json

  def update
    respond_with Candidate.update(params[:id], candidate_params)
  end

private

  def candidate_params
    params.require(:candidate).permit(:nickname,
      works_attributes: [:id, :title, :description])
  end

end

candidate.rb 模型包含以下代码,定义了与 works 模型的 has_many 关系:

class Candidate < ActiveRecord::Base

  ## Model Relationships
  belongs_to :users
  has_many :works, :dependent => :destroy  

  ## Nested model attributes
  accepts_nested_attributes_for :works, allow_destroy: true

  ## Validations
  validates_presence_of :nickname
  validates_uniqueness_of :user_id

end

最后,works.rb 模型定义了 has_many 关系的另一端:

class Work < ActiveRecord::Base
  belongs_to :candidate
end

感谢您提供的任何帮助,因为我确信我遗漏了一些相当简单的东西。

谢谢!

【问题讨论】:

  • Rails then translates this JSON into the following by adding the candidate header 什么?你确定Rails会这样做吗?更改客户端应用程序的有效负载不是 Rails 的工作!
  • 我相当有信心 Rails ActionController::ParamsWrapper 从没有根元素的 Angularjs 获取 json 输入并添加具有根元素的片段。我想我的问题是:这是否会导致问题以及为什么works_attributes 数组未包含在此参数包装更改中?
  • 请告诉我你认为这样做的代码。
  • phoet - 感谢您的提问,因为它为我指出了一个解决方案(发布在下面)。如果有更好的方法来管理 AngularJS 和 Rails 之间的 JSON 交互,请告诉我。
  • 不确定它是否“更好”——我有点喜欢在服务器而不是客户端处理它的想法——但你可以在 Angular 中添加一个请求拦截器并将请求包装在那里。拦截器的文档在这里:docs.angularjs.org/api/ng/service/$http

标签: ruby-on-rails json ruby-on-rails-3 ruby-on-rails-4 nested-attributes


【解决方案1】:

我也一直在使用 Rails 和 AngularJS 之间的 JSON API。我使用了与RTPnomad 相同的解决方案,但找到了一种不必对包含属性进行硬编码的方法:

class CandidatesController < ApplicationController
  respond_to :json

  nested_attributes_names = Candidate.nested_attributes_options.keys.map do |key| 
    key.to_s.concat('_attributes').to_sym
  end

  wrap_parameters include: Candidate.attribute_names + nested_attributes_names,
    format: :json

  # ...
end

请参阅 Rails 中的 issue 以查看他们是否/何时解决此问题。

10/17 更新
在此处等待 PR 合并:rails/rails#19254

【讨论】:

    【解决方案2】:

    我根据位于 http://edgeapi.rubyonrails.org/classes/ActionController/ParamsWrapper.html 的 Rails 文档找到了一种解决问题的方法

    基本上,Rails ParamsWrapper 默认启用以使用根元素包装来自前端的 JSON,以便在 Rails 中使用,因为 AngularJS 不会在根包装元素中返回数据。上述文档包含以下内容:

    "在没有设置:include 或:exclude 选项的ActiveRecord 模型上,它只会包装类方法attribute_names 返回的参数。"

    这意味着我必须使用以下语句显式包含嵌套属性,以确保 Rails 包含所有元素:

    class CandidatesController < ApplicationController
    
        before_filter :authenticate_user!
        respond_to :json
        wrap_parameters include: [:id, :nickname, :works_attributes]
        ...
    

    如果有更好的方法在 AngularJS 和 Rails 之间传递 JSON 数据,请添加另一个答案

    【讨论】:

    • 我不确定我是否理解您想要完成的任务...我认为您所指的文档仅适用于使用 render :json 从 Rails 模型创建 JSON 的情况@
    • 我正在使用活动模型序列化程序从 Rails 生成 JSON 输出。根据上述文档,paramswrapper 有助于传入 JSON:“将参数散列包装到嵌套散列中。这将允许客户端提交 POST 请求而无需指定任何根元素。
    • 我接受了同样的解决方案
    【解决方案3】:

    您还可以通过将其放入例如 wrap_parameters.rb 初始化程序中来猴子补丁参数包装以始终包含nested_attributes:

        module ActionController
            module ParamsWrapper
    
                Options.class_eval do
                    def include
                        return super if @include_set
    
                        m = model
                        synchronize do
                            return super if @include_set
                            @include_set = true
                            unless super || exclude
                                if m.respond_to?(:attribute_names) && m.attribute_names.any?
                                    self.include = m.attribute_names + nested_attributes_names_array_of(m)
                                end
                            end
                        end
                    end
    
                    private 
                        # added method. by default code was equivalent to this equaling to []
                        def nested_attributes_names_array_of model
                            model.nested_attributes_options.keys.map { |nested_attribute_name| 
                                nested_attribute_name.to_s + '_attributes' 
                            }
                        end
                end
    
            end
        end
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-18
      • 1970-01-01
      • 2013-08-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-02-01
      相关资源
      最近更新 更多