chimera 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. data/History.txt +4 -0
  2. data/Manifest.txt +57 -0
  3. data/PostInstall.txt +7 -0
  4. data/README.rdoc +114 -0
  5. data/Rakefile +30 -0
  6. data/doc/NOTES +11 -0
  7. data/doc/examples/config.yml +16 -0
  8. data/doc/redis6379.conf +132 -0
  9. data/lib/chimera.rb +33 -0
  10. data/lib/chimera/associations.rb +146 -0
  11. data/lib/chimera/attributes.rb +52 -0
  12. data/lib/chimera/base.rb +95 -0
  13. data/lib/chimera/config.rb +9 -0
  14. data/lib/chimera/error.rb +12 -0
  15. data/lib/chimera/finders.rb +49 -0
  16. data/lib/chimera/geo_indexes.rb +76 -0
  17. data/lib/chimera/indexes.rb +177 -0
  18. data/lib/chimera/persistence.rb +70 -0
  19. data/lib/chimera/redis_objects.rb +345 -0
  20. data/lib/redis.rb +373 -0
  21. data/lib/redis/counter.rb +94 -0
  22. data/lib/redis/dist_redis.rb +149 -0
  23. data/lib/redis/hash_ring.rb +135 -0
  24. data/lib/redis/helpers/core_commands.rb +46 -0
  25. data/lib/redis/helpers/serialize.rb +25 -0
  26. data/lib/redis/list.rb +122 -0
  27. data/lib/redis/lock.rb +83 -0
  28. data/lib/redis/objects.rb +100 -0
  29. data/lib/redis/objects/counters.rb +132 -0
  30. data/lib/redis/objects/lists.rb +45 -0
  31. data/lib/redis/objects/locks.rb +71 -0
  32. data/lib/redis/objects/sets.rb +46 -0
  33. data/lib/redis/objects/values.rb +56 -0
  34. data/lib/redis/pipeline.rb +21 -0
  35. data/lib/redis/set.rb +156 -0
  36. data/lib/redis/value.rb +35 -0
  37. data/lib/riak_raw.rb +100 -0
  38. data/lib/typhoeus.rb +55 -0
  39. data/lib/typhoeus/.gitignore +1 -0
  40. data/lib/typhoeus/easy.rb +253 -0
  41. data/lib/typhoeus/filter.rb +28 -0
  42. data/lib/typhoeus/hydra.rb +210 -0
  43. data/lib/typhoeus/multi.rb +34 -0
  44. data/lib/typhoeus/remote.rb +306 -0
  45. data/lib/typhoeus/remote_method.rb +108 -0
  46. data/lib/typhoeus/remote_proxy_object.rb +48 -0
  47. data/lib/typhoeus/request.rb +124 -0
  48. data/lib/typhoeus/response.rb +39 -0
  49. data/lib/typhoeus/service.rb +20 -0
  50. data/script/console +10 -0
  51. data/script/destroy +14 -0
  52. data/script/generate +14 -0
  53. data/test/models.rb +49 -0
  54. data/test/test_chimera.rb +238 -0
  55. data/test/test_helper.rb +7 -0
  56. metadata +243 -0
@@ -0,0 +1,122 @@
1
+ class Redis
2
+ #
3
+ # Class representing a Redis list. Instances of Redis::List are designed to
4
+ # behave as much like Ruby arrays as possible.
5
+ #
6
+ class List
7
+ require 'enumerator'
8
+ include Enumerable
9
+ require 'redis/helpers/core_commands'
10
+ include Redis::Helpers::CoreCommands
11
+ require 'redis/helpers/serialize'
12
+ include Redis::Helpers::Serialize
13
+
14
+ attr_reader :key, :options, :redis
15
+
16
+ def initialize(key, redis=$redis, options={})
17
+ @key = key
18
+ @redis = redis
19
+ @options = options
20
+ end
21
+
22
+ # Works like push. Can chain together: list << 'a' << 'b'
23
+ def <<(value)
24
+ push(value)
25
+ self # for << 'a' << 'b'
26
+ end
27
+
28
+ # Add a member to the end of the list. Redis: RPUSH
29
+ def push(value)
30
+ redis.rpush(key, to_redis(value))
31
+ end
32
+
33
+ # Remove a member from the end of the list. Redis: RPOP
34
+ def pop
35
+ from_redis redis.rpop(key)
36
+ end
37
+
38
+ # Add a member to the start of the list. Redis: LPUSH
39
+ def unshift(value)
40
+ redis.lpush(key, to_redis(value))
41
+ end
42
+
43
+ # Remove a member from the start of the list. Redis: LPOP
44
+ def shift
45
+ from_redis redis.lpop(key)
46
+ end
47
+
48
+ # Return all values in the list. Redis: LRANGE(0,-1)
49
+ def values
50
+ from_redis range(0, -1)
51
+ end
52
+ alias_method :get, :values
53
+
54
+ # Same functionality as Ruby arrays. If a single number is given, return
55
+ # just the element at that index using Redis: LINDEX. Otherwise, return
56
+ # a range of values using Redis: LRANGE.
57
+ def [](index, length=nil)
58
+ if index.is_a? Range
59
+ range(index.first, index.last)
60
+ elsif length
61
+ range(index, length)
62
+ else
63
+ at(index)
64
+ end
65
+ end
66
+
67
+ # Delete the element(s) from the list that match name. If count is specified,
68
+ # only the first-N (if positive) or last-N (if negative) will be removed.
69
+ # Use .del to completely delete the entire key.
70
+ # Redis: LREM
71
+ def delete(name, count=0)
72
+ redis.lrem(key, count, name) # weird api
73
+ end
74
+
75
+ # Iterate through each member of the set. Redis::Objects mixes in Enumerable,
76
+ # so you can also use familiar methods like +collect+, +detect+, and so forth.
77
+ def each(&block)
78
+ values.each(&block)
79
+ end
80
+
81
+ # Return a range of values from +start_index+ to +end_index+. Can also use
82
+ # the familiar list[start,end] Ruby syntax. Redis: LRANGE
83
+ def range(start_index, end_index)
84
+ from_redis redis.lrange(key, start_index, end_index)
85
+ end
86
+
87
+ # Return the value at the given index. Can also use familiar list[index] syntax.
88
+ # Redis: LINDEX
89
+ def at(index)
90
+ from_redis redis.lindex(key, index)
91
+ end
92
+
93
+ # Return the first element in the list. Redis: LINDEX(0)
94
+ def first
95
+ at(0)
96
+ end
97
+
98
+ # Return the last element in the list. Redis: LINDEX(-1)
99
+ def last
100
+ at(-1)
101
+ end
102
+
103
+ # Return the length of the list. Aliased as size. Redis: LLEN
104
+ def length
105
+ redis.llen(key)
106
+ end
107
+ alias_method :size, :length
108
+
109
+ # Returns true if there are no elements in the list. Redis: LLEN == 0
110
+ def empty?
111
+ length == 0
112
+ end
113
+
114
+ def ==(x)
115
+ values == x
116
+ end
117
+
118
+ def to_s
119
+ values.join(', ')
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,83 @@
1
+ class Redis
2
+ #
3
+ # Class representing a lock. This functions like a proxy class, in
4
+ # that you can say @object.lock_name { block } to use the lock and also
5
+ # @object.counter_name.clear to reset on it. You can use this
6
+ # directly, but it is better to use the lock :foo class method in your
7
+ # class to define a lock.
8
+ #
9
+ class Lock
10
+ class LockTimeout < StandardError; end #:nodoc:
11
+
12
+ attr_reader :key, :options, :redis
13
+ def initialize(key, redis=$redis, options={})
14
+ @key = key
15
+ @redis = redis
16
+ @options = options
17
+ @options[:timeout] ||= 5
18
+ @redis.setnx(key, @options[:start]) unless @options[:start] == 0 || @options[:init] === false
19
+ end
20
+
21
+ # Clear the lock. Should only be needed if there's a server crash
22
+ # or some other event that gets locks in a stuck state.
23
+ def clear
24
+ redis.del(key)
25
+ end
26
+ alias_method :delete, :clear
27
+
28
+ # Get the lock and execute the code block. Any other code that needs the lock
29
+ # (on any server) will spin waiting for the lock up to the :timeout
30
+ # that was specified when the lock was defined.
31
+ def lock(&block)
32
+ start = Time.now
33
+ gotit = false
34
+ expiration = nil
35
+ while Time.now - start < @options[:timeout]
36
+ expiration = generate_expiration
37
+ # Use the expiration as the value of the lock.
38
+ gotit = redis.setnx(key, expiration)
39
+ break if gotit
40
+
41
+ # Lock is being held. Now check to see if it's expired (if we're using
42
+ # lock expiration).
43
+ # See "Handling Deadlocks" section on http://code.google.com/p/redis/wiki/SetnxCommand
44
+ if !@options[:expiration].nil?
45
+ old_expiration = redis.get(key).to_f
46
+
47
+ if old_expiration < Time.now.to_f
48
+ # If it's expired, use GETSET to update it.
49
+ expiration = generate_expiration
50
+ old_expiration = redis.getset(key, expiration).to_f
51
+
52
+ # Since GETSET returns the old value of the lock, if the old expiration
53
+ # is still in the past, we know no one else has expired the locked
54
+ # and we now have it.
55
+ if old_expiration < Time.now.to_f
56
+ gotit = true
57
+ break
58
+ end
59
+ end
60
+ end
61
+
62
+ sleep 0.1
63
+ end
64
+ raise LockTimeout, "Timeout on lock #{key} exceeded #{@options[:timeout]} sec" unless gotit
65
+ begin
66
+ yield
67
+ ensure
68
+ # We need to be careful when cleaning up the lock key. If we took a really long
69
+ # time for some reason, and the lock expired, someone else may have it, and
70
+ # it's not safe for us to remove it. Check how much time has passed since we
71
+ # wrote the lock key and only delete it if it hasn't expired (or we're not using
72
+ # lock expiration)
73
+ if @options[:expiration].nil? || expiration > Time.now.to_f
74
+ redis.del(key)
75
+ end
76
+ end
77
+ end
78
+
79
+ def generate_expiration
80
+ @options[:expiration].nil? ? 1 : (Time.now + @options[:expiration].to_f + 1).to_f
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,100 @@
1
+ # Redis::Objects - Lightweight object layer around redis-rb
2
+ # See README.rdoc for usage and approach.
3
+
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+ and +lock+
10
+ # to define your primitives:
11
+ #
12
+ # class Game < ActiveRecord::Base
13
+ # include Redis::Objects
14
+ #
15
+ # counter :joined_players
16
+ # counter :active_players
17
+ # set :player_ids
18
+ # lock :archive_game
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
+
41
+ autoload :Counters, File.join(dir, 'counters')
42
+ autoload :Values, File.join(dir, 'values')
43
+ autoload :Lists, File.join(dir, 'lists')
44
+ autoload :Sets, File.join(dir, 'sets')
45
+ autoload :Locks, File.join(dir, 'locks')
46
+
47
+ class NotConnected < StandardError; end
48
+
49
+ class << self
50
+ # def redis=(conn) @redis = conn end
51
+ # def redis
52
+ # @redis ||= $redis || raise(NotConnected, "Redis::Objects.redis not set to a Redis.new connection")
53
+ # end
54
+
55
+ def included(klass)
56
+ # Core (this file)
57
+ #klass.instance_variable_set('@redis', @redis)
58
+ klass.instance_variable_set('@redis_objects', {})
59
+ klass.send :include, InstanceMethods
60
+ klass.extend ClassMethods
61
+
62
+ # Pull in each object type
63
+ klass.send :include, Redis::Objects::Counters
64
+ klass.send :include, Redis::Objects::Values
65
+ klass.send :include, Redis::Objects::Lists
66
+ klass.send :include, Redis::Objects::Sets
67
+ klass.send :include, Redis::Objects::Locks
68
+ end
69
+ end
70
+
71
+ # Class methods that appear in your class when you include Redis::Objects.
72
+ module ClassMethods
73
+ #attr_accessor :redis, :redis_objects
74
+ attr_accessor :redis_objects
75
+
76
+ # Set the Redis prefix to use. Defaults to model_name
77
+ def prefix=(prefix) @prefix = prefix end
78
+ def prefix #:nodoc:
79
+ @prefix ||= self.name.to_s.
80
+ sub(%r{(.*::)}, '').
81
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
82
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
83
+ downcase
84
+ end
85
+
86
+ def field_key(name, id) #:nodoc:
87
+ "#{prefix}:#{id}:#{name}"
88
+ end
89
+
90
+ end
91
+
92
+ # Instance methods that appear in your class when you include Redis::Objects.
93
+ module InstanceMethods
94
+ def redis() self.class.connection end
95
+ def field_key(name) #:nodoc:
96
+ self.class.field_key(name, id)
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,132 @@
1
+ # This is the class loader, for use as "include Redis::Objects::Counters"
2
+ # For the object itself, see "Redis::Counter"
3
+ require 'redis/counter'
4
+ class Redis
5
+ module Objects
6
+ class UndefinedCounter < StandardError; end #:nodoc:
7
+ class MissingID < StandardError; end #:nodoc:
8
+
9
+ module Counters
10
+ def self.included(klass)
11
+ klass.instance_variable_set('@initialized_counters', {})
12
+ klass.send :include, InstanceMethods
13
+ klass.extend ClassMethods
14
+ end
15
+
16
+ # Class methods that appear in your class when you include Redis::Objects.
17
+ module ClassMethods
18
+ attr_reader :initialized_counters
19
+
20
+ # Define a new counter. It will function like a regular instance
21
+ # method, so it can be used alongside ActiveRecord, DataMapper, etc.
22
+ def counter(name, options={})
23
+ options[:start] ||= 0
24
+ options[:type] ||= options[:start] == 0 ? :increment : :decrement
25
+ @redis_objects[name] = options.merge(:type => :counter)
26
+ if options[:global]
27
+ instance_eval <<-EndMethods
28
+ def #{name}
29
+ @#{name} ||= Redis::Counter.new(field_key(:#{name}, ''), redis, @redis_objects[:#{name}])
30
+ end
31
+ EndMethods
32
+ class_eval <<-EndMethods
33
+ def #{name}
34
+ self.class.#{name}
35
+ end
36
+ EndMethods
37
+ else
38
+ class_eval <<-EndMethods
39
+ def #{name}
40
+ @#{name} ||= Redis::Counter.new(field_key(:#{name}), redis, self.class.redis_objects[:#{name}])
41
+ end
42
+ EndMethods
43
+ end
44
+ end
45
+
46
+ # Get the current value of the counter. It is more efficient
47
+ # to use the instance method if possible.
48
+ def get_counter(name, id=nil)
49
+ verify_counter_defined!(name, id)
50
+ initialize_counter!(name, id)
51
+ redis.get(field_key(name, id)).to_i
52
+ end
53
+
54
+ # Increment a counter with the specified name and id. Accepts a block
55
+ # like the instance method. See Redis::Objects::Counter for details.
56
+ def increment_counter(name, id=nil, by=1, &block)
57
+ verify_counter_defined!(name, id)
58
+ initialize_counter!(name, id)
59
+ value = redis.incr(field_key(name, id), by).to_i
60
+ block_given? ? rewindable_block(:decrement_counter, name, id, value, &block) : value
61
+ end
62
+
63
+ # Decrement a counter with the specified name and id. Accepts a block
64
+ # like the instance method. See Redis::Objects::Counter for details.
65
+ def decrement_counter(name, id=nil, by=1, &block)
66
+ verify_counter_defined!(name, id)
67
+ initialize_counter!(name, id)
68
+ value = redis.decr(field_key(name, id), by).to_i
69
+ block_given? ? rewindable_block(:increment_counter, name, id, value, &block) : value
70
+ end
71
+
72
+ # Reset a counter to its starting value.
73
+ def reset_counter(name, id=nil, to=nil)
74
+ verify_counter_defined!(name, id)
75
+ to = @redis_objects[name][:start] if to.nil?
76
+ redis.set(field_key(name, id), to)
77
+ end
78
+
79
+ private
80
+
81
+ def verify_counter_defined!(name, id) #:nodoc:
82
+ raise Redis::Objects::UndefinedCounter, "Undefined counter :#{name} for class #{self.name}" unless @redis_objects.has_key?(name)
83
+ if id.nil? and !@redis_objects[name][:global]
84
+ raise Redis::Objects::MissingID, "Missing ID for non-global counter #{self.name}##{name}"
85
+ end
86
+ end
87
+
88
+ def initialize_counter!(name, id) #:nodoc:
89
+ key = field_key(name, id)
90
+ unless @initialized_counters[key]
91
+ redis.setnx(key, @redis_objects[name][:start])
92
+ end
93
+ @initialized_counters[key] = true
94
+ end
95
+
96
+ # Implements increment/decrement blocks on a class level
97
+ def rewindable_block(rewind, name, id, value, &block) #:nodoc:
98
+ # Unfortunately this is almost exactly duplicated from Redis::Counter
99
+ raise ArgumentError, "Missing block to rewindable_block somehow" unless block_given?
100
+ ret = nil
101
+ begin
102
+ ret = yield value
103
+ rescue
104
+ send(rewind, name, id)
105
+ raise
106
+ end
107
+ send(rewind, name, id) if ret.nil?
108
+ ret
109
+ end
110
+ end
111
+
112
+ # Instance methods that appear in your class when you include Redis::Objects.
113
+ module InstanceMethods
114
+ # Increment a counter.
115
+ # It is more efficient to use increment_[counter_name] directly.
116
+ # This is mainly just for completeness to override ActiveRecord.
117
+ def increment(name, by=1)
118
+ raise(ActiveRedis::Errors::NotSavedError) if self.new?
119
+ send(name).increment(by)
120
+ end
121
+
122
+ # Decrement a counter.
123
+ # It is more efficient to use increment_[counter_name] directly.
124
+ # This is mainly just for completeness to override ActiveRecord.
125
+ def decrement(name, by=1)
126
+ raise(ActiveRedis::Errors::NotSavedError) if self.new?
127
+ send(name).decrement(by)
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,45 @@
1
+ # This is the class loader, for use as "include Redis::Objects::Lists"
2
+ # For the object itself, see "Redis::List"
3
+ require 'redis/list'
4
+ class Redis
5
+ module Objects
6
+ module Lists
7
+ def self.included(klass)
8
+ klass.send :include, InstanceMethods
9
+ klass.extend ClassMethods
10
+ end
11
+
12
+ # Class methods that appear in your class when you include Redis::Objects.
13
+ module ClassMethods
14
+ # Define a new list. It will function like a regular instance
15
+ # method, so it can be used alongside ActiveRecord, DataMapper, etc.
16
+ def list(name, options={})
17
+ @redis_objects[name] = options.merge(:type => :list)
18
+ if options[:global]
19
+ instance_eval <<-EndMethods
20
+ def #{name}
21
+ @#{name} ||= Redis::List.new(field_key(:#{name}, ''), redis, @redis_objects[:#{name}])
22
+ end
23
+ EndMethods
24
+ class_eval <<-EndMethods
25
+ def #{name}
26
+ self.class.#{name}
27
+ end
28
+ EndMethods
29
+ else
30
+ class_eval <<-EndMethods
31
+ def #{name}
32
+ raise(ActiveRedis::Errors::NotSavedError) if self.new?
33
+ @#{name} ||= Redis::List.new(field_key(:#{name}), redis, self.class.redis_objects[:#{name}])
34
+ end
35
+ EndMethods
36
+ end
37
+ end
38
+ end
39
+
40
+ # Instance methods that appear in your class when you include Redis::Objects.
41
+ module InstanceMethods
42
+ end
43
+ end
44
+ end
45
+ end