This is a simple contact manager application. What this application primarily demonstrates is the ability to add and remove multiple associated sub-models dynamically on a single form. The idea of a contact manager is well understood by most audiences and are thus commonly used as a demonstration application. This particular contact manager demonstrates (and improves upon) Ryan Bates “Handle Multiple Models in One Form” recipe (#13) from the Advanced Rails Recipes book.
In Ryan’s original recipe, he added discrete field values (individual tasks on a to-do list) to the form. Any tasks that did not have an identifier associated with it was a new task, and any missing task identifiers from those currently in the database were assumed to be deleted.
project[new_tasks_attributes][][name] = "a new task" project[existing_task_attributes][1][name] = "task 1" project[existing_task_attributes][3][name] = "task 3"
The controller would know to a the new task, and possibly remove task 2 if one currently exists in the database.
In the original recipe, the new and existing tasks were separated out using different identifiers in the form element names (new_task_attributes
vs. existing_task_attributes
“), however, to allow Rails to both construct and consume these values more naturally as nested models, we’d like to name them as Rails would expect and make them all tasks
.
project[tasks][][name] = "a new task" project[tasks][1][name] = "task 1" project[tasks][3][name] = "task 3"
The shortcoming to this recipe is when you need to send in a whole group of fields that need to be combined for a single record.
project[tasks][][name] = "first new task" project[tasks][][priority] = "urgent" project[tasks][][name] = "another new task" project[tasks][1][name] = "task 1" project[tasks][1][priority] = "high" project[tasks][3][name] = "task 3" project[tasks][3][priority] = "low"
How does Rails know to construct the tasks with no unifying identifier? The name
and priority
values may not stay together in pairs when the request is processed, and further if you have optional fields such as the priority
in this case, which task should get the priority?
To solve this problem, this contact manager application uses a JavaScript variable to assign temporary unique identifiers to newly added nested models (addresses, phone numbers, urls, etc). They are assigned a negative value in order for the contact contoller to distinquish between new and existing records.
This sample also utilizes the information from “Combine contact attributes in model”
This code was originally written by me as part of the PeepNote application. PeepNote was written in Rails 2.3.3 as part of the 2009 Rails Rumble. For this stand-alone version of the contact manager, I ported it from Rails 2 with prototype to Rails 3 with jQuery. Rails 3’s automatic escaping of special characters for HTMl and JavaScript caused some issues. One of which was particularly problematic and I could not make it work (even with the raw()
function) without re-writing how it worked.
git clone git@github.com:pothoven/contacts.git cd contacts bundle install rake db:migrate rake db:populate_states_provinces_countries rake assets:precompile rails server
bundle install rake db:migrate rake db:populate_states_provinces_countries rails server
gem install heroku heroku create git push heroku master heroku rake db:migrate heroku rake db:populate_states_provinces_countries heroku open
git pull git push heroku master