queris 0.8.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +34 -0
- data/README.md +53 -0
- data/Rakefile +1 -0
- data/data/redis_scripts/add_low_ttl.lua +10 -0
- data/data/redis_scripts/copy_key_if_absent.lua +13 -0
- data/data/redis_scripts/copy_ttl.lua +13 -0
- data/data/redis_scripts/create_page_if_absent.lua +24 -0
- data/data/redis_scripts/debuq.lua +20 -0
- data/data/redis_scripts/delete_if_string.lua +8 -0
- data/data/redis_scripts/delete_matching_keys.lua +7 -0
- data/data/redis_scripts/expire_temp_query_keys.lua +7 -0
- data/data/redis_scripts/make_rangehack_if_needed.lua +30 -0
- data/data/redis_scripts/master_expire.lua +15 -0
- data/data/redis_scripts/match_key_type.lua +9 -0
- data/data/redis_scripts/move_key.lua +11 -0
- data/data/redis_scripts/multisize.lua +19 -0
- data/data/redis_scripts/paged_query_ready.lua +35 -0
- data/data/redis_scripts/periodic_zremrangebyscore.lua +9 -0
- data/data/redis_scripts/persist_reusable_temp_query_keys.lua +14 -0
- data/data/redis_scripts/query_ensure_existence.lua +23 -0
- data/data/redis_scripts/query_intersect_optimization.lua +31 -0
- data/data/redis_scripts/remove_from_keyspace.lua +27 -0
- data/data/redis_scripts/remove_from_sets.lua +13 -0
- data/data/redis_scripts/results_from_hash.lua +54 -0
- data/data/redis_scripts/results_with_ttl.lua +20 -0
- data/data/redis_scripts/subquery_intersect_optimization.lua +25 -0
- data/data/redis_scripts/subquery_intersect_optimization_cleanup.lua +5 -0
- data/data/redis_scripts/undo_add_low_ttl.lua +8 -0
- data/data/redis_scripts/unpaged_query_ready.lua +17 -0
- data/data/redis_scripts/unpersist_reusable_temp_query_keys.lua +11 -0
- data/data/redis_scripts/update_live_expiring_presence_index.lua +20 -0
- data/data/redis_scripts/update_query.lua +126 -0
- data/data/redis_scripts/update_rangehacks.lua +94 -0
- data/data/redis_scripts/zrangestore.lua +12 -0
- data/lib/queris.rb +400 -0
- data/lib/queris/errors.rb +8 -0
- data/lib/queris/indices.rb +735 -0
- data/lib/queris/mixin/active_record.rb +74 -0
- data/lib/queris/mixin/object.rb +398 -0
- data/lib/queris/mixin/ohm.rb +81 -0
- data/lib/queris/mixin/queris_model.rb +59 -0
- data/lib/queris/model.rb +455 -0
- data/lib/queris/profiler.rb +275 -0
- data/lib/queris/query.rb +1215 -0
- data/lib/queris/query/operations.rb +398 -0
- data/lib/queris/query/page.rb +101 -0
- data/lib/queris/query/timer.rb +42 -0
- data/lib/queris/query/trace.rb +108 -0
- data/lib/queris/query_store.rb +137 -0
- data/lib/queris/version.rb +3 -0
- data/lib/rails/log_subscriber.rb +22 -0
- data/lib/rails/request_timing.rb +29 -0
- data/lib/tasks/queris.rake +138 -0
- data/queris.gemspec +41 -0
- data/test.rb +39 -0
- data/test/current.rb +74 -0
- data/test/dsl.rb +35 -0
- data/test/ohm.rb +37 -0
- metadata +161 -0
@@ -0,0 +1,74 @@
|
|
1
|
+
module Queris
|
2
|
+
|
3
|
+
module ActiveRecordMixin
|
4
|
+
def self.included base
|
5
|
+
base.after_create :create_redis_indices
|
6
|
+
base.before_save :update_redis_indices
|
7
|
+
base.before_destroy :delete_redis_indices
|
8
|
+
|
9
|
+
def changed_cacheable_attributes
|
10
|
+
changed
|
11
|
+
end
|
12
|
+
|
13
|
+
def all_cacheable_attributes
|
14
|
+
attribute_names
|
15
|
+
end
|
16
|
+
|
17
|
+
base.extend ActiveRecordClassMixin
|
18
|
+
end
|
19
|
+
|
20
|
+
module ActiveRecordClassMixin
|
21
|
+
def redis_query(arg={}, &block)
|
22
|
+
@hashcache ||= stored_in_redis?
|
23
|
+
@hashkey ||= @hashcache.key '%s' if @hashcache
|
24
|
+
ActiveRecordQuery.new self, arg.merge(from_hash: @hashkey), &block
|
25
|
+
end
|
26
|
+
def find_all
|
27
|
+
find :all
|
28
|
+
end
|
29
|
+
def stored_in_redis?
|
30
|
+
@hashcache = redis_index(:all_attribute_hashcache, Queris::HashCache, false) || false if @hashcache.nil?
|
31
|
+
end
|
32
|
+
def find_cached(id, opt={})
|
33
|
+
@hashcache ||= stored_in_redis?
|
34
|
+
if (!opt[:assume_missing] && (obj = @hashcache.fetch(id, opt)))
|
35
|
+
return obj
|
36
|
+
elsif !opt[:nofallback]
|
37
|
+
begin
|
38
|
+
obj = find(id)
|
39
|
+
rescue
|
40
|
+
obj = nil
|
41
|
+
end
|
42
|
+
@hashcache.create obj if obj
|
43
|
+
obj
|
44
|
+
end
|
45
|
+
end
|
46
|
+
def restore(hash, id=nil)
|
47
|
+
unless (@hashcache ||= stored_in_redis?)
|
48
|
+
raise SchemaError, "Can't restore ActiveRecord model from hash -- there isn't a HashCache index present. (Don't forget to use cache_all_attributes on the model)"
|
49
|
+
end
|
50
|
+
unless (restored = @hashcache.load_cached hash)
|
51
|
+
restored = find_cached id if id
|
52
|
+
end
|
53
|
+
restored
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class ActiveRecordQuery < Query
|
60
|
+
attr_accessor :params
|
61
|
+
def initialize(model, arg=nil)
|
62
|
+
if model.kind_of?(Hash) and arg.nil?
|
63
|
+
arg, model = model, model[:model]
|
64
|
+
elsif arg.nil?
|
65
|
+
arg= {}
|
66
|
+
end
|
67
|
+
@params = {}
|
68
|
+
unless model.kind_of?(Class) && model < ActiveRecord::Base
|
69
|
+
raise ArgumentError, ":model arg must be an ActiveRecord model, got #{model.respond_to?(:superclass) ? model.superclass.name : model} instead."
|
70
|
+
end
|
71
|
+
super model, arg
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,398 @@
|
|
1
|
+
module Queris
|
2
|
+
|
3
|
+
#black hole, does nothing, is nil.
|
4
|
+
class DummyProfiler
|
5
|
+
def method_missing(*args)
|
6
|
+
self
|
7
|
+
end
|
8
|
+
def initialize(*args); end
|
9
|
+
def nil?; true; end
|
10
|
+
end
|
11
|
+
|
12
|
+
module ObjectMixin
|
13
|
+
def self.included base
|
14
|
+
base.extend ClassMixin
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMixin
|
18
|
+
def redis_index(index_match=nil, index_class = Index, strict=true)
|
19
|
+
raise ArgumentError, "#{index_class} must be a subclass of Queris::Index" unless index_class <= Index
|
20
|
+
case index_match
|
21
|
+
when index_class, NilClass, Query
|
22
|
+
return index_match
|
23
|
+
when String
|
24
|
+
index_match = index_match.to_sym
|
25
|
+
end
|
26
|
+
index = redis_index_hash[index_match]
|
27
|
+
if strict
|
28
|
+
raise SchemaError, "Index #{index_match} not found in #{name}" unless index
|
29
|
+
raise SchemaError, "Found wrong index class: expected #{index_class.name}, found #{index.class.name}" unless index.kind_of? index_class
|
30
|
+
end
|
31
|
+
index
|
32
|
+
end
|
33
|
+
|
34
|
+
#get all redis indices
|
35
|
+
#options:
|
36
|
+
# :foreign => true - look in foreign indices (other models' indices indexing from this model)
|
37
|
+
# :live => true - look in live indices
|
38
|
+
# ONLY ONE of the following will be respected
|
39
|
+
# :except => [index_name, ...] - exclude these
|
40
|
+
# :attributes => [...] - indices matching any of the given attribute names
|
41
|
+
# :names => [...] - indices with any of the given names
|
42
|
+
# :class => Queris::IndexClass - indices that are descendants of given class
|
43
|
+
def redis_indices(opt={})
|
44
|
+
unless Hash === opt
|
45
|
+
tmp, opt = opt, {}
|
46
|
+
opt[tmp]=true
|
47
|
+
end
|
48
|
+
if opt[:foreign]
|
49
|
+
indices = @foreign_redis_indices || []
|
50
|
+
else
|
51
|
+
indices = @redis_indices || (superclass.respond_to?(:redis_indices) ? superclass.redis_indices.clone : [])
|
52
|
+
end
|
53
|
+
if !opt[:attributes].nil?
|
54
|
+
attrs = opt[:attributes].map{|v| v.to_sym}.to_set
|
55
|
+
indices.select { |index| attrs.member? index.attribute }
|
56
|
+
elsif opt[:except]
|
57
|
+
except = Array === opt[:except] ? opt[:except] : [ opt[:except] ]
|
58
|
+
indices.select { |index| !except.member? index.name }
|
59
|
+
elsif opt[:live]
|
60
|
+
indices.select { |index| index.live? }
|
61
|
+
elsif !opt[:names].nil?
|
62
|
+
names = opt[:names].map{|v| v.to_sym}.to_set
|
63
|
+
indices.select { |index| names.member? index.name }
|
64
|
+
elsif !opt[:class].nil?
|
65
|
+
indices.select { |index| opt[:class] === index }
|
66
|
+
else
|
67
|
+
indices
|
68
|
+
end
|
69
|
+
#BUG: redis_indices is very static. superclass modifications after class declaration will not count.
|
70
|
+
end
|
71
|
+
def live_index?(index)
|
72
|
+
redis_indices(:live).member? redis_index(index)
|
73
|
+
end
|
74
|
+
def foreign_index?(index)
|
75
|
+
redis_indices(:foreign).member? redis_index(index)
|
76
|
+
end
|
77
|
+
|
78
|
+
def query_profiler; @profiler || DummyProfiler; end
|
79
|
+
def profile_queries?; query_profiler.nil?; end
|
80
|
+
|
81
|
+
|
82
|
+
def query(arg={}, &block)
|
83
|
+
redis_query arg, &block
|
84
|
+
end
|
85
|
+
|
86
|
+
def page_size(n=nil)
|
87
|
+
if n.nil?
|
88
|
+
@page_size || 1000
|
89
|
+
else
|
90
|
+
@page_size=n
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def info(arg={})
|
95
|
+
puts "#{self.name} model info:"
|
96
|
+
redis_indices.each do |i|
|
97
|
+
next if arg[:live] && !i.live?
|
98
|
+
next if arg[:class] && !(arg[:class] === i)
|
99
|
+
puts " #{i.info}"
|
100
|
+
end
|
101
|
+
querykeys = arg[:query_keys] || ((redis || Queris.redis).keys(query.results_key(nil, "*")))
|
102
|
+
puts " #{querykeys.count} query-related keys"
|
103
|
+
end
|
104
|
+
|
105
|
+
def clear_queries!
|
106
|
+
q = query
|
107
|
+
querykeys = redis.keys "#{q.redis_prefix}*"
|
108
|
+
#old keys, too
|
109
|
+
querykeys.concat redis.keys("#{self.prefix}#{q.class.name}*")
|
110
|
+
querykeys.uniq!
|
111
|
+
print "Deleting #{querykeys.count} query keys for #{name}..."
|
112
|
+
redis.multi do |r|
|
113
|
+
querykeys.each {|k| r.del k}
|
114
|
+
end
|
115
|
+
puts "ok"
|
116
|
+
querykeys.count
|
117
|
+
end
|
118
|
+
def clear_cache!
|
119
|
+
indices = redis_indices(:class => HashCache)
|
120
|
+
total = 0
|
121
|
+
indices.each do |i|
|
122
|
+
keymatch = i.key("*", nil, true)
|
123
|
+
allkeys = redis.keys(keymatch)
|
124
|
+
print "Clearing #{allkeys.count} cache keys for #{name} ..."
|
125
|
+
redis.multi do |r|
|
126
|
+
allkeys.each { |key| r.del key }
|
127
|
+
end
|
128
|
+
total += allkeys.count
|
129
|
+
puts "ok"
|
130
|
+
end
|
131
|
+
total
|
132
|
+
end
|
133
|
+
def redis_query(arg={})
|
134
|
+
Queris::Query.new self, arg
|
135
|
+
end
|
136
|
+
|
137
|
+
def redis
|
138
|
+
Queris.redis
|
139
|
+
end
|
140
|
+
|
141
|
+
def build_missing_redis_indices
|
142
|
+
missing_indices = redis_indices.select do |index|
|
143
|
+
|
144
|
+
unless index.skip_create?
|
145
|
+
found = false
|
146
|
+
cursor = "0"
|
147
|
+
loop do
|
148
|
+
cursor, res = redis.scan [cursor, "match", index.keypattern]
|
149
|
+
if res.count > 0
|
150
|
+
found = true
|
151
|
+
break
|
152
|
+
end
|
153
|
+
break if cursor == "0"
|
154
|
+
end
|
155
|
+
|
156
|
+
if not found then
|
157
|
+
puts "index #{name}:#{index.name} missing (#{index.keypattern})"
|
158
|
+
true
|
159
|
+
else
|
160
|
+
false
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
if missing_indices .count > 0
|
165
|
+
build_redis_indices missing_indices
|
166
|
+
else
|
167
|
+
puts "No missing indices for model #{name}."
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def build_redis_indices(indices=nil, build_foreign = true, incremental_delete=false)
|
172
|
+
indices ||= redis_indices
|
173
|
+
foreign_indices = []
|
174
|
+
indices.select! do |index|
|
175
|
+
if index.skip_create?
|
176
|
+
false
|
177
|
+
elsif Queris::ForeignIndex === index
|
178
|
+
foreign_indices << index
|
179
|
+
false
|
180
|
+
else
|
181
|
+
true
|
182
|
+
end
|
183
|
+
end
|
184
|
+
start_time = Time.now
|
185
|
+
if indices.count > 0
|
186
|
+
print "Loading #{name} data..."
|
187
|
+
all = self.find_all
|
188
|
+
fetch_time = Time.now - start_time
|
189
|
+
puts "\rLoaded #{all.count} entries for #{name} (#{fetch_time.to_f.round} sec)"
|
190
|
+
redis_start_time, printy, total =Time.now, 0, all.count - 1
|
191
|
+
index_keys = []
|
192
|
+
|
193
|
+
print "\rDeleting existing indices..." unless incremental_delete
|
194
|
+
indices.each {|index| index_keys.concat(redis.keys index.keypattern)} #BUG - race condition may prevent all index values from being deleted
|
195
|
+
no_indexing_pipeline do #prevent create_redis_index from running in its own personal multi
|
196
|
+
unless incremental_delete
|
197
|
+
redis.multi
|
198
|
+
index_keys.each { |k| redis.del k }
|
199
|
+
end
|
200
|
+
next_print, last_i, gap=Time.now, 0, 2
|
201
|
+
all.each_with_index do |row, i|
|
202
|
+
now = Time.now
|
203
|
+
if next_print <= now
|
204
|
+
rate = (i - last_i)/(now.to_f - (next_print-gap).to_f)
|
205
|
+
last_i=i
|
206
|
+
print "\rBuilding redis indices... #{((i.to_f/total) * 100).round.to_i}% (#{i}/#{all.count}) (#{rate.round}/sec)" unless total == 0
|
207
|
+
next_print = now + gap
|
208
|
+
end
|
209
|
+
if incremental_delete
|
210
|
+
redis.multi
|
211
|
+
row.eliminate_redis_indices indices
|
212
|
+
end
|
213
|
+
row.create_redis_indices indices
|
214
|
+
redis.exec if incremental_delete
|
215
|
+
end
|
216
|
+
print "\rBuilding redis indices... ok. Committing to redis..."
|
217
|
+
redis.exec unless incremental_delete
|
218
|
+
print "\rBuilt #{name} redis indices for #{total} rows in #{(Time.now - redis_start_time).round 3} sec. (#{fetch_time.round 3} sec. to fetch all data).\r\n"
|
219
|
+
end
|
220
|
+
end
|
221
|
+
#update all foreign indices
|
222
|
+
foreign_by_model={}
|
223
|
+
if build_foreign
|
224
|
+
foreign_indices.each do |index|
|
225
|
+
m = index.real_index.model
|
226
|
+
foreign_by_model[m] ||= []
|
227
|
+
foreign_by_model[m] << index.real_index
|
228
|
+
end
|
229
|
+
foreign_by_model.each { |model, indices| model.build_redis_indices indices, true, incremental_delete }
|
230
|
+
end
|
231
|
+
puts "Built #{indices.count} ind#{indices.count == 1 ? "ex" : "ices"} (#{build_foreign ? foreign_indices.count : 'skipped'} foreign) for #{self.name} in #{(Time.now - start_time).round(3)} seconds."
|
232
|
+
self
|
233
|
+
end
|
234
|
+
def build_redis_index(index_name, incremental_delete=false)
|
235
|
+
build_redis_indices [ redis_index(index_name) ], true, incremental_delete
|
236
|
+
end
|
237
|
+
|
238
|
+
def before_query(&block)
|
239
|
+
@before_query_callbacks ||= []
|
240
|
+
@before_query_callbacks << block
|
241
|
+
end
|
242
|
+
def before_query_flush(&block)
|
243
|
+
@before_query_flush_callbacks ||= []
|
244
|
+
@before_query_flush_callbacks << block
|
245
|
+
end
|
246
|
+
|
247
|
+
def run_query_callbacks(which_ones, query)
|
248
|
+
callbacks = case which_ones
|
249
|
+
when :before, :before_run, :run
|
250
|
+
@before_query_callbacks
|
251
|
+
when :before_flush, :flush
|
252
|
+
@before_query_flush_callbacks
|
253
|
+
end
|
254
|
+
if callbacks
|
255
|
+
callbacks.each {|block| block.call(query)}
|
256
|
+
callbacks.count
|
257
|
+
else
|
258
|
+
0
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
def add_redis_index(index, opt={})
|
263
|
+
raise SchemaError, "Not an index" unless index.kind_of? Index
|
264
|
+
#if (found = redis_index_hash[index.name])
|
265
|
+
#todo: same-name indices are allowed, but with some caveats.
|
266
|
+
#define and check them here.
|
267
|
+
#raise "Index #{index.name} (#{found.class.name}) already exists. Trying to add #{index.class.name}"
|
268
|
+
#end
|
269
|
+
@redis_indices ||= []
|
270
|
+
redis_indices.push index
|
271
|
+
redis_index_hash[index.name.to_sym]=index
|
272
|
+
redis_index_hash[index.class]=index
|
273
|
+
index
|
274
|
+
end
|
275
|
+
def live_queries?
|
276
|
+
@live_redis_indices && @live_redis_indices.count
|
277
|
+
end
|
278
|
+
|
279
|
+
def no_indexing_pipeline
|
280
|
+
if block_given?
|
281
|
+
@no_indexing_pipeline = true
|
282
|
+
res = yield
|
283
|
+
@no_indexing_pipeline = nil
|
284
|
+
return res
|
285
|
+
else
|
286
|
+
@no_indexing_pipeline
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
private
|
291
|
+
def redis_index_hash
|
292
|
+
@redis_index_hash ||= superclass.respond_to?(:redis_index_hash) ? superclass.redis_index_hash.clone : {}
|
293
|
+
end
|
294
|
+
|
295
|
+
def profile_queries(another_profiler=nil)
|
296
|
+
require "queris/profiler"
|
297
|
+
@profiler = case another_profiler
|
298
|
+
when :lite
|
299
|
+
Queris::QueryProfilerLite
|
300
|
+
else
|
301
|
+
Queris::QueryProfiler
|
302
|
+
end
|
303
|
+
end
|
304
|
+
attr_accessor :live_redis_indices
|
305
|
+
|
306
|
+
def index_attribute(arg={}, &block)
|
307
|
+
if arg.kind_of? Symbol
|
308
|
+
arg = {:attribute => arg }
|
309
|
+
end
|
310
|
+
index_class = arg[:index] || Queris::SearchIndex
|
311
|
+
raise ArgumentError, "index argument must be in Queris::Index if given" unless index_class <= Queris::Index
|
312
|
+
Queris.register_model self
|
313
|
+
index_class.new(arg.merge(:model => self), &block)
|
314
|
+
end
|
315
|
+
def index_attribute_for(arg)
|
316
|
+
raise ArgumentError ,"index_attribute_for requires :model argument" unless arg[:model]
|
317
|
+
index = index_attribute(arg) do |i|
|
318
|
+
i.name = "foreign_index_#{i.name}".to_sym
|
319
|
+
end
|
320
|
+
|
321
|
+
foreigner = arg[:model].send :index_attribute, arg.merge(:index=> Queris::ForeignIndex, :real_index => index)
|
322
|
+
@foreign_redis_indices ||= []
|
323
|
+
@foreign_redis_indices.push foreigner
|
324
|
+
foreigner
|
325
|
+
end
|
326
|
+
def index_attribute_from(arg)
|
327
|
+
model = arg[:model]
|
328
|
+
model.send(:include, Queris) unless model.include? Queris
|
329
|
+
model.send(:index_attribute_for, arg.merge(:model => self))
|
330
|
+
end
|
331
|
+
def index_range_attribute(arg)
|
332
|
+
if arg.kind_of? Symbol
|
333
|
+
arg = {:attribute => arg }
|
334
|
+
end
|
335
|
+
index_attribute arg.merge :index => Queris::RangeIndex
|
336
|
+
end
|
337
|
+
def index_range_attributes(*arg)
|
338
|
+
base_param = arg.last.kind_of?(Hash) ? arg.pop : {}
|
339
|
+
arg.each do |attr|
|
340
|
+
index_range_attribute base_param.merge :attribute => attr
|
341
|
+
end
|
342
|
+
end
|
343
|
+
alias index_sort_attribute index_range_attribute
|
344
|
+
def index_attributes(*arg)
|
345
|
+
base_param = arg.last.kind_of?(Hash) ? arg.pop : {}
|
346
|
+
arg.each do |attr|
|
347
|
+
index_attribute base_param.merge :attribute => attr
|
348
|
+
end
|
349
|
+
self
|
350
|
+
end
|
351
|
+
def cache_attribute(attribute_name)
|
352
|
+
Queris.register_model self
|
353
|
+
Queris::HashCache.new :model => self, :attribute => attribute_name
|
354
|
+
end
|
355
|
+
def cache_all_attributes
|
356
|
+
Queris.register_model self
|
357
|
+
Queris::HashCache.new :model => self
|
358
|
+
end
|
359
|
+
def stored_in_redis?
|
360
|
+
nil
|
361
|
+
end
|
362
|
+
alias :cache_object :cache_all_attributes
|
363
|
+
def cache_attribute_from(arg)
|
364
|
+
arg[:index]=Queris::HashCache
|
365
|
+
index_attribute_from arg
|
366
|
+
end
|
367
|
+
|
368
|
+
end
|
369
|
+
|
370
|
+
def get_cached_attribute(attr_name)
|
371
|
+
redis_index(attr_name, Queris::HashCache).fetch id
|
372
|
+
end
|
373
|
+
|
374
|
+
#in lieu of code cleanup, instance method pollution
|
375
|
+
%w(redis_index redis_indices query_profiler profile_queries redis).each do |method_name|
|
376
|
+
define_method method_name do |*arg|
|
377
|
+
self.class.send method_name, *arg
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
%w(create update delete eliminate).each do |op|
|
382
|
+
define_method "#{op}_redis_indices" do |indices=nil, redis=nil|
|
383
|
+
if self.class.no_indexing_pipeline || Redis::Pipeline === Queris.redis.client
|
384
|
+
(indices || self.class.redis_indices).each do |index|
|
385
|
+
index.send op, self unless index.respond_to?("skip_#{op}?") and index.send("skip_#{op}?")
|
386
|
+
end
|
387
|
+
else
|
388
|
+
Queris.redis.multi do #hacky, might bug out on weird configs
|
389
|
+
(indices || self.class.redis_indices).each do |index|
|
390
|
+
index.send op, self unless index.respond_to?("skip_#{op}?") and index.send("skip_#{op}?")
|
391
|
+
end
|
392
|
+
end
|
393
|
+
end
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
end
|
398
|
+
end
|