Search code examples
ruby-on-railsformsautocompletejquery-ui-autocompletevalidates-associated

Rails 4 form - text field value must match a value in an associated table (autocomplete)


I have two associated models/tables

app/models/city.rb
has_many :businesses
Fields: id, city_name, state_code

app/models/business.rb
belongs_to :city
Fields: id, biz_name, address, city_name, state_code, zip

In the new business form where you enter the business address, it is common to have a dropdown select box of states and/or countries. But for cities there are thousands so that wouldn't work. Is there a way to check that the city is listed in the cities table before allowing it to be saved? And possibly even putting the city_id in that field instead of the city_name?


Solution

  • Adding an auto-completion feature to your city_name field would solve your problem.

    First of all, because of the association between your Business and City objects, you WILL want to return a business[city_id] parameter rather than a city_name parameter (assuming you're using a form_for @business helper).

    If you're using jQuery, you can use the devbridge jQuery-Autocomplete plugin: https://github.com/devbridge/jQuery-Autocomplete

    Create a simple method that returns a list of cities in JSON format (will require the jbuilder gem, which should be included by default in your Gemfile), for instance:

    in app/controllers/businesses_controller.rb:
    
    def ac_cities
      @query = params[:query]
      @results = City.where('city_name LIKE ?', "#{@query}%").order(city_name: :asc).limit(10)
      respond_to do |format|
        format.json
      end
    end
    
    in app/views/businesses/ac_cities.json.jbuilder:
    
    json.query @query
    json.suggestions @results do |result|
      json.value "<div data-id=\"#{result.id}\">#{result.city_name}</div>"
    end
    

    Initialize the devbridge instance and connect it to your city_name field with CoffeeScript (or JS). The devbridge plugin also has an onSelect feature that will allow you to populate the hidden business[city_id] form field. For instance, given the JSON returned above:

    in app/assets/javascripts/businesses.js.coffee:
    
    BusinessCitySelect = (suggestion) ->
      $(this).next('#business_city_id').val(suggestion.value.match(/data-id="(\d+)"/i)[1])
      $(this).val(suggestion.value.replace(new RegExp('<.*?\/?>', 'gi'), ''))
    
    SearchACFormat = (suggestion, cv) ->
      pattern = '(<div.*?>.*?)('+cv+').*?'
      suggestion.value.replace(new RegExp(pattern, 'gi'), '$1<strong>$2<\/strong>')
    
    $("#city_name").devbridgeAutocomplete({
      serviceUrl: "/businesses/ac_cities.json",
      formatResult: SearchACFormat,
      onSelect: BusinessCitySelect,
      noCache: true
    })
    

    the SearchACFormat method makes use of devbridge's formatResult feature to clean up our JSON for proper display within the city_name input field while also providing highlighting.