typeahead rails 3 autocomplete with user per request

The app is using typeahead and bootstrap 3 for, autocomplete function. but now we have so many records that , it is getting really slower.

   var substringMatcher = function(strs) {
        return function findMatches(q, cb) {
            var matches, substrRegex;

            // an array that will be populated with substring matches
            matches = [];

            // regex used to determine if a string contains the substring `q`
            substrRegex = new RegExp(q, 'i');

            // iterate through the pool of strings and for any string that
            // contains the substring `q`, add it to the `matches` array
            $.each(strs, function(i, str) {
                if (substrRegex.test(str)) {
                    // the typeahead jQuery plugin expects suggestions to a
                    // JavaScript object, refer to typeahead docs for more info
                    matches.push({ value: str });
                }
            });

            cb(matches);
        };
    };

$('.typeahead').each(function(elem) {
        $(elem).typeahead({
            hint: false,
            highlight: true,
            minLength: 3
        }, {
            displayKey: 'value',
            source: substringMatcher($(elem).data('source'))
        });
    });

these are javasxript parts of typeahead fuction used by the app

on the view side of the form

<div class="form-group">
        <label class="col-sm-2 control-label" for="typeahead_book">Title</label>
        <div class="col-sm-3">
        <input value="<%[email protected](:title)%>" name="option[book_title]" type="text" class="form-control typeahead typeahead-remote-book" id="typeahead_book"  data-provide="typeahead" data-items="10" data-source='<%=@book_titles%>'>
          <p class="help-block"><small><%= $autocomplete_message %></small></p>
        </div>

i use a callback as

before_filter :get_autocomplete_lists, only: [:new, :edit]

def get_autocomplete_lists

    @book_titles = Rails.cache.fetch("booktitles", :force => true, expires_in: 10.minutes) do 
      Book.scoped.map(&:title)
    end


    @publisher_names = Rails.cache.fetch("publishernames", :force => true, expires_in: 10.minutes) do 
      Publisher.all.map(&:name)
    end


    @users_by_email_name = Rails.cache.fetch("users",  :force => true, expires_in: 7.days) do 
      User.scoped.map{|user| "#{user.email} - #{user.name}"}
    end 


    @country_names = Rails.cache.fetch("countrynames",  :force => true, expires_in: 7.days) do
      Country.all.map(&:first).sort
    end
    # @languages = LanguageList::COMMON_LANGUAGES.map(&:name)
    # @country_names = Anatolialit::Country::List.sort
    # 

    @languages = Rails.cache.fetch("languages",  :force => true, expires_in: 7.days) do 
      Anatolialit::Language::EnList.sort
    end

  end

on controller that generates source of the book , as @book_titles , THE APP IS WORKING NOW but i have to refactor the code for performance issues.Because after 10k record @book_titles is very big.


THAN WHAT I DID i created a new controller action on application controller

def book_titles
     searchterm = (params[:title])
     books =Book.any_of({ :title => /.*#{searchterm}.*/ })
     respond_to do |format|
      format.json { render json: books.map(&:title)  }
     end
  end

and i defined a route to use as application controller as an api service

get 'booktitles/:title' => 'application#book_titles', format: :json

now when i use a localhost:3000/booktitles/the it brings me as a json data all the books that indicates 'the' in the title. I think everything Until here is ok. about refactoring.


but when i use this code below for userside per request for sourcing typeahead

$(document).ready(function() {
  $('.typeahead-remote-book').typeahead({
      source: function (query, process) {
          return $.get('/booktitles', { query: query }, function (data) {
              return typeahead.process(data);
          });
      }
  });
});

and i changed the view part of the form to

<div class="form-group">
        <label class="col-sm-2 control-label" for="typeahead_book">Title</label>
        <div class="col-sm-3">
        <input value="<%[email protected](:title)%>" name="option[book_title]" type="text" class="form-control typeahead typeahead-remote-book" id="typeahead_book"  data-provide="typeahead" data-items="10" >
          <p class="help-block"><small><%= $autocomplete_message %></small></p>
        </div>
      </div>

*I do not know what is wrong with the situation , but it does not work *

could u please help me ? about solving the problem. Thanks for your kind help.. best regards.

Upvotes: 1

Views: 57

Answers (1)

lacostenycoder
lacostenycoder

Reputation: 11226

The problem is you're trying to do realime searching in the database and that is not very efficient. At least make sure you're indexing books on title. You should have a migration like:

class AddIndexOnBookTitles < ActiveRecord::Migration
  def change
    add_index :books, :title, unique: false #or omit false if dupes not allowed.
  end
end

Then you don't want book objects, but just titles, which you should probably cache somewhere. We can talk about that later.

Don't use .map on an ActiveRecordRelation object but instead .pluck to just pluck the fields you need out of the DB.

def book_titles
  books = Book.any_of({ :title => /.*#{params[:title]}.*/ }).pluck(:title)
  respond_to do |format|
    format.json { render json: books }
  end
end

That still might be slow if you have a lot of books. But give that a try first and see if it's any better. If not, we'll need to do some caching or maybe use Elasticsearch which is designed for this type of thing.

Upvotes: 2

Related Questions