Re: Creating Variable Number of Models in One Form

Also, if I debug the params[:resume][:employment_histories] I get the following, which to me shows that the params are named correctly:

["0", {"end_date(3i)"=>"19", "start_date(1i)"=>"2007", "start_date(2i)"=>"6", "start_date(3i)"=>"19",
"degree_level"=>"-- Please select --", "key_achievements"=>"", "address"=>{"city"=>"",
"zip"=>"", "street_1"=>"", "street_2"=>"", "country"=>"United States", "fax"=>"",
"phone"=>"", "state"=>""}, "end_date(1i)"=>"2007", "end_date(2i)"=>"6"}]

Re: Creating Variable Number of Models in One Form

Try going back to what you had in your earlier post, where education_histories was on its own parameter: "education_histories[#{index}]" instead of "resume[education_histories][#{index}]"

Also, I made some edits to my previous post so try following that in order to get the address working properly.

Railscasts - Free Ruby on Rails Screencasts

Re: Creating Variable Number of Models in One Form

Tried your edited changes (with and without the new address_attr) method and I get this error:

undefined method `0=' for #<EducationHistory:0x254c74c>

So, seems again like Rails is having trouble w/the array of objects params. Kinda bummed on this cuz this object graph is not that complicated and it should 'just work'.

Since the education_histories association is right off the root level object, there's gotta be something small I'm missing as your previous examples weren't too complicated and worked. I'm going to backtrack on those and see if I can't find more clues. I'll post a detailed solution here - if I find one.

Thx!

Re: Creating Variable Number of Models in One Form

Are you looping through the education_histories parameter in the create action in your controller?

# in create action
params[:education_histories].each_value { |education_history| @resume.education_histories.build(education_history) }

Railscasts - Free Ruby on Rails Screencasts

Re: Creating Variable Number of Models in One Form

Hang on a sec, so let me backup a little bit on my stuff. Maybe this is what's messing everything up. In my form_form I'm using the 'resume' model. It has multiuple 'employment_histories' and each one of those has ONE address. So I'm building my view like so:

<% @resume.education_histories.each_with_index do |education_histories,index| %>

<% fields_for "education_histories[#{index}]", education_histories do |education_histories_fields| %>

<p>highest degree:<br/><%= education_histories_fields.text_field :degree_level %></p>

  <% fields_for "education_histories[#{index}][address_attr]",
  @resume.education_histories[index].address do |@address_fields| %>
  <%= render :partial => 'address'%>
  <%end -%>

<%end -%>


So you can see in the first fields_for that I'm using it for the field that exists directly in the EmploymentHistory object. Then I'm using the next fields_for for the wrapped address object. Maybe there's some weird issue with nesting multiple fields_for tags?

I suspect also that if I get this solved then the whole param graph will populate directly from the root resume object. But first things first.

Re: Creating Variable Number of Models in One Form

Solution for multiple models on one form with nested collections and nested objects.

I got things working and the short is that it doesn't seem that (1.1.6) Rails likes creating nested collections with nested objects in them from form values. While I love Rails, I don't see why this is an issue. But I suppose as long as there's a work around, I'm cool with that and hopefully this helps others that might run into this issue.

My form is a resume form where a user can have multiple education histories and each education history can have one address. The address partial is common both to the resume object as well as the nested education history objects.

I have, as one would expect, the form broken up into multiple partials, each one wrapping their specific model. So there's an _address partial for all address objects used on the form as well as a _education_histories partial, which renders the _address partial. Also I may note that the education histories are Ajax based, so I can add/remove a history w/out forcing a page reload.

Models:

class Resume < ActiveRecord::Base
  has_one :address
  has_and_belongs_to_many :education_histories
end

class EducationHistory < ActiveRecord::Base
  has_and_belongs_to_many :resumes
  has_one :address
   
  def address_attr=(attributes)
    build_address if address.nil?
    address.attributes = attributes
  end
end

class Address < ActiveRecord::Base
  belongs_to :resume
  belongs_to :education_history
end


Controller (which I'm only listing partially and including the ajax methods):
class ResumeController < ApplicationController
 
  def new
    @resume = Resume.new
    @resume.education_histories << EducationHistory.new(:address=>Address.new)
    @resume.address = Address.new
  end

  def create
    @resume = Resume.new(params[:resume])
    params[:education_histories].each_value { |history| @resume.education_histories.build(history) }
    if @resume.save
     render :action => 'success'
    else
     render :action => 'resume'
    end
  end
 
  def add_education_history # Ajax
    @resume = Resume.new(params[:resume])
    params[:education_histories].each_value { |history| @resume.education_histories.build(history) }
    @resume.education_histories << EducationHistory.new(:address=>Address.new)
    render :partial => 'education_history'
  end
 
  def remove_education_history # Ajax
    @resume = Resume.new(params[:resume])
    params[:education_histories].each_value { |history| @resume.education_histories.build(history) }
    @resume.education_histories.pop
    render :partial => 'education_history'   
  end
 
end


Views (w/relevant snippets):

resume.rhtml

<% form_for :resume, @resume, :url => { :action => "create" }, 
:html => {:name=>'resume_form',:id=>'resume_form'} do |@resume_form| %>
<p>Resume title:<br/><%= @resume_form.text_field :title %></p>

<p>Your address:<br/>
<% fields_for :address, @resume.address do |@address_fields| %>
<%=render :partial => 'address'%>
<%end%><p>

<div id="education_history"><%= render :partial=>"education_histories" %></div>

<% end -%>


_education_histories.rhtml
...
<% # Ajax add/remove education history links %>
<% if @resume.education_histories.length < 11 %><%= link_to_remote 'Add education history',
:update=>'education_history', :url => { :action =>'add_education_history' }, 
:with => "Form.serialize($('resume_form'))" %>
<% end %>

<% if @resume.education_histories.length > 1 %> |
<%= link_to_remote 'Remove last education history',
:update=>'education_history', :url => { :action =>'remove_education_history' },
:with => "Form.serialize($('resume_form'))" %>
<%end%>
<%# end Ajax links %>

<% @resume.education_histories.each_with_index do |education_histories,index| %>
<% fields_for "education_histories[#{index}]", education_histories do |education_histories_fields| %>
<p>Highest degree:<br/><%= education_histories_fields.text_field :highest_degree %></p>

  <% fields_for "education_histories[#{index}][address_attr]",
  @resume.education_histories[index].address do |@address_fields| %>
  <%= render :partial => 'address'%>
  <%end -%>
<%end -%>
...


_address.rhtml
...
<p>Street 1:<br/><%= @address_fields.text_field :street_1 %>
<p>Street 2:<br/><%= @address_fields.text_field :street_2 %>
...

So with this I'm able to build a complex form with nested collections which have nested objects. Most of the official Rails docs and books don't really touch on this complexity for their various reasons. I can say that this works for me and much thanks to Ryan for helping solve it.

Re: Creating Variable Number of Models in One Form

i tried this code and it works perfectly for my project but i still don't understand some things about it and i would like to understand it well so it can help me in other ways. Firstable i don't understand how you can calculate the "next index" basing on the @project.tasks array when this array has always one model object, the one we build in the new action with: "@project.tasks.build". Then i don't understand how you can "regenerate" the add_task ajax link with partials since at every ajax request only the task html (for a new task) is updated in the already generated html on the browser. Thank you

Re: Creating Variable Number of Models in One Form

problems now are:

1) when i save the first/main model i notice that in the second/associated model table there is always a row filled with NULL values and i suppose that is so because we create an empty second model at the beginning of the create action.

2)if the all form don't pass some validation and it is redisplayed to the user, i see that it is added one more second model field even if i don't click the add_secondmodel ajax link

Re: Creating Variable Number of Models in One Form

@Jason for #86

Thanks a ton for your detailed and very helpful post

Re: Creating Variable Number of Models in One Form

waiting for a response of the problems above by the author of the tutorial i would like to add another issue. How is the best approach to build the equivalent edit page?

Re: Creating Variable Number of Models in One Form

I'm wondering whether anyone has encountered the same problem as me. When I add, say, 4 or 5 extra lines after my 5 initial lines using the AJAX button and save all my fields, I find them saved out of order. That is, the AJAX-generated lines are saved first in the database before the initial lines that were generated on the initial page load. So when I go and view the record, my inputs are not listed in the order they were inputted. Any advice/suggestions would be greatly appreciated.

Re: Creating Variable Number of Models in One Form

Try replacing this:

params[:tasks].each_value { |task| @project.tasks.build(task) }

with this:

params[:tasks].sort.each { |index, task| @project.tasks.build(task) }

Untested

Railscasts - Free Ruby on Rails Screencasts

Re: Creating Variable Number of Models in One Form

ryanb wrote:

Try replacing this:

params[:tasks].each_value { |task| @project.tasks.build(task) }

with this:

params[:tasks].sort.each { |index, task| @project.tasks.build(task) }

Untested

ryanb have you read my posts above?:)
Maybe it would be great to have this tutorial in a screencast wink
Anyway i still have those problems pointed above and i need some advice to do the equivalent edit code

Re: Creating Variable Number of Models in One Form

i'm still there with the edit code, my only problem is when i remove a second model "in the middle" and then i submit the edit form: Rails throws an error that says:
You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.[]

if instead i remove second models in order it doesn't happen.
my code is the following for the action:

def edit_enterprise
    @banks = Bank.find(:all, :order => "nome").map {|b| [b.nome, b.id] }
    @customer = Customer.find(params[:id])
    if request.post?
      if params[:contacts]
        params[:contacts].each_with_index do |value, index|
          if params[:contacts][index.to_s]['id'] == ''
            @customer.contacts.create(params[:contacts][index.to_s])
          else
            @customer.contacts.find_by_id(params[:contacts][index.to_s]['id']).update_attributes(params[:contacts][index.to_s])
          end
        end
      end
     
       if !params[:customer][:sede_legale].blank?
          if params[:adress_check][:domifisc_check] == "1"
            params[:customer][:domi_fisc] = params[:customer][:sede_legale]
          end
          if params[:adress_check][:indfatt_check] == "1"
            params[:customer][:ind_fatt] = params[:customer][:sede_legale]
          end
          if params[:adress_check][:indmerce_check] == "1"
            params[:customer][:ind_merce] = params[:customer][:sede_legale]
          end
        end
     
      if @customer.update_attributes(params[:customer])
        flash[:notice] = 'Cliente aggiornato con successo'
        redirect_to :action => 'index'
      end
    end
   
  end

Last edited by skyblaze (2007-08-28 09:28:32)

Re: Creating Variable Number of Models in One Form

First off great tutorial! This may seem trivial but I can't seem to get this to work for me. In your example you have a Project that has many Tasks. But lets say you wanted to add a Priority on a given task (via a drop down "high/medium/low").

I have set up another table/model as a lookup table called (priority) with one field 'name' I also added a priority_id (int) to the tasks table.

I added the appropriate "has_many tasks" to the priority model and "belongs_to priority" in the task model.

I then added this to the "_task_fields.rhtml"
     <%= collection_select :task, :priority_id, Priority.find(:all), :id, :name %>

When I create a new, I see the drop down and it values for each task, however when I save the new Project I get a NULL value for the priority_id field.

What am I doing wrong?

Re: Creating Variable Number of Models in One Form

You need to go through the form builder when generating the field so it gives it the proper name.

<%= f.collection_select :priority_id, Priority.find(:all), :id, :name %>

Railscasts - Free Ruby on Rails Screencasts

Re: Creating Variable Number of Models in One Form

awesome...worked great. thank you!

Re: Creating Variable Number of Models in One Form

Great tutorial. Thanks, man.

Re: Creating Variable Number of Models in One Form

I've been looking at both this and the post from nagash here:

http://railsforum.com/viewtopic.php?id=3686

but they both have the same problem that they work fine in IE6, but don't work in firefox.

Actually, the kind of work, in that firefox will add the input field when you click "Add a Task", but when you submit the form it won't submit any of the task values.

In nagash's tutorial I figured out that firefox throws a "this.element has no properties" error, though I don't know if that is the source of the problem.

Any thoughts would be helpful, and I'll post back if/when I figure out why.

Thanks

Re: Creating Variable Number of Models in One Form

ryan thanks for this great tutorial. This is a complex topic with very poor documentation, I'm very thankful that I could find something about this.

I took a peek at the generated code in Highrise, by 37 signals. They seem to have taken a different approach.Maybe there's something to learn from that.

If you look at the form for editing contacts of a person, we have (amongst others) a variable number of phone numbers, and corresponding buttons to add and delete them.

Look at the corresponding textfield tag:

<input type="text" size="30" name="person[contact_data][email_addresses][][address]"
id="person_contact_data__email_addresses__address" class="autofocus"/>

All textfields for that nested collection have exactly the same tag, even when adding new ones. That's because it doesn't refer to the index entry in the collection array. One clear advantage to that is that it simplifies the process quite a bit: they don't need to figure out the number of entries in the collection when adding a new tag with AJAX, they just add the very same tag.I haven't been able to implement the idea yet, the new template works, but when submitting to create I'm getting a "[name of model] expected, got HashWithIndifferentAccess" error.

Another interesting thing, is that when it refers to an already created child (not a new record), a hidden input is inserted in the fieldset, with a value of its id in the table, probably used to keep track of it, and not creating a duplicate. Haven't figured out how exactly they implemented that, but looks like a pretty good idea.

I can't get further into this right now, I'll get back to it in a couple of days. I was hoping that in the meantime you could give some insight about if this approach could be worth the trouble of trying to 'guess' its implementation.

One last thing, Highrise was released a few months ago (way after 1.2.3 release), and was very probably built with edge rails. I was wondering if edge has specific functionalities for this that 1.2.3 doesn't. Could it be required to jump to edge to be able to profit from this approach ?