通过复选框添加多个嵌套属性Rails 4(可能有多个表单) [英] Add Multiple Nested Attributes through checkboxes Rails 4 (maybe with multiple forms)

查看:176
本文介绍了通过复选框添加多个嵌套属性Rails 4(可能有多个表单)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

3/13 UPDATE:

我使用我的模型,控制器逻辑和几个表单版本创建了一个小型示例项目。

3/13 UPDATE:
I've made a small sample project with my models, controller logic and several form versions.





我正在建立一个表单,用户可以在其中添加任务 里程碑在一起。 (即任务='真空'在里程碑='清洁房子'内)。它基本上是一个任务/子任务类型模型,父级为里程碑,子级为任务。




任务和里程碑都属于项目....所以我试图通过一个嵌套表单添加任务和里程碑更新操作。我想的方式是为每个@task_template实例创建一个表单,并立即更新多个表单。


我的问题是,我也试图通过名为MilestoneTemplates和TaskTemplates的表来动态设置初始里程碑/任务。


用户拉起添加里程碑/任务页面,根据他们的项目类型,他们看到一个预建任务数组(@task_templates)&里程碑(@milestone_templates)旁边的复选框。然后,用户选中要添加的任务或里程碑旁边的复选框。这应该为具有预构建的@ task_template.name,@ task_template.description ...等的用户创建一个特定的任务。

I am building a form where a user can add "Tasks" and "Milestones" together. (ie. Task = 'Vacuum' is inside Milestone = 'clean House'). It's basically a Task/Subtask type model with the parent being 'Milestone' and the child being 'Task'.

Both Tasks and Milestones belong to "Project"....so I am trying to add the Tasks and Milestones through a nested form with an update action. I am thinking the way to go is create a form for each @task_template instance and update multiple forms at once.

My problem is that I am also trying to dynamically set "starter milestones/tasks" through tables called "MilestoneTemplates" and "TaskTemplates"....

The user pulls up the "Add Milestones/Task" page and, depending on their type of project, they see an array of prebuilt tasks(@task_templates) & milestones(@milestone_templates) next to checkboxes. The user then CHECKS the checkbox next to the task or milestone they would like to add. This should create a specific task for the User with a prebuilt @task_template.name, @task_template.description...etc

我无法获得甚至创建1.我使用Rails 4,我认为我已正确设置我的strong_params。以下是我在此处的位置:

I cannot get this to even create 1. I am using Rails 4 and I think I have set my strong_params correctly. Below is where I am on this:

模型:

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

>

Controller:

class ProjectsController < ApplicationController

def new_milestones
 @project = Project.find(params[:p])
 @project.milestones.build
 @project.tasks.build
 @milestones_templates = MilestoneTemplate.where(template_id: @project.template_id)
end

def create_milestones
 @project.milestone_ids = params[:project][:milestones]
 @project.task_ids = params[:project][:tasks]
 @milestone = Milestone.new
 @task = Task.new
 @template = Template.find( @project.template_id)
  if @project.update_attributes(project_params)
    redirect_to  view_milestones_path(p: @project.id)
    flash[:notice] = "Successfully Added Tasks & Milestones"
  else
    redirect_to  new_milestones_path(p:  @project.id )
    format.json { render json: @project.errors, status: :unprocessable_entity }
  end
end

def project_params
      params.require(:project).permit( :id, :name,
        milestones_attributes: [:id, {:milestone_ids => []}, {: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:

<%= form_for @project, url: create_milestones_path(p: @project.id) do |f| %>
     <label>Milestones</label><br>
     <div class="row">
       <%= hidden_field_tag "project[names][]", nil %>
       <% @milestones_templates.each do |m| %>
         <%= check_box_tag  "project[names][]", m.name, @milestones_templates.include?(m), id: dom_id(m)%> 
         <%= label_tag dom_id(m), m.name  %>

           <%= hidden_field_tag "project[milestone][names][]", nil %>
           <% m.task_templates.each do |t| %>
             <%= check_box_tag  "project[milestone][names][]", t.name, m.task_templates.include?(t), id: dom_id(t) %> 
             <%= label_tag dom_id(t), t.name  %>
           <% end %>
       <% end %>
     </div>
 <%= f.submit %>

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

Form Test 2(trying to submit an array of forms):

 <label>Milestones</label><br>
   <%= hidden_field_tag "project[milestone_ids][]", nil %>
   <% @milestones_templates.each do |m| %>
   <div>
      <%= f.fields_for :milestones do |fm|%>
         <%= check_box_tag    "project[milestone_ids][]",  @milestones_templates.include?(m), id: dom_id(m) %> 
         <%= label_tag dom_id(m), m.name  %></div>
      <%= hidden_field_tag :name, m.name %>
      <%= hidden_field_tag "project[milestone][task_ids][]", nil %>

         <% m.task_templates.each do |t| %>
         <%= fm.fields_for :tasks do |ft| %>
               <%= check_box_tag  "project[milestone][task_ids][]", t.name,  m.task_templates.include?(t), id: dom_id(t)%> 
               <%= label_tag dom_id(t), t.name  %>
         <% end %>
         <% end %>
      <% end %>
   <% end %>
   </div>

根据xcskier56的要求在评论中,我已经从Chrome检查器添加了我的POST代码。正如你可以看到的,表单甚至不调用任务,只是父里程碑。

as per xcskier56's request in the comments, I've added my POST code from Chrome inspector. As you can see, the form isn't even calling the Tasks, just the parent Milestones. The Milestones show up in the form, but the tasks don't....

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][]:


推荐答案

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

I haven't been able to test this code myself, but I have implemented similar code, so the ideas should be correct.

这里的诀窍是使用each_with_index,然后将该索引传递到 fields_for 调用。这样,通过复选框添加的每个附加 milestone_id 将与以前显着不同。您可以在此此处找到另一个示例。

The trick here is using each_with_index, and then passing that index to your fields_for call. This way the each additional milestone_id that you add via a checkbox will be significantly different from the previous. You can find another example of this here.

使用此方法,您的表单应该如下所示:

Using this approach, your form should look something like this:

<%= form_for @project do |f| %>
  <% @milestones_templates.each_with_index do |milestone, index| %>
    <br>
    <%= f.fields_for :milestones, index: index do |fm| %>
      <%= fm.hidden_field :name, value: milestone.name %>
      <!-- Create a checkbox to add the milestone_id to the project -->
      <%= fm.label milestone.name %>
      <%= fm.check_box :milestone_template_id,{}, milestone.id %>
      <br>
      <% milestone.task_templates.each_with_index do |task, another_index| %>
        <%= fm.fields_for :tasks, index: another_index do |ft| %>
          <!-- Create a checkbox for each task in the milestone -->
          <%= ft.label task.name %>
          <%= ft.check_box :task_ids, {}, task.id %>
        <% end %>
      <% end %>
      <br>
    <% end %>
  <% end %>
  <br>
<%= f.submit %>
<% end %>

# Working strong parameters.
params.require(:project).permit(:name, :milestones => [:name, :milestone_ids, :tasks => [:task_ids] ] )

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

This should output the milestone_template_ids with each of those's task_template_ids nested inside.

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

I forgot that if you look at the docs, the check_boxes need another param in the middle f.checkbox :task_ids, task.id => f.checkbox :task_ids, {}, task.id

现在为答案的肉。虽然这种形式工作,并给了足够的fiddling我认为你可以得到rails自动更新你的项目和通过嵌套的属性,并创建你想要的一切,我不认为这是一个好的设计。

Now for the meat of the answer. While this form does work, and given enough fiddling I think you could get rails to automagically update your project and through nested attributes, and create everything that you want it to, I don't think this is a good design.

更好的设计是使用构建器类。它只是一个PORO(普通旧Ruby对象)。这将允许你做的是围绕构建器写好测试。所以你可以更放心,它将总是工作,一些改变,没有打破它。

What is a far better design is using a builder class. It is just a PORO (Plain Old Ruby Object). What this will allow you to do is write good tests around the builder. So you can be much more assured that it will always work, and that some change to rails didn't break it.

这里有一些伪代码让你去:

Here's some pseudo code to get you going:

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

$ b b

在/lib/project_milestone_builder.rb

In /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

In /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

使用此模式,一个非常好的封装,容易测试的类,将完全按照你期望它做的。快乐编码。

With this pattern, you will end up with a very well encapsulated, easily testable class that will do exactly what you expect it to do. Happy coding.

这篇关于通过复选框添加多个嵌套属性Rails 4(可能有多个表单)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆