Topic: Autocomplete Association + Live Search

My first tutorial. There may be many better ways to do this and add on to this. And thats exactly what im hoping will come out of this post. Ive been digging around and have been helped by a lot of people with this.
These are the models we have

Company has_many Contacts
Contact belongs_to Company
Contact has_many Reminders
Reminder belongs_to Contact

The idea is to send a Reminder to a contact via email. This is the work flow that this tutorial is going to cover.
1.User starts typing either the contact's name or contact's email into the 'To' field.
2.Autocomplete kicks off and returns matching contacts.
3.User scrolls down options and selects the one he/she wants.
4.Selected option appears on the text box
   4a. Only the name appears in the text box. But name, email address and company name are shown in the options list.
5.A live search runs and updates an adjacent div with more details about the chosen contact and his employer(the associated Company model).
6. When the form is submitted, the Reminder created should carry the id of the Contact it was sent to as the foreign key (ie) the association should be created. I followed Ryan Bates tutorial - Railscasts episode 102 - Autocomplete Association for this one.
You will have to install the auto_complete plugin before you start if you are running Rails 2 or above.
script/plugin install http://svn.rubyonrails. org/rails/plugins/auto_complete

Now, the code. Starting with the view. app/views/reminders/_reminder.rhtml.erb
<%#Autocomplete Association%>
To: <%= text_field_with_auto_complete :reminder, :contact_name, {:size => 50}, {:skip_style => false, :url => list_contacts_path(), :method => :get,:with =>"'reminder_contact_name=' + element.value"}%><br/>
<br>

<%#Live search%>
<%=image_tag("/images/spinner.gif",   
:align => 'absmiddle',
:border => 0,                                       
:id => 'spinner',                                   
:style => "display: none;") %>
   
<%= observe_field 'reminder_contact_name',     

:frequency => 0.5,
:update => 'contact',
:before => "Element.show('spinner')",
:success => "Element.hide('spinner')",                   
:url => list_companies_path(),:method =>:get,
:with => 'reminder_contact_name' %>
Contact info:
<div id="contact"></div>


A few points here
-text_field_with_autocomplete is a helper method that comes with the plugin.
-most tutorials suggest setting :skip_style => true to override the default CSS styles in case you are writing your own CSS. The default CSS is good enough for me. Incidentally, if i set it to false, i get the results listed but the surrounding wrapper and the highlighting of selected options etc are gone. For the record.
-list_contacts_path is the controller action that is going to return matched results. Its a REST url and the method :get needs to be specified.
-:with is the option that lets you pass the parameters. 'reminder_contact_name' is just a variable name and can be anything you want. element.value gives the value entered in the field.
-download the spinner image if you have not got it and put it in the public/images folder for the live search.
-observe_field updates the 'contact' div. notice the :with option. Setting it to the field name sends the value in that field under the same name as parameter.
controller code for autocomplete. app/controllers/contacts_controller.rb
 def list
    @contacts = Contacts.find(:all, :conditions => ['name LIKE ? OR email LIKE ?', "%#{params[:reminder_contact_name]}%", "%#{params[:reminder_contact_name]}%"])
    if request.xml_http_request?
      render :partial => "list", :layout => false
    end
  end

the query is designed to match the contact_name as well as the email. renders the list partial. setting :layout=>false is important to avoid getting your application layout or other default layout rendered along with the autocomplete results.
app/views/contacts/_list.js.erb
<ul>
<%- for contact in @contacts do -%>
<li><%=h contact.name %>
    <div class="informal"><%=h contact.email %>|<%=h contact.company.name %></div></li>
<%- end -%>
</ul>

This shows the contact name, the email, and the company the contact belongs to. You can pretty much show anything you want here using @contacts but its a good idea to show only distinguishing details in autocomplete results.
Setting the div class to 'informal' takes care of step 4a (ie) set div class to informal for those fields that you do not want to appear on the text box when an option is selected.
Now the controller code for the live search.
app/controllers/companies_controller.rb
def list
        unless params[:reminder_contact_name].blank?
          @contact = Contacts.find_by_name(params[:reminder_contact_name])
          if @contact
           @company = @contact.company
          end
        end 
                                                       
          if request.xml_http_request?
            if !@company.blank?
              render :partial => "show", :layout => false
            elsif params[:reminder_contact_name].blank?
              render :text => "Please enter a contact's name or email address"
            elsif @company.blank?
              render :text => "Info not found for '#{params[:reminder_contact_name]}'"
            else
              render :text => "Please select a contact"
            end
          end   
    end

The second half of the code here isnt really very pretty. Was trying to cover various possible states of the live search. Definitely an area that can use polishing up.
app/views/companies/_show.js.erb
<ul>
<li>Company name: <%=h @company.name %></li>
<li>Address : <%=h @company.address_1%> <%=h @company.address_2%>
          <%=h @company.city%> <%=h @company.state%> <%=h @company.country%>
          <%=h @company.zip%></li>
</ul>

Again, you can show pretty much anything you want here using the @company instance variable.
One last piece of code. The contact_name field in the autocomplete is not really an attribute of the reminder model. Its a virtual attribute. So this virtual attribute needs a getter and setter method in the Reminder model.
def contact_name
    contact.name if contact
  end
 
  def contact_name=(name)
    unless name.blank?
      self.contact = Contacts.find_by_name(name)
      if self.contact.blank?
        self.contact = Contacts.find_or_create_by_email(name)
      end
    end
  end

I have not tested the find_or_create_by in this. What it should do is, if the contact with the given name is not found, it searches for a contact with the given email. If it does not find one, it creates a Contact with that email. And the Reminder is sent to that email address. This I am doing to make the process of sending a Reminder, either to an existing or new contact, as easy and seamless as possible for the user.
One other gotcha. Dont use too many divs in the partials that render the results of the autocomplete and live search. It does not work in Safari.
Except for that, this code works with Safari(latest), FF(latest), IE7 and IE6. I hope i have covered everything.
Thanks to Duplex, Xavier Noria, Neil and of course Ryan Bates for their direct/indirect help with this.
Some other stuff you might wanna refer.
http://groups.google.co.in/group/rubyon … &pli=1
http://railsforum.com/viewtopic.php?id=22902
http://railscasts.com/episodes/102-auto … ssociation

And finally, the small problem that I am yet to fix. In the setter method for the virtual attribute contact_name, i need to scope the query through current_user. but current_user is a helper method. And the params hash(which carries the current_user_id in my case) too is not available in the model. Hope someone can help me out with that. Details here.

Re: Autocomplete Association + Live Search

Have you found a solution to this yet?  I have run into the exact same problem.  I have tried creating a virtual attribute for current_user_id and sending that as an additional param in find_or_create_by.

I have a contact model and I am trying to autocomplete the company field to associate companies with contacts.

find_or_create_by_name(@company_name, @account_id)

The account is associated with both contacts and companies so when I perform the above code the company gets set corretly, but now my contacts.account.id = null in my database.  So I have just reversed my problem smile

Re: Autocomplete Association + Live Search

This link should help you m.onkey.org/2007/10/17/how-to-access-session-cookies-params-request-in-model

Re: Autocomplete Association + Live Search

Very informational and educational as usual!

Live Cricket Score

Last edited by velirey11 (2011-01-17 00:52:11)