通过复选框Rails 4添加多个嵌套属性(可能有多个表单)

3/13更新:
我用我的模型,控制器逻辑和几个表单版本做了一个小样本项目。

我正在构建一个表单,用户可以在其中添加“任务”和“里程碑”。 (即,任务=’真空’在里程碑里面”干净的房子’)。 它基本上是一个任务/子任务类型模型,其父级为“里程碑”,子级为“任务”。

任务和里程碑都属于“项目”….因此我尝试通过嵌套表单添加任务和里程碑以及更新操作。 我想要的方法是为每个@task_template实例创建一个表单并一次更新多个表单。

我的问题是我也试图通过名为“MilestoneTemplates”和“TaskTemplates”的表动态设置“启动里程碑/任务”….

用户启动“添加里程碑/任务”页面,根据项目类型,他们会在复选框旁边看到一系列预建任务(@task_templates)和里程碑(@milestone_templates)。 然后,用户检查他们要添加的任务或里程碑旁边的复选框。 这应该为用户创建一个具有预建@ task_template.name,@ task_template.description …等的特定任务

我甚至无法创建1.我正在使用Rails 4,我认为我已经正确设置了我的strong_params。 以下是我对此的看法:

楷模:

class Task < ActiveRecord::Base belongs_to :user belongs_to :project belongs_to :milestone class Milestone < ActiveRecord::Base belongs_to :project belongs_to :user has_many :tasks, dependent: :destroy, inverse_of: :milestone accepts_nested_attributes_for :tasks, allow_destroy: true class Project < ActiveRecord::Base has_many :milestones, dependent: :destroy has_many :tasks, dependent: :destroy accepts_nested_attributes_for :tasks, allow_destroy: true accepts_nested_attributes_for :milestones, allow_destroy: true #the "Starter Milestones & Tasks" class MilestoneTemplate < ActiveRecord::Base has_many :task_templates, dependent: :destroy, inverse_of: :milestone_template class TaskTemplate < ActiveRecord::Base belongs_to :milestone_template, inverse_of: :task_templates 

控制器:

 class ProjectsController  []}, {:ids => []}, {:names => []}, :project_id, :user_id, :name, :description, :due_date, :rank, :completed, :_destroy, tasks_attributes: [:id, {:task_ids => []}, {:names => []}, {:ids => []}, :milestone_id, :project_id, :user_id, :name, :description, :due_date, :rank, :completed, :_destroy]] ) end end 

表格测试1:

  

表单测试2(尝试提交表单数组):

  

根据xcskier56在评论中的请求,我已经从Chrome检查器添加了我的POST代码。 正如您所看到的,表单甚至没有调用任务,只是父级里程碑。 里程碑出现在表格中,但任务不……

 project[formprogress]:2 project[milestone_ids][]: project[milestone][names]:true name:Milestone 1 project[milestone][task_ids][]: project[milestone][names]:true name:Milestone 2 project[milestone][task_ids][]: project[milestone][names]:true name:Milestone 3 project[milestone][task_ids][]: project[milestone][names]:true name:Milestone 4 project[milestone][task_ids][]: 

我自己无法测试此代码,但我已经实现了类似的代码,所以这些想法应该是正确的。

这里的技巧是使用each_with_index,然后将该索引传递给fields_for调用。 这样,通过复选框添加的每个额外的milestone_id将与之前的显着不同。 你可以在这里找到另一个例子。

使用这种方法,您的表单应如下所示:

 <%= form_for @project do |f| %> <% @milestones_templates.each_with_index do |milestone, index| %> 
<%= f.fields_for :milestones, index: index do |fm| %> <%= fm.hidden_field :name, value: milestone.name %> <%= fm.label milestone.name %> <%= fm.check_box :milestone_template_id,{}, milestone.id %>
<% milestone.task_templates.each_with_index do |task, another_index| %> <%= fm.fields_for :tasks, index: another_index do |ft| %> <%= ft.label task.name %> <%= ft.check_box :task_ids, {}, task.id %> <% end %> <% end %>
<% end %> <% end %>
<%= f.submit %> <% end %> # Working strong parameters. params.require(:project).permit(:name, :milestones => [:name, :milestone_ids, :tasks => [:task_ids] ] )

这应该输出milestone_template_ids,其中每个嵌套在其中的task_template_ids。

编辑:我忘了,如果你查看文档,check_boxes需要在f.checkbox中间的另一个参数f.checkbox :task_ids, task.id => f.checkbox :task_ids, {}, task.id

现在为了答案的肉。 虽然这个表单确实有效,并且给予足够的调整,但我认为你可以通过嵌套属性自动更新项目并创建所需的一切,我不认为这是一个好的设计。

更好的设计是使用构建器类。 它只是一个PORO(Plain Old Ruby Object)。 这将允许您做的是围绕构建器编写好的测试。 因此,您可以更加放心,它将始终有效,并且某些更改为rails并未破坏它。

这里有一些伪代码可以帮助你:

 ProjectsController << ApplicationController def update @project = Project.find(params[:id]) # This should return true if everything works, and result = ProjectMilestoneBuilder.perform(@project, update_params) if result == false # Something went very wrong in the builder end if result.errors.any? #handle success else # handle failure # The project wasn't updated, but things didn't explode. end end private def update_params params.require(:project).permit(:name, :milestones => [:name, :milestone_ids, :tasks => [:task_ids] ] ) end end 

在/lib/project_milestone_builder.rb中

 class ProjectMilestoneBuilder def self.perform(project, params) milestone_params = params[:project][:milestones] milestone_params.each do |m| # Something like this # Might be able to use nested attributes for this # Milestone.create(m) end return project.update_attributes(params) end end 

在/spec/lib/project_milestone_builder_spec.rb中

 descibe ProjectMilestoneBuilder do # Create a template and project let(:template) {FactoryGirl.create :template} let(:project) {FactoryGirl.create :project, template: template} # Create the params to update the project with. # This will have to have dynamic code segments to get the appropriate milestone_template_ids in there let(:params) { "{project: {milestones ..." }) descibe '#perform' do let(:result) { ProjectMilestoneBuilder.perform(project, params) } it {expect(result.id).to eq project.id} # ... end end 

使用这种模式,您将获得一个封装良好,易于测试的类,它将完全按照您的预期执行。 快乐的编码。