【问题标题】:has_one nested attributes not savinghas_one 嵌套属性不保存
【发布时间】:2014-05-01 08:42:00
【问题描述】:

我有两个模型 Project 和 ProjectPipeline。

我想创建一个项目表单,该表单也包含来自 ProjectPipeline 模型的字段。我已经成功创建了表单,但是当我点击保存时,值并没有存储在数据库中。

project.rb

class Project < ActiveRecord::Base

  has_one :project_pipeline

  accepts_nested_attributes_for :project_pipeline

  self.primary_key = :project_id
end

projectpipeline.rb

class ProjectPipeline < ActiveRecord::Base

  belongs_to :project, autosave: :true

  validates_uniqueness_of :project_id

end

我并不总是想要一个项目管道,而是在基于用户查看项目的正确条件下。我希望构建项目管道字段,但仅在用户选择保存/填充它们时才保存。

因此,当显示项目时,我使用 project_id: from params[:id] 构建了一个临时项目管道(不确定我是否真的需要这样做)。然后当项目被保存时,我使用 create_attributes。但如果它已经创建或构建,我只想让 has_one 和 belongs_to 关联启动,然后使用 update_attributes。

我的问题是,当我尝试保存时,如果我使用 params[:project_pipeline],我会遇到“禁止属性”错误,或者如果我使用 project_params,则根本没有保存任何内容。我已经检查并重新检查了我的所有字段都在 project_params 中,甚至尝试使用 project_pipeline_params 但感觉不对。

我快疯了,我需要睡觉。

projects_controller.rb

def show
  @project = Project.find(params[:id])
  if @project.project_pipeline
  else
    @project.build_project_pipeline(project_id: params[:id])
  end
  autopopulate
end



def update
    @project = Project.find(params[:id])
    if @project.project_pipeline
    else
      @project.build_project_pipeline(project_id: params[:id], project_type: params[:project_pipeline][:project_type], project_stage: params[:project_pipeline][:project_stage])
    end
    if @project.update_attributes(project_params)
      flash[:success] = "Project Updated"
      redirect_to [@project]
    else
      render 'edit'
    end
  end


def project_params
  params.require(:project).permit(:user_id, project_pipeline_attributes:[:project_id,:project_type,:project_stage,
      :product_volume,:product_value,:project_status,:outcome, :_destroy])
end

show.html.haml

    - provide(:title, "Show Project")
%h1= @project.project_title
= simple_form_for(@project) do |f|
  = f.input  :id, :as => :hidden, :value => @project, :readonly => true  
  = f.input :user_id, label: 'Assigned to Account Manager', :collection => @account_managers, :label_method => lambda { |r| "#{r.first_name} #{r.last_name}" }
  = f.input :project_id, :readonly => true
  = f.input :status, :readonly => true
  = f.input :project_stage, :readonly => true

  - if @project.project_codename = "project pipeline"
    = simple_fields_for @project.project_pipeline do |i|
      %h2 Project Pipeline
      - if @project.user_id == current_user.id
        = i.input :project_volume, label: 'Project Status', collection: @project_status
        = i.input :project_value, label: 'Project Status', collection: @project_status
        = i.input :project_status, label: 'Project Status', collection: @project_status
      = i.input :outcome, label: 'Outcome', collection: @outcome


    = f.submit 'Save'

如果你能走到这一步,我真诚地感谢你。

【问题讨论】:

    标签: ruby-on-rails ruby rails-activerecord model-associations strong-parameters


    【解决方案1】:

    解决方案

    您需要在此处更改一些内容。首先:

    = simple_fields_for @project.project_pipeline do |i|
    

    当您传递对象时,rails 不知道它是否要与父对象关联,因此会创建一个名为 project[project_pipeline] 的字段,而不是 project[project_pipeline_attributes]。相反,您需要传递关联名称并在表单构建器上调用此方法:

    = f.simple_fields_for :project_pipeline do |i|
    

    这将检查您是否已定义 project_pipeline_attributes= 方法(使用 accept_nested_attributes_for` 并将其视为关联。然后在您的控制器中将您的显示操作更改为:

    def update
      @project = Project.find(params[:id])
      @project.assign_attributes(project_params)
      if @project.save
        flash[:success] = "Project Updated"
        redirect_to @project
      else
        render 'edit'
      end
    end
    

    一切都应该工作。作为单独的说明,由于您在嵌套参数中允许 :_destroy 属性,我假设您希望能够使用嵌套属性删除记录。如果是这样,您需要将allow_destroy: true 添加到您的accepts_nested_attributes_for 呼叫中。

    现在有点样式:

    你可以稍微改进一下你的表演动作。首先,我注意到如果尚未声明任何操作,您正在每个操作中构建一个空管道。这意味着您可能应该将此逻辑移动到您的模型中:

    class Project < AR::Base
    
      after_initalize :add_pipeline
    
      private
    
      def add_pipeline
        project_pipeline || build_project_pipeline
      end
    end
    

    你还有神秘的方法prepopulate - 很可能它也应该是模型问题。

    另外一点:这个语法:

    if something
    else
      # do sth
    end
    

    不知何故非常流行,使代码难以阅读。相反,使用:

    if !something
      # do something
    end
    

    或(首选)

    unless something
      # do something
    end
    

    【讨论】:

    • simple_fields_for 只是 fields_for 助手的包装,fields_for 允许关联对象而不是 :project_pipeline 我们可以传递 :project_pipeline, @project.project_pipeline
    • @Monk_Code - 是的,你都可以通过。然而,这会有所不同——在第一种情况下,它将创建base_object[:association_attributes],在第二种情况下,它将创建base_object[:this_object]。后者已被添加以支持没有任何对象的表单,即您可以为form_for :form 内的两个完全不相关的对象创建一个表单,然后您可以执行f.fields_for @first_objectf.fields_for @second_object。但是,在使用nested_attributes 时,您应该始终使用符号。
    • 谢谢。我正在努力实现你所说的。我认为您的 after_initialize 调用中有拼写错误。我已经使用after_initialize :add_pipeline 删除了“do”位以及它引发了预期的“end”错误。我仍在尝试找出其余部分,因为现在我的 project_params 以 nil 或 '[]' 的形式出现
    • 很好的解决方案,我找到了一个类似的解决方案,但这比我的要好
    【解决方案2】:

    从您的描述中我不确定这是否是问题所在,但其中一个问题是,默认情况下,带有 has_one 的 update_attributes 将重建子项(!),因此您将丢失初始化的属性。您应该为accepts_nested_attributes_for 提供de update_only: true 选项。 你可以在这里找到更多信息,in the rails docs。该行将是这样的:

    accepts_nested_attributes_for :project_pipeline, update_only: true
    

    考虑到 after_initialize,这将导致每个项目总是有一个管道。虽然这可能是可取的,但不一定,这取决于您的域,所以我会小心一点。

    干杯, 尼尔斯

    【讨论】:

      猜你喜欢
      • 2014-01-08
      • 2022-01-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多