has_one嵌套属性不保存

我有两个模型Project和ProjectPipeline。

我想创建一个Project表单,该表单也包含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构建一个临时项目管道:来自params [:id](不确定我是否真的需要这样做)。 然后,当项目保存时,我使用create_attributes。 但是如果它已经被创建或构建,我只想让has_one和belongs_to关联启动,然后使用update_attributes。

我的问题是当我试图保存时,如果我使用params [:project_pipeline]或者如果我使用project_params则根本没有保存,我要么遇到’Forbidden Attribute’错误。 我已经检查并重新检查了我在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' 

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

你需要在这里改变一些事情。 首先:

 = 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`并将其视为关联。然后在您的控制器中将您的show动作更改为:

 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嵌套参数中的:_destroy属性,我假设您希望能够使用嵌套属性删除记录。 如果是这样,您需要在allow_destroy: true调用中添加allow_destroy: true

现在有点造型:

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

 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 

我不确定你的描述是否存在问题,但有一点是,默认情况下,带有has_one的update_attributes将重建子节点(!),因此你将失去初始化的属性。 您应该为accepts_nested_attributes_for提供de update_only: true选项。 您可以在rails docs中找到更多相关信息。 这条线是这样的:

 accepts_nested_attributes_for :project_pipeline, update_only: true 

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

干杯,尼尔斯