Search code examples
ruby-on-railshas-manyapache-cocoon

Cocoon nested_form render yourself


I'm trying to create a form that calls itself with cocoon, question has_many questions, is generating infinite loop :(

Model

class Question < ActiveRecord::Base
  has_many :questions,:foreign_key => "parent_id", :dependent =>:destroy
  belongs_to :basic_component

  attr_accessible :description, :questions_attributes, :questions

end

my question _form

<%= semantic_form_for [:admin, @question] do |f| %>
  <%= f.inputs do %>
    <%= f.input :description %>
    <div class="questions">
      <%= f.semantic_fields_for :questions do |question| %>
          <%= render 'question_fields', :f => question %>
      <% end %>
      <div class="links">
        <%= link_to_add_association("Nova Pergunta", f, :questions, class: 'button') %>
      </div>
    </div>
  <% end %>
  <%= f.actions %>
<% end %>

my _question_fields

<div class="nested-fields">
  <%= f.inputs do %>
    <%= f.input :description} %>
    <div class="questions">
      <%= f.semantic_fields_for :questions do |question| %>
        <%= render 'question_fields', :f => question %>
      <% end %>
      <div class="links">
        <%= link_to_add_association("Nova Pergunta", f, :questions, class: 'button') %>
      </div>
    </div>
  <% end %>
</div>

Infinite loop :(, how to solve?

  Rendered admin/questions/_question_fields.html.erb (168.4ms)
  Rendered admin/questions/_question_fields.html.erb (376.2ms)
  Rendered admin/questions/_question_fields.html.erb (586.4ms)
  Rendered admin/questions/_question_fields.html.erb (780.2ms)

Solution

  • The link_to_add_association also pre-renders the nested form, at server side, so when the link is clicked, it can insert the "new" item.

    So that is where your endless loop comes from: the link_to_add_association renders the nested-form, which also renders the nested-form and the link_to_add_association which also ... ad infinitum ;)

    If you really want to be able to build an indefinite/unlimited tree, cocoon is not the tool for you. You will have to resort to using ajax.

    However if you can limit the maximum depth, it is rather easy to add an extra parameter to the view which only renders the link_to_add_association if the level is below your maximum level.

    This has come up before in the cocoon issues, and a possible solution was demonstrated

    In short, assume the maximum depth is 5, you do something like (haml for readability) :

    = semantic_form_for [:admin, @question] do |f| 
      = f.inputs do
        = f.input :description
          .questions
            = f.semantic_fields_for :questions do |question|
              = render 'question_fields', f: question, depth: 0
          .links
            = link_to_add_association "Nova Pergunta", f, :questions, 
                 class: 'button', render_options: {locals: {depth: 0}}
      = f.actions
    

    and your question_fields partial then tests for this depth (and propagates it)

    .nested-fields
      = f.inputs do
        = f.input :description
        .questions
          = f.semantic_fields_for :questions do |question| 
            = render 'question_fields', :f => question, depth: depth + 1
        - if depth < 5
          .links
            = link_to_add_association "Nova Pergunta", f, :questions, 
              class: 'button', render_options: {locals: {depth: depth + 1}}