redis-search 0.9.5 → 0.9.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: