redis-search 0.9.6 → 0.9.7
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 +4 -4
- data/README.md +25 -4
- data/lib/redis/search/base.rb +137 -103
- data/lib/redis/search/finder.rb +11 -12
- data/lib/redis/search/index.rb +2 -1
- data/lib/redis/search/railtie.rb +1 -0
- data/lib/redis/search/tasks.rb +65 -34
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 715f373324e8d649a86d38d05ce25e044b4ebacc
|
4
|
+
data.tar.gz: bc32f91272a550a62f697366df1c807d2b7c58c2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dc3272074ff70fd82998bd46d7b566a6072ceb7c3820411e8169386f395cecf98896100934edae3e15e1bf50406a7e3556de1c15f0f32e402deb01d548f882ad
|
7
|
+
data.tar.gz: c115f7733556b42db81f707d0a857697e9881cf78b5e7dabbd3b2d67dd3d72d953e84a4b04a98af23190aae3546287abaf9c8902f36c93b0a19ec82d0a5385fe
|
data/README.md
CHANGED
@@ -107,7 +107,7 @@ And there is an [Example App](https://github.com/huacnlee/redis-search-example)
|
|
107
107
|
field :followers_count
|
108
108
|
|
109
109
|
redis_search_index(:title_field => :name,
|
110
|
-
|
110
|
+
:alias_field => :alias_names,
|
111
111
|
:prefix_index_enable => true,
|
112
112
|
:score_field => :followers_count,
|
113
113
|
:ext_fields => [:email,:tagline])
|
@@ -130,10 +130,32 @@ And there is an [Example App](https://github.com/huacnlee/redis-search-example)
|
|
130
130
|
|
131
131
|
## Index data to Redis
|
132
132
|
|
133
|
-
|
133
|
+
### Specify Model
|
134
|
+
|
135
|
+
Redis-Search index data to Redis from your model (pass name as CLASS environment variable).
|
136
|
+
|
137
|
+
```bash
|
138
|
+
$ rake redis_search:index:model CLASS='MyModel'
|
139
|
+
```
|
140
|
+
|
141
|
+
Customize the batch size:
|
134
142
|
|
135
143
|
```bash
|
136
|
-
$ rake redis_search:index
|
144
|
+
$ rake redis_search:index:model CLASS='MyModel' BATCH=100
|
145
|
+
```
|
146
|
+
|
147
|
+
### All Models
|
148
|
+
|
149
|
+
Redis-Search all index data to Redis from `app/models` (or use DIR environment variabl).
|
150
|
+
|
151
|
+
```bash
|
152
|
+
$ rake redis_search:index DIR=app/models
|
153
|
+
```
|
154
|
+
|
155
|
+
Customize the batch size:
|
156
|
+
|
157
|
+
```bash
|
158
|
+
$ rake redis_search:index DIR=app/models BATCH=100
|
137
159
|
```
|
138
160
|
|
139
161
|
## Documentation
|
@@ -148,7 +170,6 @@ There is my performance test result.
|
|
148
170
|
|
149
171
|
* [https://gist.github.com/1150933](https://gist.github.com/1150933)
|
150
172
|
|
151
|
-
|
152
173
|
## License
|
153
174
|
|
154
175
|
* MIT
|
data/lib/redis/search/base.rb
CHANGED
@@ -1,134 +1,168 @@
|
|
1
1
|
# coding: utf-8
|
2
|
-
|
3
2
|
class Redis
|
4
3
|
module Search
|
5
4
|
autoload :PinYin, 'ruby-pinyin'
|
6
5
|
|
7
6
|
extend ::ActiveSupport::Concern
|
8
7
|
|
8
|
+
included do
|
9
|
+
cattr_reader :redis_search_options
|
10
|
+
|
11
|
+
before_destroy :redis_search_index_before_destroy
|
12
|
+
after_update :redis_search_index_after_update
|
13
|
+
after_save :redis_search_index_after_save
|
14
|
+
end
|
15
|
+
|
16
|
+
def redis_search_fields_to_hash(ext_fields)
|
17
|
+
exts = {}
|
18
|
+
ext_fields.each do |f|
|
19
|
+
exts[f] = instance_eval(f.to_s)
|
20
|
+
end
|
21
|
+
exts
|
22
|
+
end
|
23
|
+
|
24
|
+
def redis_search_alias_value(field)
|
25
|
+
return [] if field.blank? || field == "_was".freeze
|
26
|
+
val = (instance_eval("self.#{field}") || "".freeze).clone
|
27
|
+
return [] if !val.class.in?([String,Array])
|
28
|
+
if val.is_a?(String)
|
29
|
+
val = val.to_s.split(",")
|
30
|
+
end
|
31
|
+
val
|
32
|
+
end
|
33
|
+
|
34
|
+
# Rebuild search index with create
|
35
|
+
def redis_search_index_create
|
36
|
+
s = Search::Index.new(title: self.send(self.redis_search_options[:title_field]),
|
37
|
+
aliases: self.redis_search_alias_value(self.redis_search_options[:alias_field]),
|
38
|
+
id: self.id,
|
39
|
+
exts: self.redis_search_fields_to_hash(self.redis_search_options[:ext_fields]),
|
40
|
+
type: self.redis_search_options[:class_name] || self.class.name,
|
41
|
+
condition_fields: self.redis_search_options[:condition_fields],
|
42
|
+
score: self.send(self.redis_search_options[:score_field]).to_i,
|
43
|
+
prefix_index_enable: self.redis_search_options[:prefix_index_enable])
|
44
|
+
s.save
|
45
|
+
# release s
|
46
|
+
s = nil
|
47
|
+
true
|
48
|
+
end
|
49
|
+
|
50
|
+
def redis_search_index_delete(titles)
|
51
|
+
titles.uniq!
|
52
|
+
titles.each do |title|
|
53
|
+
next if title.blank?
|
54
|
+
Search::Index.remove(id: self.id, title: title, type: self.class.name)
|
55
|
+
end
|
56
|
+
true
|
57
|
+
end
|
58
|
+
|
59
|
+
def redis_search_index_before_destroy
|
60
|
+
titles = []
|
61
|
+
titles = redis_search_alias_value(self.redis_search_options[:alias_field])
|
62
|
+
titles << self.send(self.redis_search_options[:title_field])
|
63
|
+
|
64
|
+
redis_search_index_delete(titles)
|
65
|
+
true
|
66
|
+
end
|
67
|
+
|
68
|
+
def redis_search_index_need_reindex
|
69
|
+
index_fields_changed = false
|
70
|
+
self.redis_search_options[:ext_fields].each do |f|
|
71
|
+
next if f.to_s == "id".freeze
|
72
|
+
field_method = "#{f}_changed?"
|
73
|
+
if self.methods.index(field_method.to_sym) == nil
|
74
|
+
Redis::Search.warn("#{self.class.name} model reindex on update need #{field_method} method.")
|
75
|
+
next
|
76
|
+
end
|
77
|
+
|
78
|
+
index_fields_changed = true if instance_eval(field_method)
|
79
|
+
end
|
80
|
+
|
81
|
+
begin
|
82
|
+
if self.send("#{self.redis_search_options[:title_field]}_changed?")
|
83
|
+
index_fields_changed = true
|
84
|
+
end
|
85
|
+
|
86
|
+
if self.send(self.redis_search_options[:alias_field]) || self.send("#{self.redis_search_options[:title_field]}_changed?")
|
87
|
+
index_fields_changed = true
|
88
|
+
end
|
89
|
+
rescue
|
90
|
+
end
|
91
|
+
|
92
|
+
return index_fields_changed
|
93
|
+
end
|
94
|
+
|
95
|
+
def redis_search_index_after_update
|
96
|
+
if self.redis_search_index_need_reindex
|
97
|
+
titles = []
|
98
|
+
titles = redis_search_alias_value("#{self.redis_search_options[:alias_field]}_was")
|
99
|
+
titles << self.send("#{self.redis_search_options[:title_field]}_was")
|
100
|
+
redis_search_index_delete(titles)
|
101
|
+
end
|
102
|
+
|
103
|
+
true
|
104
|
+
end
|
105
|
+
|
106
|
+
def redis_search_index_after_save
|
107
|
+
if self.redis_search_index_need_reindex || self.new_record?
|
108
|
+
self.redis_search_index_create
|
109
|
+
end
|
110
|
+
true
|
111
|
+
end
|
112
|
+
|
9
113
|
module ClassMethods
|
10
114
|
# Config redis-search index for Model
|
11
115
|
# == Params:
|
12
116
|
# title_field Query field for Search
|
13
|
-
# alias_field Alias field for search, can accept multi field (String or Array type)
|
117
|
+
# alias_field Alias field for search, can accept multi field (String or Array type) it type is String, redis-search will split by comma
|
14
118
|
# prefix_index_enable Is use prefix index search
|
15
119
|
# ext_fields What kind fields do you need inlucde to search indexes
|
16
120
|
# score_field Give a score for search sort, need Integer value, default is `created_at`
|
17
|
-
def redis_search_index(
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
121
|
+
def redis_search_index(opts = {})
|
122
|
+
opts[:title_field] ||= :title
|
123
|
+
opts[:alias_field] ||= nil
|
124
|
+
opts[:prefix_index_enable] ||= false
|
125
|
+
opts[:ext_fields] ||= []
|
126
|
+
opts[:score_field] ||= :created_at
|
127
|
+
opts[:condition_fields] ||= []
|
128
|
+
opts[:class_name] ||= nil
|
24
129
|
|
25
130
|
# Add score field to ext_fields
|
26
|
-
ext_fields
|
27
|
-
# Add condition fields to ext_fields
|
28
|
-
ext_fields |= condition_fields
|
131
|
+
opts[:ext_fields] += [opts[:score_field]]
|
29
132
|
|
30
|
-
|
31
|
-
|
32
|
-
end
|
133
|
+
# Add condition fields to ext_fields
|
134
|
+
opts[:ext_fields] += opts[:condition_fields] if opts[:condition_fields].is_a?(Array)
|
33
135
|
|
34
136
|
# store Model name to indexed_models for Rake tasks
|
35
137
|
Search.indexed_models = [] if Search.indexed_models == nil
|
36
138
|
Search.indexed_models << self
|
37
|
-
# bind instance methods and callback events
|
38
|
-
class_eval %(
|
39
|
-
def redis_search_fields_to_hash(ext_fields)
|
40
|
-
exts = {}
|
41
|
-
ext_fields.each do |f|
|
42
|
-
exts[f] = instance_eval(f.to_s)
|
43
|
-
end
|
44
|
-
exts
|
45
|
-
end
|
46
|
-
|
47
|
-
def redis_search_alias_value(field)
|
48
|
-
return [] if field.blank? || field == "_was"
|
49
|
-
val = (instance_eval("self.\#{field}") || "").clone
|
50
|
-
return [] if !val.class.in?([String,Array])
|
51
|
-
if val.is_a?(String)
|
52
|
-
val = val.to_s.split(",")
|
53
|
-
end
|
54
|
-
val
|
55
|
-
end
|
56
|
-
|
57
|
-
def redis_search_index_create
|
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}),
|
62
|
-
:type => self.class.to_s,
|
63
|
-
:condition_fields => #{condition_fields},
|
64
|
-
:score => self.#{score_field}.to_i,
|
65
|
-
:prefix_index_enable => #{prefix_index_enable})
|
66
|
-
s.save
|
67
|
-
# release s
|
68
|
-
s = nil
|
69
|
-
true
|
70
|
-
end
|
71
|
-
|
72
|
-
def redis_search_index_delete(titles)
|
73
|
-
titles.uniq.each do |title|
|
74
|
-
next if title.blank?
|
75
|
-
Search::Index.remove(:id => self.id, :title => title, :type => self.class.to_s)
|
76
|
-
end
|
77
|
-
true
|
78
|
-
end
|
79
|
-
|
80
|
-
before_destroy do
|
81
|
-
titles = []
|
82
|
-
titles = redis_search_alias_value("#{alias_field}")
|
83
|
-
titles << self.#{title_field}
|
84
139
|
|
85
|
-
|
86
|
-
|
87
|
-
end
|
140
|
+
class_variable_set("@@redis_search_options".freeze, opts)
|
141
|
+
end
|
88
142
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
end
|
98
|
-
if instance_eval(field_method)
|
99
|
-
index_fields_changed = true
|
100
|
-
end
|
143
|
+
def redis_search_index_batch_create(batch_size = 1000, progressbar = false)
|
144
|
+
count = 0
|
145
|
+
if self.ancestors.collect { |klass| klass.to_s }.include?("ActiveRecord::Base".freeze)
|
146
|
+
find_in_batches(:batch_size => batch_size) do |items|
|
147
|
+
items.each do |item|
|
148
|
+
item.redis_search_index_create
|
149
|
+
count += 1
|
150
|
+
print "." if progressbar
|
101
151
|
end
|
102
|
-
begin
|
103
|
-
if self.#{title_field}_changed?
|
104
|
-
index_fields_changed = true
|
105
|
-
end
|
106
|
-
if self.#{alias_field || title_field}_changed?
|
107
|
-
index_fields_changed = true
|
108
|
-
end
|
109
|
-
rescue
|
110
|
-
end
|
111
|
-
return index_fields_changed
|
112
152
|
end
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
redis_search_index_delete(titles)
|
153
|
+
elsif self.included_modules.collect { |m| m.to_s }.include?("Mongoid::Document".freeze)
|
154
|
+
all.each_slice(batch_size) do |items|
|
155
|
+
items.each do |item|
|
156
|
+
item.redis_search_index_create
|
157
|
+
count += 1
|
158
|
+
print "." if progressbar
|
120
159
|
end
|
121
|
-
true
|
122
160
|
end
|
161
|
+
else
|
162
|
+
puts "skiped, not support this ORM in current."
|
163
|
+
end
|
123
164
|
|
124
|
-
|
125
|
-
def redis_search_index_update
|
126
|
-
if self.redis_search_index_need_reindex || self.new_record?
|
127
|
-
self.redis_search_index_create
|
128
|
-
end
|
129
|
-
true
|
130
|
-
end
|
131
|
-
)
|
165
|
+
count
|
132
166
|
end
|
133
167
|
end
|
134
168
|
end
|
data/lib/redis/search/finder.rb
CHANGED
@@ -19,7 +19,7 @@ class Redis
|
|
19
19
|
# * Redis::Search.complete("Tag","red") => ["Redis", "Redmine"]
|
20
20
|
# * Redis::Search.complete("Tag","redi") => ["Redis"]
|
21
21
|
def complete(type, w, options = {})
|
22
|
-
limit
|
22
|
+
limit = options[:limit] || 10
|
23
23
|
conditions = options[:conditions] || []
|
24
24
|
return [] if (w.blank? && conditions.blank?) || type.blank?
|
25
25
|
|
@@ -89,9 +89,9 @@ class Redis
|
|
89
89
|
end
|
90
90
|
|
91
91
|
ids = self.config.redis.sort(temp_store_key,
|
92
|
-
|
93
|
-
|
94
|
-
|
92
|
+
limit: [0,limit],
|
93
|
+
by: self.mk_score_key(type,"*"),
|
94
|
+
order: "desc")
|
95
95
|
return [] if ids.blank?
|
96
96
|
self.hmget(type,ids)
|
97
97
|
end
|
@@ -105,10 +105,9 @@ class Redis
|
|
105
105
|
# h3. usage:
|
106
106
|
# * Redis::Search.query("Tag","Ruby vs Python")
|
107
107
|
def query(type, text, options = {})
|
108
|
-
tm
|
109
|
-
result
|
110
|
-
|
111
|
-
limit = options[:limit] || 10
|
108
|
+
tm = Time.now
|
109
|
+
result = []
|
110
|
+
limit = options[:limit] || 10
|
112
111
|
sort_field = options[:sort_field] || "id"
|
113
112
|
conditions = options[:conditions] || []
|
114
113
|
|
@@ -164,10 +163,10 @@ class Redis
|
|
164
163
|
|
165
164
|
# 根据需要的数量取出 ids
|
166
165
|
ids = self.config.redis.sort(temp_store_key,
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
result = self.hmget(type,ids, :
|
166
|
+
limit: [0,limit],
|
167
|
+
by: self.mk_score_key(type,"*"),
|
168
|
+
order: "desc")
|
169
|
+
result = self.hmget(type, ids, sort_field: sort_field)
|
171
170
|
self.info("{#{type} : \"#{text}\"} | Time spend: #{Time.now - tm}s")
|
172
171
|
result
|
173
172
|
end
|
data/lib/redis/search/index.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# coding: utf-8
|
1
2
|
class Redis
|
2
3
|
module Search
|
3
4
|
class Index
|
@@ -70,7 +71,7 @@ class Redis
|
|
70
71
|
return if @title.blank?
|
71
72
|
|
72
73
|
self.redis.pipelined do
|
73
|
-
data = {:
|
74
|
+
data = {title: @title, id: @id, type: @type}
|
74
75
|
self.exts.each do |f|
|
75
76
|
data[f[0]] = f[1]
|
76
77
|
end
|
data/lib/redis/search/railtie.rb
CHANGED
data/lib/redis/search/tasks.rb
CHANGED
@@ -1,42 +1,73 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
require "redis-search"
|
3
3
|
namespace :redis_search do
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
else
|
33
|
-
puts "skiped, not support this ORM in current."
|
4
|
+
task index: 'index:all'
|
5
|
+
|
6
|
+
namespace :index do
|
7
|
+
index_model_desc = <<-DESC.gsub(/ /, '')
|
8
|
+
Redis-Search index data to Redis from your model (pass name as CLASS environment variable).
|
9
|
+
|
10
|
+
$ rake environment redis_search:index:model CLASS='MyModel'
|
11
|
+
|
12
|
+
Customize the batch size:
|
13
|
+
|
14
|
+
$ rake environment redis_search:index:model CLASS='Article' BATCH=100
|
15
|
+
DESC
|
16
|
+
|
17
|
+
index_all_desc = <<-DESC.gsub(/ /, '')
|
18
|
+
Redis-Search all index data to Redis from `app/models` (or use DIR environment variabl).
|
19
|
+
|
20
|
+
$ rake environment redis_search:index:all DIR=app/models
|
21
|
+
|
22
|
+
Customize the batch size:
|
23
|
+
|
24
|
+
$ rake environment redis_search:index:all DIR=app/models BATCH=100
|
25
|
+
DESC
|
26
|
+
|
27
|
+
desc index_model_desc
|
28
|
+
task model: :environment do
|
29
|
+
if ENV['CLASS'].to_s == ''
|
30
|
+
puts '='*90, 'USAGE', '='*90, index_model_desc, ""
|
31
|
+
exit(1)
|
34
32
|
end
|
33
|
+
|
34
|
+
klass = eval(ENV['CLASS'].to_s)
|
35
|
+
batch = ENV['BATCH'].to_i > 0 ? ENV['BATCH'].to_i : 1000
|
36
|
+
tm = Time.now
|
37
|
+
puts "Redis-Search index data to Redis from [#{klass.to_s}]"
|
38
|
+
count = klass.redis_search_index_batch_create(batch, true)
|
35
39
|
puts ""
|
40
|
+
puts "Indexed #{count} rows | Time spend: #{(Time.now - tm)}s"
|
41
|
+
puts "Rebuild Index done."
|
42
|
+
end
|
43
|
+
|
44
|
+
desc index_all_desc
|
45
|
+
task all: :environment do
|
46
|
+
tm = Time.now
|
47
|
+
count = 0
|
48
|
+
dir = ENV['DIR'].to_s != '' ? ENV['DIR'] : 'app/models'
|
49
|
+
batch = ENV['BATCH'].to_i > 0 ? ENV['BATCH'].to_i : 1000
|
50
|
+
|
51
|
+
Dir.glob(File.join("#{dir}/**/*.rb")).each do |path|
|
52
|
+
model_filename = path[/#{Regexp.escape(dir.to_s)}\/([^\.]+).rb/, 1]
|
53
|
+
|
54
|
+
next if model_filename.match(/^concerns\//i) # Skip concerns/ folder
|
55
|
+
|
56
|
+
begin
|
57
|
+
klass = model_filename.camelize.constantize
|
58
|
+
rescue NameError
|
59
|
+
require(path) ? retry : raise(RuntimeError, "Cannot load class '#{klass}'")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
puts "Redis-Search index data to Redis from [#{dir}]"
|
64
|
+
Redis::Search.indexed_models.each do |klass|
|
65
|
+
puts "[#{klass.to_s}]"
|
66
|
+
count += klass.redis_search_index_batch_create(batch, true)
|
67
|
+
puts ""
|
68
|
+
end
|
69
|
+
puts "Indexed #{count} rows | Time spend: #{(Time.now - tm)}s"
|
70
|
+
puts "Rebuild Index done."
|
36
71
|
end
|
37
|
-
puts ""
|
38
|
-
puts "-"*120
|
39
|
-
puts "Indexed #{count} rows | Time spend: #{(Time.now - tm)}s".rjust(120)
|
40
|
-
puts "Rebuild Index done.".rjust(120)
|
41
72
|
end
|
42
73
|
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.
|
4
|
+
version: 0.9.7
|
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-
|
11
|
+
date: 2014-10-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ruby-pinyin
|