redis-search 0.9.5 → 0.9.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 60fec212ee4e4748c4133d4e0c4b9aff8ef79f08
4
- data.tar.gz: 835d412d57bcea35bd328d5a988667e34d4e79fb
3
+ metadata.gz: 9f06b8b05df240f527992050fa3174ae7726cb0e
4
+ data.tar.gz: 1f37d2e08628b4a5fe6a9a5416a88f5c6b1b989d
5
5
  SHA512:
6
- metadata.gz: 01a0c60c0594feb93a1ede4d305790ba28191d8f2319a1483f5f9ba83264404f11167c429c80e2dbdbd7356a33c1603ced9b345a511795080a32016b03c57055
7
- data.tar.gz: ae3a0a94241cdfd30a4448625c7a8efbb698c152c913701caeacf8b21dd26522a542da3b6fa9dbf3955d1dbe85523f0736b157fa384a5a20a9beb77e895dec6b
6
+ metadata.gz: ec74e499cf300aeab20a0851c2228f0bacbbae75683d4517f44894bb4c331f83c753ae98c112b342762e59457b9adf36b645728dcf495b890d516a52351871fe
7
+ data.tar.gz: c6ea104e95e3d83ba984a18522925dc881eb37fb021750c5b1476b14c8ae90eab70d07b14fe88c56a3bd3e298f3923a8fb83236b52c0a5033370e382be9bf63a
data/README.md CHANGED
@@ -8,7 +8,7 @@ High performance real-time search (Support Chinese), index in Redis for Rails ap
8
8
 
9
9
  ![](http://l.ruby-china.org/photo/34368688ee1c1928c2841eb2f41306ec.png)
10
10
 
11
- You can try the search feature in [`720p.so`](http://720p.so) | [`shu.im`](http://shu.im)
11
+ You can try the search feature in [`IMAX.im`](http://imax.im) | [`shu.im`](http://shu.im)
12
12
 
13
13
  And there is an [Example App](https://github.com/huacnlee/redis-search-example) to show you how to use redis-search.
14
14
 
@@ -38,7 +38,7 @@ And there is an [Example App](https://github.com/huacnlee/redis-search-example)
38
38
 
39
39
  ```ruby
40
40
  gem 'ruby-pinyin', '0.3.0'
41
- gem 'redis-search', '0.9.0'
41
+ gem 'redis-search', '0.9.6'
42
42
  ```
43
43
 
44
44
  ```bash
@@ -151,4 +151,4 @@ There is my performance test result.
151
151
 
152
152
  ## License
153
153
 
154
- * [Apache V2](http://choosealicense.com/licenses/nses/apache)
154
+ * MIT
data/lib/redis-search.rb CHANGED
@@ -3,4 +3,4 @@ require "redis/search/base"
3
3
  require "redis/search/finder"
4
4
  require "redis/search/index"
5
5
  require "redis/search/config"
6
- require 'redis/search/railtie' if defined?(Rails)
6
+ require 'redis/search/railtie' if defined?(Rails)
@@ -3,7 +3,7 @@
3
3
  class Redis
4
4
  module Search
5
5
  autoload :PinYin, 'ruby-pinyin'
6
-
6
+
7
7
  extend ::ActiveSupport::Concern
8
8
 
9
9
  module ClassMethods
@@ -15,21 +15,22 @@ class Redis
15
15
  # ext_fields What kind fields do you need inlucde to search indexes
16
16
  # score_field Give a score for search sort, need Integer value, default is `created_at`
17
17
  def redis_search_index(options = {})
18
- title_field = options[:title_field] || :title
19
- alias_field = options[:alias_field] || nil
18
+ title_field = options[:title_field] || :title
19
+ alias_field = options[:alias_field] || nil
20
20
  prefix_index_enable = options[:prefix_index_enable] || false
21
- ext_fields = options[:ext_fields] || []
22
- score_field = options[:score_field] || :created_at
23
- condition_fields = options[:condition_fields] || []
21
+ ext_fields = options[:ext_fields] || []
22
+ score_field = options[:score_field] || :created_at
23
+ condition_fields = options[:condition_fields] || []
24
+
24
25
  # Add score field to ext_fields
25
26
  ext_fields |= [score_field]
26
27
  # Add condition fields to ext_fields
27
28
  ext_fields |= condition_fields
28
-
29
+
29
30
  if RUBY_VERSION.start_with?('1.8')
30
31
  condition_fields = "[#{condition_fields.collect { |c| "'#{c}'" }.join(',')}]"
31
32
  end
32
-
33
+
33
34
  # store Model name to indexed_models for Rake tasks
34
35
  Search.indexed_models = [] if Search.indexed_models == nil
35
36
  Search.indexed_models << self
@@ -42,9 +43,9 @@ class Redis
42
43
  end
43
44
  exts
44
45
  end
45
-
46
+
46
47
  def redis_search_alias_value(field)
47
- return [] if field.blank? or field == "_was"
48
+ return [] if field.blank? || field == "_was"
48
49
  val = (instance_eval("self.\#{field}") || "").clone
49
50
  return [] if !val.class.in?([String,Array])
50
51
  if val.is_a?(String)
@@ -54,10 +55,10 @@ class Redis
54
55
  end
55
56
 
56
57
  def redis_search_index_create
57
- s = Search::Index.new(:title => self.#{title_field},
58
- :aliases => self.redis_search_alias_value(#{alias_field.inspect}),
59
- :id => self.id,
60
- :exts => self.redis_search_fields_to_hash(#{ext_fields.inspect}),
58
+ s = Search::Index.new(:title => self.#{title_field},
59
+ :aliases => self.redis_search_alias_value(#{alias_field.inspect}),
60
+ :id => self.id,
61
+ :exts => self.redis_search_fields_to_hash(#{ext_fields.inspect}),
61
62
  :type => self.class.to_s,
62
63
  :condition_fields => #{condition_fields},
63
64
  :score => self.#{score_field}.to_i,
@@ -67,7 +68,7 @@ class Redis
67
68
  s = nil
68
69
  true
69
70
  end
70
-
71
+
71
72
  def redis_search_index_delete(titles)
72
73
  titles.uniq.each do |title|
73
74
  next if title.blank?
@@ -75,24 +76,22 @@ class Redis
75
76
  end
76
77
  true
77
78
  end
78
-
79
79
 
80
80
  before_destroy do
81
81
  titles = []
82
82
  titles = redis_search_alias_value("#{alias_field}")
83
83
  titles << self.#{title_field}
84
-
84
+
85
85
  redis_search_index_delete(titles)
86
86
  true
87
87
  end
88
-
89
-
88
+
90
89
  def redis_search_index_need_reindex
91
90
  index_fields_changed = false
92
91
  #{ext_fields.inspect}.each do |f|
93
92
  next if f.to_s == "id"
94
93
  field_method = f.to_s + "_changed?"
95
- if !self.methods.include?(field_method.to_sym)
94
+ if self.methods.index(field_method.to_sym) == nil
96
95
  Search.warn("#{self.class.name} model reindex on update need "+field_method+" method.")
97
96
  next
98
97
  end
@@ -111,7 +110,7 @@ class Redis
111
110
  end
112
111
  return index_fields_changed
113
112
  end
114
-
113
+
115
114
  after_update do
116
115
  if self.redis_search_index_need_reindex
117
116
  titles = []
@@ -124,7 +123,7 @@ class Redis
124
123
 
125
124
  after_save :redis_search_index_update
126
125
  def redis_search_index_update
127
- if self.redis_search_index_need_reindex or self.new_record?
126
+ if self.redis_search_index_need_reindex || self.new_record?
128
127
  self.redis_search_index_create
129
128
  end
130
129
  true
@@ -3,20 +3,20 @@ class Redis
3
3
  module Search
4
4
  class << self
5
5
  attr_accessor :config, :indexed_models
6
-
6
+
7
7
  def configure
8
- yield self.config ||= Config.new
9
-
10
- if not self.config.disable_rmmseg
8
+ yield @config ||= Config.new
9
+
10
+ if not @config.disable_rmmseg
11
11
  require "rmmseg"
12
12
  # loading RMMSeg chinese word dicts.
13
13
  RMMSeg::Dictionary.load_dictionaries
14
14
  end
15
15
  end
16
16
  end
17
-
17
+
18
18
  class Config
19
- # Redis
19
+ # Redis
20
20
  attr_accessor :redis
21
21
  # Debug toggle, default false
22
22
  attr_accessor :debug
@@ -32,14 +32,14 @@ class Redis
32
32
  attr_accessor :pinyin_match
33
33
  # Disable RMMSeg, if you don't need, disable this will save memory. (true|false) default = true
34
34
  attr_accessor :disable_rmmseg
35
-
35
+
36
36
  def initialize
37
- self.debug = false
38
- self.redis = nil
39
- self.complete_max_length = 100
40
- self.pinyin_match = false
41
- self.disable_rmmseg = false
37
+ @debug = false
38
+ @redis = nil
39
+ @complete_max_length = 100
40
+ @pinyin_match = false
41
+ @disable_rmmseg = false
42
42
  end
43
43
  end
44
44
  end
45
- end
45
+ end
@@ -1,248 +1,249 @@
1
1
  # coding: utf-8
2
2
  class Redis
3
- module Search
4
- # use rmmseg to split words
5
- def self.split(text)
6
- _split(text)
7
- end
8
-
9
- # Use for short title search, this method is search by chars, for example Tag, User, Category ...
10
- #
11
- # h3. params:
12
- # type model name
13
- # w search char
14
- # :limit result limit
15
- # h3. usage:
16
- # * Redis::Search.complete("Tag","r") => ["Ruby","Rails", "REST", "Redis", "Redmine"]
17
- # * Redis::Search.complete("Tag","re") => ["Redis", "Redmine"]
18
- # * Redis::Search.complete("Tag","red") => ["Redis", "Redmine"]
19
- # * Redis::Search.complete("Tag","redi") => ["Redis"]
20
- def self.complete(type, w, options = {})
21
- limit = options[:limit] || 10
22
- conditions = options[:conditions] || []
23
- return [] if (w.blank? and conditions.blank?) or type.blank?
24
-
25
- prefix_matchs = []
26
- # This is not random, try to get replies < MTU size
27
- rangelen = Redis::Search.config.complete_max_length
28
- prefix = w.downcase
29
- key = Search.mk_complete_key(type)
30
-
31
-
32
- if start = Redis::Search.config.redis.zrank(key,prefix)
33
- count = limit
34
- max_range = start+(rangelen*limit)-1
35
- range = Redis::Search.config.redis.zrange(key,start,max_range)
36
- while prefix_matchs.length <= count
37
- start += rangelen
38
- break if !range or range.length == 0
39
- range.each {|entry|
40
- minlen = [entry.length,prefix.length].min
41
- if entry[0...minlen] != prefix[0...minlen]
42
- count = prefix_matchs.count
43
- break
44
- end
45
- if entry[-1..-1] == "*" and prefix_matchs.length != count
46
- prefix_matchs << entry[0...-1]
3
+ module Search
4
+ class << self
5
+ # use rmmseg to split words
6
+ def split(text)
7
+ _split(text)
8
+ end
9
+
10
+ # Use for short title search, this method is search by chars, for example Tag, User, Category ...
11
+ #
12
+ # h3. params:
13
+ # type model name
14
+ # w search char
15
+ # :limit result limit
16
+ # h3. usage:
17
+ # * Redis::Search.complete("Tag","r") => ["Ruby","Rails", "REST", "Redis", "Redmine"]
18
+ # * Redis::Search.complete("Tag","re") => ["Redis", "Redmine"]
19
+ # * Redis::Search.complete("Tag","red") => ["Redis", "Redmine"]
20
+ # * Redis::Search.complete("Tag","redi") => ["Redis"]
21
+ def complete(type, w, options = {})
22
+ limit = options[:limit] || 10
23
+ conditions = options[:conditions] || []
24
+ return [] if (w.blank? && conditions.blank?) || type.blank?
25
+
26
+ prefix_matchs = []
27
+ # This is not random, try to get replies < MTU size
28
+ rangelen = self.config.complete_max_length
29
+ prefix = w.downcase
30
+ key = self.mk_complete_key(type)
31
+
32
+ if start = self.config.redis.zrank(key,prefix)
33
+ count = limit
34
+ max_range = start + (rangelen * limit) - 1
35
+ range = self.config.redis.zrange(key,start,max_range)
36
+ while prefix_matchs.length <= count
37
+ start += rangelen
38
+ break if !range || range.length == 0
39
+ range.each do |entry|
40
+ minlen = [entry.length,prefix.length].min
41
+ if entry[0...minlen] != prefix[0...minlen]
42
+ count = prefix_matchs.count
43
+ break
44
+ end
45
+ if entry[-1..-1] == "*" && prefix_matchs.length != count
46
+ prefix_matchs << entry[0...-1]
47
+ end
47
48
  end
48
- }
49
- range = range[start..max_range]
49
+
50
+ range = range[start..max_range]
51
+ end
50
52
  end
51
- end
52
-
53
- # 组合 words 的特别 key 名
54
- words = []
55
- words = prefix_matchs.uniq.collect { |w| Search.mk_sets_key(type,w) }
56
-
57
- # 组合特别 key ,但这里不会像 query 那样放入 words, 因为在 complete 里面 words 是用 union 取的,condition_keys words 应该取交集
58
- condition_keys = []
59
- if !conditions.blank?
60
- conditions = conditions[0] if conditions.is_a?(Array)
61
- conditions.keys.each do |c|
62
- condition_keys << Search.mk_condition_key(type,c,conditions[c])
53
+ prefix_matchs.uniq!
54
+
55
+ # 组合 words 的特别 key 名
56
+ words = prefix_matchs.collect { |w| self.mk_sets_key(type,w) }
57
+
58
+ # 组合特别 key ,但这里不会像 query 那样放入 words, 因为在 complete 里面 words 是用 union 取的,condition_keys 和 words 应该取交集
59
+ condition_keys = []
60
+ if !conditions.blank?
61
+ conditions = conditions[0] if conditions.is_a?(Array)
62
+ conditions.keys.each do |c|
63
+ condition_keys << self.mk_condition_key(type,c,conditions[c])
64
+ end
63
65
  end
64
- end
65
-
66
- # 按词语搜索
67
- temp_store_key = "tmpsunionstore:#{words.join("+")}"
68
- if words.length > 1
69
- if !Redis::Search.config.redis.exists(temp_store_key)
70
- # 将多个词语组合对比,得到并集,并存入临时区域
71
- Redis::Search.config.redis.sunionstore(temp_store_key,*words)
72
- # 将临时搜索设为1天后自动清除
73
- Redis::Search.config.redis.expire(temp_store_key,86400)
66
+
67
+ # 按词语搜索
68
+ temp_store_key = "tmpsunionstore:#{words.join("+")}"
69
+ if words.length > 1
70
+ if !self.config.redis.exists(temp_store_key)
71
+ # 将多个词语组合对比,得到并集,并存入临时区域
72
+ self.config.redis.sunionstore(temp_store_key,*words)
73
+ # 将临时搜索设为1天后自动清除
74
+ self.config.redis.expire(temp_store_key,86400)
75
+ end
76
+ # 根据需要的数量取出 ids
77
+ else
78
+ temp_store_key = words.first
74
79
  end
75
- # 根据需要的数量取出 ids
76
- else
77
- temp_store_key = words.first
78
- end
79
-
80
- # 如果有条件,这里再次组合一下
81
- if !condition_keys.blank?
82
- condition_keys << temp_store_key if !words. blank?
83
- temp_store_key = "tmpsinterstore:#{condition_keys.join('+')}"
84
- if !Redis::Search.config.redis.exists(temp_store_key)
85
- Redis::Search.config.redis.sinterstore(temp_store_key,*condition_keys)
86
- Redis::Search.config.redis.expire(temp_store_key,86400)
80
+
81
+ # 如果有条件,这里再次组合一下
82
+ if !condition_keys.blank?
83
+ condition_keys << temp_store_key if !words.blank?
84
+ temp_store_key = "tmpsinterstore:#{condition_keys.join('+')}"
85
+ if !self.config.redis.exists(temp_store_key)
86
+ self.config.redis.sinterstore(temp_store_key,*condition_keys)
87
+ self.config.redis.expire(temp_store_key,86400)
88
+ end
87
89
  end
90
+
91
+ ids = self.config.redis.sort(temp_store_key,
92
+ :limit => [0,limit],
93
+ :by => self.mk_score_key(type,"*"),
94
+ :order => "desc")
95
+ return [] if ids.blank?
96
+ self.hmget(type,ids)
88
97
  end
89
-
90
- ids = Redis::Search.config.redis.sort(temp_store_key,
91
- :limit => [0,limit],
92
- :by => Search.mk_score_key(type,"*"),
93
- :order => "desc")
94
- return [] if ids.blank?
95
- hmget(type,ids)
96
- end
97
98
 
98
- # Search items, this will split words by Libmmseg
99
- #
100
- # h3. params:
101
- # type model name
102
- # text search text
103
- # :limit result limit
104
- # h3. usage:
105
- # * Redis::Search.query("Tag","Ruby vs Python")
106
- def self.query(type, text,options = {})
107
- tm = Time.now
108
- result = []
109
-
110
- limit = options[:limit] || 10
111
- sort_field = options[:sort_field] || "id"
112
- conditions = options[:conditions] || []
113
-
114
- # 如果搜索文本和查询条件均没有,那就直接返回 []
115
- return result if text.strip.blank? and conditions.blank?
116
-
117
- words = Search.split(text)
118
- words = words.collect { |w| Search.mk_sets_key(type,w) }
119
-
120
-
121
- condition_keys = []
122
- if !conditions.blank?
123
- conditions = conditions[0] if conditions.is_a?(Array)
124
- conditions.keys.each do |c|
125
- condition_keys << Search.mk_condition_key(type,c,conditions[c])
99
+ # Search items, this will split words by Libmmseg
100
+ #
101
+ # h3. params:
102
+ # type model name
103
+ # text search text
104
+ # :limit result limit
105
+ # h3. usage:
106
+ # * Redis::Search.query("Tag","Ruby vs Python")
107
+ def query(type, text, options = {})
108
+ tm = Time.now
109
+ result = []
110
+
111
+ limit = options[:limit] || 10
112
+ sort_field = options[:sort_field] || "id"
113
+ conditions = options[:conditions] || []
114
+
115
+ # 如果搜索文本和查询条件均没有,那就直接返回 []
116
+ return result if text.strip.blank? && conditions.blank?
117
+
118
+ words = self.split(text)
119
+ words = words.collect { |w| self.mk_sets_key(type,w) }
120
+
121
+ condition_keys = []
122
+ if !conditions.blank?
123
+ conditions = conditions[0] if conditions.is_a?(Array)
124
+ conditions.keys.each do |c|
125
+ condition_keys << self.mk_condition_key(type,c,conditions[c])
126
+ end
127
+ # 将条件的 key 放入关键词搜索集合内,用于 sinterstore 搜索
128
+ words += condition_keys
126
129
  end
127
- # 将条件的 key 放入关键词搜索集合内,用于 sinterstore 搜索
128
- words += condition_keys
129
- end
130
-
131
- return result if words.blank?
132
-
133
- temp_store_key = "tmpinterstore:#{words.join("+")}"
134
-
135
- if words.length > 1
136
- if !Redis::Search.config.redis.exists(temp_store_key)
137
- # 将多个词语组合对比,得到交集,并存入临时区域
138
- Redis::Search.config.redis.sinterstore(temp_store_key,*words)
139
- # 将临时搜索设为1天后自动清除
140
- Redis::Search.config.redis.expire(temp_store_key,86400)
141
-
142
- # 拼音搜索
143
- if Search.config.pinyin_match
144
- pinyin_words = Search.split_pinyin(text)
145
- pinyin_words = pinyin_words.collect { |w| Search.mk_sets_key(type,w) }
146
- pinyin_words += condition_keys
147
- temp_sunion_key = "tmpsunionstore:#{words.join("+")}"
148
- if Search.config.pinyin_match
149
- temp_pinyin_store_key = "tmpinterstore:#{pinyin_words.join("+")}"
130
+
131
+ return result if words.blank?
132
+
133
+ temp_store_key = "tmpinterstore:#{words.join("+")}"
134
+
135
+ if words.length > 1
136
+ if !self.config.redis.exists(temp_store_key)
137
+ self.config.redis.pipelined do
138
+ # 将多个词语组合对比,得到交集,并存入临时区域
139
+ self.config.redis.sinterstore(temp_store_key,*words)
140
+ # 将临时搜索设为1天后自动清除
141
+ self.config.redis.expire(temp_store_key,86400)
142
+
143
+ # 拼音搜索
144
+ if self.config.pinyin_match
145
+ pinyin_words = self.split_pinyin(text)
146
+ pinyin_words = pinyin_words.collect { |w| self.mk_sets_key(type,w) }
147
+ pinyin_words += condition_keys
148
+ temp_sunion_key = "tmpsunionstore:#{words.join("+")}"
149
+ temp_pinyin_store_key = "tmpinterstore:#{pinyin_words.join("+")}"
150
+ # 找出拼音的
151
+ self.config.redis.sinterstore(temp_pinyin_store_key,*pinyin_words)
152
+ # 合并中文和拼音的搜索结果
153
+ self.config.redis.sunionstore(temp_sunion_key,*[temp_store_key,temp_pinyin_store_key])
154
+ # 将临时搜索设为1天后自动清除
155
+ self.config.redis.expire(temp_pinyin_store_key,86400)
156
+ self.config.redis.expire(temp_sunion_key,86400)
157
+ end
158
+ temp_store_key = temp_sunion_key
150
159
  end
151
- # 找出拼音的
152
- Redis::Search.config.redis.sinterstore(temp_pinyin_store_key,*pinyin_words)
153
- # 合并中文和拼音的搜索结果
154
- Redis::Search.config.redis.sunionstore(temp_sunion_key,*[temp_store_key,temp_pinyin_store_key])
155
- # 将临时搜索设为1天后自动清除
156
- Redis::Search.config.redis.expire(temp_pinyin_store_key,86400)
157
- Redis::Search.config.redis.expire(temp_sunion_key,86400)
158
- temp_store_key = temp_sunion_key
159
160
  end
161
+ else
162
+ temp_store_key = words.first
160
163
  end
161
- else
162
- temp_store_key = words.first
164
+
165
+ # 根据需要的数量取出 ids
166
+ ids = self.config.redis.sort(temp_store_key,
167
+ :limit => [0,limit],
168
+ :by => self.mk_score_key(type,"*"),
169
+ :order => "desc")
170
+ result = self.hmget(type,ids, :sort_field => sort_field)
171
+ self.info("{#{type} : \"#{text}\"} | Time spend: #{Time.now - tm}s")
172
+ result
163
173
  end
164
-
165
- # 根据需要的数量取出 ids
166
- ids = Search.config.redis.sort(temp_store_key,
167
- :limit => [0,limit],
168
- :by => Search.mk_score_key(type,"*"),
169
- :order => "desc")
170
- result = hmget(type,ids, :sort_field => sort_field)
171
- Search.info("{#{type} : \"#{text}\"} | Time spend: #{Time.now - tm}s")
172
- result
173
- end
174
-
174
+ end # end class << self
175
+
175
176
  protected
176
- def self.split_pinyin(text)
177
- # Pinyin search split as pinyin again
178
- _split(PinYin.sentence(text))
179
- end
180
-
177
+ def self.split_pinyin(text)
178
+ # Pinyin search split as pinyin again
179
+ _split(PinYin.sentence(text))
180
+ end
181
+
181
182
  private
182
- def self._split(text)
183
- return [] if text.blank?
184
- # return chars if disabled rmmseg
185
- return text.split("") if Search.config.disable_rmmseg
186
-
187
- algor = RMMSeg::Algorithm.new(text)
188
- words = []
189
- loop do
190
- tok = algor.next_token
191
- break if tok.nil?
192
- words << tok.text
193
- end
194
- words
195
- end
196
-
197
- def self.warn(msg)
198
- return if not Redis::Search.config.debug
199
- msg = "\e[33m[Redis::Search] #{msg}\e[0m"
200
- if defined?(Rails) == 'constant' && Rails.class == Class
201
- ::Rails.logger.warn(msg)
202
- else
203
- puts msg
204
- end
205
- end
206
-
207
- def self.info(msg)
208
- return if not Redis::Search.config.debug
209
- msg = "\e[32m[Redis::Search] #{msg}\e[0m"
210
- if defined?(Rails) == 'constant' && Rails.class == Class
211
- ::Rails.logger.debug(msg)
212
- else
213
- puts msg
214
- end
215
- end
216
-
217
- # 生成 uuid,用于作为 hashes 的 field, sets 关键词的值
218
- def self.mk_sets_key(type, key)
219
- "#{type}:#{key.downcase}"
220
- end
221
-
222
- def self.mk_score_key(type, id)
223
- "#{type}:_score_:#{id}"
183
+ def self._split(text)
184
+ return [] if text.blank?
185
+ # return chars if disabled rmmseg
186
+ return text.split("") if Search.config.disable_rmmseg
187
+
188
+ algor = RMMSeg::Algorithm.new(text)
189
+ words = []
190
+ loop do
191
+ tok = algor.next_token
192
+ break if tok.nil?
193
+ words << tok.text
224
194
  end
225
-
226
- def self.mk_condition_key(type, field, id)
227
- "#{type}:_by:_#{field}:#{id}"
195
+ words
196
+ end
197
+
198
+ def self.warn(msg)
199
+ return if not Redis::Search.config.debug
200
+ msg = "\e[33m[Redis::Search] #{msg}\e[0m"
201
+ if defined?(Rails) == 'constant' && Rails.class == Class
202
+ ::Rails.logger.warn(msg)
203
+ else
204
+ puts msg
228
205
  end
229
-
230
- def self.mk_complete_key(type)
231
- "Compl#{type}"
206
+ end
207
+
208
+ def self.info(msg)
209
+ return if not Redis::Search.config.debug
210
+ msg = "\e[32m[Redis::Search] #{msg}\e[0m"
211
+ if defined?(Rails) == 'constant' && Rails.class == Class
212
+ ::Rails.logger.debug(msg)
213
+ else
214
+ puts msg
232
215
  end
233
-
234
- def self.hmget(type, ids, options = {})
235
- result = []
236
- sort_field = options[:sort_field] || "id"
237
- return result if ids.blank?
238
- Redis::Search.config.redis.hmget(type,*ids).each do |r|
239
- begin
240
- result << JSON.parse(r) if !r.blank?
241
- rescue => e
242
- Search.warn("Search.query failed: #{e}")
243
- end
216
+ end
217
+
218
+ # 生成 uuid,用于作为 hashes 的 field, sets 关键词的值
219
+ def self.mk_sets_key(type, key)
220
+ "#{type}:#{key.downcase}"
221
+ end
222
+
223
+ def self.mk_score_key(type, id)
224
+ "#{type}:_score_:#{id}"
225
+ end
226
+
227
+ def self.mk_condition_key(type, field, id)
228
+ "#{type}:_by:_#{field}:#{id}"
229
+ end
230
+
231
+ def self.mk_complete_key(type)
232
+ "Compl#{type}"
233
+ end
234
+
235
+ def self.hmget(type, ids, options = {})
236
+ result = []
237
+ sort_field = options[:sort_field] || "id"
238
+ return result if ids.blank?
239
+ self.config.redis.hmget(type,*ids).each do |r|
240
+ begin
241
+ result << JSON.parse(r) if !r.blank?
242
+ rescue => e
243
+ self.warn("Search.query failed: #{e}")
244
244
  end
245
- result
246
245
  end
247
- end
246
+ result
247
+ end
248
+ end # end Search
248
249
  end
@@ -1,109 +1,143 @@
1
1
  class Redis
2
2
  module Search
3
3
  class Index
4
- attr_accessor :type, :title, :id,:score, :aliases, :exts, :condition_fields, :prefix_index_enable
4
+ attr_accessor :type, :title, :id, :score, :aliases, :exts, :condition_fields, :prefix_index_enable
5
+
6
+ class << self
7
+ def redis
8
+ Redis::Search.config.redis
9
+ end
10
+
11
+ def remove(options = {})
12
+ type = options[:type]
13
+
14
+ self.redis.pipelined do
15
+ self.redis.hdel(type,options[:id])
16
+ self.redis.del(Search.mk_score_key(type,options[:id]))
17
+
18
+ words = self.split_words_for_index(options[:title])
19
+ words.each do |word|
20
+ self.redis.srem(Search.mk_sets_key(type,word), options[:id])
21
+ end
22
+
23
+ # remove set for prefix index key
24
+ self.redis.srem(Search.mk_sets_key(type,options[:title]),options[:id])
25
+ end
26
+ end
27
+
28
+ def split_words_for_index(title)
29
+ words = Search.split(title)
30
+ if Search.config.pinyin_match
31
+ # covert Chinese to pinyin to as an index
32
+ pinyin_full = Search.split_pinyin(title)
33
+ pinyin_first = pinyin_full.collect { |p| p[0] }.join("")
34
+ words += pinyin_full
35
+ words << pinyin_first
36
+ pinyin_full = nil
37
+ pinyin_first = nil
38
+ end
39
+ words.uniq
40
+ end
41
+
42
+ def bm
43
+ t1 = Time.now
44
+ yield
45
+ t2 = Time.now
46
+ puts "spend (#{t2 - t1}s)"
47
+ end
48
+ end # end class << self
49
+
50
+ def redis
51
+ self.class.redis
52
+ end
53
+
5
54
  def initialize(options = {})
6
55
  # default data
7
- self.condition_fields = []
8
- self.exts = []
9
- self.aliases = []
10
- self.prefix_index_enable = false
56
+ @condition_fields = []
57
+ @exts = []
58
+ @aliases = []
59
+ @prefix_index_enable = false
11
60
 
12
61
  # set attributes value from params
13
62
  options.keys.each do |k|
14
- eval("self.#{k} = options[k]")
63
+ self.send("#{k}=", options[k])
15
64
  end
16
- self.aliases << self.title
17
- self.aliases.uniq!
65
+ @aliases << self.title
66
+ @aliases.uniq!
18
67
  end
19
68
 
20
69
  def save
21
- return if self.title.blank?
22
- data = {:title => self.title, :id => self.id, :type => self.type}
23
- self.exts.each do |f|
24
- data[f[0]] = f[1]
25
- end
26
-
27
- # 将原始数据存入 hashes
28
- res = Redis::Search.config.redis.hset(self.type, self.id, data.to_json)
70
+ return if @title.blank?
29
71
 
30
- # 将目前的编号保存到条件(conditions)字段所创立的索引上面
31
- self.condition_fields.each do |field|
32
- Redis::Search.config.redis.sadd(Search.mk_condition_key(self.type,field,data[field.to_sym]), self.id)
33
- end
72
+ self.redis.pipelined do
73
+ data = {:title => @title, :id => @id, :type => @type}
74
+ self.exts.each do |f|
75
+ data[f[0]] = f[1]
76
+ end
34
77
 
35
- # score for search sort
36
- Redis::Search.config.redis.set(Search.mk_score_key(self.type,self.id),self.score)
78
+ # 将原始数据存入 hashes
79
+ res = self.redis.hset(@type, @id, data.to_json)
37
80
 
38
- # 保存 sets 索引,以分词的单词为key,用于后面搜索,里面存储 ids
39
- self.aliases.each do |val|
40
- words = Search::Index.split_words_for_index(val)
41
- return if words.blank?
42
- words.each do |word|
43
- Redis::Search.config.redis.sadd(Search.mk_sets_key(self.type,word), self.id)
81
+ # 将目前的编号保存到条件(conditions)字段所创立的索引上面
82
+ self.condition_fields.each do |field|
83
+ self.redis.sadd(Search.mk_condition_key(@type,field,data[field.to_sym]), @id)
44
84
  end
45
- end
46
85
 
47
- # 建立前缀索引
48
- save_prefix_index if prefix_index_enable
49
- end
86
+ # score for search sort
87
+ self.redis.set(Search.mk_score_key(@type,@id),@score)
50
88
 
51
- def self.remove(options = {})
52
- type = options[:type]
53
- Redis::Search.config.redis.hdel(type,options[:id])
54
- Redis::Search.config.redis.del(Search.mk_score_key(type,options[:id]))
89
+ # 保存 sets 索引,以分词的单词为key,用于后面搜索,里面存储 ids
90
+ self.aliases.each do |val|
91
+ words = Search::Index.split_words_for_index(val)
92
+ next if words.blank?
93
+ words.each do |word|
94
+ self.redis.sadd(Search.mk_sets_key(@type,word), @id)
95
+ end
96
+ end
55
97
 
56
- words = Search::Index.split_words_for_index(options[:title])
57
- words.each do |word|
58
- Redis::Search.config.redis.srem(Search.mk_sets_key(type,word), options[:id])
98
+ # 建立前缀索引
99
+ save_prefix_index if prefix_index_enable
59
100
  end
60
-
61
- # remove set for prefix index key
62
- Redis::Search.config.redis.srem(Search.mk_sets_key(type,options[:title]),options[:id])
63
101
  end
64
102
 
65
103
  private
66
- def self.split_words_for_index(title)
67
- words = Search.split(title)
104
+ def save_prefix_index
105
+ sorted_set_key = Search.mk_complete_key(@type)
106
+ sorted_vals = []
107
+
108
+ self.aliases.each do |val|
109
+ words = []
110
+ words << val.downcase
111
+
112
+ self.redis.sadd(Search.mk_sets_key(@type,val), @id)
113
+
68
114
  if Search.config.pinyin_match
69
- # covert Chinese to pinyin to as an index
70
- pinyin_full = Search.split_pinyin(title)
115
+ pinyin_full = Search.split_pinyin(val.downcase)
71
116
  pinyin_first = pinyin_full.collect { |p| p[0] }.join("")
72
- words += pinyin_full
117
+ pinyin = pinyin_full.join("")
118
+
119
+ words << pinyin
73
120
  words << pinyin_first
121
+
122
+ self.redis.sadd(Search.mk_sets_key(@type,pinyin), @id)
123
+
74
124
  pinyin_full = nil
75
125
  pinyin_first = nil
126
+ pinyin = nil
76
127
  end
77
- words.uniq
78
- end
79
128
 
80
- def save_prefix_index
81
- self.aliases.each do |val|
82
- words = []
83
- words << val.downcase
84
- Redis::Search.config.redis.sadd(Search.mk_sets_key(self.type,val), self.id)
85
- if Search.config.pinyin_match
86
- pinyin_full = Search.split_pinyin(val.downcase)
87
- pinyin_first = pinyin_full.collect { |p| p[0] }.join("")
88
- pinyin = pinyin_full.join("")
89
- words << pinyin
90
- words << pinyin_first
91
- Redis::Search.config.redis.sadd(Search.mk_sets_key(self.type,pinyin), self.id)
92
- pinyin_full = nil
93
- pinyin_first = nil
94
- pinyin = nil
95
- end
96
-
97
- words.each do |word|
98
- key = Search.mk_complete_key(self.type)
99
- (1..(word.length)).each do |l|
100
- prefix = word[0...l]
101
- Redis::Search.config.redis.zadd(key, 0, prefix)
102
- end
103
- Redis::Search.config.redis.zadd(key, 0, word + "*")
129
+ words.each do |word|
130
+ (1..(word.length)).each do |l|
131
+ prefix = word[0...l]
132
+ sorted_vals << [0, prefix]
104
133
  end
134
+ sorted_vals << [0, "#{word}*"]
105
135
  end
106
136
  end
107
- end
137
+
138
+ self.redis.zadd(sorted_set_key, sorted_vals)
139
+ end
140
+
141
+ end # end Index
108
142
  end
109
143
  end
@@ -21,11 +21,13 @@ namespace :redis_search do
21
21
  end
22
22
  end
23
23
  elsif klass.included_modules.collect { |m| m.to_s }.include?("Mongoid::Document")
24
- klass.all.each do |item|
25
- item.redis_search_index_create
26
- item = nil
27
- count += 1
28
- print "."
24
+ klass.all.each_slice(1000) do |items|
25
+ items.each do |item|
26
+ item.redis_search_index_create
27
+ item = nil
28
+ count += 1
29
+ print "."
30
+ end
29
31
  end
30
32
  else
31
33
  puts "skiped, not support this ORM in current."
@@ -37,4 +39,4 @@ namespace :redis_search do
37
39
  puts "Indexed #{count} rows | Time spend: #{(Time.now - tm)}s".rjust(120)
38
40
  puts "Rebuild Index done.".rjust(120)
39
41
  end
40
- end
42
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis-search
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.5
4
+ version: 0.9.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jason Lee
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-02-17 00:00:00.000000000 Z
11
+ date: 2014-04-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby-pinyin
@@ -36,30 +36,30 @@ dependencies:
36
36
  requirements:
37
37
  - - ">="
38
38
  - !ruby/object:Gem::Version
39
- version: 1.0.2
39
+ version: 1.3.0
40
40
  type: :runtime
41
41
  prerelease: false
42
42
  version_requirements: !ruby/object:Gem::Requirement
43
43
  requirements:
44
44
  - - ">="
45
45
  - !ruby/object:Gem::Version
46
- version: 1.0.2
46
+ version: 1.3.0
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: redis
49
49
  requirement: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - ">="
52
52
  - !ruby/object:Gem::Version
53
- version: 2.1.1
53
+ version: 3.0.0
54
54
  type: :runtime
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - ">="
59
59
  - !ruby/object:Gem::Version
60
- version: 2.1.1
61
- description: 'High performance real-time search (Support Chinese), index in Redis
62
- for Rails application. '
60
+ version: 3.0.0
61
+ description: 'High performance real-time search (Support Chinese), indexes store in
62
+ Redis for Rails applications. '
63
63
  email:
64
64
  - huacnlee@gmail.com
65
65
  executables: []
@@ -95,9 +95,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
95
95
  version: 1.3.6
96
96
  requirements: []
97
97
  rubyforge_project:
98
- rubygems_version: 2.2.0.rc.1
98
+ rubygems_version: 2.2.2
99
99
  signing_key:
100
100
  specification_version: 4
101
- summary: High performance real-time search (Support Chinese), index in Redis for Rails
102
- application.
101
+ summary: High performance real-time search (Support Chinese), indexes store in Redis
102
+ for Rails applications.
103
103
  test_files: []
104
+ has_rdoc: