queris 0.8.1
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 +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
|