marcamillion
marcamillion

Reputation: 33795

Getting rails3-autocomplete-jquery gem to work nicely with Simple_Form with multiple inputs

So I am trying to implement multiple autocomplete using this gem and simple_form and am getting an error.

I tried this:

<%= f.input_field :neighborhood_id, collection: Neighborhood.order(:name), :url => autocomplete_neighborhood_name_searches_path, :as => :autocomplete, 'data-delimiter' => ',', :multiple => true, :class => "span8" %>

This is the error I get:

undefined method `to_i' for ["Alley Park, Madison"]:Array

In my params, it is sending this in neighborhood_id:

"search"=>{"neighborhood_id"=>["Alley Park, Madison"],

So it isn't even using the IDs for those values.

Does anyone have any ideas?

Edit 1:

In response to @jvnill's question, I am not explicitly doing anything with params[:search] in the controller. A search creates a new record, and is searching listings.

In my Searches Controller, create action, I am simply doing this:

@search = Search.create!(params[:search])

Then my search.rb (i.e. search model) has this:

def listings
    @listings ||= find_listings
end

private
  def find_listings
    key = "%#{keywords}%"
    listings = Listing.order(:headline)
    listings = listings.includes(:neighborhood).where("listings.headline like ? or neighborhoods.name like ?", key, key) if keywords.present?
    listings = listings.where(neighborhood_id: neighborhood_id) if neighborhood_id.present?
    #truncated for brevity
    listings
  end

Upvotes: 0

Views: 1368

Answers (2)

jvnill
jvnill

Reputation: 29599

First of all, this would be easier if the form is returning the ids instead of the name of the neighborhood. I haven't used the gem yet so I'm not familiar how it works. Reading on the readme says that it will return ids but i don't know why you're only getting names. I'm sure once you figure out how to return the ids, you'll be able to change the code below to suit that.

You need to create a join table between a neighborhood and a search. Let's call that search_neighborhoods.

rails g model search_neighborhood neighborhood_id:integer search_id:integer
# dont forget to add indexes in the migration

After that, you'd want to setup your models.

# search.rb
has_many :search_neighborhoods
has_many :neighborhoods, through: :search_neighborhoods

# search_neighborhood.rb
belongs_to :search
belongs_to :neighborhood

# neighborhood.rb
has_many :search_neighborhoods
has_many :searches, through: :search_neighborhoods

Now that we've setup the associations, we need to setup the setters and the attributes

# search.rb
attr_accessible :neighborhood_names

# this will return a list of neighborhood names which is usefull with prepopulating
def neighborhood_names
  neighborhoods.map(&:name).join(',')
end

# we will use this to find the ids of the neighborhoods given their names
# this will be called when you call create!
def neighborhood_names=(names)
  names.split(',').each do |name|
    next if name.blank?
    if neighborhood = Neighborhood.find_by_name(name)
      search_neighborhoods.build neighborhood_id: neighborhood.id
    end
  end
end

# view
# you need to change your autocomplete to use the getter method
<%= f.input :neighborhood_names, url: autocomplete_neighborhood_name_searches_path, as: :autocomplete, input_html: { data: { delimiter: ',', multiple: true, class: "span8" } %>

last but not the least is to update find_listings

def find_listings
  key = "%#{keywords}%"
  listings = Listing.order(:headline).includes(:neighborhood)

  if keywords.present?
    listings = listings.where("listings.headline LIKE :key OR neighborhoods.name LIKE :key", { key: "#{keywords}")
  end

  if neighborhoods.exists?
    listings = listings.where(neighborhood_id: neighborhood_ids)
  end

  listings
end

And that's it :)

UPDATE: using f.input_field

# view
<%= f.input_field :neighborhood_names, url: autocomplete_neighborhood_name_searches_path, as: :autocomplete, data: { delimiter: ',' }, multiple: true, class: "span8" %>

# model
# we need to put [0] because it returns an array with a single element containing
# the string of comma separated neighborhoods
def neighborhood_names=(names)
  names[0].split(',').each do |name|
    next if name.blank?
    if neighborhood = Neighborhood.find_by_name(name)
      search_neighborhoods.build neighborhood_id: neighborhood.id
    end
  end
end

Upvotes: 1

TheIrishGuy
TheIrishGuy

Reputation: 2583

Your problem is how you're collecting values from the neighborhood Model

 Neighborhood.order(:name)

will return an array of names, you need to also collect the id, but just display the names use collect and pass a block, I beleive this might owrk for you

Neighborhood.collect {|n| [n.name, n.id]}

Declare a scope on the Neighborhood class to order it by name if you like to get theat functionality back, as that behavior also belongs in the model anyhow.

edit> To add a scope/class method to neighborhood model, you'd typically do soemthing like this

scope :desc, where("name DESC")

Than you can write something like:

Neighborhood.desc.all

which will return an array, thus allowing the .collect but there are other way to get those name and id attributes recognized by the select option.

Upvotes: 0

Related Questions