Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tree: dcba86b9a7
Fetching contributors…

Cannot retrieve contributors at this time

108 lines (86 sloc) 4.07 kb

Introduction

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.

Install

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

Run locally

bundle install
rake db:migrate
rake db:populate_states_provinces_countries
rails server

Run on Heroku

gem install heroku
heroku create
git push heroku master
heroku rake db:migrate
heroku rake db:populate_states_provinces_countries
heroku open

Update

git pull
git push heroku master
Jump to Line
Something went wrong with that request. Please try again.