Добавьте несколько вложенных атрибутов через флажки Rails 4 (возможно, с несколькими формами)
3/13 обновление:
Я сделал небольшой примерный проект с моими моделями, логикой контроллера и несколькими версиями формы.
И задачи, и вехи относятся к "проекту".... поэтому я пытаюсь добавить задачи и вехи через вложенную форму с действием обновления. Я думаю, что нужно создать форму для каждого экземпляра @task_template и обновить сразу несколько форм.
Моя проблема заключается в том, что я также пытаюсь динамически устанавливать "стартовые вехи/задачи" через таблицы, называемые "MilestoneTemplates" и "TaskTemplates"....
Пользователь открывает страницу "Add Milestones/Task" и, в зависимости от типа проекта, видит массив готовых задач (@task_templates) & вехи (@milestone_templates) рядом с флажками. Затем пользователь устанавливает флажок рядом с задачей или вехой, которую он хотел бы добавить. Это должно создать определенную задачу для пользователя с предварительно построенным @task_template.name, @task_template.описание...и т.д.
Я не могу заставить это даже создать 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 < 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 (попытка представить массив форм):
<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 inspector. Как вы можете видеть, форма даже не вызывает задачи, а только родительские вехи. Вехи отображаются в форме, но задачи-нет....
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][]:
1 ответ:
Я не смог проверить этот код сам, но я реализовал аналогичный код, поэтому идеи должны быть правильными.
Фокус здесь заключается в использовании each_with_index, а затем передаче этого индекса вашему вызовуfields_for
. Таким образом, каждый дополнительныйmilestone_id
, который вы добавляете с помощью флажка, будет значительно отличаться от предыдущего. Вы можете найти другой пример этого здесь .Используя этот подход, ваша форма должна выглядеть примерно так:
<%= 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.
Edit: я забыл, что если вы посмотрите на документы, то check_boxes нуждаются в другом param в середине
Теперь перейдем к мясу ответа. Хотя эта форма действительно работает, и при достаточном количестве манипуляций я думаю, что вы могли бы заставить rails автоматически обновлять ваш проект и через вложенные атрибуты, и создавать все, что вы хотите, я не думаю, что это хороший дизайн.f.checkbox :task_ids, task.id
=>f.checkbox :task_ids, {}, task.id
Что гораздо лучший дизайн-это использование класса builder. Это просто PORO (простой старый рубиновый объект). Что это позволит вам сделать, так это написать хорошие тесты вокруг строителя. Так что вы можете быть гораздо более уверены, что он всегда будет работать, и что некоторые изменения в рельсах не сломали его.
Вот какой-то псевдокод, чтобы вы начали:
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
С этим шаблоном вы закончите с очень хорошо инкапсулированным, легко тестируемым классом, который будет делать именно то, что вы ожидаете от него. Счастливого кодирования.