LoginSignup
6
7

More than 5 years have passed since last update.

Redisで入力補完機能を作ってみる

Posted at

目的

普段作っているGreenというサービスにて、企業名の入力補完を実装したい。
ただMySQLで実装するのは、性能的にもシステム設計的にも微妙すぎるので、webpayさんのブログを参考にredisで実装してみました。

Redisの説明は流石に不要だと思うので、割愛させて頂きますw

実装方法

概要

  • js側で入力をフックにapiにリクエストを飛ばす
  • api側でredisに入れたデータを追加する

コード


class FluctuatedCompany
  REDIS_KEY = "fluctuated_company_names_inverted_index_v2"

  def initialize
    @redis = Redis.new()
  end 

  def search(query)
    found = []
    next_cursor = 0 
    begin
      next_cursor_resp, resp = @redis.hscan(REDIS_KEY, next_cursor, 'MATCH', '*%s*' % query, 'COUNT', 100)
      next_cursor = next_cursor_resp.to_i
      resp.each_slice(2) { |pair| found += decode(pair[1]) }
    end while next_cursor > 0 
    found.uniq
  end 

  def add(company_id, *fluctuated_names)
    fluctuated_names.reject{ |fluctuated_name| fluctuated_name.blank? }.each do |fluctuated_name|
      ids = decode(@redis.hget(REDIS_KEY, [fluctuated_name]))
      ids << company_id
      @redis.hset(REDIS_KEY, fluctuated_name, ids.uniq.join(','))
    end 
  end 

  private
  def decode(value)
    (value || '').split(',').map(&:to_i)
  end 

  def encode(ids)
    ids.join(',')
  end 
end


$(function() {
  // 以下企業名のオートコンプリート
  var suggestTimerID;
  var suggestBoxManager = new SuggestBoxManager({
    inputSelector: ".js-user_company_name_input",
    listSelector: ".js-suggest_company_box",
    eventSelector: ".js-user_company_name_box"
  }); 
  suggestBoxManager.generate();

  $(document).on('keyup paste', 'input.js-user_company_name_input', function() {
    var _self = $(this);
    var index = $('.js-user_company_name_input').index(_self);
    var suggestBox = suggestBoxManager.findByIndex(index);
    var suggestClearTimer = clearTimeout(suggestTimerID);

    suggestTimerID = setTimeout(function() {
      suggestBox.show();
      suggestBox.suggest(_self.val(), {
        done: function(data) {
          suggestBox.reset();
          for(var i = 0, len = data.companies.length; i < len; i++) {
            suggestBox.dom.append('※htmlを追加');
          }

          // リストに対してのclickイベントをbind
          $('.js-suggest_company_list').click(function() {
            $(this).closest('td').find('.js-user_company_id').val($(this).data('id'));
            suggestBox.inputDom.val($(this).data('name')).change();
            suggestBox.hide();
          }); 
        }   
      }); 
    },300);

  }); 
});

var SuggestBoxManager = function(customOptions) {                                                                                                                                              
  this.container = [];
  this.options = $.extend({}, {
                   inputSelector: '.js-user_company_name_input',
                   listSelector: ".js-suggest_company_box",
                   eventSelector: ".js-user_company_name_box"
                 }, customOptions);
}

SuggestBoxManager.prototype = {
  add: function(suggestBox) {
    this.container.push(suggestBox);
  },
  generate: function(){
    var _self = this;
    var target = $(_self.options.eventSelector);
    target.each( function(){
      var suggestBox = new SuggestBox($(this).find(_self.options.listSelector), $(this).find(_self.options.inputSelector));
      _self.add(suggestBox);
    });
  },
  regenere: function(){
    this.generate();
  },
  findByIndex: function(index){
     return this.container[index];
  }
}

var SuggestBox = function(dom, inputDom, customOptions){
  if (customOptions === undefined) customOptions = {};
  this.dom = dom;
  this.inputDom = inputDom;
  this.options = $.extend({}, {
                   resourceUrl: '/apis/companies.json'
                 }, customOptions);
}

SuggestBox.prototype = {
  show: function() {
    this.dom.show();
  },
  hide: function() {
    this.dom.hide();
  },
  reset: function() {
    this.dom.html('');
  },
  suggest: function(resourceName, callback) {
    var callback = (callback === undefined) ? {} : callback;
    $.ajax({
      method: "GET",
      data: { resource_name: resourceName },
      url : this.options.resourceUrl,
      context: this,
      beforeSend: function(){
        if(callback.beforeSend) callback.beforeSend();
      }
    }).done(function(data, textStatus, jqXHR){
      if(callback.done) callback.done(data, textStatus, jqXHR);                                                                                                                                
    }).fail(function(jqXHR, textStatus, errorThrown){
      if(callback.fail) callback.fail(jqXHR, textStatus, errorThrown);
    }).always(function(xhr,status){
      if(callback.always) callback.always(xhr,status);
    });
  }
}

準備

addメソッドを使って入力補完に使いたい候補データを流し込んで下さい。

結果

UIは下記の通りです。いい感じに仕上がりました。
さすがredis良い仕事をしてくれますね!

gif_m.gif

6
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
7