redis-search 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.textile ADDED
@@ -0,0 +1,55 @@
1
+ h1. RedisSearch
2
+
3
+ High performance real-time search (Support Chinese), index in Redis for Rails application
4
+
5
+ h2. Features
6
+
7
+ * Real-time search
8
+ * High performance
9
+
10
+ h2. Requirements
11
+
12
+ * Redis 2.2
13
+ * Libmmseg
14
+
15
+ h2. Install
16
+
17
+ * gem install redis-search
18
+
19
+ h2. Configure
20
+
21
+ # config/initializers/redis_search.rb
22
+ require "redis_search"
23
+ redis = Redis.new(:host => redis_config[:host],:port => redis_config[:port])
24
+ redis.select("app_name.search")
25
+ RedisSearch.configure do |config|
26
+ config.redis = redis
27
+ end
28
+
29
+ h2. Usage
30
+
31
+ class Post
32
+ include Mongoid::Document
33
+ include RedisSearch
34
+
35
+ field :title
36
+ field :body
37
+
38
+ belongs_to :user
39
+ belongs_to :category
40
+
41
+ # bind RedisSearch callback event, it will to rebuild search indexes when data create or update.
42
+ redis_search_index(:title_field => :title,
43
+ :ext_fields => [:category_name])
44
+
45
+ def category_name
46
+ self.category.name
47
+ end
48
+ end
49
+
50
+ # GET /searchs?q=title
51
+ class SearchController < ApplicationController
52
+ def index
53
+ RedisSearch::Search.query(params[:q], :type => "Post")
54
+ end
55
+ end
@@ -0,0 +1,11 @@
1
+ require "redis_search/base"
2
+ require "redis_search/search"
3
+ require "redis_search/config"
4
+
5
+ module RedisSearch
6
+ class << self
7
+ def configure
8
+ yield self.config ||= Config.new
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,53 @@
1
+ module RedisSearch
2
+ extend ActiveSupport::Concern
3
+
4
+ module ClassMethods
5
+ def redis_search_index(options = {})
6
+ title_field = options[:title_field] || :title
7
+ ext_fields = options[:ext_fields] || []
8
+ class_eval %(
9
+ def redis_search_ext_fields(ext_fields)
10
+ exts = {}
11
+ ext_fields.each do |f|
12
+ exts[f] = instance_eval(f.to_s)
13
+ end
14
+ exts
15
+ end
16
+
17
+ after_create :redis_search_index_create
18
+ def redis_search_index_create
19
+ s = Search.new(:title => self.#{title_field}, :id => self.id,
20
+ :exts => self.redis_search_ext_fields(#{ext_fields}),
21
+ :type => self.class.to_s)
22
+ s.save
23
+ end
24
+
25
+ before_destroy :redis_search_index_remove
26
+ def redis_search_index_remove
27
+ Search.remove(:id => self.id, :title => self.#{title_field}, :type => self.class.to_s)
28
+ end
29
+
30
+ before_update :redis_search_index_update
31
+ def redis_search_index_update
32
+ index_fields_changed = false
33
+ #{ext_fields}.each do |f|
34
+ next if f.to_s == "id"
35
+ if instance_eval(f.to_s + "_changed?")
36
+ index_fields_changed = true
37
+ end
38
+ end
39
+ begin
40
+ if(self.#{title_field}_changed?)
41
+ index_fields_changed = true
42
+ end
43
+ rescue
44
+ end
45
+ if index_fields_changed
46
+ Search.remove(:id => self.id, :title => self.#{title_field}_was, :type => self.class.to_s)
47
+ self.redis_search_index_create
48
+ end
49
+ end
50
+ )
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,14 @@
1
+ module RedisSearch
2
+ class << self
3
+ attr_accessor :config
4
+ end
5
+
6
+ class Config
7
+ attr_accessor :redis, :debug
8
+
9
+ def initialize
10
+ self.debug = false
11
+ self.redis = nil
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,173 @@
1
+ # coding: utf-8
2
+ require "mmseg"
3
+ module RedisSearch
4
+ class Search
5
+ attr_accessor :type, :title, :id, :exts
6
+ def initialize(options = {})
7
+ self.exts = []
8
+ options.keys.each do |k|
9
+ eval("self.#{k} = options[k]")
10
+ end
11
+ end
12
+
13
+ def self.split(text)
14
+ algor = RMMSeg::Algorithm.new(text)
15
+ words = []
16
+ loop do
17
+ tok = algor.next_token
18
+ break if tok.nil?
19
+ words << tok.text
20
+ end
21
+ words
22
+ end
23
+
24
+ def self.warn(msg)
25
+ puts "[RedisSearch][warn]: #{msg}"
26
+ end
27
+
28
+ # 生成 uuid,用于作为 hashes 的 field, sets 关键词的值
29
+ def self.mk_sets_key(type, key)
30
+ "#{type}:#{key.downcase}"
31
+ end
32
+
33
+ def self.mk_complete_key(type)
34
+ "Compl#{type}"
35
+ end
36
+
37
+ def self.word?(word)
38
+ return !/^[\w\u4e00-\u9fa5]+$/i.match(word.force_encoding("UTF-8")).blank?
39
+ end
40
+
41
+ def save
42
+ return if self.title.blank?
43
+ data = {:title => self.title, :id => self.id, :type => self.type}
44
+ self.exts.each do |f|
45
+ data[f[0]] = f[1]
46
+ end
47
+
48
+ # 将原始数据存入 hashes
49
+ res = RedisSearch.config.redis.hset(self.type, self.id, data.to_json)
50
+ # 保存 sets 索引,以分词的单词为key,用于后面搜索,里面存储 ids
51
+ words = Search.split(self.title)
52
+ return if words.blank?
53
+ words.each do |word|
54
+ next if not Search.word?(word)
55
+ save_zindex(word)
56
+ key = Search.mk_sets_key(self.type,word)
57
+ RedisSearch.config.redis.sadd(key, self.id)
58
+ end
59
+ end
60
+
61
+ def save_zindex(word)
62
+ return if not Search.word?(word)
63
+ word = word.downcase
64
+ key = Search.mk_complete_key(self.type)
65
+ (1..(word.length)).each do |l|
66
+ prefix = word[0...l]
67
+ RedisSearch.config.redis.zadd(key, 0, prefix)
68
+ end
69
+ RedisSearch.config.redis.zadd(key, 0, word + "*")
70
+ end
71
+
72
+ def self.remove(options = {})
73
+ puts options.inspect
74
+ type = options[:type]
75
+ RedisSearch.config.redis.hdel(type,options[:id])
76
+ words = Search.split(options[:title])
77
+ words.each do |word|
78
+ next if not Search.word?(word)
79
+ key = Search.mk_sets_key(type,word)
80
+ RedisSearch.config.redis.srem(key, options[:id])
81
+ end
82
+ end
83
+
84
+ # Use for short title search, this method is search by chars, for example Tag, User, Category ...
85
+ #
86
+ # h3. params:
87
+ # type model name
88
+ # w search char
89
+ # :limit result limit
90
+ # h3. usage:
91
+ # * RedisSearch::Search.complete("Tag","r") => ["Ruby","Rails", "REST", "Redis", "Redmine"]
92
+ # * RedisSearch::Search.complete("Tag","re") => ["Redis", "Redmine"]
93
+ # * RedisSearch::Search.complete("Tag","red") => ["Redis", "Redmine"]
94
+ # * RedisSearch::Search.complete("Tag","redi") => ["Redis"]
95
+ def self.complete(type, w, options = {})
96
+ limit = options[:limit] || 10
97
+
98
+ prefix_matchs = []
99
+ rangelen = 100 # This is not random, try to get replies < MTU size
100
+ prefix = w.downcase
101
+ key = Search.mk_complete_key(type)
102
+ start = RedisSearch.config.redis.zrank(key,prefix)
103
+
104
+ return [] if !start
105
+ count = limit
106
+ while prefix_matchs.length <= count
107
+ range = RedisSearch.config.redis.zrange(key,start,start+rangelen-1)
108
+ start += rangelen
109
+ break if !range or range.length == 0
110
+ range.each {|entry|
111
+ minlen = [entry.length,prefix.length].min
112
+ if entry[0...minlen] != prefix[0...minlen]
113
+ count = prefix_matchs.count
114
+ break
115
+ end
116
+ if entry[-1..-1] == "*" and prefix_matchs.length != count
117
+ prefix_matchs << entry[0...-1]
118
+ end
119
+ }
120
+ end
121
+ words = []
122
+ words = prefix_matchs.uniq.collect { |w| Search.mk_sets_key(type,w) }
123
+ ids = RedisSearch.config.redis.sunion(*words)
124
+ return [] if ids.blank?
125
+ hmget(type,ids, :limit => limit)
126
+ end
127
+
128
+ # Search items, this will split words by Libmmseg
129
+ #
130
+ # h3. params:
131
+ # type model name
132
+ # text search text
133
+ # :limit result limit
134
+ # h3. usage:
135
+ # * RedisSearch::Search.query("Tag","Ruby vs Python")
136
+ def self.query(type, text,options = {})
137
+ result = []
138
+ return result if text.strip.blank?
139
+
140
+ words = Search.split(text)
141
+ limit = options[:limit] || 10
142
+ sort_field = options[:sort_field] || "id"
143
+ words = words.collect { |w| Search.mk_sets_key(type,w) }
144
+ return result if words.blank?
145
+ ids = RedisSearch.config.redis.sinter(*words)
146
+ hmget(type,ids, :limit => limit, :sort_field => sort_field)
147
+ end
148
+
149
+ private
150
+ def self.hmget(type, ids, options = {})
151
+ result = []
152
+ limit = options[:limit] || 10
153
+ sort_field = options[:sort_field] || "id"
154
+ return result if ids.blank?
155
+ RedisSearch.config.redis.hmget(type,*ids).each do |r|
156
+ begin
157
+ result << JSON.parse(r)
158
+ rescue => e
159
+ Search.warn("Search.query failed: #{e}")
160
+ end
161
+ end
162
+ items = sort_result(result, type, sort_field)
163
+ items = items[0..limit-1] if items.length > limit
164
+ items
165
+ end
166
+
167
+ def self.sort_result(items, type, sort_field)
168
+ return items if items.blank?
169
+ items = items.sort { |x,y| y[sort_field] <=> x[sort_field] }
170
+ items
171
+ end
172
+ end
173
+ end
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: redis-search
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jason Lee
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-08-16 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rmmseg-cpp-huacnlee
16
+ requirement: &2159834180 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 0.2.8
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *2159834180
25
+ - !ruby/object:Gem::Dependency
26
+ name: redis
27
+ requirement: &2159833700 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: 2.1.1
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *2159833700
36
+ description: High performance real-time search (Support Chinese), index in Redis for
37
+ Rails application.
38
+ email:
39
+ - huacnlee@gmail.com
40
+ executables: []
41
+ extensions: []
42
+ extra_rdoc_files: []
43
+ files:
44
+ - lib/redis_search/base.rb
45
+ - lib/redis_search/config.rb
46
+ - lib/redis_search/search.rb
47
+ - lib/redis_search.rb
48
+ - README.textile
49
+ homepage: http://github.com/huacnlee/redis-search
50
+ licenses: []
51
+ post_install_message:
52
+ rdoc_options: []
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ! '>='
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ! '>='
65
+ - !ruby/object:Gem::Version
66
+ version: 1.3.6
67
+ requirements: []
68
+ rubyforge_project:
69
+ rubygems_version: 1.8.6
70
+ signing_key:
71
+ specification_version: 3
72
+ summary: High performance real-time search (Support Chinese), index in Redis for Rails
73
+ application.
74
+ test_files: []