Class: Queris::Model

Inherits:
Object
  • Object
show all
Includes:
Queris, ObjectMixin, QuerisModelMixin
Defined in:
lib/queris/model.rb

Direct Known Subclasses

Profiler, QueryStore

Constant Summary

Constants included from Queris

VERSION

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from QuerisModelMixin

included

Methods included from ObjectMixin

#get_cached_attribute, included

Methods included from Queris

add_redis, all_redises, build_missing_indices!, clear!, clear_cache!, clear_queries!, debug?, digest, disconnect, duplicate_redis_client, from_redis_float, import_lua_script, included, info, load_lua_script, #log_stats_per_request!, #log_stats_per_request?, model, rebuild!, reconnect, redis_prefix, redis_role, redises, register_model, run_script, script, script_hash, #stats, to_redis_float, #track_stats!, #track_stats?

Constructor Details

#initialize(id = nil, arg = {}) ⇒ Model

Returns a new instance of Model.



205
206
207
208
209
210
211
212
# File 'lib/queris/model.rb', line 205

def initialize(id=nil, arg={})
  @attributes = {}
  @attributes_to_save = {}
  @attributes_to_incr = {}
  @attributes_were = {}
  @redis = (arg || {})[:redis]
  set_id id unless id.nil?
end

Instance Attribute Details

#idObject

Returns the value of attribute id.



5
6
7
# File 'lib/queris/model.rb', line 5

def id
  @id
end

#query_scoreObject

Returns the value of attribute query_score.



6
7
8
# File 'lib/queris/model.rb', line 6

def query_score
  @query_score
end

Class Method Details

.attr_val_blockObject



12
13
14
# File 'lib/queris/model.rb', line 12

def self.attr_val_block
  @attr_val_block ||= {}
end

.attribute(*arg) ⇒ Object Also known as: attr

Raises:

  • (ArgumentError)


50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/queris/model.rb', line 50

def attribute(*arg)
  if arg.first
    attr_name = arg.first.to_sym
  end
  if arg.count == 2
    if Hash===arg.last
      opt=arg.last
    else
      raise "Invalid \"attribute\" params" unless arg.last.nil?
    end
  elsif arg.count > 2
    raise "Too many arguments for \"attribute\""
  end
  
  @attributes ||= [] #Class instance var
  raise ArgumentError, "Attribute #{attr_name} already exists in Queris model #{self.name}." if @attributes.member? attr_name
  if block_given?
    bb=Proc.new
    self.attr_val_block[attr_name]=bb
  end
  define_method "#{attr_name}" do |no_attr_load=false|
    binding.pry if @attributes.nil?
    1
    if (val = @attributes[attr_name]).nil? && !@loaded && !no_attr_load && !noload?
      load
      send attr_name, true
    else
      val
    end
  end
  
  define_method "#{attr_name}=" do |val| #setter
    if opt
      type = opt[:type]
      unless val.nil?
        if type == Float
          val=Float(val)
        elsif type == String
          val=val.to_s
        elsif type == Fixnum
          val = val.to_s if Symbol === val
          val=val.to_i
        elsif type == Symbol
          val = val.to_s if Numeric > val.class # first to string, then to symbol.
          val=val.to_sym
        elsif type == :boolean || type == :bool
          if val=="1" || val==1 || val=="true"
            val=true
          elsif val=="0" || val==0 || val=="false"
            val=false
          else
            val=val ? true : false
          end
        elsif type == :flag
          val=val ? true : nil
        elsif type.nil?
          #nothing
        else
          raise "Unknown attribute type #{opt[:type]}"
        end
      end
    end
    if self.class.attr_val_block[attr_name]
      val = self.class.attr_val_block[attr_name].call(val, self)
    end
    if !@loading
      if @attributes_were[attr_name].nil?
        @attributes_were[attr_name] = @attributes[attr_name]
      end
      @attributes_to_save[attr_name]=val
    end
    @attributes[attr_name]=val
  end
  define_method "#{attr_name}_was" do 
    @attributes_were[attr_name]
  end
  define_method "#{attr_name}_was=" do |val|
    @attributes_were[attr_name]=val
  end
  private "#{attr_name}_was="
  attributes << attr_name
end

.attributes(*arg) ⇒ Object

get/setter



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/queris/model.rb', line 32

def attributes(*arg)
  if Hash===arg.last
    attributes = arg[0..-2]
    opt=arg.last
  else
    attributes= arg
  end
  unless attributes.nil?
    attributes.each do |attr|
      attribute attr, opt
      if block_given?
        self.attr_val_block[attr.to_sym]=Proc.new
      end
    end
  end
  @attributes
end

.attrsObject

get/setter



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/queris/model.rb', line 133

def attributes(*arg)
  if Hash===arg.last
    attributes = arg[0..-2]
    opt=arg.last
  else
    attributes= arg
  end
  unless attributes.nil?
    attributes.each do |attr|
      attribute attr, opt
      if block_given?
        self.attr_val_block[attr.to_sym]=Proc.new
      end
    end
  end
  @attributes
end

.expire(seconds = nil) ⇒ Object

get/setter



136
137
138
139
140
141
142
143
# File 'lib/queris/model.rb', line 136

def expire(seconds=nil)
  #note that using expire will not update indices, leading to some serious staleness
  unless seconds.nil?
    @expire = seconds
  else
    @expire
  end
end

.find(id, opt = {}) ⇒ Object Also known as: find_cached



145
146
147
148
# File 'lib/queris/model.rb', line 145

def find(id, opt={})
  got= get id, opt
  got.loaded? ? got : nil
end

.find_allObject

NOT FOR PRODUCTION USE!



162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/queris/model.rb', line 162

def find_all #NOT FOR PRODUCTION USE!
  keys = redis.keys "#{prefix}*"
  objs = []
  keys.map! do |key|
    begin
      found = self.find key[prefix.length..-1]
      objs << found if found
    rescue Exception => e
      nil 
    end
  end
  objs
end

.get(id, opt = nil) ⇒ Object



151
152
153
154
155
156
157
158
159
160
# File 'lib/queris/model.rb', line 151

def get(id, opt=nil)
  ret=new(id)
  opt ||= {}
  if opt[:redis]
    ret.load(nil, redis: opt[:redis])
  else
    ret.load
  end
  ret
end

.keyfObject



27
28
29
# File 'lib/queris/model.rb', line 27

def keyf
  "#{prefix}%s"
end

.prefixObject



24
25
26
# File 'lib/queris/model.rb', line 24

def prefix
  @prefix ||= "#{Queris.redis_prefix}#{self.superclass.name.split('::').last}:#{self.name}:"
end

.redis(redis_client = nil) ⇒ Object



17
18
19
20
21
22
# File 'lib/queris/model.rb', line 17

def redis(redis_client=nil)
  if redis_client.kind_of? Redis
    @redis = redis_client
  end
  @redis || Queris.redis
end

.restore(hash, id) ⇒ Object



176
177
178
# File 'lib/queris/model.rb', line 176

def restore(hash, id)
  new(id).load(hash)
end

Instance Method Details

#as_json(*arg) ⇒ Object



414
415
416
417
418
# File 'lib/queris/model.rb', line 414

def as_json(*arg)
  rest={id: self.id}
  rest[:query_score]= self.query_score if query_score
  @attributes.merge(rest)
end

#attribute_diff(attr) ⇒ Object



292
293
294
# File 'lib/queris/model.rb', line 292

def attribute_diff(attr)
  @attributes_to_incr[attr.to_sym]
end

#changedObject

list of changed attributes



296
297
298
299
300
# File 'lib/queris/model.rb', line 296

def changed
  delta = (@attributes_to_save.keys + @attributes_to_incr.keys)
  delta.uniq!
  delta
end

#changed?Boolean

any unsaved changes?

Returns:

  • (Boolean)


302
303
304
# File 'lib/queris/model.rb', line 302

def changed?
  @attributes_to_save.empty? && @attributes_to_incr.empty?
end

#deleteObject



314
315
316
317
318
319
320
321
322
323
324
# File 'lib/queris/model.rb', line 314

def delete
  noload do
    key = hash_key
    redis.multi do
      redis.del key
      delete_redis_indices if defined? :delete_redis_indices
    end
  end
  @deleted= true
  self
end

#deleted?Boolean

Returns:

  • (Boolean)


310
311
312
# File 'lib/queris/model.rb', line 310

def deleted?
  @deleted && self
end

#hash_key(custom_id = nil) ⇒ Object Also known as: key



402
403
404
405
406
407
# File 'lib/queris/model.rb', line 402

def hash_key(custom_id=nil)
  if custom_id.nil? && id.nil?
    @id = new_id
  end
  @hash_key ||= "#{prefix}#{custom_id || id}"
end

#import(attrs = {}) ⇒ Object



382
383
384
385
386
387
388
# File 'lib/queris/model.rb', line 382

def import(attrs={})
  attrs.each do |attr_name, val|
    send "#{attr_name}=", val
    @attributes_were[attr_name] = val
  end
  self
end

#increment(attr_name, delta_val) ⇒ Object

Raises:

  • (ArgumentError)


280
281
282
283
284
285
286
287
288
289
# File 'lib/queris/model.rb', line 280

def increment(attr_name, delta_val)
  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
  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
  
  @attributes_to_incr[attr_name.to_sym]=delta_val
  unless (val = send(attr_name, true)).nil?
    send "#{attr_name}=", val + delta_val
  end
  self
end

#load(hash = nil, opt = {}) ⇒ Object

Raises:



326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
# File 'lib/queris/model.rb', line 326

def load(hash=nil, opt={})
  raise SchemaError, "Can't load #{self.class.name} with id #{id} -- model was specified index_only, so it was never saved." if index_only
  unless hash
    hash_future, hash_exists = nil, nil
    hash_key
    (opt[:redis] || redis).multi do |r|
      hash_future = r.hgetall hash_key
      hash_exists = r.exists hash_key
    end
    if hash_exists.value
      hash = hash_future.value
    elsif not hash
      return nil
    end
  end
  case hash
  when Array
    attr_name= nil
    hash.each_with_index do |v, i|
      if i % 2 == 0 
        attr_name = v
        next
      else
        raw_load_attr(attr_name, v, !opt[:nil_only])
      end
    end
    @loaded = true
  when Hash
    hash.each do |k, v|
      raw_load_attr(k, v, !opt[:nil_only])
    end
    @loaded = true
  else
    raise Queris::ArgumentError, "Invalid thing to load"
  end
  
  self
end

#load_missingObject

load only missing attributes



365
366
367
# File 'lib/queris/model.rb', line 365

def load_missing #load only missing attributes
  load nil, nil_only: true
end

#loaded?Boolean

Returns:

  • (Boolean)


306
307
308
# File 'lib/queris/model.rb', line 306

def loaded?
  @loaded && self
end

#noloadObject



420
421
422
423
424
425
426
# File 'lib/queris/model.rb', line 420

def noload
  @noload||=0
  @noload+=1
  ret = yield
  @noload-=1
  ret
end

#noload?Boolean

Returns:

  • (Boolean)


427
428
429
# File 'lib/queris/model.rb', line 427

def noload?
  (@noload ||0) > 0
end

#redis(no_fallback = false) ⇒ Object



394
395
396
397
398
399
400
# File 'lib/queris/model.rb', line 394

def redis(no_fallback=false)
  if no_fallback
    @redis || self.class.redis
  else
    @redis || self.class.redis || Queris.redis
  end
end

#redis=(r) ⇒ Object



391
392
393
# File 'lib/queris/model.rb', line 391

def redis=(r)
  @redis=r
end

#saveObject



227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/queris/model.rb', line 227

def save
  key = hash_key #before multi
  noload do
    # to ensure atomicity, we unfortunately need two round trips to redis
    run_callbacks :before_save
    begin
      if @attributes_to_save.length > 0
        attrs_to_save = @attributes_to_save.keys
        bulk_response = redis.pipelined do
          redis.watch key
          redis.hmget key, *attrs_to_save
        end
        current_saved_attr_vals = bulk_response.last
        attrs_to_save.each_with_index do |attr,i| #sync with server
          val=current_saved_attr_vals[i]
          @attributes_were[attr]=val
        end
        
        run_callbacks :during_save
        
        bulk_response = redis.multi do |r|
          unless index_only
            @attributes_to_incr.each do |attr, incr_by_val|
              r.hincrbyfloat key, attr, incr_by_val #redis server >= 2.6
              unless (val = send(attr, true)).nil?
                @attributes_were[attr]=val
              end
            end
            r.mapped_hmset key, @attributes_to_save
            # a little hacky to first set to "", then delete. 
            # meh. will optimize when needed.
            @attributes_to_save.each do |attr, val|
              r.hdel(key, attr) if val.nil?
            end
            expire_sec = self.class.expire
          end

          update_redis_indices if defined? :update_redis_indices
          @attributes_to_save.each {|attr, val| @attributes_were[attr]=val }
          r.expire key, expire_sec unless expire_sec.nil?
          run_callbacks :during_save_multi, r
        end
      end
    end while @attributes_to_save.length > 0 && bulk_response.nil?
    @attributes_to_save.clear
    @attributes_to_incr.clear
    ret= self
    run_callbacks :after_save, redis
    ret
  end
end

#set_id(nid, overwrite = false) ⇒ Object



214
215
216
217
218
219
220
221
222
# File 'lib/queris/model.rb', line 214

def set_id(nid, overwrite=false)
  noload do
    raise Error, "id cannot be a Hash" if Hash === nid
    raise Error, "id cannot be an Array" if Array === nid
    raise Error, "id already exists and is #{self.id}" unless overwrite || self.id.nil?
  end
  @id= nid
  self
end

#to_json(*arg) ⇒ Object



410
411
412
# File 'lib/queris/model.rb', line 410

def to_json(*arg)
  as_json.to_json(*arg)
end

#while_loadingObject



193
194
195
196
197
198
# File 'lib/queris/model.rb', line 193

def while_loading
  loading_was=@loading
  @loading=true
  yield
  @loading=loading_was
end