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,81 @@
|
|
1
|
+
module Queris
|
2
|
+
module OhmMixin
|
3
|
+
def self.included(base)
|
4
|
+
begin
|
5
|
+
require "ohm/contrib"
|
6
|
+
rescue Exception => e
|
7
|
+
raise LoadError, "ohm-contrib not found. Please ensure the ohm-contrib gem is available."
|
8
|
+
end
|
9
|
+
|
10
|
+
base.class_eval do
|
11
|
+
include Ohm::Callbacks
|
12
|
+
%w(create update delete).each do |action|
|
13
|
+
hook="before_#{action}"
|
14
|
+
alias_method hook "old_#{hook}" if respond_to?(hook)
|
15
|
+
define_method "brefore_#{action}" do
|
16
|
+
call("old_#{hook}") if respond_to? "old_#{hook}"
|
17
|
+
call "#{action}_redis_indices"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
base.extend OhmClassMixin
|
23
|
+
end
|
24
|
+
|
25
|
+
module OhmClassMixin
|
26
|
+
def redis_query(arg={})
|
27
|
+
query = OhmQuery.new self, arg
|
28
|
+
yield query if block_given?
|
29
|
+
query
|
30
|
+
end
|
31
|
+
|
32
|
+
def find_cached(id, *arg)
|
33
|
+
self[id]
|
34
|
+
end
|
35
|
+
def restore(hash, arg)
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class OhmQuery < Query
|
41
|
+
attr_accessor :params
|
42
|
+
def ensure_same_redis(model)
|
43
|
+
quer, ohmr = Queris.redis, self.db
|
44
|
+
if !ohmr && quer
|
45
|
+
Ohm.connect url: quer.id
|
46
|
+
elsif !quer && ohmr
|
47
|
+
Queris.add_redis Redis.new(url: ohmr.id)
|
48
|
+
elsif quer && ohmr
|
49
|
+
unless quer.id == ohmr.id
|
50
|
+
raise Error, "Queris redis master server and Ohm redis server must be the same. There's just no reason to have them on separate servers."
|
51
|
+
end
|
52
|
+
end
|
53
|
+
yield Queris.redis if block_given?
|
54
|
+
end
|
55
|
+
def initialize(model, arg=nil)
|
56
|
+
if model.kind_of?(Hash) and arg.nil?
|
57
|
+
arg, model = model, model[:model]
|
58
|
+
elsif arg.nil?
|
59
|
+
arg= {}
|
60
|
+
end
|
61
|
+
@params = {}
|
62
|
+
unless model.kind_of?(Class) && model < Ohm::Model
|
63
|
+
raise ArgumentError, ":model arg must be an Ohm model model, got #{model.respond_to?(:superclass) ? model.superclass.name : model} instead."
|
64
|
+
end
|
65
|
+
super model, arg
|
66
|
+
end
|
67
|
+
|
68
|
+
def results(*arg)
|
69
|
+
res_ids = super(*arg)
|
70
|
+
res = []
|
71
|
+
res_ids.each_with_index do |id, i|
|
72
|
+
unless (cached = @model.find_cached id).nil?
|
73
|
+
res << cached
|
74
|
+
end
|
75
|
+
end
|
76
|
+
res
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Queris
|
2
|
+
module QuerisModelMixin
|
3
|
+
def self.included(base)
|
4
|
+
base.extend QuerisModelClassMixin
|
5
|
+
end
|
6
|
+
|
7
|
+
module QuerisModelClassMixin
|
8
|
+
def redis_query(arg={})
|
9
|
+
@hash_keyf ||= new.key '%s'
|
10
|
+
query = QuerisModelQuery.new self, arg.merge(redis: redis(true), from_hash: @hash_keyf, delete_missing: true)
|
11
|
+
yield query if block_given?
|
12
|
+
query
|
13
|
+
end
|
14
|
+
alias :query :redis_query
|
15
|
+
def add_redis_index(index, opt={})
|
16
|
+
#support for incremental attributes
|
17
|
+
@incremental_attr ||= {}
|
18
|
+
ret = super(index, opt)
|
19
|
+
if @incremental_attr[index.attribute].nil?
|
20
|
+
@incremental_attr[index.attribute] = index.incremental?
|
21
|
+
else
|
22
|
+
@incremental_attr[index.attribute] &&= index.incremental?
|
23
|
+
end
|
24
|
+
ret
|
25
|
+
end
|
26
|
+
def stored_in_redis?; true; end
|
27
|
+
def can_increment_attribute?( attr_name )
|
28
|
+
@incremental_attr[attr_name.to_sym]
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
#don't save attributes, just index them. useful at times.
|
33
|
+
def index_only
|
34
|
+
@index_only = true
|
35
|
+
end
|
36
|
+
|
37
|
+
def index_attribute(arg={}, &block)
|
38
|
+
if arg.kind_of? Symbol
|
39
|
+
arg = {:attribute => arg }
|
40
|
+
end
|
41
|
+
super arg.merge(:redis => redis), &block
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class QuerisModelQuery < Query
|
47
|
+
#TODO
|
48
|
+
attr_accessor :params
|
49
|
+
def initialize(model, arg=nil)
|
50
|
+
if model.kind_of?(Hash) and arg.nil?
|
51
|
+
arg, model = model, model[:model]
|
52
|
+
elsif arg.nil?
|
53
|
+
arg= {}
|
54
|
+
end
|
55
|
+
@params = {}
|
56
|
+
super model, arg
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/queris/model.rb
ADDED
@@ -0,0 +1,455 @@
|
|
1
|
+
require "redis"
|
2
|
+
module Queris
|
3
|
+
|
4
|
+
class Model
|
5
|
+
attr_reader :id
|
6
|
+
attr_accessor :query_score
|
7
|
+
include Queris #this doesn't trigger Queris::included as it seems it ought to...
|
8
|
+
require "queris/mixin/queris_model"
|
9
|
+
include ObjectMixin
|
10
|
+
include QuerisModelMixin
|
11
|
+
|
12
|
+
def self.attr_val_block
|
13
|
+
@attr_val_block ||= {}
|
14
|
+
end
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def redis(redis_client=nil)
|
18
|
+
if redis_client.kind_of? Redis
|
19
|
+
@redis = redis_client
|
20
|
+
end
|
21
|
+
@redis || Queris.redis
|
22
|
+
end
|
23
|
+
|
24
|
+
def prefix
|
25
|
+
@prefix ||= "#{Queris.redis_prefix}#{self.superclass.name.split('::').last}:#{self.name}:"
|
26
|
+
end
|
27
|
+
def keyf
|
28
|
+
"#{prefix}%s"
|
29
|
+
end
|
30
|
+
|
31
|
+
#get/setter
|
32
|
+
def attributes(*arg)
|
33
|
+
if Hash===arg.last
|
34
|
+
attributes = arg[0..-2]
|
35
|
+
opt=arg.last
|
36
|
+
else
|
37
|
+
attributes= arg
|
38
|
+
end
|
39
|
+
unless attributes.nil?
|
40
|
+
attributes.each do |attr|
|
41
|
+
attribute attr, opt
|
42
|
+
if block_given?
|
43
|
+
self.attr_val_block[attr.to_sym]=Proc.new
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
@attributes
|
48
|
+
end
|
49
|
+
|
50
|
+
def attribute(*arg)
|
51
|
+
if arg.first
|
52
|
+
attr_name = arg.first.to_sym
|
53
|
+
end
|
54
|
+
if arg.count == 2
|
55
|
+
if Hash===arg.last
|
56
|
+
opt=arg.last
|
57
|
+
else
|
58
|
+
raise "Invalid \"attribute\" params" unless arg.last.nil?
|
59
|
+
end
|
60
|
+
elsif arg.count > 2
|
61
|
+
raise "Too many arguments for \"attribute\""
|
62
|
+
end
|
63
|
+
|
64
|
+
@attributes ||= [] #Class instance var
|
65
|
+
raise ArgumentError, "Attribute #{attr_name} already exists in Queris model #{self.name}." if @attributes.member? attr_name
|
66
|
+
if block_given?
|
67
|
+
bb=Proc.new
|
68
|
+
self.attr_val_block[attr_name]=bb
|
69
|
+
end
|
70
|
+
define_method "#{attr_name}" do |no_attr_load=false|
|
71
|
+
binding.pry if @attributes.nil?
|
72
|
+
1
|
73
|
+
if (val = @attributes[attr_name]).nil? && !@loaded && !no_attr_load && !noload?
|
74
|
+
load
|
75
|
+
send attr_name, true
|
76
|
+
else
|
77
|
+
val
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
define_method "#{attr_name}=" do |val| #setter
|
82
|
+
if opt
|
83
|
+
type = opt[:type]
|
84
|
+
unless val.nil?
|
85
|
+
if type == Float
|
86
|
+
val=Float(val)
|
87
|
+
elsif type == String
|
88
|
+
val=val.to_s
|
89
|
+
elsif type == Fixnum
|
90
|
+
val = val.to_s if Symbol === val
|
91
|
+
val=val.to_i
|
92
|
+
elsif type == Symbol
|
93
|
+
val = val.to_s if Numeric > val.class # first to string, then to symbol.
|
94
|
+
val=val.to_sym
|
95
|
+
elsif type == :boolean || type == :bool
|
96
|
+
if val=="1" || val==1 || val=="true"
|
97
|
+
val=true
|
98
|
+
elsif val=="0" || val==0 || val=="false"
|
99
|
+
val=false
|
100
|
+
else
|
101
|
+
val=val ? true : false
|
102
|
+
end
|
103
|
+
elsif type == :flag
|
104
|
+
val=val ? true : nil
|
105
|
+
elsif type.nil?
|
106
|
+
#nothing
|
107
|
+
else
|
108
|
+
raise "Unknown attribute type #{opt[:type]}"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
if self.class.attr_val_block[attr_name]
|
113
|
+
val = self.class.attr_val_block[attr_name].call(val, self)
|
114
|
+
end
|
115
|
+
if !@loading
|
116
|
+
if @attributes_were[attr_name].nil?
|
117
|
+
@attributes_were[attr_name] = @attributes[attr_name]
|
118
|
+
end
|
119
|
+
@attributes_to_save[attr_name]=val
|
120
|
+
end
|
121
|
+
@attributes[attr_name]=val
|
122
|
+
end
|
123
|
+
define_method "#{attr_name}_was" do
|
124
|
+
@attributes_were[attr_name]
|
125
|
+
end
|
126
|
+
define_method "#{attr_name}_was=" do |val|
|
127
|
+
@attributes_were[attr_name]=val
|
128
|
+
end
|
129
|
+
private "#{attr_name}_was="
|
130
|
+
attributes << attr_name
|
131
|
+
end
|
132
|
+
alias :attr :attribute
|
133
|
+
alias :attrs :attributes
|
134
|
+
|
135
|
+
#get/setter
|
136
|
+
def expire(seconds=nil)
|
137
|
+
#note that using expire will not update indices, leading to some serious staleness
|
138
|
+
unless seconds.nil?
|
139
|
+
@expire = seconds
|
140
|
+
else
|
141
|
+
@expire
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def find(id, opt={})
|
146
|
+
got= get id, opt
|
147
|
+
got.loaded? ? got : nil
|
148
|
+
end
|
149
|
+
alias :find_cached :find
|
150
|
+
|
151
|
+
def get(id, opt=nil)
|
152
|
+
ret=new(id)
|
153
|
+
opt ||= {}
|
154
|
+
if opt[:redis]
|
155
|
+
ret.load(nil, redis: opt[:redis])
|
156
|
+
else
|
157
|
+
ret.load
|
158
|
+
end
|
159
|
+
ret
|
160
|
+
end
|
161
|
+
|
162
|
+
def find_all #NOT FOR PRODUCTION USE!
|
163
|
+
keys = redis.keys "#{prefix}*"
|
164
|
+
objs = []
|
165
|
+
keys.map! do |key|
|
166
|
+
begin
|
167
|
+
found = self.find key[prefix.length..-1]
|
168
|
+
objs << found if found
|
169
|
+
rescue Exception => e
|
170
|
+
nil
|
171
|
+
end
|
172
|
+
end
|
173
|
+
objs
|
174
|
+
end
|
175
|
+
|
176
|
+
def restore(hash, id)
|
177
|
+
new(id).load(hash)
|
178
|
+
end
|
179
|
+
|
180
|
+
%w(during_save during_save_multi before_save after_save).each do |callback|
|
181
|
+
define_method callback do |&block|
|
182
|
+
@callbacks ||= {}
|
183
|
+
if block
|
184
|
+
@callbacks[callback] ||= []
|
185
|
+
@callbacks[callback] << block
|
186
|
+
else
|
187
|
+
@callbacks[callback] || []
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def while_loading
|
194
|
+
loading_was=@loading
|
195
|
+
@loading=true
|
196
|
+
yield
|
197
|
+
@loading=loading_was
|
198
|
+
end
|
199
|
+
|
200
|
+
def run_callbacks(callback, redis=nil)
|
201
|
+
(self.class.send(callback) || []).each {|block| block.call(self, redis)}
|
202
|
+
end
|
203
|
+
private :run_callbacks
|
204
|
+
|
205
|
+
def initialize(id=nil, arg={})
|
206
|
+
@attributes = {}
|
207
|
+
@attributes_to_save = {}
|
208
|
+
@attributes_to_incr = {}
|
209
|
+
@attributes_were = {}
|
210
|
+
@redis = (arg || {})[:redis]
|
211
|
+
set_id id unless id.nil?
|
212
|
+
end
|
213
|
+
|
214
|
+
def set_id(nid, overwrite=false)
|
215
|
+
noload do
|
216
|
+
raise Error, "id cannot be a Hash" if Hash === nid
|
217
|
+
raise Error, "id cannot be an Array" if Array === nid
|
218
|
+
raise Error, "id already exists and is #{self.id}" unless overwrite || self.id.nil?
|
219
|
+
end
|
220
|
+
@id= nid
|
221
|
+
self
|
222
|
+
end
|
223
|
+
def id=(nid)
|
224
|
+
set_id nid
|
225
|
+
end
|
226
|
+
|
227
|
+
def save
|
228
|
+
key = hash_key #before multi
|
229
|
+
noload do
|
230
|
+
# to ensure atomicity, we unfortunately need two round trips to redis
|
231
|
+
run_callbacks :before_save
|
232
|
+
begin
|
233
|
+
if @attributes_to_save.length > 0
|
234
|
+
attrs_to_save = @attributes_to_save.keys
|
235
|
+
bulk_response = redis.pipelined do
|
236
|
+
redis.watch key
|
237
|
+
redis.hmget key, *attrs_to_save
|
238
|
+
end
|
239
|
+
current_saved_attr_vals = bulk_response.last
|
240
|
+
attrs_to_save.each_with_index do |attr,i| #sync with server
|
241
|
+
val=current_saved_attr_vals[i]
|
242
|
+
@attributes_were[attr]=val
|
243
|
+
end
|
244
|
+
|
245
|
+
run_callbacks :during_save
|
246
|
+
|
247
|
+
bulk_response = redis.multi do |r|
|
248
|
+
unless index_only
|
249
|
+
@attributes_to_incr.each do |attr, incr_by_val|
|
250
|
+
r.hincrbyfloat key, attr, incr_by_val #redis server >= 2.6
|
251
|
+
unless (val = send(attr, true)).nil?
|
252
|
+
@attributes_were[attr]=val
|
253
|
+
end
|
254
|
+
end
|
255
|
+
r.mapped_hmset key, @attributes_to_save
|
256
|
+
# a little hacky to first set to "", then delete.
|
257
|
+
# meh. will optimize when needed.
|
258
|
+
@attributes_to_save.each do |attr, val|
|
259
|
+
r.hdel(key, attr) if val.nil?
|
260
|
+
end
|
261
|
+
expire_sec = self.class.expire
|
262
|
+
end
|
263
|
+
|
264
|
+
update_redis_indices if defined? :update_redis_indices
|
265
|
+
@attributes_to_save.each {|attr, val| @attributes_were[attr]=val }
|
266
|
+
r.expire key, expire_sec unless expire_sec.nil?
|
267
|
+
run_callbacks :during_save_multi, r
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end while @attributes_to_save.length > 0 && bulk_response.nil?
|
271
|
+
@attributes_to_save.clear
|
272
|
+
@attributes_to_incr.clear
|
273
|
+
ret= self
|
274
|
+
run_callbacks :after_save, redis
|
275
|
+
ret
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
|
280
|
+
def increment(attr_name, delta_val)
|
281
|
+
raise ArgumentError, "Can't increment attribute #{attr_name} because it is used by at least one non-incrementable index." unless self.class.can_increment_attribute? attr_name
|
282
|
+
raise ArgumentError, "Can't increment attribute #{attr_name} by non-numeric value <#{delta_val}>. Increment only by numbers, please." unless delta_val.kind_of? Numeric
|
283
|
+
|
284
|
+
@attributes_to_incr[attr_name.to_sym]=delta_val
|
285
|
+
unless (val = send(attr_name, true)).nil?
|
286
|
+
send "#{attr_name}=", val + delta_val
|
287
|
+
end
|
288
|
+
self
|
289
|
+
end
|
290
|
+
|
291
|
+
|
292
|
+
def attribute_diff(attr)
|
293
|
+
@attributes_to_incr[attr.to_sym]
|
294
|
+
end
|
295
|
+
#list of changed attributes
|
296
|
+
def changed
|
297
|
+
delta = (@attributes_to_save.keys + @attributes_to_incr.keys)
|
298
|
+
delta.uniq!
|
299
|
+
delta
|
300
|
+
end
|
301
|
+
#any unsaved changes?
|
302
|
+
def changed?
|
303
|
+
@attributes_to_save.empty? && @attributes_to_incr.empty?
|
304
|
+
end
|
305
|
+
|
306
|
+
def loaded?
|
307
|
+
@loaded && self
|
308
|
+
end
|
309
|
+
|
310
|
+
def deleted?
|
311
|
+
@deleted && self
|
312
|
+
end
|
313
|
+
|
314
|
+
def delete
|
315
|
+
noload do
|
316
|
+
key = hash_key
|
317
|
+
redis.multi do
|
318
|
+
redis.del key
|
319
|
+
delete_redis_indices if defined? :delete_redis_indices
|
320
|
+
end
|
321
|
+
end
|
322
|
+
@deleted= true
|
323
|
+
self
|
324
|
+
end
|
325
|
+
|
326
|
+
def load(hash=nil, opt={})
|
327
|
+
raise SchemaError, "Can't load #{self.class.name} with id #{id} -- model was specified index_only, so it was never saved." if index_only
|
328
|
+
unless hash
|
329
|
+
hash_future, hash_exists = nil, nil
|
330
|
+
hash_key
|
331
|
+
(opt[:redis] || redis).multi do |r|
|
332
|
+
hash_future = r.hgetall hash_key
|
333
|
+
hash_exists = r.exists hash_key
|
334
|
+
end
|
335
|
+
if hash_exists.value
|
336
|
+
hash = hash_future.value
|
337
|
+
elsif not hash
|
338
|
+
return nil
|
339
|
+
end
|
340
|
+
end
|
341
|
+
case hash
|
342
|
+
when Array
|
343
|
+
attr_name= nil
|
344
|
+
hash.each_with_index do |v, i|
|
345
|
+
if i % 2 == 0
|
346
|
+
attr_name = v
|
347
|
+
next
|
348
|
+
else
|
349
|
+
raw_load_attr(attr_name, v, !opt[:nil_only])
|
350
|
+
end
|
351
|
+
end
|
352
|
+
@loaded = true
|
353
|
+
when Hash
|
354
|
+
hash.each do |k, v|
|
355
|
+
raw_load_attr(k, v, !opt[:nil_only])
|
356
|
+
end
|
357
|
+
@loaded = true
|
358
|
+
else
|
359
|
+
raise Queris::ArgumentError, "Invalid thing to load"
|
360
|
+
end
|
361
|
+
|
362
|
+
self
|
363
|
+
end
|
364
|
+
|
365
|
+
def load_missing #load only missing attributes
|
366
|
+
load nil, nil_only: true
|
367
|
+
end
|
368
|
+
|
369
|
+
def raw_load_attr(attr_name, val, overwrite=true)
|
370
|
+
if attr_name.to_sym == :____score
|
371
|
+
@query_score = val.to_f
|
372
|
+
else
|
373
|
+
if overwrite || send(attr_name).nil?
|
374
|
+
while_loading do
|
375
|
+
send "#{attr_name}=", val
|
376
|
+
end
|
377
|
+
end
|
378
|
+
end
|
379
|
+
end
|
380
|
+
private :raw_load_attr
|
381
|
+
|
382
|
+
def import(attrs={})
|
383
|
+
attrs.each do |attr_name, val|
|
384
|
+
send "#{attr_name}=", val
|
385
|
+
@attributes_were[attr_name] = val
|
386
|
+
end
|
387
|
+
self
|
388
|
+
end
|
389
|
+
|
390
|
+
|
391
|
+
def redis=(r)
|
392
|
+
@redis=r
|
393
|
+
end
|
394
|
+
def redis(no_fallback=false)
|
395
|
+
if no_fallback
|
396
|
+
@redis || self.class.redis
|
397
|
+
else
|
398
|
+
@redis || self.class.redis || Queris.redis
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
def hash_key(custom_id=nil)
|
403
|
+
if custom_id.nil? && id.nil?
|
404
|
+
@id = new_id
|
405
|
+
end
|
406
|
+
@hash_key ||= "#{prefix}#{custom_id || id}"
|
407
|
+
end
|
408
|
+
alias :key :hash_key
|
409
|
+
|
410
|
+
def to_json(*arg)
|
411
|
+
as_json.to_json(*arg)
|
412
|
+
end
|
413
|
+
|
414
|
+
def as_json(*arg)
|
415
|
+
rest={id: self.id}
|
416
|
+
rest[:query_score]= self.query_score if query_score
|
417
|
+
@attributes.merge(rest)
|
418
|
+
end
|
419
|
+
|
420
|
+
def noload
|
421
|
+
@noload||=0
|
422
|
+
@noload+=1
|
423
|
+
ret = yield
|
424
|
+
@noload-=1
|
425
|
+
ret
|
426
|
+
end
|
427
|
+
def noload?
|
428
|
+
(@noload ||0) > 0
|
429
|
+
end
|
430
|
+
|
431
|
+
private
|
432
|
+
def prefix
|
433
|
+
self.class.prefix
|
434
|
+
end
|
435
|
+
def index_only
|
436
|
+
@index_only ||= self.class.class_eval do @index_only end #ugly
|
437
|
+
end
|
438
|
+
def attributes
|
439
|
+
self.class.attributes
|
440
|
+
end
|
441
|
+
def attr_hash
|
442
|
+
@attr_hash ||= {}
|
443
|
+
attributes.each do |attr_name|
|
444
|
+
val = send attr_name, true
|
445
|
+
@attr_hash[attr_name]= val unless attribute_was(attr_name) == val
|
446
|
+
end
|
447
|
+
@attr_hash
|
448
|
+
end
|
449
|
+
|
450
|
+
def new_id
|
451
|
+
@last_id_key ||= "#{Queris.redis_prefix}#{self.class.superclass.name}:last_id:#{self.class.name}"
|
452
|
+
redis.incr @last_id_key
|
453
|
+
end
|
454
|
+
end
|
455
|
+
end
|