Skip to content
This repository has been archived by the owner on Mar 6, 2018. It is now read-only.

Commit

Permalink
Version to 0.4;
Browse files Browse the repository at this point in the history
Rename RedisSearch -> Redis::Search, see the documents.
  • Loading branch information
huacnlee committed Sep 22, 2011
1 parent d7f4cde commit 1f300a1
Show file tree
Hide file tree
Showing 13 changed files with 206 additions and 194 deletions.
20 changes: 12 additions & 8 deletions README.markdown
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# RedisSearch
# Redis-Search

High performance real-time search (Support Chinese), index in Redis for Rails application

Expand Down Expand Up @@ -29,22 +29,22 @@ install bundlers

create file in: config/initializers/redis_search.rb

require "redis_search"
require "redis-search"
redis = Redis.new(:host => "127.0.0.1",:port => "6379")
# change redis database to 3, you need use a special database for search feature.
redis.select(3)
RedisSearch.configure do |config|
Redis::Search.configure do |config|
config.redis = redis
config.complete_max_length = 100
end

## Usage

bind RedisSearch callback event, it will to rebuild search indexes when data create or update.
bind Redis::Search callback event, it will to rebuild search indexes when data create or update.

class Post
include Mongoid::Document
include RedisSearch
include Redis::Search

field :title
field :body
Expand All @@ -62,7 +62,7 @@ bind RedisSearch callback event, it will to rebuild search indexes when data cre

class User
include Mongoid::Document
include RedisSearch
include Redis::Search
field :name
field :tagline
Expand All @@ -76,15 +76,19 @@ bind RedisSearch callback event, it will to rebuild search indexes when data cre
class SearchController < ApplicationController
# GET /searchs?q=title
def index
RedisSearch::Search.query("Post", params[:q])
Redis::Search.query("Post", params[:q])
end
# GET /search_users?q=j
def search_users
RedisSearch::Search.complete("Post", params[:q])
Redis::Search.complete("Post", params[:q])
end
end

## Index data to Redis

$ rake redis_search:index

## Benchmark test

* [https://gist.github.com/1150933](https://gist.github.com/1150933)
Expand Down
7 changes: 6 additions & 1 deletion lib/redis-search.rb
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
require "redis_search"
# coding: utf-8
require "redis/search/base"
require "redis/search/finder"
require "redis/search/index"
require "redis/search/config"
require 'redis/search/railtie' if defined?(Rails)
66 changes: 66 additions & 0 deletions lib/redis/search/base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# coding: utf-8
class Redis
module Search
extend ActiveSupport::Concern

module ClassMethods
def redis_search_index(options = {})
title_field = options[:title_field] || :title
prefix_index_enable = options[:prefix_index_enable] || false
ext_fields = options[:ext_fields] || []

# store Model name to indexed_models for Rake tasks
Search.indexed_models = [] if Search.indexed_models == nil
Search.indexed_models << self
# bind instance methods and callback events
class_eval %(
def redis_search_ext_fields(ext_fields)
exts = {}
ext_fields.each do |f|
exts[f] = instance_eval(f.to_s)
end
exts
end
after_create :redis_search_index_create
def redis_search_index_create
s = Search::Index.new(:title => self.#{title_field},
:id => self.id,
:exts => self.redis_search_ext_fields(#{ext_fields.inspect}),
:type => self.class.to_s,
:prefix_index_enable => #{prefix_index_enable})
s.save
# release s
s = nil
end
before_destroy :redis_search_index_remove
def redis_search_index_remove
Search::Index.remove(:id => self.id, :title => self.#{title_field}, :type => self.class.to_s)
end
before_update :redis_search_index_update
def redis_search_index_update
index_fields_changed = false
#{ext_fields.inspect}.each do |f|
next if f.to_s == "id"
if instance_eval(f.to_s + "_changed?")
index_fields_changed = true
end
end
begin
if(self.#{title_field}_changed?)
index_fields_changed = true
end
rescue
end
if index_fields_changed
Search::Index.remove(:id => self.id, :title => self.#{title_field}_was, :type => self.class.to_s)
self.redis_search_index_create
end
end
)
end
end
end
end
30 changes: 30 additions & 0 deletions lib/redis/search/config.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# coding: utf-8
class Redis
module Search
class << self
attr_accessor :config, :indexed_models

def configure
yield self.config ||= Config.new
end
end

class Config
# Redis
attr_accessor :redis
# Debug toggle
attr_accessor :debug
# config for max length of content with Redis::Search.complete method,default 100
# Please change this with your real data length, short is fast
# For example: You use complete search for your User model name field, and the "name" as max length in 15 chars, then you can set here to 15
# warring! The long content will can't be found, if the config length less than real content.
attr_accessor :complete_max_length

def initialize
self.debug = false
self.redis = nil
self.complete_max_length = 100
end
end
end
end
100 changes: 22 additions & 78 deletions lib/redis_search/search.rb → lib/redis/search/finder.rb
Original file line number Diff line number Diff line change
@@ -1,19 +1,7 @@
# coding: utf-8
require "rmmseg"
module RedisSearch
class Search
class << self
attr_accessor :indexed_models
end

attr_accessor :type, :title, :id, :exts, :prefix_index_enable
def initialize(options = {})
self.exts = []
options.keys.each do |k|
eval("self.#{k} = options[k]")
end
end

class Redis
module Search
def self.split(text)
algor = RMMSeg::Algorithm.new(text)
words = []
Expand All @@ -26,7 +14,7 @@ def self.split(text)
end

def self.warn(msg)
puts "[RedisSearch][warn]: #{msg}"
puts "[Redis::Search][warn]: #{msg}"
end

# 生成 uuid,用于作为 hashes 的 field, sets 关键词的值
Expand All @@ -38,75 +26,31 @@ def self.mk_complete_key(type)
"Compl#{type}"
end

def save
return if self.title.blank?
data = {:title => self.title, :id => self.id, :type => self.type}
self.exts.each do |f|
data[f[0]] = f[1]
end

# 将原始数据存入 hashes
res = RedisSearch.config.redis.hset(self.type, self.id, data.to_json)
# 保存 sets 索引,以分词的单词为key,用于后面搜索,里面存储 ids
words = Search.split(self.title)
return if words.blank?
words.each do |word|
key = Search.mk_sets_key(self.type,word)
RedisSearch.config.redis.sadd(key, self.id)
end

# 建立前最索引
if prefix_index_enable
save_prefix_index
end
end

def save_prefix_index
word = self.title.downcase
RedisSearch.config.redis.sadd(Search.mk_sets_key(self.type,self.title), self.id)
key = Search.mk_complete_key(self.type)
(1..(word.length)).each do |l|
prefix = word[0...l]
RedisSearch.config.redis.zadd(key, 0, prefix)
end
RedisSearch.config.redis.zadd(key, 0, word + "*")
end

def self.remove(options = {})
type = options[:type]
RedisSearch.config.redis.hdel(type,options[:id])
words = Search.split(options[:title])
words.each do |word|
key = Search.mk_sets_key(type,word)
RedisSearch.config.redis.srem(key, options[:id])
end
end

# Use for short title search, this method is search by chars, for example Tag, User, Category ...
#
# h3. params:
# type model name
# w search char
# :limit result limit
# h3. usage:
# * RedisSearch::Search.complete("Tag","r") => ["Ruby","Rails", "REST", "Redis", "Redmine"]
# * RedisSearch::Search.complete("Tag","re") => ["Redis", "Redmine"]
# * RedisSearch::Search.complete("Tag","red") => ["Redis", "Redmine"]
# * RedisSearch::Search.complete("Tag","redi") => ["Redis"]
# * Redis::Search.complete("Tag","r") => ["Ruby","Rails", "REST", "Redis", "Redmine"]
# * Redis::Search.complete("Tag","re") => ["Redis", "Redmine"]
# * Redis::Search.complete("Tag","red") => ["Redis", "Redmine"]
# * Redis::Search.complete("Tag","redi") => ["Redis"]
def self.complete(type, w, options = {})
limit = options[:limit] || 10

prefix_matchs = []
# This is not random, try to get replies < MTU size
rangelen = RedisSearch.config.complete_max_length
rangelen = Redis::Search.config.complete_max_length
prefix = w.downcase
key = Search.mk_complete_key(type)

start = RedisSearch.config.redis.zrank(key,prefix)
start = Redis::Search.config.redis.zrank(key,prefix)
return [] if !start
count = limit
max_range = start+(rangelen*limit)-1
range = RedisSearch.config.redis.zrange(key,start,max_range)
range = Redis::Search.config.redis.zrange(key,start,max_range)
while prefix_matchs.length <= count
start += rangelen
break if !range or range.length == 0
Expand All @@ -126,16 +70,16 @@ def self.complete(type, w, options = {})
words = prefix_matchs.uniq.collect { |w| Search.mk_sets_key(type,w) }
if words.length > 1
temp_store_key = "tmpsunionstore:#{words.join("+")}"
if !RedisSearch.config.redis.exists(temp_store_key)
if !Redis::Search.config.redis.exists(temp_store_key)
# 将多个词语组合对比,得到并集,并存入临时区域
RedisSearch.config.redis.sunionstore(temp_store_key,*words)
Redis::Search.config.redis.sunionstore(temp_store_key,*words)
# 将临时搜索设为1天后自动清除
RedisSearch.config.redis.expire(temp_store_key,86400)
Redis::Search.config.redis.expire(temp_store_key,86400)
end
# 根据需要的数量取出 ids
ids = RedisSearch.config.redis.sort(temp_store_key,:limit => [0,limit])
ids = Redis::Search.config.redis.sort(temp_store_key,:limit => [0,limit])
else
ids = RedisSearch.config.redis.sort(words.first,:limit => [0,limit])
ids = Redis::Search.config.redis.sort(words.first,:limit => [0,limit])
end
return [] if ids.blank?
hmget(type,ids)
Expand All @@ -148,7 +92,7 @@ def self.complete(type, w, options = {})
# text search text
# :limit result limit
# h3. usage:
# * RedisSearch::Search.query("Tag","Ruby vs Python")
# * Redis::Search.query("Tag","Ruby vs Python")
def self.query(type, text,options = {})
result = []
return result if text.strip.blank?
Expand All @@ -160,17 +104,17 @@ def self.query(type, text,options = {})
return result if words.blank?
temp_store_key = "tmpinterstore:#{words.join("+")}"
if words.length > 1
if !RedisSearch.config.redis.exists(temp_store_key)
if !Redis::Search.config.redis.exists(temp_store_key)
# 将多个词语组合对比,得到交集,并存入临时区域
RedisSearch.config.redis.sinterstore(temp_store_key,*words)
Redis::Search.config.redis.sinterstore(temp_store_key,*words)
# 将临时搜索设为1天后自动清除
RedisSearch.config.redis.expire(temp_store_key,86400)
Redis::Search.config.redis.expire(temp_store_key,86400)
end
# 根据需要的数量取出 ids
ids = RedisSearch.config.redis.sort(temp_store_key,:limit => [0,limit])
ids = Redis::Search.config.redis.sort(temp_store_key,:limit => [0,limit])
else
# 根据需要的数量取出 ids
ids = RedisSearch.config.redis.sort(words.first,:limit => [0,limit])
ids = Redis::Search.config.redis.sort(words.first,:limit => [0,limit])
end
hmget(type,ids, :sort_field => sort_field)
end
Expand All @@ -180,7 +124,7 @@ def self.hmget(type, ids, options = {})
result = []
sort_field = options[:sort_field] || "id"
return result if ids.blank?
RedisSearch.config.redis.hmget(type,*ids).each do |r|
Redis::Search.config.redis.hmget(type,*ids).each do |r|
begin
result << JSON.parse(r)
rescue => e
Expand Down
Loading

0 comments on commit 1f300a1

Please sign in to comment.