class RedisOrm::Base

Attributes

persisted[RW]

Public Class Methods

after_create(callback) click to toggle source
    # File lib/redis_orm/redis_orm.rb
351 def after_create(callback)        
352   @@callbacks[model_name][:after_create] << callback
353 end
after_destroy(callback) click to toggle source
    # File lib/redis_orm/redis_orm.rb
359 def after_destroy(callback)
360   @@callbacks[model_name][:after_destroy] << callback
361 end
after_save(callback) click to toggle source
    # File lib/redis_orm/redis_orm.rb
343 def after_save(callback)        
344   @@callbacks[model_name][:after_save] << callback
345 end
all(options = {}) click to toggle source

TODO refactor this messy function

    # File lib/redis_orm/redis_orm.rb
198 def all(options = {})
199   limit = if options[:limit] && options[:offset]
200     [options[:offset].to_i, options[:limit].to_i]
201   elsif options[:limit]
202     [0, options[:limit].to_i]
203   else
204     [0, -1]
205   end
206   
207   order_max_limit = Time.now.to_f
208   ids_key = "#{model_name}:ids"
209   index = nil
210 
211   prepared_index = if !options[:conditions].blank? && options[:conditions].is_a?(Hash)
212     properties = options[:conditions].collect{|key, value| key}
213     
214     # if some condition includes object => get only the id of this object
215     conds = options[:conditions].inject({}) do |sum, item|
216       key, value = item
217       if value.respond_to?(:model_name)
218         sum.merge!({key => value.id})
219       else
220         sum.merge!({key => value})
221       end
222     end
223 
224     index = find_indices(properties, {first: true})
225     
226     raise NotIndexFound if !index
227 
228     construct_prepared_index(index, conds)
229   else
230     if options[:order] && options[:order].is_a?(Array)
231       model_name
232     else
233       ids_key
234     end
235   end
236 
237   order_by_property_is_string = false
238   
239   # if not array => created_at native order (in which ids were pushed to "#{model_name}:ids" set by default)
240   direction = if !options[:order].blank?
241     property = {}
242     dir = if options[:order].is_a?(Array)
243       property = @@properties[model_name].detect{|prop| prop[:name].to_s == options[:order].first.to_s}
244       # for String values max limit for search key could be 1.0, but for Numeric values there's actually no limit
245       order_max_limit = 100_000_000_000
246       ids_key = "#{prepared_index}:#{options[:order].first}_ids"
247       options[:order].size == 2 ? options[:order].last : 'asc'
248     else
249       property = @@properties[model_name].detect{|prop| prop[:name].to_s == options[:order].to_s}
250       ids_key = prepared_index
251       options[:order]
252     end
253     if property && property[:class].eql?("String") && property[:options][:sortable]
254       order_by_property_is_string = true
255     end
256     dir
257   else
258     ids_key = prepared_index
259     'asc'
260   end
261   
262   if order_by_property_is_string
263     if direction.to_s == 'desc'
264       ids_length = $redis.llen(ids_key)
265       limit = if options[:offset] && options[:limit]
266         [(ids_length - options[:offset].to_i - options[:limit].to_i), (ids_length - options[:offset].to_i - 1)]
267       elsif options[:limit]
268         [ids_length - options[:limit].to_i, ids_length]
269       elsif options[:offset]
270         [0, (ids_length - options[:offset].to_i - 1)]
271       else
272         [0, -1]
273       end
274       $redis.lrange(ids_key, *limit).reverse.compact.collect{|id| find(id.split(':').last)}
275     else
276       limit = if options[:offset] && options[:limit]
277         [options[:offset].to_i, (options[:offset].to_i + options[:limit].to_i)]
278       elsif options[:limit]
279         [0, options[:limit].to_i - 1]
280       elsif options[:offset]
281         [options[:offset].to_i, -1]
282       else
283         [0, -1]
284       end
285       $redis.lrange(ids_key, *limit).compact.collect{|id| find(id.split(':').last)}
286     end
287   else
288     if index && index[:options][:unique]
289       id = $redis.get prepared_index
290       model_name.to_s.camelize.constantize.find(id)
291     else
292       if direction.to_s == 'desc'
293         $redis.zrevrangebyscore(ids_key, order_max_limit, 0, :limit => limit).compact.collect{|id| find(id)}
294       else
295         $redis.zrangebyscore(ids_key, 0, order_max_limit, :limit => limit).compact.collect{|id| find(id)}
296       end
297     end
298   end
299 end
before_create(callback) click to toggle source
    # File lib/redis_orm/redis_orm.rb
355 def before_create(callback)
356   @@callbacks[model_name][:before_create] << callback
357 end
before_destroy(callback) click to toggle source
    # File lib/redis_orm/redis_orm.rb
363 def before_destroy(callback)        
364   @@callbacks[model_name][:before_destroy] << callback
365 end
before_save(callback) click to toggle source
    # File lib/redis_orm/redis_orm.rb
347 def before_save(callback)        
348   @@callbacks[model_name][:before_save] << callback
349 end
construct_prepared_index(index, conditions_hash) click to toggle source
    # File lib/redis_orm/redis_orm.rb
178 def construct_prepared_index(index, conditions_hash)
179   prepared_index = model_name.to_s
180  
181   # in order not to depend on order of keys in *:conditions* hash we rather interate over the index itself and find corresponding values in *:conditions* hash
182   if index[:name].is_a?(Array)
183     index[:name].each do |key|
184       # raise if User.find_by_firstname_and_castname => there's no *castname* in User's properties
185       raise ArgumentsMismatch if !@@properties[model_name].detect{|p| p[:name] == key.to_sym}
186       prepared_index += ":#{key}:#{conditions_hash[key]}"
187     end
188   else
189     prepared_index += ":#{index[:name]}:#{conditions_hash[index[:name]]}"
190   end
191         
192   prepared_index.downcase! if index[:options][:case_insensitive]
193   
194   prepared_index
195 end
count() click to toggle source
    # File lib/redis_orm/redis_orm.rb
142 def count
143   $redis.zcard("#{model_name}:ids").to_i
144 end
create(options = {}) click to toggle source
    # File lib/redis_orm/redis_orm.rb
367 def create(options = {})
368   obj = new(options, nil, false)
369   obj.save
370 
371   # make possible binding related models while creating class instance
372   options.each do |k, v|
373     if @@associations[model_name].detect{|h| h[:foreign_model] == k || h[:options][:as] == k}
374       obj.send("#{k}=", v)
375     end
376   end
377   
378   $redis.expire(obj.__redis_record_key, options[:expire_in].to_i) if !options[:expire_in].blank?
379 
380   obj
381 end
Also aliased as: create!
create!(options = {})
Alias for: create
descendants() click to toggle source
   # File lib/redis_orm/redis_orm.rb
68 def descendants
69   @@descendants
70 end
expire(seconds, options = {}) click to toggle source
    # File lib/redis_orm/redis_orm.rb
133 def expire(seconds, options = {})
134   @@expire[model_name] = {seconds: seconds, options: options}
135 end
find(*args) click to toggle source
    # File lib/redis_orm/redis_orm.rb
301 def find(*args)
302   if args.first.is_a?(Array)
303     return [] if args.first.empty?
304     args.first.inject([]) do |array, id|
305       record = $redis.hgetall "#{model_name}:#{id}"
306       if record && !record.empty?
307         array << new(record, id, true)
308       end
309     end
310   else
311     return nil if args.empty? || args.first.nil?
312     case first = args.shift
313       when :all
314         options = args.last
315         options = {} if !options.is_a?(Hash)
316         all(options)
317       when :first
318         options = args.last
319         options = {} if !options.is_a?(Hash)
320         all(options.merge({:limit => 1}))[0]
321       when :last
322         options = args.last
323         options = {} if !options.is_a?(Hash)
324         reversed = options[:order] == 'desc' ? 'asc' : 'desc'
325         all(options.merge({:limit => 1, :order => reversed}))[0]
326       else
327         id = first
328         record = $redis.hgetall "#{model_name}:#{id}"
329         record && record.empty? ? nil : new(record, id, true)
330     end
331   end        
332 end
find!(*args) click to toggle source
    # File lib/redis_orm/redis_orm.rb
334 def find!(*args)
335   result = find(*args)
336   if result.nil?
337     raise RecordNotFound
338   else
339     result
340   end
341 end
find_indices(properties, options = {}) click to toggle source
    # File lib/redis_orm/redis_orm.rb
164 def find_indices(properties, options = {})
165   properties.map!{|p| p.to_sym}
166   method = options[:first] ? :detect : :select
167   
168   @@indices[model_name].send(method) do |models_index|
169     if models_index[:name].is_a?(Array) && models_index[:name].size == properties.size
170       # check the elements not taking into account their order
171       (models_index[:name] & properties).size == properties.size
172     elsif !models_index[:name].is_a?(Array) && properties.size == 1
173       models_index[:name] == properties[0]
174     end
175   end
176 end
first(options = {}) click to toggle source
    # File lib/redis_orm/redis_orm.rb
146 def first(options = {})
147   if options.empty?
148     id = $redis.zrangebyscore("#{model_name}:ids", 0, Time.now.to_f, :limit => [0, 1])
149     id.empty? ? nil : find(id[0])
150   else
151     find(:first, options)
152   end
153 end
index(name, options = {}) click to toggle source

options currently supports

*unique* Boolean
*case_insensitive* Boolean
   # File lib/redis_orm/redis_orm.rb
75 def index(name, options = {})
76   @@indices[model_name] << {:name => name, :options => options}
77 end
inherited(from) click to toggle source
   # File lib/redis_orm/redis_orm.rb
60 def inherited(from)
61   [:after_save, :before_save, :after_create, :before_create, :after_destroy, :before_destroy].each do |callback_name|
62     @@callbacks[from.model_name][callback_name] = []
63   end
64   
65   @@descendants << from
66 end
last(options = {}) click to toggle source
    # File lib/redis_orm/redis_orm.rb
155 def last(options = {})
156   if options.empty?
157     id = $redis.zrevrangebyscore("#{model_name}:ids", Time.now.to_f, 0, :limit => [0, 1])
158     id.empty? ? nil : find(id[0])
159   else
160     find(:last, options)
161   end
162 end
method_missing(method_name, *args, &block) click to toggle source

dynamic finders

    # File lib/redis_orm/redis_orm.rb
386 def method_missing(method_name, *args, &block)
387   if method_name =~ /^find_(all_)?by_(\w*)/
388     
389     index = if $2
390       properties = $2.split('_and_')
391       raise ArgumentsMismatch if properties.size != args.size
392       properties_hash = {}
393       properties.each_with_index do |prop, i| 
394         properties_hash.merge!({prop.to_sym => args[i]})
395       end
396       find_indices(properties, :first => true)
397     end
398 
399     raise NotIndexFound if !index
400     
401     prepared_index = construct_prepared_index(index, properties_hash)
402 
403     if method_name =~ /^find_by_(\w*)/
404       id = if index[:options][:unique]            
405         $redis.get prepared_index
406       else
407         $redis.zrangebyscore(prepared_index, 0, Time.now.to_f, :limit => [0, 1])[0]
408       end
409       model_name.to_s.camelize.constantize.find(id)
410     elsif method_name =~ /^find_all_by_(\w*)/
411       records = []          
412 
413       if index[:options][:unique]            
414         id = $redis.get prepared_index
415         records << model_name.to_s.camelize.constantize.find(id)
416       else
417         ids = $redis.zrangebyscore(prepared_index, 0, Time.now.to_f)
418         records += model_name.to_s.camelize.constantize.find(ids)
419       end          
420 
421       records
422     else
423       nil
424     end
425   end
426 end
new(attributes = {}, id = nil, persisted = false) click to toggle source
    # File lib/redis_orm/redis_orm.rb
465 def initialize(attributes = {}, id = nil, persisted = false)
466   @persisted = persisted
467 
468   # if this model uses uuid then id is a string otherwise it should be casted to Integer class
469   id = @@use_uuid_as_id[model_name] ? id : id.to_i
470 
471   instance_variable_set(:"@id", id) if id
472 
473   # when object is created with empty attribute set @#{prop[:name]}_changes array properly
474   @@properties[model_name].each do |prop|
475     if prop[:options][:default]
476       instance_variable_set :"@#{prop[:name]}_changes", [prop[:options][:default]]
477     else
478       instance_variable_set :"@#{prop[:name]}_changes", []
479     end
480   end
481 
482   # cast all attributes' keys to symbols
483   attributes = attributes.inject({}){|sum, el| sum.merge({el[0].to_sym => el[1]})} if attributes.is_a?(Hash)
484 
485   # get all names of properties to assign only those attributes from attributes hash whose key are in prop_names
486   # we're not using *self.respond_to?("#{key}=".to_sym)* since *belongs_to* and other assocs could create their own methods
487   # with *key=* name, that in turn will mess up indices
488   if attributes.is_a?(Hash) && !attributes.empty?        
489     @@properties[model_name].each do |property|
490       if !(value = attributes[property[:name]]).nil? # check for nil because we want to pass falses too (and value could be 'false')
491         value = Marshal.load(value) if ["Array", "Hash"].include?(property[:class]) && value.is_a?(String)
492         self.send("#{property[:name]}=".to_sym, value)
493       end
494     end
495   end
496   self
497 end
property(property_name, class_name, options = {}) click to toggle source
    # File lib/redis_orm/redis_orm.rb
 79 def property(property_name, class_name, options = {})
 80   @@properties[model_name] << {:name => property_name, :class => class_name.to_s, :options => options}
 81 
 82   send(:define_method, property_name) do
 83     value = instance_variable_get(:"@#{property_name}")
 84 
 85     return nil if value.nil? # we must return nil here so :default option will work when saving, otherwise it'll return "" or 0 or 0.0
 86     if /DateTime|Time/ =~ class_name.to_s            
 87       # we're using to_datetime here because to_time doesn't manage timezone correctly
 88       value.to_s.to_datetime rescue nil
 89     elsif Integer == class_name
 90       value.to_i
 91     elsif Float == class_name
 92       value.to_f
 93     elsif RedisOrm::Boolean == class_name
 94       ((value == "false" || value == false) ? false : true)
 95     else
 96       value
 97     end
 98   end
 99     
100   send(:define_method, "#{property_name}=".to_sym) do |value|
101     if instance_variable_get(:"@#{property_name}_changes") && !instance_variable_get(:"@#{property_name}_changes").empty?
102       initial_value = instance_variable_get(:"@#{property_name}_changes")[0]
103       instance_variable_set(:"@#{property_name}_changes", [initial_value, value])
104     elsif instance_variable_get(:"@#{property_name}")
105       instance_variable_set(:"@#{property_name}_changes", [self.send(property_name), value])
106     else
107       instance_variable_set(:"@#{property_name}_changes", [value])
108     end
109     instance_variable_set(:"@#{property_name}", value)
110   end
111   
112   send(:define_method, "#{property_name}_changes".to_sym) do
113     instance_variable_get(:"@#{property_name}_changes")
114   end
115   
116   send(:define_method, "#{property_name}_changed?".to_sym) do
117     instance_variable_get(:"@#{property_name}_changes").size > 1
118   end
119 end
timestamps() click to toggle source
    # File lib/redis_orm/redis_orm.rb
121 def timestamps
122   #if !@@properties[model_name].detect{|p| p[:name] == :created_at && p[:class] == "Time"}
123   if !instance_methods.include?(:created_at) && !instance_methods.include?(:"created_at=")
124     property :created_at, Time
125   end
126   
127   #if !@@properties[model_name].detect{|p| p[:name] == :modified_at && p[:class] == "Time"}
128   if !instance_methods.include?(:modified_at) && !instance_methods.include?(:"modified_at=")
129     property :modified_at, Time
130   end
131 end
use_uuid_as_id() click to toggle source
    # File lib/redis_orm/redis_orm.rb
137 def use_uuid_as_id
138   @@use_uuid_as_id[model_name] = true
139   @@uuid = UUID.new
140 end

Public Instance Methods

==(other) click to toggle source
    # File lib/redis_orm/redis_orm.rb
517 def ==(other)
518   raise "this object could be comparable only with object of the same class" if other.class != self.class
519   same = true
520   @@properties[model_name].each do |prop|
521     self_var = instance_variable_get(:"@#{prop[:name]}")
522     same = false if other.send(prop[:name]).to_s != self_var.to_s
523   end
524   same = false if self.id != other.id
525   same
526 end
__redis_record_key() click to toggle source
    # File lib/redis_orm/redis_orm.rb
435 def __redis_record_key
436   "#{model_name}:#{id}"
437 end
destroy() click to toggle source
    # File lib/redis_orm/redis_orm.rb
631 def destroy
632   @@callbacks[model_name][:before_destroy].each do |callback|
633     self.send(callback)
634   end
635 
636   @@properties[model_name].each do |prop|
637     property_value = instance_variable_get(:"@#{prop[:name]}").to_s
638     $redis.hdel("#{model_name}:#{@id}", prop[:name].to_s)
639     
640     if prop[:options][:sortable]
641       if prop[:class].eql?("String")
642         $redis.lrem "#{model_name}:#{prop[:name]}_ids", 1, "#{property_value}:#{@id}"
643       else
644         $redis.zrem "#{model_name}:#{prop[:name]}_ids", @id
645       end
646     end
647   end
648 
649   $redis.zrem "#{model_name}:ids", @id
650 
651   # also we need to delete *indices* of associated records
652   if !@@associations[model_name].empty?
653     @@associations[model_name].each do |assoc|        
654       if :belongs_to == assoc[:type]
655         # if assoc has :as option
656         foreign_model_name = assoc[:options][:as] ? assoc[:options][:as].to_sym : assoc[:foreign_model].to_sym
657         
658         if !self.send(foreign_model_name).nil?
659           @@indices[model_name].each do |index|
660             keys_to_delete = if index[:name].is_a?(Array)
661               full_index = index[:name].inject([]){|sum, index_part| sum << index_part}.join(':')
662               $redis.keys "#{foreign_model_name}:#{self.send(foreign_model_name).id}:#{model_name.to_s.pluralize}:#{full_index}:*"
663             else
664               ["#{foreign_model_name}:#{self.send(foreign_model_name).id}:#{model_name.to_s.pluralize}:#{index[:name]}:#{self.send(index[:name])}"]
665             end
666             keys_to_delete.each do |key| 
667               index[:options][:unique] ? $redis.del(key) : $redis.zrem(key, @id)
668             end
669           end
670         end
671       end
672     end
673   end
674   
675   # also we need to delete *links* to associated records
676   if !@@associations[model_name].empty?
677     @@associations[model_name].each do |assoc|
678 
679       foreign_model  = ""
680       records = []
681 
682       case assoc[:type]
683         when :belongs_to
684           foreign_model = assoc[:foreign_model].to_s
685           foreign_model_name = assoc[:options][:as] ? assoc[:options][:as] : assoc[:foreign_model]
686           if assoc[:options][:polymorphic]
687             records << self.send(foreign_model_name)
688             # get real foreign_model's name in order to delete backlinks properly
689             foreign_model = $redis.get("#{model_name}:#{id}:#{foreign_model_name}_type")
690             $redis.del("#{model_name}:#{id}:#{foreign_model_name}_type")
691             $redis.del("#{model_name}:#{id}:#{foreign_model_name}_id")
692           else
693             records << self.send(foreign_model_name)
694             $redis.del "#{model_name}:#{@id}:#{assoc[:foreign_model]}"
695           end
696         when :has_one
697           foreign_model = assoc[:foreign_model].to_s
698           foreign_model_name = assoc[:options][:as] ? assoc[:options][:as] : assoc[:foreign_model]
699           records << self.send(foreign_model_name)
700 
701           $redis.del "#{model_name}:#{@id}:#{assoc[:foreign_model]}"
702         when :has_many
703           foreign_model = assoc[:foreign_models].to_s.singularize
704           foreign_models_name = assoc[:options][:as] ? assoc[:options][:as] : assoc[:foreign_models]
705           records += self.send(foreign_models_name)
706 
707           # delete all members
708           $redis.zremrangebyscore "#{model_name}:#{@id}:#{assoc[:foreign_models]}", 0, Time.now.to_f
709       end
710 
711       # check whether foreign_model also has an assoc to the destroying record
712       # and remove an id of destroing record from each of associated sets
713       if !records.compact.empty?
714         records.compact.each do |record|
715           # we make 3 different checks rather then 1 with elsif to ensure that all associations will be processed
716           # it's covered in test/option_test in "should delete link to associated record when record was deleted" scenario
717           # for if class Album; has_one :photo, :as => :front_photo; has_many :photos; end
718           # end some photo from the album will be deleted w/o these checks only first has_one will be triggered
719           if @@associations[foreign_model].detect{|h| h[:type] == :belongs_to && h[:foreign_model] == model_name.to_sym}
720             $redis.del "#{foreign_model}:#{record.id}:#{model_name}"
721           end
722 
723           if @@associations[foreign_model].detect{|h| h[:type] == :has_one && h[:foreign_model] == model_name.to_sym}
724             $redis.del "#{foreign_model}:#{record.id}:#{model_name}"
725           end
726 
727           if @@associations[foreign_model].detect{|h| h[:type] == :has_many && h[:foreign_models] == model_name.pluralize.to_sym}
728             $redis.zrem "#{foreign_model}:#{record.id}:#{model_name.pluralize}", @id
729           end
730         end
731       end
732 
733       if assoc[:options][:dependent] == :destroy
734         if !records.compact.empty?
735           records.compact.each do |r|
736             r.destroy
737           end
738         end
739       end
740     end
741   end
742 
743   # remove all associated indices
744   @@indices[model_name].each do |index|
745     prepared_index = _construct_prepared_index(index) # instance method not class one!
746 
747     if index[:options][:unique]
748       $redis.del(prepared_index)
749     else
750       $redis.zremrangebyscore(prepared_index, 0, Time.now.to_f)
751     end
752   end
753 
754   @@callbacks[model_name][:after_destroy].each do |callback|
755     self.send(callback)
756   end
757 
758   true # if there were no errors just return true, so *if* conditions would work
759 end
find_position_to_insert(sortable_key, value) click to toggle source
    # File lib/redis_orm/redis_orm.rb
576 def find_position_to_insert(sortable_key, value)
577   end_index = $redis.llen(sortable_key)
578 
579   return 0 if end_index == 0
580   
581   start_index = 0
582   pivot_index = end_index / 2
583 
584   start_el = $redis.lindex(sortable_key, start_index)
585   end_el   = $redis.lindex(sortable_key, end_index - 1)
586   pivot_el = $redis.lindex(sortable_key, pivot_index)
587 
588   while start_index != end_index
589     # aa..ab..ac..bd <- ad
590     if start_el.split(':').first > value # Michael > Abe
591       return 0
592     elsif end_el.split(':').first < value # Abe < Todd
593       return end_el
594     elsif start_el.split(':').first == value # Abe == Abe
595       return start_el
596     elsif pivot_el.split(':').first == value # Todd == Todd
597       return pivot_el
598     elsif end_el.split(':').first == value
599       return end_el
600     elsif (start_el.split(':').first < value) && (pivot_el.split(':').first > value)
601       start_index = start_index
602       prev_pivot_index = pivot_index
603       pivot_index = start_index + ((end_index - pivot_index) / 2)
604       end_index   = prev_pivot_index
605     elsif (pivot_el.split(':').first < value) && (end_el.split(':').first > value) # M < V && Y > V
606       start_index = pivot_index
607       pivot_index = pivot_index + ((end_index - pivot_index) / 2)
608       end_index   = end_index          
609     end
610     start_el = $redis.lindex(sortable_key, start_index)
611     end_el   = $redis.lindex(sortable_key, end_index - 1)
612     pivot_el = $redis.lindex(sortable_key, pivot_index)
613   end
614   start_el
615 end
get_associations() click to toggle source

is called from RedisOrm::Associations::HasMany to save backlinks to saved records

    # File lib/redis_orm/redis_orm.rb
456 def get_associations
457   @@associations[self.model_name]
458 end
get_indices() click to toggle source

is called from RedisOrm::Associations::HasMany to correctly save indices for associated records

    # File lib/redis_orm/redis_orm.rb
461 def get_indices
462   @@indices[self.model_name]
463 end
get_next_id() click to toggle source
    # File lib/redis_orm/redis_orm.rb
532 def get_next_id
533   if @@use_uuid_as_id[model_name]
534     @@uuid.generate(:compact)
535   else
536     $redis.incr("#{model_name}:id")
537   end
538 end
id() click to toggle source
    # File lib/redis_orm/redis_orm.rb
499 def id
500   @id
501 end
Also aliased as: to_key
persisted?() click to toggle source
    # File lib/redis_orm/redis_orm.rb
528 def persisted?
529   @persisted
530 end
save() click to toggle source
    # File lib/redis_orm/redis_orm.rb
540 def save
541   return false if !valid?
542 
543   _check_mismatched_types_for_values
544   
545   # store here initial persisted flag so we could invoke :after_create callbacks in the end of *save* function
546   was_persisted = persisted?
547 
548   if persisted? # then there might be old indices
549     _check_indices_for_persisted # remove old indices if needed
550   else # !persisted?
551     @@callbacks[model_name][:before_create].each{ |callback| self.send(callback) }
552  
553     @id = get_next_id
554     $redis.zadd "#{model_name}:ids", Time.now.to_f, @id
555     @persisted = true
556     self.created_at = Time.now if respond_to? :created_at
557   end
558 
559   @@callbacks[model_name][:before_save].each{ |callback| self.send(callback) }
560 
561   # automatically update *modified_at* property if it was defined
562   self.modified_at = Time.now if respond_to? :modified_at
563 
564   _save_to_redis # main work done here
565   _save_new_indices
566 
567   @@callbacks[model_name][:after_save].each{ |callback| self.send(callback) }
568 
569   if ! was_persisted
570     @@callbacks[model_name][:after_create].each{ |callback| self.send(callback) }
571   end
572 
573   true # if there were no errors just return true, so *if obj.save* conditions would work
574 end
set_expire_on_reference_key(key) click to toggle source
    # File lib/redis_orm/redis_orm.rb
439 def set_expire_on_reference_key(key)
440   class_expire = @@expire[model_name]
441 
442   # if class method *expire* was invoked and number of seconds was specified then set expiry date on the HSET record key
443   if class_expire[:seconds]
444     set_expire = true
445 
446     if class_expire[:options][:if] && class_expire[:options][:if].class == Proc
447       # *self* here refers to the instance of class which has_one association
448       set_expire = class_expire[:options][:if][self]  # invoking specified *:if* Proc with current record as *self*
449     end
450 
451     $redis.expire(key, class_expire[:seconds].to_i) if set_expire
452   end
453 end
to_a() click to toggle source

could be invoked from has_many module (<< method)

    # File lib/redis_orm/redis_orm.rb
431 def to_a
432   [self]
433 end
to_key()
Alias for: id
to_s() click to toggle source
    # File lib/redis_orm/redis_orm.rb
505 def to_s
506   inspected = "<#{model_name.capitalize} id: #{@id}, "
507   inspected += @@properties[model_name].inject([]) do |sum, prop|
508     property_value = instance_variable_get(:"@#{prop[:name]}")
509     property_value = '"' + property_value.to_s + '"' if prop[:class].eql?("String")
510     property_value = 'nil' if property_value.nil?
511     sum << "#{prop[:name]}: " + property_value.to_s
512   end.join(', ')
513   inspected += ">"
514   inspected
515 end
update_attribute(attribute_name, attribute_value) click to toggle source
    # File lib/redis_orm/redis_orm.rb
626 def update_attribute(attribute_name, attribute_value)
627   self.send("#{attribute_name}=".to_sym, attribute_value) if self.respond_to?("#{attribute_name}=".to_sym)
628   save
629 end
update_attributes(attributes) click to toggle source
    # File lib/redis_orm/redis_orm.rb
617 def update_attributes(attributes)
618   if attributes.is_a?(Hash)
619     attributes.each do |key, value|
620       self.send("#{key}=".to_sym, value) if self.respond_to?("#{key}=".to_sym)
621     end
622   end
623   save
624 end

Protected Instance Methods

_check_indices_for_persisted() click to toggle source
    # File lib/redis_orm/redis_orm.rb
787 def _check_indices_for_persisted
788   # check whether there's old indices exists and if yes - delete them
789   @@properties[model_name].each do |prop|
790     # if there were no changes for current property skip it (indices remains the same)
791     next if ! self.send(:"#{prop[:name]}_changed?")
792     
793     prev_prop_value = instance_variable_get(:"@#{prop[:name]}_changes").first
794     prop_value = instance_variable_get(:"@#{prop[:name]}")
795     # TODO DRY in destroy also
796     if prop[:options][:sortable]
797       if prop[:class].eql?("String")
798         $redis.lrem "#{model_name}:#{prop[:name]}_ids", 1, "#{prev_prop_value}:#{@id}"
799         # remove id from every indexed property
800         @@indices[model_name].each do |index|
801           $redis.lrem "#{_construct_prepared_index(index)}:#{prop[:name]}_ids", 1, "#{prop_value}:#{@id}"
802         end
803       else
804         $redis.zrem "#{model_name}:#{prop[:name]}_ids", @id
805         # remove id from every indexed property
806         @@indices[model_name].each do |index|
807           $redis.zrem "#{_construct_prepared_index(index)}:#{prop[:name]}_ids", @id
808         end
809       end
810     end
811 
812     indices = @@indices[model_name].inject([]) do |sum, models_index|
813       if models_index[:name].is_a?(Array)
814         if models_index[:name].include?(prop[:name])
815           sum << models_index
816         else
817           sum
818         end
819       else
820         if models_index[:name] == prop[:name]
821           sum << models_index
822         else
823           sum
824         end
825       end
826     end
827 
828     if !indices.empty?
829       indices.each do |index|
830         if index[:name].is_a?(Array)
831           keys_to_delete = if index[:name].index(prop) == 0
832             $redis.keys "#{model_name}:#{prop[:name]}#{prev_prop_value}*"
833           else
834             $redis.keys "#{model_name}:*#{prop[:name]}:#{prev_prop_value}*"
835           end
836 
837           keys_to_delete.each{|key| $redis.del(key)}
838         else
839           key_to_delete = "#{model_name}:#{prop[:name]}:#{prev_prop_value}"
840           $redis.del key_to_delete
841         end
842 
843         # also we need to delete associated records *indices*
844         if !@@associations[model_name].empty?
845           @@associations[model_name].each do |assoc|
846             if :belongs_to == assoc[:type]
847               # if association has :as option use it, otherwise use standard :foreign_model
848               foreign_model_name = assoc[:options][:as] ? assoc[:options][:as].to_sym : assoc[:foreign_model].to_sym
849               if !self.send(foreign_model_name).nil?
850                 if index[:name].is_a?(Array)
851                   keys_to_delete = if index[:name].index(prop) == 0
852                     $redis.keys "#{assoc[:foreign_model]}:#{self.send(assoc[:foreign_model]).id}:#{model_name.to_s.pluralize}:#{prop[:name]}#{prev_prop_value}*"
853                   else
854                     $redis.keys "#{assoc[:foreign_model]}:#{self.send(assoc[:foreign_model]).id}:#{model_name.to_s.pluralize}:*#{prop[:name]}:#{prev_prop_value}*"
855                   end
856 
857                   keys_to_delete.each{|key| $redis.del(key)}
858                 else
859                   beginning_of_the_key = "#{assoc[:foreign_model]}:#{self.send(assoc[:foreign_model]).id}:#{model_name.to_s.pluralize}:#{prop[:name]}:"
860 
861                   $redis.del(beginning_of_the_key + prev_prop_value.to_s)
862 
863                   index[:options][:unique] ? $redis.set((beginning_of_the_key + prop_value.to_s), @id) : $redis.zadd((beginning_of_the_key + prop_value.to_s), Time.now.to_f, @id)
864                 end
865               end
866             end
867           end
868         end # deleting associated records *indices*
869       end
870     end
871   end
872 end
_check_mismatched_types_for_values() click to toggle source
    # File lib/redis_orm/redis_orm.rb
776 def _check_mismatched_types_for_values
777   # an exception should be raised before all saving procedures if wrong value type is specified (especcially true for Arrays and Hashes)
778   @@properties[model_name].each do |prop|
779     prop_value = self.send(prop[:name].to_sym)
780     
781     if prop_value && prop[:class] != prop_value.class.to_s && ['Array', 'Hash'].include?(prop[:class].to_s)
782       raise TypeMismatchError 
783     end
784   end
785 end
_construct_prepared_index(index) click to toggle source
    # File lib/redis_orm/redis_orm.rb
762 def _construct_prepared_index(index)
763   prepared_index = if index[:name].is_a?(Array) # TODO sort alphabetically
764     index[:name].inject([model_name]) do |sum, index_part|
765       sum += [index_part, self.instance_variable_get(:"@#{index_part}")]
766     end.join(':')          
767   else
768     [model_name, index[:name], self.instance_variable_get(:"@#{index[:name]}")].join(':')
769   end
770   
771   prepared_index.downcase! if index[:options][:case_insensitive]
772   
773   prepared_index
774 end
_save_new_indices() click to toggle source
    # File lib/redis_orm/redis_orm.rb
951 def _save_new_indices
952   # save new indices (not *reference* onces (for example not these *belongs_to :note, :index => true*)) in order to sort by finders
953   # city:name:Chicago => 1
954   @@indices[model_name].reject{|index| index[:options][:reference]}.each do |index|
955     prepared_index = _construct_prepared_index(index) # instance method not class one!
956 
957     if index[:options][:unique]
958       $redis.set(prepared_index, @id)
959     else
960       $redis.zadd(prepared_index, Time.now.to_f, @id)
961     end
962   end
963 end
_save_to_redis() click to toggle source
    # File lib/redis_orm/redis_orm.rb
874 def _save_to_redis
875   @@properties[model_name].each do |prop|
876     prop_value = self.send(prop[:name].to_sym)
877     
878     if prop_value.nil? && !prop[:options][:default].nil?
879       prop_value = prop[:options][:default]
880 
881       # cast prop_value to proper class if they are not in it
882       # for example 'property :wage, Float, :sortable => true, :default => 20_000' turn 20_000 to 20_000.0
883       if prop[:class] != prop_value.class.to_s
884         prop_value = case prop[:class]
885                      when 'Time'
886                        begin
887                          value.to_s.to_time(:local)
888                        rescue ArgumentError => e
889                          nil
890                        end
891                      when 'Integer'
892                        prop_value.to_i
893                      when 'Float'
894                        prop_value.to_f
895                      when 'RedisOrm::Boolean'
896                        (prop_value == "false" || prop_value == false) ? false : true
897                      end
898       end
899 
900       # set instance variable in order to properly save indexes here
901       self.instance_variable_set(:"@#{prop[:name]}", prop_value)
902       instance_variable_set :"@#{prop[:name]}_changes", [prop_value]
903     end
904 
905     # serialize array- and hash-type properties
906     if ['Array', 'Hash'].include?(prop[:class]) && !prop_value.is_a?(String)
907       prop_value = Marshal.dump(prop_value)
908     end
909 
910     #TODO put out of loop
911     $redis.hset(__redis_record_key, prop[:name].to_s, prop_value)
912 
913     set_expire_on_reference_key(__redis_record_key)
914     
915     # reducing @#{prop[:name]}_changes array to the last value
916     prop_changes = instance_variable_get :"@#{prop[:name]}_changes"
917 
918     if prop_changes && prop_changes.size > 2
919       instance_variable_set :"@#{prop[:name]}_changes", [prop_changes.last]
920     end
921     
922     # if some property need to be sortable add id of the record to the appropriate sorted set
923     if prop[:options][:sortable]
924       property_value = instance_variable_get(:"@#{prop[:name]}").to_s
925       if prop[:class].eql?("String")
926         sortable_key = "#{model_name}:#{prop[:name]}_ids"
927         el_or_position_to_insert = find_position_to_insert(sortable_key, property_value)
928         el_or_position_to_insert == 0 ? $redis.lpush(sortable_key, "#{property_value}:#{@id}") : $redis.linsert(sortable_key, "AFTER", el_or_position_to_insert, "#{property_value}:#{@id}")
929         # add to every indexed property
930         @@indices[model_name].each do |index|
931           sortable_key = "#{_construct_prepared_index(index)}:#{prop[:name]}_ids"
932           el_or_position_to_insert == 0 ? $redis.lpush(sortable_key, "#{property_value}:#{@id}") : $redis.linsert(sortable_key, "AFTER", el_or_position_to_insert, "#{property_value}:#{@id}")
933         end
934       else
935         score = case prop[:class]
936           when "Integer"; property_value.to_f
937           when "Float"; property_value.to_f
938           when "RedisOrm::Boolean"; (property_value == true ? 1.0 : 0.0)
939           when "Time"; property_value.to_f
940         end
941         $redis.zadd "#{model_name}:#{prop[:name]}_ids", score, @id
942         # add to every indexed property
943         @@indices[model_name].each do |index|
944           $redis.zadd "#{_construct_prepared_index(index)}:#{prop[:name]}_ids", score, @id
945         end
946       end
947     end
948   end
949 end