redis-objects 0.5.0 → 0.5.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.
- data/CHANGELOG.rdoc +10 -0
- data/README.rdoc +5 -5
- data/Rakefile +1 -0
- data/VERSION +1 -1
- data/lib/redis/base_object.rb +2 -2
- data/lib/redis/hash_key.rb +27 -6
- data/lib/redis/helpers/core_commands.rb +6 -8
- data/lib/redis/helpers/serialize.rb +5 -5
- data/lib/redis/lock.rb +1 -1
- data/lib/redis/objects/counters.rb +4 -2
- data/lib/redis/objects.rb +27 -7
- data/lib/redis/sorted_set.rb +5 -0
- data/redis-objects.gemspec +33 -41
- data/spec/redis_namespace_compat_spec.rb +14 -8
- data/spec/redis_objects_active_record_spec.rb +105 -0
- data/spec/redis_objects_instance_spec.rb +66 -0
- data/spec/redis_objects_model_spec.rb +25 -7
- data/spec/spec_helper.rb +8 -0
- metadata +9 -28
- data/.gitignore +0 -3
data/CHANGELOG.rdoc
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
= Changelog for Redis::Objects
|
2
2
|
|
3
|
+
== 0.5.1 [Final] (23 May 2011)
|
4
|
+
|
5
|
+
* Fixed super class delegation conflicts with Redis Counters vs ActiveRecord [Tim Aßmann]
|
6
|
+
|
7
|
+
* Added zcount method to SortedSet [dunedain289]
|
8
|
+
|
9
|
+
* Updated redis-objects to look for Redis.current and prefer it over global $redis variable [Jean Boussier]
|
10
|
+
|
11
|
+
* Updated URLs to reflect new redis.io website [Jérémy Lecour]
|
12
|
+
|
3
13
|
== 0.5.0 [Final] (8 November 2010)
|
4
14
|
|
5
15
|
* Incompatible change: Had to rename Redis::Hash to Redis::HashKey due to internal conflicts with Redis lib and Ruby [Nate Wiger]
|
data/README.rdoc
CHANGED
@@ -7,7 +7,7 @@ on _individual_ data structures, like counters, lists, and sets. The *atomic* p
|
|
7
7
|
Using an ORM wrapper that retrieves a "record", updates values, then sends those values back,
|
8
8
|
_removes_ the atomicity, cutting the nuts off the major advantage of Redis. Just use MySQL, k?
|
9
9
|
|
10
|
-
This gem provides a Rubyish interface to Redis, by mapping {Redis types}[http://
|
10
|
+
This gem provides a Rubyish interface to Redis, by mapping {Redis types}[http://redis.io/commands]
|
11
11
|
to Ruby objects, via a thin layer over Ezra's +redis+ gem. It offers several advantages
|
12
12
|
over the lower-level redis-rb API:
|
13
13
|
|
@@ -111,12 +111,12 @@ Finally, for free, you get a +redis+ method that points directly to a Redis conn
|
|
111
111
|
@team.redis.get('somekey')
|
112
112
|
@team.redis.smembers('someset')
|
113
113
|
|
114
|
-
You can use the +redis+ handle to directly call any {Redis API command}[http://
|
114
|
+
You can use the +redis+ handle to directly call any {Redis API command}[http://redis.io/commands].
|
115
115
|
|
116
116
|
== Example 2: Standalone Usage
|
117
117
|
|
118
118
|
There is a Ruby class that maps to each Redis type, with methods for each
|
119
|
-
{Redis API command}[http://
|
119
|
+
{Redis API command}[http://redis.io/commands].
|
120
120
|
Note that calling +new+ does not imply it's actually a "new" value - it just
|
121
121
|
creates a mapping between that object and the corresponding Redis data structure,
|
122
122
|
which may already exist on the redis-server.
|
@@ -154,7 +154,7 @@ See the section on "Atomicity" for cool uses of atomic counter blocks.
|
|
154
154
|
|
155
155
|
=== Locks
|
156
156
|
|
157
|
-
A convenience class that wraps the pattern of {using +setnx+ to perform locking}[http://
|
157
|
+
A convenience class that wraps the pattern of {using +setnx+ to perform locking}[http://redis.io/commands/setnx].
|
158
158
|
|
159
159
|
require 'redis/lock'
|
160
160
|
@lock = Redis::Lock.new('image_resizing', :expiration => 15, :timeout => 0.1)
|
@@ -331,7 +331,7 @@ a Hash and an Array. You assign like a Hash, but retrieve like an Array:
|
|
331
331
|
@sorted_set.incr('Peter') # shorthand
|
332
332
|
@sorted_set.incr('Jeff', 4)
|
333
333
|
|
334
|
-
The other Redis Sorted Set commands are supported as well; see {Sorted Sets API}[http://
|
334
|
+
The other Redis Sorted Set commands are supported as well; see {Sorted Sets API}[http://redis.io/commands#sorted_set].
|
335
335
|
|
336
336
|
== Atomic Counters and Locks
|
337
337
|
|
data/Rakefile
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.5.
|
1
|
+
0.5.1
|
data/lib/redis/base_object.rb
CHANGED
@@ -4,9 +4,9 @@ class Redis
|
|
4
4
|
def initialize(key, *args)
|
5
5
|
@key = key.is_a?(Array) ? key.flatten.join(':') : key
|
6
6
|
@options = args.last.is_a?(Hash) ? args.pop : {}
|
7
|
-
@redis = args.first || $redis
|
7
|
+
@redis = args.first || $redis || Redis.current
|
8
8
|
end
|
9
|
-
|
9
|
+
|
10
10
|
alias :inspect :to_s # Ruby 1.9.2
|
11
11
|
end
|
12
12
|
end
|
data/lib/redis/hash_key.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/base_object'
|
2
|
+
|
1
3
|
class Redis
|
2
4
|
#
|
3
5
|
# Class representing a Redis hash.
|
@@ -12,6 +14,16 @@ class Redis
|
|
12
14
|
|
13
15
|
attr_reader :key, :options, :redis
|
14
16
|
|
17
|
+
def initialize(key, *args)
|
18
|
+
super
|
19
|
+
@options[:marshal_keys] ||= {}
|
20
|
+
end
|
21
|
+
|
22
|
+
# Needed since Redis::Hash masks bare Hash in redis.rb
|
23
|
+
def self.[](*args)
|
24
|
+
::Hash[*args]
|
25
|
+
end
|
26
|
+
|
15
27
|
# Sets a field to value
|
16
28
|
def []=(field, value)
|
17
29
|
store(field, to_redis(value))
|
@@ -24,12 +36,12 @@ class Redis
|
|
24
36
|
|
25
37
|
# Redis: HSET
|
26
38
|
def store(field, value)
|
27
|
-
redis.hset(key, field, to_redis(value))
|
39
|
+
redis.hset(key, field, to_redis(value, options[:marshal_keys][field]))
|
28
40
|
end
|
29
41
|
|
30
42
|
# Redis: HGET
|
31
43
|
def fetch(field)
|
32
|
-
from_redis redis.hget(key, field)
|
44
|
+
from_redis redis.hget(key, field), options[:marshal_keys][field]
|
33
45
|
end
|
34
46
|
|
35
47
|
# Verify that a field exists. Redis: HEXISTS
|
@@ -58,7 +70,9 @@ class Redis
|
|
58
70
|
|
59
71
|
# Retrieve the entire hash. Redis: HGETALL
|
60
72
|
def all
|
61
|
-
|
73
|
+
h = redis.hgetall(key)
|
74
|
+
h.each { |k,v| h[k] = from_redis(v, options[:marshal_keys][k]) }
|
75
|
+
h
|
62
76
|
end
|
63
77
|
alias_method :clone, :all
|
64
78
|
|
@@ -97,7 +111,9 @@ class Redis
|
|
97
111
|
# Set keys in bulk, takes a hash of field/values {'field1' => 'val1'}. Redis: HMSET
|
98
112
|
def bulk_set(*args)
|
99
113
|
raise ArgumentError, "Argument to bulk_set must be hash of key/value pairs" unless args.last.is_a?(::Hash)
|
100
|
-
redis.hmset(key, *args.last.inject([]){ |arr,kv|
|
114
|
+
redis.hmset(key, *args.last.inject([]){ |arr,kv|
|
115
|
+
arr + [kv[0], to_redis(kv[1], options[:marshal_keys][kv[0]])]
|
116
|
+
})
|
101
117
|
end
|
102
118
|
|
103
119
|
# Get keys in bulk, takes an array of fields as arguments. Redis: HMGET
|
@@ -105,14 +121,19 @@ class Redis
|
|
105
121
|
hsh = {}
|
106
122
|
res = redis.hmget(key, *fields.flatten)
|
107
123
|
fields.each do |k|
|
108
|
-
hsh[k] = from_redis(res.shift)
|
124
|
+
hsh[k] = from_redis(res.shift, options[:marshal_keys][k])
|
109
125
|
end
|
110
126
|
hsh
|
111
127
|
end
|
112
128
|
|
113
129
|
# Increment value by integer at field. Redis: HINCRBY
|
114
130
|
def incrby(field, val = 1)
|
115
|
-
redis.hincrby(key, field, val)
|
131
|
+
ret = redis.hincrby(key, field, val)
|
132
|
+
unless ret.is_a? Array
|
133
|
+
ret.to_i
|
134
|
+
else
|
135
|
+
nil
|
136
|
+
end
|
116
137
|
end
|
117
138
|
alias_method :incr, :incrby
|
118
139
|
|
@@ -41,14 +41,12 @@ class Redis
|
|
41
41
|
def move(dbindex)
|
42
42
|
redis.move key, dbindex
|
43
43
|
end
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
# args += ['sort']
|
50
|
-
# from_redis redis.sort key
|
51
|
-
# end
|
44
|
+
|
45
|
+
def sort(options={})
|
46
|
+
options[:order] ||= "asc alpha"
|
47
|
+
redis.sort(key, options)
|
48
|
+
end
|
52
49
|
end
|
50
|
+
|
53
51
|
end
|
54
52
|
end
|
@@ -3,8 +3,8 @@ class Redis
|
|
3
3
|
module Serialize
|
4
4
|
include Marshal
|
5
5
|
|
6
|
-
def to_redis(value)
|
7
|
-
return value unless options[:marshal]
|
6
|
+
def to_redis(value, marshal=false)
|
7
|
+
return value unless options[:marshal] || marshal
|
8
8
|
case value
|
9
9
|
when String, Fixnum, Bignum, Float
|
10
10
|
value
|
@@ -13,8 +13,8 @@ class Redis
|
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
|
-
def from_redis(value)
|
17
|
-
return value unless options[:marshal]
|
16
|
+
def from_redis(value, marshal=false)
|
17
|
+
return value unless options[:marshal] || marshal
|
18
18
|
case value
|
19
19
|
when Array
|
20
20
|
value.collect{|v| from_redis(v)}
|
@@ -26,4 +26,4 @@ class Redis
|
|
26
26
|
end
|
27
27
|
end
|
28
28
|
end
|
29
|
-
end
|
29
|
+
end
|
data/lib/redis/lock.rb
CHANGED
@@ -41,7 +41,7 @@ class Redis
|
|
41
41
|
|
42
42
|
# Lock is being held. Now check to see if it's expired (if we're using
|
43
43
|
# lock expiration).
|
44
|
-
# See "Handling Deadlocks" section on http://
|
44
|
+
# See "Handling Deadlocks" section on http://redis.io/commands/setnx
|
45
45
|
if !@options[:expiration].nil?
|
46
46
|
old_expiration = redis.get(key).to_f
|
47
47
|
|
@@ -55,6 +55,7 @@ class Redis
|
|
55
55
|
# Increment a counter with the specified name and id. Accepts a block
|
56
56
|
# like the instance method. See Redis::Objects::Counter for details.
|
57
57
|
def increment_counter(name, id=nil, by=1, &block)
|
58
|
+
return super(name, id) unless counter_defined?(name)
|
58
59
|
verify_counter_defined!(name, id)
|
59
60
|
initialize_counter!(name, id)
|
60
61
|
value = redis.incrby(redis_field_key(name, id), by).to_i
|
@@ -64,6 +65,7 @@ class Redis
|
|
64
65
|
# Decrement a counter with the specified name and id. Accepts a block
|
65
66
|
# like the instance method. See Redis::Objects::Counter for details.
|
66
67
|
def decrement_counter(name, id=nil, by=1, &block)
|
68
|
+
return super(name, id) unless counter_defined?(name)
|
67
69
|
verify_counter_defined!(name, id)
|
68
70
|
initialize_counter!(name, id)
|
69
71
|
value = redis.decrby(redis_field_key(name, id), by).to_i
|
@@ -81,7 +83,7 @@ class Redis
|
|
81
83
|
private
|
82
84
|
|
83
85
|
def verify_counter_defined!(name, id) #:nodoc:
|
84
|
-
raise
|
86
|
+
raise NoMethodError, "Undefined counter :#{name} for class #{self.name}" unless counter_defined?(name)
|
85
87
|
if id.nil? and !@redis_objects[name][:global]
|
86
88
|
raise Redis::Objects::MissingID, "Missing ID for non-global counter #{self.name}##{name}"
|
87
89
|
end
|
@@ -141,4 +143,4 @@ class Redis
|
|
141
143
|
end
|
142
144
|
end
|
143
145
|
end
|
144
|
-
end
|
146
|
+
end
|
data/lib/redis/objects.rb
CHANGED
@@ -46,12 +46,13 @@ class Redis
|
|
46
46
|
autoload :Values, File.join(dir, 'values')
|
47
47
|
autoload :Hashes, File.join(dir, 'hashes')
|
48
48
|
|
49
|
-
class NotConnected
|
49
|
+
class NotConnected < StandardError; end
|
50
|
+
class NilObjectId < StandardError; end
|
50
51
|
|
51
52
|
class << self
|
52
53
|
def redis=(conn) @redis = conn end
|
53
54
|
def redis
|
54
|
-
@redis ||= $redis || raise(NotConnected, "Redis::Objects.redis not set to a Redis.new connection")
|
55
|
+
@redis ||= $redis || Redis.current || raise(NotConnected, "Redis::Objects.redis not set to a Redis.new connection")
|
55
56
|
end
|
56
57
|
|
57
58
|
def included(klass)
|
@@ -74,7 +75,9 @@ class Redis
|
|
74
75
|
|
75
76
|
# Class methods that appear in your class when you include Redis::Objects.
|
76
77
|
module ClassMethods
|
77
|
-
|
78
|
+
attr_writer :redis
|
79
|
+
attr_accessor :redis_objects
|
80
|
+
def redis() @redis ||= Objects.redis end
|
78
81
|
|
79
82
|
# Set the Redis redis_prefix to use. Defaults to model_name
|
80
83
|
def redis_prefix=(redis_prefix) @redis_prefix = redis_prefix end
|
@@ -86,10 +89,19 @@ class Redis
|
|
86
89
|
downcase
|
87
90
|
end
|
88
91
|
|
89
|
-
def redis_field_key(name, id=
|
92
|
+
def redis_field_key(name, id=nil) #:nodoc:
|
90
93
|
klass = first_ancestor_with(name)
|
91
|
-
# This can never ever ever ever change or upgrades will corrupt all data
|
92
|
-
|
94
|
+
# READ THIS: This can never ever ever ever change or upgrades will corrupt all data
|
95
|
+
# I don't think people were using Proc as keys before (that would create a weird key). Should be ok
|
96
|
+
key = klass.redis_objects[name.to_sym][:key]
|
97
|
+
if key && key.respond_to?(:call)
|
98
|
+
key = key.call self
|
99
|
+
end
|
100
|
+
if id.nil? and !klass.redis_objects[name.to_sym][:global]
|
101
|
+
raise NilObjectId,
|
102
|
+
"[#{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}]"
|
103
|
+
end
|
104
|
+
key || "#{redis_prefix(klass)}:#{id}:#{name}"
|
93
105
|
end
|
94
106
|
|
95
107
|
def first_ancestor_with(name)
|
@@ -107,8 +119,16 @@ class Redis
|
|
107
119
|
def redis_field_key(name) #:nodoc:
|
108
120
|
klass = self.class.first_ancestor_with(name)
|
109
121
|
if key = klass.redis_objects[name.to_sym][:key]
|
110
|
-
|
122
|
+
if key.respond_to?(:call)
|
123
|
+
key.call self
|
124
|
+
else
|
125
|
+
eval "%(#{key})"
|
126
|
+
end
|
111
127
|
else
|
128
|
+
if id.nil? and !klass.redis_objects[name.to_sym][:global]
|
129
|
+
raise NilObjectId,
|
130
|
+
"Attempt to address redis-object :#{name} on class #{klass.name} with nil id (unsaved record?) [object_id=#{object_id}]"
|
131
|
+
end
|
112
132
|
# don't try to refactor into class redis_field_key because fucks up eval context
|
113
133
|
"#{klass.redis_prefix}:#{id}:#{name}"
|
114
134
|
end
|
data/lib/redis/sorted_set.rb
CHANGED
data/redis-objects.gemspec
CHANGED
@@ -1,66 +1,58 @@
|
|
1
1
|
# Generated by jeweler
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
-
# Instead, edit Jeweler::Tasks in Rakefile, and run
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{redis-objects}
|
8
|
-
s.version = "0.5.
|
8
|
+
s.version = "0.5.1"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Nate Wiger"]
|
12
|
-
s.date = %q{
|
12
|
+
s.date = %q{2011-05-23}
|
13
13
|
s.description = %q{Map Redis types directly to Ruby objects. Works with any class or ORM.}
|
14
14
|
s.email = %q{nate@wiger.org}
|
15
15
|
s.extra_rdoc_files = [
|
16
16
|
"README.rdoc"
|
17
17
|
]
|
18
18
|
s.files = [
|
19
|
-
".
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
19
|
+
"ATOMICITY.rdoc",
|
20
|
+
"CHANGELOG.rdoc",
|
21
|
+
"README.rdoc",
|
22
|
+
"Rakefile",
|
23
|
+
"VERSION",
|
24
|
+
"lib/redis/base_object.rb",
|
25
|
+
"lib/redis/counter.rb",
|
26
|
+
"lib/redis/hash_key.rb",
|
27
|
+
"lib/redis/helpers/core_commands.rb",
|
28
|
+
"lib/redis/helpers/serialize.rb",
|
29
|
+
"lib/redis/list.rb",
|
30
|
+
"lib/redis/lock.rb",
|
31
|
+
"lib/redis/objects.rb",
|
32
|
+
"lib/redis/objects/counters.rb",
|
33
|
+
"lib/redis/objects/hashes.rb",
|
34
|
+
"lib/redis/objects/lists.rb",
|
35
|
+
"lib/redis/objects/locks.rb",
|
36
|
+
"lib/redis/objects/sets.rb",
|
37
|
+
"lib/redis/objects/sorted_sets.rb",
|
38
|
+
"lib/redis/objects/values.rb",
|
39
|
+
"lib/redis/set.rb",
|
40
|
+
"lib/redis/sorted_set.rb",
|
41
|
+
"lib/redis/value.rb",
|
42
|
+
"redis-objects.gemspec",
|
43
|
+
"spec/redis_namespace_compat_spec.rb",
|
44
|
+
"spec/redis_objects_active_record_spec.rb",
|
45
|
+
"spec/redis_objects_instance_spec.rb",
|
46
|
+
"spec/redis_objects_model_spec.rb",
|
47
|
+
"spec/spec_helper.rb"
|
48
48
|
]
|
49
49
|
s.homepage = %q{http://github.com/nateware/redis-objects}
|
50
|
-
s.rdoc_options = ["--charset=UTF-8"]
|
51
50
|
s.require_paths = ["lib"]
|
52
51
|
s.requirements = ["redis, v2.1.1 or greater"]
|
53
|
-
s.rubygems_version = %q{1.
|
52
|
+
s.rubygems_version = %q{1.7.2}
|
54
53
|
s.summary = %q{Map Redis types directly to Ruby objects}
|
55
|
-
s.test_files = [
|
56
|
-
"spec/redis_namespace_compat_spec.rb",
|
57
|
-
"spec/redis_objects_instance_spec.rb",
|
58
|
-
"spec/redis_objects_model_spec.rb",
|
59
|
-
"spec/spec_helper.rb"
|
60
|
-
]
|
61
54
|
|
62
55
|
if s.respond_to? :specification_version then
|
63
|
-
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
64
56
|
s.specification_version = 3
|
65
57
|
|
66
58
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
@@ -4,15 +4,21 @@ require 'redis/objects'
|
|
4
4
|
Redis::Objects.redis = $redis
|
5
5
|
|
6
6
|
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../../redis-namespace/lib')
|
7
|
-
|
7
|
+
begin
|
8
|
+
require 'redis/namespace'
|
8
9
|
|
9
|
-
describe 'Redis::Namespace compat' do
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
10
|
+
describe 'Redis::Namespace compat' do
|
11
|
+
it "tests the compatibility of Hash and ::Hash conflicts" do
|
12
|
+
ns = Redis::Namespace.new("resque", :redis => $redis)
|
13
|
+
ns.instance_eval { rem_namespace({"resque:x" => nil}) }.should == {"x"=>nil}
|
14
|
+
class Foo
|
15
|
+
include Redis::Objects
|
16
|
+
end
|
17
|
+
ns.instance_eval { rem_namespace({"resque:x" => nil}) }.should == {"x"=>nil}
|
15
18
|
end
|
16
|
-
ns.instance_eval { rem_namespace({"resque:x" => nil}) }.should == {"x"=>nil}
|
17
19
|
end
|
20
|
+
|
21
|
+
rescue LoadError
|
22
|
+
# Redis::Namespace not installed
|
23
|
+
puts "Skipping Redis::Namespace tests as redis-namespace is not installed"
|
18
24
|
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
3
|
+
|
4
|
+
require 'redis/objects'
|
5
|
+
Redis::Objects.redis = $redis
|
6
|
+
|
7
|
+
begin
|
8
|
+
require 'active_record'
|
9
|
+
ActiveRecord::Base.establish_connection(
|
10
|
+
:adapter => 'sqlite3',
|
11
|
+
:database => File.expand_path(File.dirname(__FILE__) + '/redis_objects_test.sqlite3')
|
12
|
+
)
|
13
|
+
|
14
|
+
class CreateBlogs < ActiveRecord::Migration
|
15
|
+
def self.up
|
16
|
+
create_table :blogs do |t|
|
17
|
+
t.string :name
|
18
|
+
t.integer :posts_count, :default => 0
|
19
|
+
t.timestamps
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.down
|
24
|
+
drop_table :blogs
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Blog < ActiveRecord::Base
|
29
|
+
include Redis::Objects
|
30
|
+
has_many :posts
|
31
|
+
end
|
32
|
+
|
33
|
+
class CreatePosts < ActiveRecord::Migration
|
34
|
+
def self.up
|
35
|
+
create_table :posts do |t|
|
36
|
+
t.string :title
|
37
|
+
t.string :description, :length => 200
|
38
|
+
t.integer :total
|
39
|
+
t.integer :blog_id
|
40
|
+
t.timestamps
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.down
|
45
|
+
drop_table :posts
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class Post < ActiveRecord::Base
|
50
|
+
include Redis::Objects
|
51
|
+
counter :total
|
52
|
+
belongs_to :blog, :counter_cache => true
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
describe Redis::Objects do
|
57
|
+
before do
|
58
|
+
CreatePosts.up
|
59
|
+
CreateBlogs.up
|
60
|
+
end
|
61
|
+
after do
|
62
|
+
CreatePosts.down
|
63
|
+
CreateBlogs.down
|
64
|
+
end
|
65
|
+
|
66
|
+
it "exercises ActiveRecord in more detail" do
|
67
|
+
@ar = Post.new
|
68
|
+
@ar.save!
|
69
|
+
@ar.destroy
|
70
|
+
|
71
|
+
# @ar.total.reset
|
72
|
+
@ar2 = Post.new
|
73
|
+
@ar2.save!
|
74
|
+
@ar2.total.reset
|
75
|
+
@ar2.total.increment.should == 1
|
76
|
+
@ar2.id.should == 2
|
77
|
+
@ar2.increment(:total).should == 2
|
78
|
+
@ar2[:total].should == nil # DB column
|
79
|
+
@ar2.redis.get(@ar2.redis_field_key('total')).to_i.should == 2
|
80
|
+
@ar2[:total] = 3 # DB column
|
81
|
+
@ar2.total.decrement.should == 1
|
82
|
+
@ar2.total.reset
|
83
|
+
@ar2.total.should == 0
|
84
|
+
@ar2.destroy
|
85
|
+
end
|
86
|
+
|
87
|
+
it "falls back to ActiveRecord if redis counter is not defined" do
|
88
|
+
blog = Blog.create
|
89
|
+
blog.reload.posts_count.should == 0
|
90
|
+
post = Post.create :blog => blog
|
91
|
+
blog.reload.posts_count.should == 1
|
92
|
+
blog2 = Blog.create
|
93
|
+
Post.create :blog => blog2
|
94
|
+
Post.create :blog => blog2
|
95
|
+
blog.reload.posts_count.should == 1
|
96
|
+
blog2.reload.posts_count.should == 2
|
97
|
+
blog.posts_count.should == 1
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
rescue LoadError
|
103
|
+
# ActiveRecord not install
|
104
|
+
puts "Skipping ActiveRecord tests as active_record is not installed"
|
105
|
+
end
|
@@ -360,6 +360,39 @@ end
|
|
360
360
|
|
361
361
|
|
362
362
|
describe Redis::HashKey do
|
363
|
+
describe "With Marshal" do
|
364
|
+
before do
|
365
|
+
@hash = Redis::HashKey.new('test_hash', $redis,
|
366
|
+
{:marshal_keys=>{'created_at'=>true}})
|
367
|
+
@hash.clear
|
368
|
+
end
|
369
|
+
|
370
|
+
it "should marshal specified keys" do
|
371
|
+
@hash['created_at'] = Time.now
|
372
|
+
@hash['created_at'].class.should == Time
|
373
|
+
end
|
374
|
+
|
375
|
+
it "should not marshal unless required" do
|
376
|
+
@hash['updated_at'] = Time.now
|
377
|
+
@hash['updated_at'].class.should == String
|
378
|
+
end
|
379
|
+
|
380
|
+
it "should marshall appropriate key with bulk set and get" do
|
381
|
+
@hash.bulk_set({'created_at'=>Time.now, 'updated_at'=>Time.now})
|
382
|
+
|
383
|
+
@hash['created_at'].class.should == Time
|
384
|
+
@hash['updated_at'].class.should == String
|
385
|
+
|
386
|
+
h = @hash.bulk_get('created_at', 'updated_at')
|
387
|
+
h['created_at'].class.should == Time
|
388
|
+
h['updated_at'].class.should == String
|
389
|
+
|
390
|
+
h = @hash.all
|
391
|
+
h['created_at'].class.should == Time
|
392
|
+
h['updated_at'].class.should == String
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
363
396
|
before do
|
364
397
|
@hash = Redis::HashKey.new('test_hash')
|
365
398
|
@hash.clear
|
@@ -601,11 +634,40 @@ describe Redis::Set do
|
|
601
634
|
@set.redis.del('spec/set2')
|
602
635
|
end
|
603
636
|
|
637
|
+
it "should support sorting" do
|
638
|
+
@set_1 << 'a' << 'b' << 'c' << 'd' << 'e'
|
639
|
+
@set_2 << 1 << 2 << 3 << 4 << 5
|
640
|
+
@set_3 << 'm_1' << 'm_2'
|
641
|
+
@set_1.sort.should == %w(a b c d e)
|
642
|
+
@set_2.sort.should == %w(1 2 3 4 5)
|
643
|
+
|
644
|
+
@set_1.sort(SORT_ORDER).should == %w(e d c b a)
|
645
|
+
@set_3.sort(SORT_BY).should == %w(m_1 m_2)
|
646
|
+
@set_2.sort(SORT_LIMIT).should == %w(3 4)
|
647
|
+
|
648
|
+
val1 = Redis::Value.new('spec/3/sorted')
|
649
|
+
val2 = Redis::Value.new('spec/4/sorted')
|
650
|
+
|
651
|
+
val1.set('val3')
|
652
|
+
val2.set('val4')
|
653
|
+
|
654
|
+
@set_2.sort(SORT_GET).should == ['val3', 'val4']
|
655
|
+
@set_2.sort(SORT_STORE).should == 2
|
656
|
+
@set_2.redis.type(SORT_STORE[:store]).should == 'list'
|
657
|
+
@set_2.redis.lrange(SORT_STORE[:store], 0, -1).should == ['val3', 'val4']
|
658
|
+
|
659
|
+
@set_1.redis.del val1.key
|
660
|
+
@set_1.redis.del val2.key
|
661
|
+
@set_1.redis.del SORT_STORE[:store]
|
662
|
+
|
663
|
+
end
|
664
|
+
|
604
665
|
after do
|
605
666
|
@set.clear
|
606
667
|
@set_1.clear
|
607
668
|
@set_2.clear
|
608
669
|
@set_3.clear
|
670
|
+
|
609
671
|
end
|
610
672
|
end
|
611
673
|
|
@@ -698,6 +760,10 @@ describe Redis::SortedSet do
|
|
698
760
|
@set.length.should == 4
|
699
761
|
@set.size.should == 4
|
700
762
|
|
763
|
+
@set.range_size(100, 120).should == 0
|
764
|
+
@set.range_size(0, 100).should == 2
|
765
|
+
@set.range_size('-inf', 'inf').should == 4
|
766
|
+
|
701
767
|
@set.delete_if{|m| m == 'b'}
|
702
768
|
@set.size.should == 3
|
703
769
|
end
|
@@ -9,7 +9,7 @@ class Roster
|
|
9
9
|
counter :available_slots, :start => 10
|
10
10
|
counter :pitchers, :limit => :max_pitchers
|
11
11
|
counter :basic
|
12
|
-
hash_key :contact_information
|
12
|
+
hash_key :contact_information, :marshal_keys=>{'updated_at'=>true}
|
13
13
|
lock :resort, :timeout => 2
|
14
14
|
value :starting_pitcher, :marshal => true
|
15
15
|
list :player_stats, :marshal => true
|
@@ -18,17 +18,19 @@ class Roster
|
|
18
18
|
|
19
19
|
# global class counters
|
20
20
|
counter :total_players_online, :global => true
|
21
|
-
list :all_player_stats, :global => true
|
22
21
|
set :all_players_online, :global => true
|
23
22
|
value :last_player, :global => true
|
24
23
|
|
25
24
|
# custom keys
|
26
25
|
counter :player_totals, :key => 'players/#{username}/total'
|
27
|
-
list :all_player_stats, :key => 'players:all_stats'
|
26
|
+
list :all_player_stats, :key => 'players:all_stats', :global => true
|
28
27
|
set :total_wins, :key => 'players:#{id}:all_stats'
|
29
28
|
value :my_rank, :key => 'players:my_rank:#{username}'
|
30
29
|
value :weird_key, :key => 'players:weird_key:#{raise}', :global => true
|
31
30
|
|
31
|
+
#callable as key
|
32
|
+
counter :daily, :global => true, :key => Proc.new { |roster| "#{roster.name}:#{Time.now.strftime('%Y-%m-%dT%H')}:daily" }
|
33
|
+
|
32
34
|
def initialize(id=1) @id = id end
|
33
35
|
def id; @id; end
|
34
36
|
def username; "user#{id}"; end
|
@@ -100,6 +102,8 @@ describe Redis::Objects do
|
|
100
102
|
@roster.total_wins.clear
|
101
103
|
@roster.my_rank.clear
|
102
104
|
|
105
|
+
@roster.daily.clear
|
106
|
+
|
103
107
|
@custom_roster.basic.reset
|
104
108
|
@custom_roster.special.reset
|
105
109
|
end
|
@@ -108,7 +112,7 @@ describe Redis::Objects do
|
|
108
112
|
Roster.redis.should == Redis::Objects.redis
|
109
113
|
# Roster.redis.should.be.kind_of(Redis)
|
110
114
|
end
|
111
|
-
|
115
|
+
|
112
116
|
it "should support interpolation of key names" do
|
113
117
|
@roster.player_totals.incr
|
114
118
|
@roster.redis.get('players/user1/total').should == '1'
|
@@ -123,6 +127,10 @@ describe Redis::Objects do
|
|
123
127
|
@roster.redis.get('players:my_rank:user1').should == 'a'
|
124
128
|
Roster.weird_key = 'tuka'
|
125
129
|
Roster.redis.get('players:weird_key:#{raise}').should == 'tuka'
|
130
|
+
|
131
|
+
k = "Roster:#{Time.now.strftime('%Y-%m-%dT%H')}:daily"
|
132
|
+
@roster.daily.incr
|
133
|
+
@roster.redis.get(k).should == '1'
|
126
134
|
end
|
127
135
|
|
128
136
|
it "should be able to get/set contact info" do
|
@@ -134,6 +142,10 @@ describe Redis::Objects do
|
|
134
142
|
@roster.contact_information.size.should == 2
|
135
143
|
end
|
136
144
|
|
145
|
+
it "should be marshalling hash keys" do
|
146
|
+
@roster.contact_information['updated_at'] = Time.now
|
147
|
+
@roster.contact_information['updated_at'].class.should == Time
|
148
|
+
end
|
137
149
|
|
138
150
|
|
139
151
|
it "should create counter accessors" do
|
@@ -327,7 +339,7 @@ describe Redis::Objects do
|
|
327
339
|
Roster.increment_counter(:badness, 2)
|
328
340
|
rescue => error
|
329
341
|
end
|
330
|
-
error.should.be.kind_of(
|
342
|
+
error.should.be.kind_of(NoMethodError)
|
331
343
|
|
332
344
|
error = nil
|
333
345
|
begin
|
@@ -758,6 +770,7 @@ describe Redis::Objects do
|
|
758
770
|
@custom_roster.basic.increment.should == 1
|
759
771
|
@roster2.basic.should == 0
|
760
772
|
CustomRoster.new.basic.should == 1
|
773
|
+
@custom_roster.basic.decrement.should == 0
|
761
774
|
end
|
762
775
|
|
763
776
|
it "should handle new subclass objects" do
|
@@ -767,10 +780,15 @@ describe Redis::Objects do
|
|
767
780
|
it "should allow passing of increment/decrement to super class" do
|
768
781
|
@custom_method_roster = CustomMethodRoster.new
|
769
782
|
@custom_method_roster.counter.should.be.nil
|
770
|
-
|
783
|
+
|
771
784
|
@custom_method_roster.increment(:counter).should == 42
|
772
|
-
|
785
|
+
|
773
786
|
@custom_method_roster.increment(:basic).should == 1
|
787
|
+
@custom_method_roster.basic.increment.should == 2
|
788
|
+
@custom_method_roster.decrement(:basic).should == 1
|
789
|
+
@custom_method_roster.basic.decrement.should == 0
|
790
|
+
@custom_method_roster.basic.reset.should.be.true
|
791
|
+
@custom_method_roster.basic.should == 0
|
774
792
|
@custom_method_roster.basic.should.be.kind_of(Redis::Counter)
|
775
793
|
end
|
776
794
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -12,3 +12,11 @@ UNIONSTORE_KEY = 'test:unionstore'
|
|
12
12
|
INTERSTORE_KEY = 'test:interstore'
|
13
13
|
DIFFSTORE_KEY = 'test:diffstore'
|
14
14
|
|
15
|
+
|
16
|
+
SORT_ORDER = {:order => 'desc alpha'}
|
17
|
+
SORT_LIMIT = {:limit => [2, 2]}
|
18
|
+
SORT_BY = {:by => 'm_*'}
|
19
|
+
SORT_GET = {:get => 'spec/*/sorted'}.merge!(SORT_LIMIT)
|
20
|
+
SORT_STORE = {:store => "spec/aftersort"}.merge!(SORT_GET)
|
21
|
+
|
22
|
+
|
metadata
CHANGED
@@ -1,12 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redis-objects
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
prerelease:
|
5
|
-
|
6
|
-
- 0
|
7
|
-
- 5
|
8
|
-
- 0
|
9
|
-
version: 0.5.0
|
4
|
+
prerelease:
|
5
|
+
version: 0.5.1
|
10
6
|
platform: ruby
|
11
7
|
authors:
|
12
8
|
- Nate Wiger
|
@@ -14,8 +10,7 @@ autorequire:
|
|
14
10
|
bindir: bin
|
15
11
|
cert_chain: []
|
16
12
|
|
17
|
-
date:
|
18
|
-
default_executable:
|
13
|
+
date: 2011-05-23 00:00:00 Z
|
19
14
|
dependencies:
|
20
15
|
- !ruby/object:Gem::Dependency
|
21
16
|
name: bacon
|
@@ -25,8 +20,6 @@ dependencies:
|
|
25
20
|
requirements:
|
26
21
|
- - ">="
|
27
22
|
- !ruby/object:Gem::Version
|
28
|
-
segments:
|
29
|
-
- 0
|
30
23
|
version: "0"
|
31
24
|
type: :development
|
32
25
|
version_requirements: *id001
|
@@ -38,10 +31,6 @@ dependencies:
|
|
38
31
|
requirements:
|
39
32
|
- - ">="
|
40
33
|
- !ruby/object:Gem::Version
|
41
|
-
segments:
|
42
|
-
- 2
|
43
|
-
- 1
|
44
|
-
- 1
|
45
34
|
version: 2.1.1
|
46
35
|
type: :runtime
|
47
36
|
version_requirements: *id002
|
@@ -54,7 +43,6 @@ extensions: []
|
|
54
43
|
extra_rdoc_files:
|
55
44
|
- README.rdoc
|
56
45
|
files:
|
57
|
-
- .gitignore
|
58
46
|
- ATOMICITY.rdoc
|
59
47
|
- CHANGELOG.rdoc
|
60
48
|
- README.rdoc
|
@@ -80,16 +68,16 @@ files:
|
|
80
68
|
- lib/redis/value.rb
|
81
69
|
- redis-objects.gemspec
|
82
70
|
- spec/redis_namespace_compat_spec.rb
|
71
|
+
- spec/redis_objects_active_record_spec.rb
|
83
72
|
- spec/redis_objects_instance_spec.rb
|
84
73
|
- spec/redis_objects_model_spec.rb
|
85
74
|
- spec/spec_helper.rb
|
86
|
-
has_rdoc: true
|
87
75
|
homepage: http://github.com/nateware/redis-objects
|
88
76
|
licenses: []
|
89
77
|
|
90
78
|
post_install_message:
|
91
|
-
rdoc_options:
|
92
|
-
|
79
|
+
rdoc_options: []
|
80
|
+
|
93
81
|
require_paths:
|
94
82
|
- lib
|
95
83
|
required_ruby_version: !ruby/object:Gem::Requirement
|
@@ -97,26 +85,19 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
97
85
|
requirements:
|
98
86
|
- - ">="
|
99
87
|
- !ruby/object:Gem::Version
|
100
|
-
segments:
|
101
|
-
- 0
|
102
88
|
version: "0"
|
103
89
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
104
90
|
none: false
|
105
91
|
requirements:
|
106
92
|
- - ">="
|
107
93
|
- !ruby/object:Gem::Version
|
108
|
-
segments:
|
109
|
-
- 0
|
110
94
|
version: "0"
|
111
95
|
requirements:
|
112
96
|
- redis, v2.1.1 or greater
|
113
97
|
rubyforge_project:
|
114
|
-
rubygems_version: 1.
|
98
|
+
rubygems_version: 1.7.2
|
115
99
|
signing_key:
|
116
100
|
specification_version: 3
|
117
101
|
summary: Map Redis types directly to Ruby objects
|
118
|
-
test_files:
|
119
|
-
|
120
|
-
- spec/redis_objects_instance_spec.rb
|
121
|
-
- spec/redis_objects_model_spec.rb
|
122
|
-
- spec/spec_helper.rb
|
102
|
+
test_files: []
|
103
|
+
|
data/.gitignore
DELETED