Autocomplete forms in Rails 2.3.3

Posted by Brian in Howto, Rails (August 28th, 2009)

When Rails 2 was released, the core team dropped some of the Javascript Ajax helpers like in-place editing and autocompleting, and released them as plugins. However, if you’ve ever tried to use them with a modern Rails application, you may have encountered problems with routing and authenticity tokens. In this brief post, I’ll walk you through a trivial example that shows how to make Autocomplete work, using the advantages that Rails 2.3 has, including format responses.

First, let’s get a basic Rails app running with a Projects table with a single field, the project’s name :

 rails autocomplete
 cd autocomplete
 ruby script/generate model project name:string
 ruby script/generate resource projects
 rake db:migrate

Next, let’s load some test data.

 ruby script/runner "
 ['The Ninja project', 'The Top Secret project', 'World Domination'].each do |p|
    Project.create(:name => p)
 end
 "

Grab the autocomplete plugin from Rails’ Github repository and install it as a plugin:

 ruby script/plugin install git://github.com/rails/auto_complete.git

Our basic app is installed, but we need to implement a controller action to display projects.

Open app/controllers/projects_controller.rb and add this method:

  def index
    @projects = Project.all :limit => 10
    respond_to do |format|
      format.html
    end
  end

When the user requests HTML, we’ll show the last 10 projects created. Good enough for now. Let’s implement the layout and the view.

Open app/views/layouts/application.html.erb and build a very simple layout:


  
    Autocomplete
    <% =javascript_include_tag :defaults %>
  
  
  
    <%= yield %>
  


This very basic layout loads the default Javascript fiels for Rails and yields the template.

Now open app/views/projects/index.html and add this code:


<% if @projects.any? %>
    <%=render @projects %>
<% else %>

There are no projects to show.

<% end %>

Here we have a list of projects, and when we click the submit button, we’ll filter the results. We’re using render here, and when you pass the render helper a collection, it looks for a partial that it can use render each of the elements in the collection.

Create app/views/projects/_project.erb and add this line:

  • <%=h(project.name)%>
  • Now we have to do the filtering in the controller. Since the search box is simply applying a filter to the list of projects, it makes sense for us to use the index action for the request. This means changing the controller’s index action slightly.

    def index
      keyword = params[:query]
      if keyword.present?
        keyword = "%#{keyword}%"
        @projects = Project.all :conditions => ["name like ?", keyword], :order => "name asc", :limit => 10
      else
        @projects = Project.all :limit => 10, :order => "created_at desc"
      end
      
      respond_to do |format|
        format.html
        format.js do
          render :inline => "<%= auto_complete_result(@projects, 'name') %>"
        end
      end
    end
    

    If the query parameter comes in, then we want to filter. We build up a LIKE query and get the results. If no query parameter is present, we just get the top ten recent projects.

    However, this is really messy. Let’s get that search logic out of the controller.

    Open app/models/project.rb. Move the search logic into a new method:

    def self.find_all_by_keyword(keyword)
      if keyword.present?
        keyword = "%#{keyword}%"
        @projects = Project.all :conditions => ["name like ?", keyword], :order => "name asc", :limit => 10
      else
        @projects = Project.all :limit => 10, :order => "created_at desc"
      end
    end
    

    Now change app/controllers/projects_controller.rb to call the method instead of doing the search:

    def index
      @projects = Project.find_by_keyword(params[:query])
      respond_to do |format|
        format.html
        format.js do
          render :inline => "<%= auto_complete_result(@projects, 'name') %>"
        end
      end
    end
    

    Much better. At this point you have a non-Javascript search for your projects in a very short time.

    Let’s add the autocomplete functionality. First, in the view, change the text field to this:

    <%= text_field_with_auto_complete :project, :name, 
        { :name => "query" },
        {:method => :get, :url => projects_path(:format => :js) } %>
    

    This sets up the autocomplete field to send its queries to our index action, but specifies that we should use the “js” format, perfectly appropriate for this, as it’s a Javascript-only request, so we should provide our response with Javascript.

    Open the controller and modify the controller’s code so it looks like this:

    def index
      @projects = Project.find_all_by_keyword(params[:query])
      respond_to do |format|
        format.html
        format.js do
          render :inline => "<%= auto_complete_result(@projects, 'name') %>"
        end
      end
    end
    

    The only real change is adding the format.js block of code. Here, we’re using Rails’ inline renderer which renders ERb code. Within that ERB, we’re calling the auto_complete_response helper provided by the autocomplete plugin to render the specially formatted list that the auto compelte text field expects. We’re using inline rendering here, but you could use a view with the .js extension. Since it’s one line, I’ll save the file I/O.

    With the responder in place, the autocomplete should start working. This is a pretty simple example, but it does illustrate how to make the autocomplete plugin work in a current Rails application, without working about

    One Response to ' Autocomplete forms in Rails 2.3.3 '

    Subscribe to comments with RSS or TrackBack to ' Autocomplete forms in Rails 2.3.3 '.

    1. on December 17th, 2009 at 1:30 pm

      I’ve been looking forever for something just like this. You’re a lifesaver.

    Leave a reply

    :mrgreen: :neutral: :twisted: :shock: :smile: :???: :cool: :evil: :grin: :oops: :razz: :roll: :wink: :cry: :eek: :lol: :mad: :sad: