Update November 2009: My View Mapper gem now supports generating scaffolding code for a complex form with auto_complete behavior like the one I describe below right inside your application, using your models and attributes. For more info see: http://patshaughnessy.net/2009/11/25/scaffolding-for-auto-complete-on-a-complex-nested-form. You can read more about my fork of the auto_complete plugin here: http://patshaughnessy.net/repeated_auto_complete.
In his “Complex Forms” series (part 1, part 2 and part 3) Ryan Bates does a fantastic job explaining how to create a complex form containing a series of parent/child text fields while still using simple, clean code. Ryan also pushed the sample application from the screen cast onto github, here: http://github.com/ryanb/complex-form-examples
Here’s what Ryan’s sample complex form looks like:

One problem I ran into while using Ryan’s suggestions on a complex form I was writing was how to get auto complete behavior to work properly using the auto_complete plugin for fields that are repeated, like the “task” field here. As I explained in a previous blog post, this causes a lot of problems for the auto_complete plugin since the <input id=””> attributes are no longer unique, breaking the javascript used for auto complete. I was able to solve the problem by modifying the auto_complete plugin to generate unique <input id=””> attributes, among other things.
Here I want to take some time to show how to use my modified auto_complete plugin, using the same sample application from Ryan’s screencast. To get started, let’s clone the git repository for the sample app - this command refers to my fork of Ryan's complex-form-examples repository: http://github.com/patshaughnessy/complex-form-examples
$ git clone git://github.com/patshaughnessy/complex-form-examples.git Initialized empty Git repository in /Users/pat/rails-apps/complex-form-examples/.git/ remote: Counting objects: 192, done. remote: Compressing objects: 100% (122/122), done. remote: Total 192 (delta 71), reused 159 (delta 58) Receiving objects: 100% (192/192), 86.19 KiB | 68 KiB/s, done. Resolving deltas: 100% (71/71), done.
Ryan had saved various versions of the sample app in different git branches, so to avoid confusion I’ve saved my auto complete related changes in a branch called “auto_complete.” So next you should switch to that branch:
$ cd complex-form-examples $ git checkout origin/auto_complete Note: moving to "origin/auto_complete" which isn't a local branch If you want to create a new branch from this checkout, you may do so (now or later) by using -b with the checkout command again. Example: git checkout -b <new_branch_name> HEAD is now at 4f3e908... Sample app code changes for auto_complete
Now you will see my changes in Ryans’ code, except for one more detail: I saved my version of the auto_complete plugin in this git repository as a submodule. To get the plugin’s code for this sample app you need to run these commands:
$ git submodule init Submodule 'vendor/plugins/auto_complete' (git://github.com/patshaughnessy/auto_complete.git) registered for path 'vendor/plugins/auto_complete' $ git submodule update Initialized empty Git repository in /Users/pat/rails-apps/complex-form-examples/vendor/plugins/auto_complete/.git/ remote: Counting objects: 22, done. remote: Compressing objects: 100% (21/21), done. remote: Total 22 (delta 5), reused 0 (delta 0) Receiving objects: 100% (22/22), 7.65 KiB, done. Resolving deltas: 100% (5/5), done. Submodule path 'vendor/plugins/auto_complete': checked out '0814a25a754a235c5cf6f7a258fa405059a5ca6f'
(Note that normally to install the plugin in your app you would just run “script/plugin install git://github.com/patshaughnessy/auto_complete.git” – the submodule is only present in this sample app.) Now to setup and run the application you just need to:
- Enter your MySQL details in config/database.yml
- Run rake db:migrate
- Run script/server to launch the app
If you enter a few records you should be able to see the auto complete drop down, even for the repeated field:

Let’s review the changes I’ve made to Ryan’s code aside from adding my modified version of auto_complete to vendor/plugins. First, I added the standard auto_complete handlers to projects_controller.rb for both the project and task fields:
class ProjectsController < ApplicationController auto_complete_for :project, :name auto_complete_for :task, :name …
Next I modified the project text field to use auto complete (in views/projects/_form.html.erb):
<p>
<%= f.label :name, "Project:" %>
<%= text_field_with_auto_complete :project, :name, {}, {:method => :get } %>
</p>
These two changes enable auto complete for the single project text field, just the same way you would with any text field and the standard auto_complete plugin. However, to get auto complete to work with the repeated tasks field, we need to use changes I’ve made to auto_complete. First, in helpers/projects_helper.rb change the “fields_for_task” method to use my new auto_complete_fields_for method, like this:
def fields_for_task(task, &block)
new_or_existing = task.new_record? ? 'new' : 'existing'
prefix = "project[#{new_or_existing}_task_attributes][]"
auto_complete_fields_for(prefix, task, &block)
end
This causes my code in auto_complete to provide a custom form builder object, which we can use in the view as follows (views/projects/_task.html.erb):
<% fields_for_task task do |f| -%>
<%= error_messages_for :task, :object => task %>
<%= f.label :name, "Task:" %>
<%= f.text_field_with_auto_complete :task, :name, {}, {:method => :get } %>
<%= link_to_function "remove", "$(this).up('.task').remove()" %>
<% end -%>
Here I’ve called “text_field_with_auto_complete” as a method on the “f” form builder object yielded by fields_for_task. This will cause the auto complete script and HTML to be generated with unique <input id=””> attributes, allowing the auto complete behavior to work properly.
One other change I made was also to helpers/projects_helper.rb:
def add_task_link(name)
link_to_remote "Add a task", :url => {
:controller => "projects",
:action => "add_task_script"
}
end
Here I’ve changed Ryan’s “link_to_function” call to “link_to_remote.” As Ryan explains in part 2 of his complex forms screen cast, link_to_function avoids an AJAX call to the server to obtain the HTML for each new task <input> tag, avoiding unnecessary load on the server since all of the task fields are the same. However, with my changes to auto_complete the HTML generated for the task field contains random numbers which are different for each copy of the field… meaning that we do need a separate call to the server to obtain the task field HTML and script. To handle the call from link_to_remote, I’ve added a new file, views/projects/add_task_script.rjs:
page.insert_html :bottom, :tasks, :partial => 'task', :object => Task.new
… which works essentially the same way as described by Ryan, but is called each time the user clicks “Add a task.”
The last change I made to the sample app is in routes.rb; these changes are required to allow the controller to map the Ajax requests, and to insure that these requests use GET, and not POST HTTP requests:
map.connect 'projects/auto_complete_for_project_name',
:controller => 'projects',
:action => 'auto_complete_for_project_name'
map.connect 'projects/auto_complete_for_task_name',
:controller => 'projects',
:action => 'auto_complete_for_task_name'
map.connect 'projects/add_task_script',
:controller => 'projects',
:action => 'add_task_script'
map.resources :projects,
:collection => {
:auto_complete_for_project_name => :get,
:auto_complete_for_task_name => :get
}
This certainly seems very ugly, and probably could be simplified! But for now, we need this code to avoid problems with CRSF protection; see http://www.ruby-forum.com/topic/128970.
13 responses so far ↓
1 Javix // Feb 18, 2009 at 08:33 AM
2 pat // Feb 19, 2009 at 11:26 AM
Hi Javix,
Auto_complete should work just the same way. With a has_many :through relationship ActiveRecord is smart enough to expose the other model in the same way it does with a simple has_many. Using your example, if Project has_many :members, :through => :participations and if p = Project.new you could just call p.members to get the associated members. (Note you have to also put has_many :participations in Project also.)
The form and auto_complete related code will all be the same: “auto_complete_for” in one of your controllers to get the selection list (for example ProjectsController might have auto_complete_for :member, :name) and the rest of the form would work the same way it does with a simple has_many.
The only question you have to think about would be the design of the form: would the user be able to edit a project, add members and also select profiles all at the same time? Sounds very complicated to me. I would maybe let the user first add members to the project, and then have a second form appear after the first allowing the user to select a profile for each new member. Or maybe there’s a default profile each member is assigned initially, and then later the user can optionally change it. The auto complete part of this might be the simplest part…
I did modify my sample app to test this, at least for the simple case of adding members to a project, associated through participations; if you’re interested I could post this in a new branch on github so you can take a look. Or let me know how else I can help. Good luck!
3 Javix // Feb 24, 2009 at 10:22 AM
4 Javix // Feb 24, 2009 at 10:32 AM
f.text_field_with_auto_complete :member, :name, {}, {:method => :get } %> <%end> The problem - is that I'll have to get all selected members IDs + Profile but I don't see how for the moment.
5 Erik // Feb 24, 2009 at 09:53 PM
6 pat // Feb 24, 2009 at 11:47 PM
@Erik – yup… typo on script/plugin – thanks for that. I just updated the text. Turns out I was also missing a step for rake db:migrate; just run that after getting the code from github to create the projects and tasks tables.
I have heard about the recent changes to Rails for complex forms, which sound great. Ryan Bates wrote the original complex forms sample app way back in July, and it uses Rails 2.1.0 so it’s a bit dated by now... Seems to me the auto complete behavior should work exactly the same way, but I’ll have to try it to be sure. I’m guessing some or all of the model code (e.g. save_tasks, and existing_task_attributes=) will be simpler or completely unnecessary. I’ll update the sample when I have time. Thanks for your interest... let me know if it works for you or not.
7 pat // Feb 24, 2009 at 11:53 PM
@Javix… The code you posted here (no worries about the formatting) is just the same as what I had tried last week after reading your earlier comment. The auto complete behavior should be working fine for you, since it just queries the “name” column on the members table, and really isn’t impacted by the has_many through associations you have setup or anything else.
I’m not sure exactly how to make this work, but for now one suggestion I have for you is to try using a separate virtual attribute on your project model for each contact/member field. Ryan Bates did a screen cast explaining this idea and I believe his screen casts on complex forms also mentions it at one point. My idea is that your project model could have a virtual attribute for each type of contact, so 5 methods for each of Tech, HR, Network, etc… for example: def tech_contact= and def hr_contact= … then the code inside these methods would create the necessary participation and member records as needed using your ActiveRecord associations. And your form would be simple and easy to read since the 5 field names would correspond to each of these methods. Does this make sense? I might have time later this week to try to add this sort of behavior to the sample app. Or let me know if you can get it to work somehow.
8 SpirosK // Feb 26, 2009 at 07:40 AM
9 SpirosK // Feb 26, 2009 at 08:20 AM
10 pat // Feb 26, 2009 at 10:09 AM
Hi Spiros, Sorry for all the confusion. “git submodule” is only required for this particular sample app because of the way it was setup in git. Looks like your first error message happened because you forgot the submodule steps, and so the plugin was missing. Undefined "auto_complete_for" just means you don't have the auto_complete plugin, or it is not loading for some reason.
For a normal Rails project all you need to do to get my version of the auto_complete plugin is:
11 SpirosK // Feb 27, 2009 at 03:29 AM
12 fahri // Aug 24, 2009 at 12:22 PM
13 pat // Aug 24, 2009 at 11:11 PM
Hi Fahri, sorry I haven’t gotten around to updating this sample app yet; I will definitely get to it soon. As I mentioned at the top, in June I made some changes to the my auto_complete plugin that make it easier to use; see: http://patshaughnessy.net/2009/6/15/repeated-auto-complete-plugin-usage-change. In a nutshell, you don’t need “auto_complete_fields_for” any more; just use form_for or fields_for. This would explain your error message. Also, don’t rename and keep the original auto_complete plugin – just delete it. Rails will load any code under vendor/plugins, so having both copies of the same plugin with different names will probably cause problems.
With regard to the git commands: looks like I forgot “cd complex-form-examples” between the git clone command and the git checkout command. After running “git clone…” you should have a new subfolder called “complex-form-examples” – just cd into that folder before running the rest of the commands. I'll add this missing command to the article right now...
Thanks for trying the plugin!
Leave a Comment