ohm 0.0.3 → 0.0.4
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/lib/ohm/redis.rb +245 -0
- data/lib/ohm/validations.rb +1 -1
- data/lib/ohm.rb +32 -24
- data/test/benchmarks.rb +42 -16
- data/test/db/dump.rdb +0 -0
- data/test/indices_test.rb +5 -5
- data/test/model_test.rb +11 -11
- data/test/redis_test.rb +314 -0
- data/test/test_helper.rb +8 -3
- data/test/validations_test.rb +1 -1
- metadata +5 -12
data/lib/ohm/redis.rb
ADDED
@@ -0,0 +1,245 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
begin
|
4
|
+
if (RUBY_VERSION >= '1.9')
|
5
|
+
require 'timeout'
|
6
|
+
RedisTimer = Timeout
|
7
|
+
else
|
8
|
+
require 'system_timer'
|
9
|
+
RedisTimer = SystemTimer
|
10
|
+
end
|
11
|
+
rescue LoadError
|
12
|
+
RedisTimer = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
module Ohm
|
16
|
+
class Redis
|
17
|
+
class ProtocolError < RuntimeError
|
18
|
+
def initialize(reply_type)
|
19
|
+
super("Protocol error, got '#{reply_type}' as initial reply byte")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
BulkCommands = {
|
24
|
+
:set => true,
|
25
|
+
:setnx => true,
|
26
|
+
:rpush => true,
|
27
|
+
:lpush => true,
|
28
|
+
:lset => true,
|
29
|
+
:lrem => true,
|
30
|
+
:sadd => true,
|
31
|
+
:srem => true,
|
32
|
+
:sismember => true,
|
33
|
+
:echo => true,
|
34
|
+
:getset => true,
|
35
|
+
:smove => true
|
36
|
+
}
|
37
|
+
|
38
|
+
ProcessorIdentity = lambda { |reply| reply }
|
39
|
+
ProcessorConvertToBool = lambda { |reply| reply == 0 ? false : reply }
|
40
|
+
ProcessorSplitKeys = lambda { |reply| reply.split(" ") }
|
41
|
+
ProcessorInfo = lambda do |reply|
|
42
|
+
info = Hash.new
|
43
|
+
reply.each_line do |line|
|
44
|
+
key, value = line.split(":", 2).map { |part| part.chomp }
|
45
|
+
info[key.to_sym] = value
|
46
|
+
end
|
47
|
+
info
|
48
|
+
end
|
49
|
+
|
50
|
+
ReplyProcessor = {
|
51
|
+
:exists => ProcessorConvertToBool,
|
52
|
+
:sismember=> ProcessorConvertToBool,
|
53
|
+
:sadd=> ProcessorConvertToBool,
|
54
|
+
:srem=> ProcessorConvertToBool,
|
55
|
+
:smove=> ProcessorConvertToBool,
|
56
|
+
:move=> ProcessorConvertToBool,
|
57
|
+
:setnx=> ProcessorConvertToBool,
|
58
|
+
:del=> ProcessorConvertToBool,
|
59
|
+
:renamenx=> ProcessorConvertToBool,
|
60
|
+
:expire=> ProcessorConvertToBool,
|
61
|
+
:keys => ProcessorSplitKeys,
|
62
|
+
:info => ProcessorInfo
|
63
|
+
}
|
64
|
+
|
65
|
+
ReplyProcessor.send(:initialize) do |hash, key|
|
66
|
+
hash[key] = ProcessorIdentity
|
67
|
+
end
|
68
|
+
|
69
|
+
def initialize(opts={})
|
70
|
+
@host = opts[:host] || '127.0.0.1'
|
71
|
+
@port = opts[:port] || 6379
|
72
|
+
@db = opts[:db] || 0
|
73
|
+
@timeout = opts[:timeout] || 0
|
74
|
+
connect
|
75
|
+
end
|
76
|
+
|
77
|
+
def to_s
|
78
|
+
"Redis Client connected to #{@host}:#{@port} against DB #{@db}"
|
79
|
+
end
|
80
|
+
|
81
|
+
# Shorthand for getting all the elements in a list.
|
82
|
+
def list(key)
|
83
|
+
call_command([:lrange, key, 0, -1])
|
84
|
+
end
|
85
|
+
|
86
|
+
# We need to define type because otherwise it will escape method_missing.
|
87
|
+
def type(key)
|
88
|
+
call_command([:type, key])
|
89
|
+
end
|
90
|
+
|
91
|
+
def sort(key, opts = {})
|
92
|
+
cmd = []
|
93
|
+
cmd << "SORT #{key}"
|
94
|
+
cmd << "BY #{opts[:by]}" if opts[:by]
|
95
|
+
cmd << "GET #{[opts[:get]].flatten * ' GET '}" if opts[:get]
|
96
|
+
cmd << "#{opts[:order]}" if opts[:order]
|
97
|
+
cmd << "LIMIT #{opts[:limit].join(' ')}" if opts[:limit]
|
98
|
+
call_command(cmd)
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def connect
|
104
|
+
connect_to(@host, @port, @timeout == 0 ? nil : @timeout)
|
105
|
+
call_command([:select, @db]) if @db != 0
|
106
|
+
end
|
107
|
+
|
108
|
+
def connect_to(host, port, timeout = nil)
|
109
|
+
|
110
|
+
# We support connect() timeout only if system_timer is availabe or
|
111
|
+
# if we are running against Ruby >= 1.9. Timeout reading from the
|
112
|
+
# socket instead will be supported anyway.
|
113
|
+
if @timeout != 0 and RedisTimer
|
114
|
+
begin
|
115
|
+
@sock = TCPSocket.new(host, port, 0)
|
116
|
+
rescue Timeout::Error
|
117
|
+
@sock = nil
|
118
|
+
raise Timeout::Error, "Timeout connecting to the server"
|
119
|
+
end
|
120
|
+
else
|
121
|
+
@sock = TCPSocket.new(host, port, 0)
|
122
|
+
end
|
123
|
+
|
124
|
+
@sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
|
125
|
+
|
126
|
+
# If the timeout is set we configure the low level socket options in
|
127
|
+
# order to make sure a blocking read will return after the specified
|
128
|
+
# number of seconds. This hack is from the Memcached Ruby client.
|
129
|
+
if timeout
|
130
|
+
secs = Integer(timeout)
|
131
|
+
usecs = Integer((timeout - secs) * 1_000_000)
|
132
|
+
optval = [secs, usecs].pack("l_2")
|
133
|
+
@sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
|
134
|
+
@sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def connected?
|
139
|
+
!! @sock
|
140
|
+
end
|
141
|
+
|
142
|
+
def disconnect
|
143
|
+
@sock.close
|
144
|
+
end
|
145
|
+
|
146
|
+
def reconnect
|
147
|
+
disconnect and connect
|
148
|
+
end
|
149
|
+
|
150
|
+
def method_missing(*argv)
|
151
|
+
call_command(argv)
|
152
|
+
end
|
153
|
+
|
154
|
+
|
155
|
+
# Wrap raw_call_command to handle reconnection on socket error. We
|
156
|
+
# try to reconnect just one time, otherwise let the error araise.
|
157
|
+
def call_command(argv)
|
158
|
+
connect unless connected?
|
159
|
+
raw_call_command(argv)
|
160
|
+
rescue Errno::ECONNRESET
|
161
|
+
reconnect
|
162
|
+
raw_call_command(argv)
|
163
|
+
end
|
164
|
+
|
165
|
+
def raw_call_command(argv)
|
166
|
+
bulk = extract_bulk_argument(argv)
|
167
|
+
@sock.write(argv.join(" ") + "\r\n")
|
168
|
+
@sock.write(bulk + "\r\n") if bulk
|
169
|
+
process_reply(argv[0])
|
170
|
+
end
|
171
|
+
|
172
|
+
def bulk_command?(argv)
|
173
|
+
BulkCommands[argv[0]] and argv.length > 1
|
174
|
+
end
|
175
|
+
|
176
|
+
def extract_bulk_argument(argv)
|
177
|
+
if bulk_command?(argv)
|
178
|
+
bulk = argv[-1].to_s
|
179
|
+
argv[-1] = bulk.length
|
180
|
+
bulk
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def process_reply(command)
|
185
|
+
ReplyProcessor[command][read_reply]
|
186
|
+
end
|
187
|
+
|
188
|
+
def read_reply
|
189
|
+
|
190
|
+
# We read the first byte using read() mainly because gets() is
|
191
|
+
# immune to raw socket timeouts.
|
192
|
+
begin
|
193
|
+
reply_type = @sock.read(1)
|
194
|
+
rescue Errno::EAGAIN
|
195
|
+
|
196
|
+
# We want to make sure it reconnects on the next command after the
|
197
|
+
# timeout. Otherwise the server may reply in the meantime leaving
|
198
|
+
# the protocol in a desync status.
|
199
|
+
@sock = nil
|
200
|
+
raise Errno::EAGAIN, "Timeout reading from the socket"
|
201
|
+
end
|
202
|
+
|
203
|
+
raise Errno::ECONNRESET, "Connection lost" unless reply_type
|
204
|
+
|
205
|
+
format_reply(reply_type, @sock.gets)
|
206
|
+
end
|
207
|
+
|
208
|
+
def format_reply(reply_type, line)
|
209
|
+
case reply_type
|
210
|
+
when "-" then format_error_reply(line)
|
211
|
+
when "+" then format_status_reply(line)
|
212
|
+
when ":" then format_integer_reply(line)
|
213
|
+
when "$" then format_bulk_reply(line)
|
214
|
+
when "*" then format_multi_bulk_reply(line)
|
215
|
+
else raise ProtocolError.new(reply_type)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def format_error_reply(line)
|
220
|
+
raise "-" + line.strip
|
221
|
+
end
|
222
|
+
|
223
|
+
def format_status_reply(line)
|
224
|
+
line.strip
|
225
|
+
end
|
226
|
+
|
227
|
+
def format_integer_reply(line)
|
228
|
+
line.to_i
|
229
|
+
end
|
230
|
+
|
231
|
+
def format_bulk_reply(line)
|
232
|
+
bulklen = line.to_i
|
233
|
+
return nil if bulklen == -1
|
234
|
+
reply = @sock.read(bulklen)
|
235
|
+
@sock.read(2) # Discard CRLF.
|
236
|
+
reply
|
237
|
+
end
|
238
|
+
|
239
|
+
def format_multi_bulk_reply(line)
|
240
|
+
reply = []
|
241
|
+
line.to_i.times { reply << read_reply }
|
242
|
+
reply
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
data/lib/ohm/validations.rb
CHANGED
data/lib/ohm.rb
CHANGED
@@ -1,13 +1,24 @@
|
|
1
|
-
require "
|
2
|
-
require "redis"
|
1
|
+
require File.join(File.dirname(__FILE__), "ohm", "redis")
|
3
2
|
require File.join(File.dirname(__FILE__), "ohm", "validations")
|
4
3
|
|
5
4
|
module Ohm
|
5
|
+
def redis
|
6
|
+
@redis
|
7
|
+
end
|
8
|
+
|
9
|
+
def connect(*attrs)
|
10
|
+
@redis = Ohm::Redis.new(*attrs)
|
11
|
+
end
|
12
|
+
|
13
|
+
def flush
|
14
|
+
@redis.flushdb
|
15
|
+
end
|
16
|
+
|
6
17
|
def key(*args)
|
7
18
|
args.join(":")
|
8
19
|
end
|
9
20
|
|
10
|
-
module_function :key
|
21
|
+
module_function :key, :connect, :flush, :redis
|
11
22
|
|
12
23
|
module Attributes
|
13
24
|
class Collection < Array
|
@@ -22,29 +33,29 @@ module Ohm
|
|
22
33
|
|
23
34
|
class List < Collection
|
24
35
|
def retrieve
|
25
|
-
db.
|
36
|
+
db.list(key)
|
26
37
|
end
|
27
38
|
|
28
39
|
def << value
|
29
|
-
super(value) if db.
|
40
|
+
super(value) if db.rpush(key, value)
|
30
41
|
end
|
31
42
|
end
|
32
43
|
|
33
44
|
class Set < Collection
|
34
45
|
def retrieve
|
35
|
-
db.
|
46
|
+
db.smembers(key).sort
|
36
47
|
end
|
37
48
|
|
38
49
|
def << value
|
39
|
-
super(value) if db.
|
50
|
+
super(value) if db.sadd(key, value)
|
40
51
|
end
|
41
52
|
|
42
53
|
def delete(value)
|
43
|
-
super(value) if db.
|
54
|
+
super(value) if db.srem(key, value)
|
44
55
|
end
|
45
56
|
|
46
57
|
def include?(value)
|
47
|
-
db.
|
58
|
+
db.sismember(key, value)
|
48
59
|
end
|
49
60
|
end
|
50
61
|
end
|
@@ -55,7 +66,7 @@ module Ohm
|
|
55
66
|
|
56
67
|
def assert_unique(attrs)
|
57
68
|
index_key = index_key_for(attrs, read_locals(attrs))
|
58
|
-
assert(db.
|
69
|
+
assert(db.scard(index_key).zero? || db.sismember(index_key, id), [attrs, :not_unique])
|
59
70
|
end
|
60
71
|
end
|
61
72
|
|
@@ -135,10 +146,7 @@ module Ohm
|
|
135
146
|
new(*args).create
|
136
147
|
end
|
137
148
|
|
138
|
-
# TODO Add a method that receives several arguments and returns a
|
139
|
-
# string with the values separated by colons.
|
140
149
|
def self.find(attribute, value)
|
141
|
-
# filter("#{attribute}:#{value}")
|
142
150
|
filter(Ohm.key(attribute, value))
|
143
151
|
end
|
144
152
|
|
@@ -200,7 +208,7 @@ module Ohm
|
|
200
208
|
private
|
201
209
|
|
202
210
|
def self.db
|
203
|
-
|
211
|
+
Ohm.redis
|
204
212
|
end
|
205
213
|
|
206
214
|
def self.key(*args)
|
@@ -208,13 +216,13 @@ module Ohm
|
|
208
216
|
end
|
209
217
|
|
210
218
|
def self.filter(name)
|
211
|
-
db.
|
219
|
+
db.smembers(key(name)).map do |id|
|
212
220
|
new(:id => id)
|
213
221
|
end
|
214
222
|
end
|
215
223
|
|
216
224
|
def self.exists?(id)
|
217
|
-
db.
|
225
|
+
db.sismember(key(:all), id)
|
218
226
|
end
|
219
227
|
|
220
228
|
def initialize_id
|
@@ -222,21 +230,21 @@ module Ohm
|
|
222
230
|
end
|
223
231
|
|
224
232
|
def db
|
225
|
-
|
233
|
+
Ohm.redis
|
226
234
|
end
|
227
235
|
|
228
236
|
def delete_attributes(atts)
|
229
237
|
atts.each do |att|
|
230
|
-
db.
|
238
|
+
db.del(key(att))
|
231
239
|
end
|
232
240
|
end
|
233
241
|
|
234
242
|
def create_model_membership
|
235
|
-
db.
|
243
|
+
db.sadd(self.class.key(:all), id)
|
236
244
|
end
|
237
245
|
|
238
246
|
def delete_model_membership
|
239
|
-
db.
|
247
|
+
db.srem(self.class.key(:all), id)
|
240
248
|
end
|
241
249
|
|
242
250
|
def save!
|
@@ -251,13 +259,13 @@ module Ohm
|
|
251
259
|
|
252
260
|
def add_to_indices
|
253
261
|
indices.each do |attrs|
|
254
|
-
db.
|
262
|
+
db.sadd(index_key_for(attrs, read_locals(attrs)), id)
|
255
263
|
end
|
256
264
|
end
|
257
265
|
|
258
266
|
def delete_from_indices
|
259
267
|
indices.each do |attrs|
|
260
|
-
db.
|
268
|
+
db.srem(index_key_for(attrs, read_remotes(attrs)), id)
|
261
269
|
end
|
262
270
|
end
|
263
271
|
|
@@ -270,11 +278,11 @@ module Ohm
|
|
270
278
|
end
|
271
279
|
|
272
280
|
def read_remote(att)
|
273
|
-
id && db
|
281
|
+
id && db.get(key(att))
|
274
282
|
end
|
275
283
|
|
276
284
|
def write_remote(att, value)
|
277
|
-
db
|
285
|
+
db.set(key(att), value)
|
278
286
|
end
|
279
287
|
|
280
288
|
def read_locals(attrs)
|
data/test/benchmarks.rb
CHANGED
@@ -1,9 +1,12 @@
|
|
1
1
|
require "rubygems"
|
2
2
|
require "bench"
|
3
3
|
require File.dirname(__FILE__) + "/../lib/ohm"
|
4
|
+
require "redis"
|
4
5
|
|
5
|
-
|
6
|
-
|
6
|
+
Ohm.connect(:port => 6381)
|
7
|
+
Ohm.flush
|
8
|
+
|
9
|
+
$r = Redis.new(:port => 6381)
|
7
10
|
|
8
11
|
class Event < Ohm::Model
|
9
12
|
attribute :name
|
@@ -17,36 +20,59 @@ end
|
|
17
20
|
event = Event.create(:name => "Ruby Tuesday")
|
18
21
|
array = []
|
19
22
|
|
20
|
-
benchmark "
|
21
|
-
|
23
|
+
benchmark "add to set with ohm redis" do
|
24
|
+
Ohm.redis.sadd("foo", 1)
|
25
|
+
end
|
26
|
+
|
27
|
+
benchmark "add to set with redis" do
|
28
|
+
$r.set_add("foo", 1)
|
22
29
|
end
|
23
30
|
|
24
|
-
benchmark "
|
31
|
+
benchmark "add to set with ohm" do
|
25
32
|
event.attendees << 1
|
26
33
|
end
|
27
34
|
|
28
|
-
|
29
|
-
|
35
|
+
Ohm.redis.sadd("bar", 1)
|
36
|
+
Ohm.redis.sadd("bar", 2)
|
37
|
+
|
38
|
+
benchmark "retrieve a set of two members with ohm redis" do
|
39
|
+
Ohm.redis.sadd("bar", 3)
|
40
|
+
Ohm.redis.srem("bar", 3)
|
41
|
+
Ohm.redis.smembers("bar")
|
42
|
+
end
|
43
|
+
|
44
|
+
$r.set_add("bar", 1)
|
45
|
+
$r.set_add("bar", 2)
|
46
|
+
|
47
|
+
benchmark "retrieve a set of two members with redis" do
|
48
|
+
$r.set_add("bar", 3)
|
49
|
+
$r.set_delete("bar", 3)
|
50
|
+
$r.set_members("bar")
|
30
51
|
end
|
31
52
|
|
32
|
-
|
33
|
-
$
|
53
|
+
Ohm.redis.del("Event:#{event.id}:attendees")
|
54
|
+
$r.delete("Event:#{event.id}:attendees")
|
55
|
+
|
56
|
+
event.attendees << 1
|
57
|
+
event.attendees << 2
|
34
58
|
|
35
|
-
benchmark "retrieve a set of two members" do
|
36
|
-
|
59
|
+
benchmark "retrieve a set of two members with ohm" do
|
60
|
+
event.attendees << 3
|
61
|
+
event.attendees.delete(3)
|
62
|
+
event.attendees
|
37
63
|
end
|
38
64
|
|
39
65
|
benchmark "retrieve membership status and set count" do
|
40
|
-
|
41
|
-
|
66
|
+
Ohm.redis.scard("bar")
|
67
|
+
Ohm.redis.sismember("bar", "1")
|
42
68
|
end
|
43
69
|
|
44
70
|
benchmark "retrieve set count" do
|
45
|
-
|
71
|
+
Ohm.redis.scard("bar").zero?
|
46
72
|
end
|
47
73
|
|
48
74
|
benchmark "retrieve membership status" do
|
49
|
-
|
75
|
+
Ohm.redis.sismember("bar", "1")
|
50
76
|
end
|
51
77
|
|
52
|
-
run
|
78
|
+
run 10_000
|
data/test/db/dump.rdb
CHANGED
Binary file
|
data/test/indices_test.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require File.dirname(__FILE__)
|
1
|
+
require File.join(File.dirname(__FILE__), "test_helper")
|
2
2
|
|
3
3
|
class IndicesTest < Test::Unit::TestCase
|
4
4
|
class User < Ohm::Model
|
@@ -9,14 +9,14 @@ class IndicesTest < Test::Unit::TestCase
|
|
9
9
|
|
10
10
|
context "A model with an indexed attribute" do
|
11
11
|
setup do
|
12
|
-
|
12
|
+
Ohm.redis.flushdb
|
13
13
|
|
14
14
|
@user1 = User.create(:email => "foo")
|
15
15
|
@user2 = User.create(:email => "bar")
|
16
16
|
end
|
17
17
|
|
18
18
|
should "be able to find by the given attribute" do
|
19
|
-
assert_equal [@user1], User.find(:email, "foo")
|
19
|
+
assert_equal [@user1], User.find(:email, "foo")
|
20
20
|
end
|
21
21
|
|
22
22
|
should "update indices when changing attribute values" do
|
@@ -24,13 +24,13 @@ class IndicesTest < Test::Unit::TestCase
|
|
24
24
|
@user1.save
|
25
25
|
|
26
26
|
assert_equal [], User.find(:email, "foo").to_a
|
27
|
-
assert_equal [@user1], User.find(:email, "baz")
|
27
|
+
assert_equal [@user1], User.find(:email, "baz")
|
28
28
|
end
|
29
29
|
|
30
30
|
should "remove from the index after deleting" do
|
31
31
|
@user2.delete
|
32
32
|
|
33
|
-
assert_equal [], User.find(:email, "bar")
|
33
|
+
assert_equal [], User.find(:email, "bar")
|
34
34
|
end
|
35
35
|
end
|
36
36
|
end
|
data/test/model_test.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require File.dirname(__FILE__)
|
1
|
+
require File.join(File.dirname(__FILE__), "test_helper")
|
2
2
|
|
3
3
|
class Event < Ohm::Model
|
4
4
|
attribute :name
|
@@ -32,8 +32,8 @@ class TestRedis < Test::Unit::TestCase
|
|
32
32
|
|
33
33
|
context "Finding an event" do
|
34
34
|
setup do
|
35
|
-
|
36
|
-
|
35
|
+
Ohm.redis.sadd("Event", 1)
|
36
|
+
Ohm.redis.set("Event:1:name", "Concert")
|
37
37
|
end
|
38
38
|
|
39
39
|
should "return an instance of Event" do
|
@@ -45,8 +45,8 @@ class TestRedis < Test::Unit::TestCase
|
|
45
45
|
|
46
46
|
context "Finding a user" do
|
47
47
|
setup do
|
48
|
-
|
49
|
-
|
48
|
+
Ohm.redis.sadd("User:all", 1)
|
49
|
+
Ohm.redis.set("User:1:email", "albert@example.com")
|
50
50
|
end
|
51
51
|
|
52
52
|
should "return an instance of User" do
|
@@ -58,8 +58,8 @@ class TestRedis < Test::Unit::TestCase
|
|
58
58
|
|
59
59
|
context "Updating a user" do
|
60
60
|
setup do
|
61
|
-
|
62
|
-
|
61
|
+
Ohm.redis.sadd("User:all", 1)
|
62
|
+
Ohm.redis.set("User:1:email", "albert@example.com")
|
63
63
|
|
64
64
|
@user = User[1]
|
65
65
|
end
|
@@ -131,10 +131,10 @@ class TestRedis < Test::Unit::TestCase
|
|
131
131
|
|
132
132
|
@model.delete
|
133
133
|
|
134
|
-
assert_nil
|
135
|
-
assert_nil
|
136
|
-
assert_equal
|
137
|
-
assert_equal Array.new,
|
134
|
+
assert_nil Ohm.redis.get(ModelToBeDeleted.key(id))
|
135
|
+
assert_nil Ohm.redis.get(ModelToBeDeleted.key(id, :name))
|
136
|
+
assert_equal Array.new, Ohm.redis.smembers(ModelToBeDeleted.key(id, :foos))
|
137
|
+
assert_equal Array.new, Ohm.redis.list(ModelToBeDeleted.key(id, :bars))
|
138
138
|
|
139
139
|
assert ModelToBeDeleted.all.empty?
|
140
140
|
end
|
data/test/redis_test.rb
ADDED
@@ -0,0 +1,314 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "test_helper")
|
2
|
+
|
3
|
+
class Foo
|
4
|
+
attr_accessor :bar
|
5
|
+
def initialize(bar)
|
6
|
+
@bar = bar
|
7
|
+
end
|
8
|
+
|
9
|
+
def ==(other)
|
10
|
+
@bar == other.bar
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class RedisTest < Test::Unit::TestCase
|
15
|
+
describe "redis" do
|
16
|
+
setup do
|
17
|
+
@r ||= Ohm.redis
|
18
|
+
@r.set("foo", "bar")
|
19
|
+
end
|
20
|
+
|
21
|
+
teardown do
|
22
|
+
@r.flushdb
|
23
|
+
end
|
24
|
+
|
25
|
+
should "should be able to GET a key" do
|
26
|
+
assert_equal "bar", @r.get("foo")
|
27
|
+
end
|
28
|
+
|
29
|
+
should "should be able to SET a key" do
|
30
|
+
@r.set("foo", "nik")
|
31
|
+
assert_equal "nik", @r.get("foo")
|
32
|
+
end
|
33
|
+
|
34
|
+
should "should be able to SETNX(setnx)" do
|
35
|
+
@r.set("foo", "nik")
|
36
|
+
assert_equal "nik", @r.get("foo")
|
37
|
+
@r.setnx("foo", "bar")
|
38
|
+
assert_equal "nik", @r.get("foo")
|
39
|
+
end
|
40
|
+
|
41
|
+
should "should be able to INCR(increment) a key" do
|
42
|
+
@r.del("counter")
|
43
|
+
assert_equal 1, @r.incr("counter")
|
44
|
+
assert_equal 2, @r.incr("counter")
|
45
|
+
assert_equal 3, @r.incr("counter")
|
46
|
+
end
|
47
|
+
|
48
|
+
should "should be able to DECR(decrement) a key" do
|
49
|
+
@r.del("counter")
|
50
|
+
assert_equal 1, @r.incr("counter")
|
51
|
+
assert_equal 2, @r.incr("counter")
|
52
|
+
assert_equal 3, @r.incr("counter")
|
53
|
+
assert_equal 2, @r.decr("counter")
|
54
|
+
assert_equal 0, @r.decrby("counter", 2)
|
55
|
+
end
|
56
|
+
|
57
|
+
should "should be able to RANDKEY(return a random key)" do
|
58
|
+
assert_not_nil @r.randomkey
|
59
|
+
end
|
60
|
+
|
61
|
+
should "should be able to RENAME a key" do
|
62
|
+
@r.del "foo"
|
63
|
+
@r.del "bar"
|
64
|
+
@r.set("foo", "hi")
|
65
|
+
@r.rename "foo", "bar"
|
66
|
+
assert_equal "hi", @r.get("bar")
|
67
|
+
end
|
68
|
+
|
69
|
+
should "should be able to RENAMENX(rename unless the new key already exists) a key" do
|
70
|
+
@r.del "foo"
|
71
|
+
@r.del "bar"
|
72
|
+
@r.set("foo", "hi")
|
73
|
+
@r.set("bar", "ohai")
|
74
|
+
|
75
|
+
@r.renamenx "foo", "bar"
|
76
|
+
|
77
|
+
assert_equal "ohai", @r.get("bar")
|
78
|
+
end
|
79
|
+
|
80
|
+
should "should be able to EXISTS(check if key exists)" do
|
81
|
+
@r.set("foo", "nik")
|
82
|
+
assert @r.exists("foo")
|
83
|
+
@r.del "foo"
|
84
|
+
assert_equal false, @r.exists("foo")
|
85
|
+
end
|
86
|
+
|
87
|
+
should "should be able to KEYS(glob for keys)" do
|
88
|
+
@r.keys("f*").each do |key|
|
89
|
+
@r.del key
|
90
|
+
end
|
91
|
+
@r.set("f", "nik")
|
92
|
+
@r.set("fo", "nak")
|
93
|
+
@r.set("foo", "qux")
|
94
|
+
assert_equal ["f","fo", "foo"], @r.keys("f*").sort
|
95
|
+
end
|
96
|
+
|
97
|
+
should "should be able to check the TYPE of a key" do
|
98
|
+
@r.set("foo", "nik")
|
99
|
+
assert_equal "string", @r.type("foo")
|
100
|
+
@r.del "foo"
|
101
|
+
assert_equal "none", @r.type("foo")
|
102
|
+
end
|
103
|
+
|
104
|
+
should "should be able to push to the head of a list" do
|
105
|
+
@r.lpush "list", "hello"
|
106
|
+
@r.lpush "list", 42
|
107
|
+
assert_equal "list", @r.type("list")
|
108
|
+
assert_equal 2, @r.llen("list")
|
109
|
+
assert_equal "42", @r.lpop("list")
|
110
|
+
@r.del("list")
|
111
|
+
end
|
112
|
+
|
113
|
+
should "should be able to push to the tail of a list" do
|
114
|
+
@r.rpush "list", "hello"
|
115
|
+
assert_equal "list", @r.type("list")
|
116
|
+
assert_equal 1, @r.llen("list")
|
117
|
+
@r.del("list")
|
118
|
+
end
|
119
|
+
|
120
|
+
should "should be able to pop the tail of a list" do
|
121
|
+
@r.rpush "list", "hello"
|
122
|
+
@r.rpush "list", "goodbye"
|
123
|
+
assert_equal "list", @r.type("list")
|
124
|
+
assert_equal 2, @r.llen("list")
|
125
|
+
assert_equal "goodbye", @r.rpop("list")
|
126
|
+
@r.del("list")
|
127
|
+
end
|
128
|
+
|
129
|
+
should "should be able to pop the head of a list" do
|
130
|
+
@r.rpush "list", "hello"
|
131
|
+
@r.rpush "list", "goodbye"
|
132
|
+
assert_equal "list", @r.type("list")
|
133
|
+
assert_equal 2, @r.llen("list")
|
134
|
+
assert_equal "hello", @r.lpop("list")
|
135
|
+
@r.del("list")
|
136
|
+
end
|
137
|
+
|
138
|
+
should "should be able to get the length of a list" do
|
139
|
+
@r.rpush "list", "hello"
|
140
|
+
@r.rpush "list", "goodbye"
|
141
|
+
assert_equal "list", @r.type("list")
|
142
|
+
assert_equal 2, @r.llen("list")
|
143
|
+
@r.del("list")
|
144
|
+
end
|
145
|
+
|
146
|
+
should "should be able to get a range of values from a list" do
|
147
|
+
@r.rpush "list", "hello"
|
148
|
+
@r.rpush "list", "goodbye"
|
149
|
+
@r.rpush "list", "1"
|
150
|
+
@r.rpush "list", "2"
|
151
|
+
@r.rpush "list", "3"
|
152
|
+
assert_equal "list", @r.type("list")
|
153
|
+
assert_equal 5, @r.llen("list")
|
154
|
+
assert_equal ["1", "2", "3"], @r.lrange("list", 2, -1)
|
155
|
+
@r.del("list")
|
156
|
+
end
|
157
|
+
|
158
|
+
should "should be able to get all the values from a list" do
|
159
|
+
@r.rpush "list", "1"
|
160
|
+
@r.rpush "list", "2"
|
161
|
+
@r.rpush "list", "3"
|
162
|
+
assert_equal "list", @r.type("list")
|
163
|
+
assert_equal 3, @r.llen("list")
|
164
|
+
assert_equal ["1", "2", "3"], @r.list("list")
|
165
|
+
@r.del("list")
|
166
|
+
end
|
167
|
+
|
168
|
+
should "should be able to trim a list" do
|
169
|
+
@r.rpush "list", "hello"
|
170
|
+
@r.rpush "list", "goodbye"
|
171
|
+
@r.rpush "list", "1"
|
172
|
+
@r.rpush "list", "2"
|
173
|
+
@r.rpush "list", "3"
|
174
|
+
assert_equal "list", @r.type("list")
|
175
|
+
assert_equal 5, @r.llen("list")
|
176
|
+
@r.ltrim "list", 0, 1
|
177
|
+
assert_equal 2, @r.llen("list")
|
178
|
+
assert_equal ["hello", "goodbye"], @r.lrange("list", 0, -1)
|
179
|
+
@r.del("list")
|
180
|
+
end
|
181
|
+
|
182
|
+
should "should be able to get a value by indexing into a list" do
|
183
|
+
@r.rpush "list", "hello"
|
184
|
+
@r.rpush "list", "goodbye"
|
185
|
+
assert_equal "list", @r.type("list")
|
186
|
+
assert_equal 2, @r.llen("list")
|
187
|
+
assert_equal "goodbye", @r.lindex("list", 1)
|
188
|
+
@r.del("list")
|
189
|
+
end
|
190
|
+
|
191
|
+
should "should be able to set a value by indexing into a list" do
|
192
|
+
@r.rpush "list", "hello"
|
193
|
+
@r.rpush "list", "hello"
|
194
|
+
assert_equal "list", @r.type("list")
|
195
|
+
assert_equal 2, @r.llen("list")
|
196
|
+
assert @r.lset("list", 1, "goodbye")
|
197
|
+
assert_equal "goodbye", @r.lindex("list", 1)
|
198
|
+
@r.del("list")
|
199
|
+
end
|
200
|
+
|
201
|
+
should "should be able to remove values from a list LREM" do
|
202
|
+
@r.rpush "list", "hello"
|
203
|
+
@r.rpush "list", "goodbye"
|
204
|
+
assert_equal "list", @r.type("list")
|
205
|
+
assert_equal 2, @r.llen("list")
|
206
|
+
assert_equal 1, @r.lrem("list", 1, "hello")
|
207
|
+
assert_equal ["goodbye"], @r.lrange("list", 0, -1)
|
208
|
+
@r.del("list")
|
209
|
+
end
|
210
|
+
|
211
|
+
should "should be able add members to a set" do
|
212
|
+
@r.sadd "set", "key1"
|
213
|
+
@r.sadd "set", "key2"
|
214
|
+
assert_equal "set", @r.type("set")
|
215
|
+
assert_equal 2, @r.scard("set")
|
216
|
+
assert_equal ["key1", "key2"], @r.smembers("set").sort
|
217
|
+
@r.del("set")
|
218
|
+
end
|
219
|
+
|
220
|
+
should "should be able delete members to a set" do
|
221
|
+
@r.sadd "set", "key1"
|
222
|
+
@r.sadd "set", "key2"
|
223
|
+
assert_equal "set", @r.type("set")
|
224
|
+
assert_equal 2, @r.scard("set")
|
225
|
+
assert_equal ["key1", "key2"], @r.smembers("set").sort
|
226
|
+
@r.srem("set", "key1")
|
227
|
+
assert_equal 1, @r.scard("set")
|
228
|
+
assert_equal ["key2"], @r.smembers("set")
|
229
|
+
@r.del("set")
|
230
|
+
end
|
231
|
+
|
232
|
+
should "should be able count the members of a set" do
|
233
|
+
@r.sadd "set", "key1"
|
234
|
+
@r.sadd "set", "key2"
|
235
|
+
assert_equal "set", @r.type("set")
|
236
|
+
assert_equal 2, @r.scard("set")
|
237
|
+
@r.del("set")
|
238
|
+
end
|
239
|
+
|
240
|
+
should "should be able test for set membership" do
|
241
|
+
@r.sadd "set", "key1"
|
242
|
+
@r.sadd "set", "key2"
|
243
|
+
assert_equal "set", @r.type("set")
|
244
|
+
assert_equal 2, @r.scard("set")
|
245
|
+
assert @r.sismember("set", "key1")
|
246
|
+
assert @r.sismember("set", "key2")
|
247
|
+
assert_equal false, @r.sismember("set", "notthere")
|
248
|
+
@r.del("set")
|
249
|
+
end
|
250
|
+
|
251
|
+
should "should be able to do set intersection" do
|
252
|
+
@r.sadd "set", "key1"
|
253
|
+
@r.sadd "set", "key2"
|
254
|
+
@r.sadd "set2", "key2"
|
255
|
+
assert_equal ["key2"], @r.sinter("set", "set2")
|
256
|
+
@r.del("set")
|
257
|
+
end
|
258
|
+
|
259
|
+
should "should be able to do set intersection and store the results in a key" do
|
260
|
+
@r.sadd "set", "key1"
|
261
|
+
@r.sadd "set", "key2"
|
262
|
+
@r.sadd "set2", "key2"
|
263
|
+
@r.sinterstore("newone", "set", "set2")
|
264
|
+
assert_equal ["key2"], @r.smembers("newone")
|
265
|
+
@r.del("set")
|
266
|
+
end
|
267
|
+
|
268
|
+
should "should be able to do crazy SORT queries" do
|
269
|
+
@r.set("dog_1", "louie")
|
270
|
+
@r.rpush "dogs", 1
|
271
|
+
@r.set("dog_2", "lucy")
|
272
|
+
@r.rpush "dogs", 2
|
273
|
+
@r.set("dog_3", "max")
|
274
|
+
@r.rpush "dogs", 3
|
275
|
+
@r.set("dog_4", "taj")
|
276
|
+
@r.rpush "dogs", 4
|
277
|
+
assert_equal ["louie"], @r.sort("dogs", :get => "dog_*", :limit => [0,1])
|
278
|
+
assert_equal ["taj"], @r.sort("dogs", :get => "dog_*", :limit => [0,1], :order => "desc alpha")
|
279
|
+
end
|
280
|
+
|
281
|
+
should "should provide info" do
|
282
|
+
[:last_save_time, :redis_version, :total_connections_received, :connected_clients, :total_commands_processed, :connected_slaves, :uptime_in_seconds, :used_memory, :uptime_in_days, :changes_since_last_save].each do |x|
|
283
|
+
assert @r.info.keys.include?(x)
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
should "should be able to flush the database" do
|
288
|
+
@r.set("key1", "keyone")
|
289
|
+
@r.set("key2", "keytwo")
|
290
|
+
assert_equal ["foo", "key1", "key2"], @r.keys("*").sort #foo from before
|
291
|
+
@r.flushdb
|
292
|
+
assert_equal [], @r.keys("*")
|
293
|
+
end
|
294
|
+
|
295
|
+
should "should be able to provide the last save time" do
|
296
|
+
savetime = @r.lastsave
|
297
|
+
assert_equal Time, Time.at(savetime).class
|
298
|
+
assert Time.at(savetime) <= Time.now
|
299
|
+
end
|
300
|
+
|
301
|
+
should "should be able to MGET keys" do
|
302
|
+
@r.set("foo", 1000)
|
303
|
+
@r.set("bar", 2000)
|
304
|
+
assert_equal ["1000", "2000"], @r.mget("foo", "bar")
|
305
|
+
assert_equal ["1000", "2000", nil], @r.mget("foo", "bar", "baz")
|
306
|
+
end
|
307
|
+
|
308
|
+
should "should bgsave" do
|
309
|
+
assert_nothing_raised do
|
310
|
+
@r.bgsave
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
require "rubygems"
|
2
|
-
|
2
|
+
|
3
|
+
begin
|
4
|
+
require "ruby-debug"
|
5
|
+
rescue LoadError
|
6
|
+
end
|
7
|
+
|
3
8
|
require "contest"
|
4
9
|
require File.dirname(__FILE__) + "/../lib/ohm"
|
5
10
|
|
6
|
-
|
7
|
-
|
11
|
+
Ohm.connect(:port => 6381)
|
12
|
+
Ohm.flush
|
data/test/validations_test.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ohm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michel Martens, Damian Janowski
|
@@ -11,17 +11,8 @@ cert_chain: []
|
|
11
11
|
|
12
12
|
date: 2009-03-13 00:00:00 -02:00
|
13
13
|
default_executable:
|
14
|
-
dependencies:
|
15
|
-
|
16
|
-
name: ezmobius-redis-rb
|
17
|
-
type: :runtime
|
18
|
-
version_requirement:
|
19
|
-
version_requirements: !ruby/object:Gem::Requirement
|
20
|
-
requirements:
|
21
|
-
- - ">="
|
22
|
-
- !ruby/object:Gem::Version
|
23
|
-
version: 0.0.3
|
24
|
-
version:
|
14
|
+
dependencies: []
|
15
|
+
|
25
16
|
description:
|
26
17
|
email: michel@soveran.com
|
27
18
|
executables: []
|
@@ -31,6 +22,7 @@ extensions: []
|
|
31
22
|
extra_rdoc_files: []
|
32
23
|
|
33
24
|
files:
|
25
|
+
- lib/ohm/redis.rb
|
34
26
|
- lib/ohm/validations.rb
|
35
27
|
- lib/ohm.rb
|
36
28
|
- README.markdown
|
@@ -42,6 +34,7 @@ files:
|
|
42
34
|
- test/db/redis.pid
|
43
35
|
- test/indices_test.rb
|
44
36
|
- test/model_test.rb
|
37
|
+
- test/redis_test.rb
|
45
38
|
- test/test.conf
|
46
39
|
- test/test_helper.rb
|
47
40
|
- test/validations_test.rb
|