Как создать тройные вложенные объекты в Rails с разнонаправленными ассоциациями - PullRequest
0 голосов
/ 29 октября 2019

Статья под названием Тройные вложенные формы в Rails представляет хорошее описание создания формы для сохранения трех вложенных объектов. В приведенном примере создается Show, has_many Seasons, и каждый Season has_many Episodes. Также Episode --> belongs_to --> Season --> belongs_to --> Show. Шоу создаются следующим образом:

def new
@show = Show.new
@show.seasons.build.episodes.build
end

Форма выглядит следующим образом:

<%= form.fields_for :seasons do |s| %>
    <%= s.label :number %>
    <%= s.number_field :number %>    <%= s.fields_for :episodes do |e| %>
      <%= e.label :title %>
      <%= e.text_field :title %>
    <% end %> 
 <% end %>
<% end %>

Это кажется простым, поскольку все ассоциации выполняются в одном направлении. Я пытаюсь сделать что-то похожее, но более сложное. У меня есть модель Parent, где каждый Parent имеет несколько Children, а каждый Child зарегистрирован в School. После указания, что Children является множественным числом Child, ассоциация должна быть такой:

Parent has_many Children, accepts_nested_attributes_for :children
Child belongs_to Parent, belongs_to School, accepts_nested_attributes_for :school
School has_many Children, accepts_nested_attributes_for :children

Графически это будет выглядеть так:

Parent <-- belongs_to <-- Child --> belongs_to --> School

КаждаяРодитель также связан с пользователем, например:

User has_many :parents

Данные о родителях, детях и школах вводятся в следующей форме (сгенерированной с использованием жемчужины простой формы), где школы вводятся каквыпадающий селектор из таблицы школ:

@schools = School.all

<%= simple_form_for (@parent) do |f| %>
     <%= f.input :name, label: 'name' %>
     <%= f.simple_fields_for :children, @children do |child_form| %>
         <%= child_form.input :name, label: "Child Name" %>
         <%= child_form.simple_fields_for :school, @school do |school %>
             <%= school.collection_select :id, @schools, :id, :name, {}, {} %>
            <% end %>
     <% end %>
<% end %>

Я настроил метод контроллера new, чтобы создать Parent с тремя Children, зарегистрированными в существующем School. Затем я попытался связать Children с School, который уже существует в таблице schools с id = 1.

def new
   @parent = Parent.new
   # creating 3 children
   @children = Array.new(3) {@parent.children.build}
   @school = School.find(1)
   @school.children.build
end

Это выдает ошибку

Couldn't find School with ID=1 for Child with ID=

Ошибка находится в первой строке метода create, которая выглядит следующим образом:

def create
    @parent = Parent.new(parent_params.merge(:user => current_user))
    if @parent.save
        redirect_to root_path
    else
        render :new, :status => :unprocessable_entity
    end
end

def parent_params
    params.require(:parent).permit(:name, :child_attributes => [:id, :name, age, :school_attributes => [:id, :name]])
end

Поскольку текст ошибки содержит состояние "Child with ID= ", ошибка должна быть выдана, прежде чем будут назначены идентификаторы для новых Children. ,Почему School с ID = 1 нельзя найти, если он существует в таблице schools? Или это означает, что запись School не была правильно связана с экземпляром Child до того, как была предпринята попытка сохранить этот экземпляр? Если так, как я могу исправить ассоциацию?

1 Ответ

1 голос
/ 29 октября 2019

Одно из самых распространенных заблуждений / ошибок в отношении вложенных атрибутов - думать, что оно необходимо для простого назначения ассоциации. Вы неВам просто нужно передать идентификатор установщику assocation_name_id=.

Если факт, использующий вложенные атрибуты, даже не сделает того, что вы хотите. Он не создаст ассоциацию из существующей записи, когда вы выполните child.school_attributes = [{ id: 1 }], скорее он попытается создать новую запись или обновить существующую школьную запись.

Вам нужно будет только принять вложенные атрибуты для школы, еслипользователь одновременно создает школу. И в этом случае, вероятно, лучше использовать Ajax, а не помещать все в одно мега-действие.

<%= simple_form_for (@parent) do |f| %>
  <%= f.input :name, label: 'name' %>
  <%= f.simple_fields_for :children, @children do |child_form| %>
    <%= child_form.input :name, label: "Child Name" %>
    <%= child_form.associaton :school, 
        collection: @schools, label_method: :name %>
  <% end %>
<% end %>
def parent_params
  params.require(:parent).permit( :name, 
    child_attributes: [:id, :name, :age, :school_id]]
  )
end
...