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!
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_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