redis-search 0.9.6 → 0.9.7
Sign up to get free protection for your applications and to get access to all the features.
- 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
|