Search code examples
javascriptruby-on-railsrailscastsfields-for

Rails - fields_for and javascript to dynamically add and remove fields in a form (railscasts ep. 196-197)


UPDATE

Thank you for your answer.

But If I remove

for requested_role in @project.requested_roles

from the partial, then I can't access to the requested_role.role value, because I don't have the parameter X obtained from the code

for X in @projects.requested_roles 

and I can't write X.role

How can I access this value without using for or .each to scroll the requested_roles of the project?

END UPDATE


I've a problem with a social network I'm developing with Ruby on Rails. I followed the railscasts 196 and 197 to create a form with fields_for and to add fields dinamically with javascript, but I have 2 major problems.

A User can create a Project and this Project must have 1+ Requested_roles. When I open the project edit page to change the roles, if there are N requested_roles for the project, I see N*N forms to change the requested_roles. So if I have 2 requested_roles (for example Director and Producer) I see 4 select fields, Director - Producer - Director -Producer. They are repeated N times. And I can't modify them because I can have max 1 requested_role of each type. It's fine if I have only 1 requested_role (because 1x1=1)

Project.rb

class Project < ActiveRecord::Base
attr_accessible :title, :requested_roles_attributes, :video, :num_followers, :num_likes

belongs_to :user

has_many :requested_roles, dependent: :destroy
accepts_nested_attributes_for :requested_roles, :reject_if => lambda { |a| a[:ruolo].blank? }, :allow_destroy => true

Requested_role.rb

class RequestedRole < ActiveRecord::Base
attr_accessible :role, :project_id 

belongs_to :project

Projects_controller.rb

class ProjectsController < ApplicationController

def new
  @project= Project.new
  @requested_role= @project.requested_roles.build    
end   

Projects/edit.html.erb

<div class="row">
 <div class="span6 offset3">
  <%= form_for(@project) do |f| %>
    <%= render 'shared/error_messages', object: f.object%> 

    <%= f.label :title, "Project title" %>
    <%= f.text_field :title %>

    <%= f.fields_for :requested_roles do |builder| %>
          ciao
         <%= render 'requested_role', :f => builder   %>

    <% end %>

    <div class="fields">
    <p><%= link_to_add_fields "Add requested role", f, :requested_roles %></p>
    </div>

    </br>

    <%= f.submit 'Apply changes', class: 'btn btn-large btn-primary' %>

  <% end %>
</div>
</div>

I think that the error is in this view (Projects/edit):

    <%= f.fields_for :requested_roles do |builder| %>
          ciao
         <%= render 'requested_role', :f => builder   %>
    <% end %>

this code, even without the partial, lead to a N-times repeated requested_roles. In fact, without the partial _requested_role, we have N "ciao", but we should have only one.

projects/_requested_role.html.erb

<% if @project.requested_roles.any? %>
    <p>Modifica ruoli richiesti </p>
<%end%>
<%= @project.requested_roles.count %>
<% for requested_role in @project.requested_roles %>
    <div class="fields">

    <p>
    <p>Requested role: <%= role_to_string(requested_role.role) %></p>

    <%= f.label :role, "Modify role" %>
    <%= f.select :role, options_for_select([["Regista",1],["Sceneggiatore", 2],["Direttore della fotografia", 3], ["Operatore",4],
                                      ["Fonico", 5], ["Montatore", 6], ["Truccatrice",7], ["Costumista",8], ["VFX Artist",9],
                                      ["Produttore", 10], ["Attore",11], ["Attrice",12], ["Grip/Runner",13]], :selected => requested_role.role) %>


    <%= link_to_remove_fields "remove", f %>    #dinamically remove a field
    </p>

<% end %>

</div>

Can you help me please? I can't figure out where the error is. Thank you in advance.

The other problem is related to the links to dinamically delete and add requested_roles (javascript-jquery).

If I have 3 requested_roles (9 select fields instead of 3 because of the error that I mentioned before) and I delete (through link_to_remove_fields) the last one there's no problem. But if I delete the first one, the fields and even the submit button below it disappear and I can't modify the roles or submit the changes.

When I add (through link_to_add_fields) a new role and I already have, for example, 2 requested_roles (Director, Producer), when I click on the link to add the new requested_role another bug occurres. Instead of a select field to choose the role, a copy of the 2 existing select fields (Director, Producer) appears.

application_helper.rb

def link_to_remove_fields(name, f)
  f.hidden_field(:_destroy) + link_to_function(name, "remove_fields(this)")
end


def link_to_add_fields(name, f, association)
  new_object = f.object.class.reflect_on_association(association).klass.new
  fields = f.fields_for(association, new_object, :child_index => "new_#{association}") do |builder|
    render(association.to_s.singularize, :f => builder)
  end
  link_to_function(name, "add_fields(this, \"#{association}\", \"#{escape_javascript(fields)}\")")
end

Application.js

function remove_fields(link) {
    $(link).prev("input[type=hidden]").val("1");
    $(link).closest(".fields").hide();
}


function add_fields(link, association, content) {
    var new_id = new Date().getTime();
    var regexp = new RegExp("new_" + association, "g")
    $(link).parent().before(content.replace(regexp, new_id));
}

I can't understand what goes wrong. If you have some idea can you give me some advice? Thank you very much.

Dario


Solution

  • The problem is that you're iterating twice.

    <%= f.fields_for :requested_roles do |builder| %>
      ciao
      <%= render 'requested_role', :f => builder   %>
    <% end %>
    

    Will automatically repeat the requested_role partial for each requested role. That's why it shows "ciao" N times, because that's what fields_for does at render. You probably need to read the doc to understand how it works: http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-fields_for

    So there is no need to have

    for requested_role in @project.requested_roles
    

    in your partial. It will only repeat all requested roles each time fields_for renders it. Here's what your code should look like in your edit.html.erb :

    <% if @project.requested_roles.any? %>
      <p>Modifica ruoli richiesti </p>
    <%end%>
    
    <%= @project.requested_roles.count %>
    
    <%= f.fields_for :requested_roles do |builder| %>
       <%= render 'requested_role', :f => builder   %>
    <% end %>
    
    <p><%= link_to_add_fields "Add requested role", f, :requested_roles %></p>
    

    And the requested_role partial should simply be:

    <div class="fields">
      <div>
        <p>Requested role: <%= role_to_string(f.object.role) %></p>
    
        <%= f.label :role, "Modify role" %>
        <%= f.select :role, options_for_select([["Regista",1],["Sceneggiatore", 2],["Direttore della fotografia", 3], ["Operatore",4],
                                      ["Fonico", 5], ["Montatore", 6], ["Truccatrice",7], ["Costumista",8], ["VFX Artist",9],
                                      ["Produttore", 10], ["Attore",11], ["Attrice",12], ["Grip/Runner",13]], :selected => f.object.role) %>
    
    
        <%= link_to_remove_fields "remove", f %>
      </div>
    </div>
    

    Fixing your partial should fix your second problem with the links.

    You might want to consider using Ryan's gem for nested_forms

    https://github.com/ryanb/nested_form