Posted at
AtraeDay 7

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

More than 3 years have passed since last update.


目的

普段作っている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