PageRenderTime 26ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/redis/objects.rb

https://github.com/quasor/redis-objects
Ruby | 138 lines | 85 code | 10 blank | 43 comment | 12 complexity | 3c8e23dd0bf6c94924a50c2da57ae37d MD5 | raw file
  1. # Redis::Objects - Lightweight object layer around redis-rb
  2. # See README.rdoc for usage and approach.
  3. require 'redis'
  4. class Redis
  5. #
  6. # Redis::Objects enables high-performance atomic operations in your app
  7. # by leveraging the atomic features of the Redis server. To use Redis::Objects,
  8. # first include it in any class you want. (This example uses an ActiveRecord
  9. # subclass, but that is *not* required.) Then, use +counter+, +lock+, +set+, etc
  10. # to define your primitives:
  11. #
  12. # class Game < ActiveRecord::Base
  13. # include Redis::Objects
  14. #
  15. # counter :joined_players
  16. # counter :active_players, :key => 'game:#{id}:act_plyr'
  17. # lock :archive_game
  18. # set :player_ids
  19. # end
  20. #
  21. # The, you can use these counters both for bookeeping and as atomic actions:
  22. #
  23. # @game = Game.find(id)
  24. # @game_user = @game.joined_players.increment do |val|
  25. # break if val > @game.max_players
  26. # gu = @game.game_users.create!(:user_id => @user.id)
  27. # @game.active_players.increment
  28. # gu
  29. # end
  30. # if @game_user.nil?
  31. # # game is full - error screen
  32. # else
  33. # # success
  34. # end
  35. #
  36. #
  37. #
  38. module Objects
  39. dir = File.expand_path(__FILE__.sub(/\.rb$/,''))
  40. autoload :Counters, File.join(dir, 'counters')
  41. autoload :Lists, File.join(dir, 'lists')
  42. autoload :Locks, File.join(dir, 'locks')
  43. autoload :Sets, File.join(dir, 'sets')
  44. autoload :SortedSets, File.join(dir, 'sorted_sets')
  45. autoload :Values, File.join(dir, 'values')
  46. autoload :Hashes, File.join(dir, 'hashes')
  47. class NotConnected < StandardError; end
  48. class NilObjectId < StandardError; end
  49. class << self
  50. def redis=(conn) @redis = conn end
  51. def redis
  52. @redis ||= $redis || Redis.current || raise(NotConnected, "Redis::Objects.redis not set to a Redis.new connection")
  53. end
  54. def included(klass)
  55. # Core (this file)
  56. klass.instance_variable_set('@redis', @redis)
  57. klass.instance_variable_set('@redis_objects', {})
  58. klass.send :include, InstanceMethods
  59. klass.extend ClassMethods
  60. # Pull in each object type
  61. klass.send :include, Redis::Objects::Counters
  62. klass.send :include, Redis::Objects::Lists
  63. klass.send :include, Redis::Objects::Locks
  64. klass.send :include, Redis::Objects::Sets
  65. klass.send :include, Redis::Objects::SortedSets
  66. klass.send :include, Redis::Objects::Values
  67. klass.send :include, Redis::Objects::Hashes
  68. end
  69. end
  70. # Class methods that appear in your class when you include Redis::Objects.
  71. module ClassMethods
  72. attr_writer :redis
  73. attr_accessor :redis_objects
  74. def redis() @redis ||= Objects.redis end
  75. # Set the Redis redis_prefix to use. Defaults to model_name
  76. def redis_prefix=(redis_prefix) @redis_prefix = redis_prefix end
  77. def redis_prefix(klass = self) #:nodoc:
  78. @redis_prefix ||= klass.name.to_s.
  79. sub(%r{(.*::)}, '').
  80. gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
  81. gsub(/([a-z\d])([A-Z])/,'\1_\2').
  82. downcase
  83. end
  84. def redis_field_key(name, id=nil) #:nodoc:
  85. klass = first_ancestor_with(name)
  86. # READ THIS: This can never ever ever ever change or upgrades will corrupt all data
  87. # I don't think people were using Proc as keys before (that would create a weird key). Should be ok
  88. key = klass.redis_objects[name.to_sym][:key]
  89. if key && key.respond_to?(:call)
  90. key = key.call self
  91. end
  92. if id.nil? and !klass.redis_objects[name.to_sym][:global]
  93. raise NilObjectId,
  94. "[#{klass.redis_objects[name.to_sym]}] Attempt to address redis-object :#{name} on class #{klass.name} with nil id (unsaved record?) [object_id=#{object_id}]"
  95. end
  96. key || "#{redis_prefix(klass)}:#{id}:#{name}"
  97. end
  98. def first_ancestor_with(name)
  99. if redis_objects && redis_objects.key?(name.to_sym)
  100. self
  101. elsif superclass && superclass.respond_to?(:redis_objects)
  102. superclass.first_ancestor_with(name)
  103. end
  104. end
  105. end
  106. # Instance methods that appear in your class when you include Redis::Objects.
  107. module InstanceMethods
  108. def redis() self.class.redis end
  109. def redis_field_key(name) #:nodoc:
  110. klass = self.class.first_ancestor_with(name)
  111. if key = klass.redis_objects[name.to_sym][:key]
  112. if key.respond_to?(:call)
  113. key.call self
  114. else
  115. eval "%(#{key})"
  116. end
  117. else
  118. if id.nil? and !klass.redis_objects[name.to_sym][:global]
  119. raise NilObjectId,
  120. "Attempt to address redis-object :#{name} on class #{klass.name} with nil id (unsaved record?) [object_id=#{object_id}]"
  121. end
  122. # don't try to refactor into class redis_field_key because fucks up eval context
  123. "#{klass.redis_prefix}:#{id}:#{name}"
  124. end
  125. end
  126. end
  127. end
  128. end