redis-model 0.1.0
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/.gitignore +3 -0
- data/README.markdown +46 -0
- data/Rakefile +30 -0
- data/VERSION +1 -0
- data/bench.rb +36 -0
- data/examples/model.rb +40 -0
- data/lib/redis/model.rb +364 -0
- data/spec/redis/model_spec.rb +188 -0
- data/spec/spec_helper.rb +4 -0
- metadata +65 -0
data/.gitignore
ADDED
data/README.markdown
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
## redis-model
|
2
|
+
|
3
|
+
Minimal model support for [redis-rb](http://github.com/ezmobius/redis-rb).
|
4
|
+
Directly maps ruby properties to `model_name:id:field_name` keys in redis.
|
5
|
+
Scalar, list and set properties are supported.
|
6
|
+
|
7
|
+
Values can be marshaled to/from `Integer`, `Float`, `DateTime`, `JSON`. See `Redis::Model::Marshal` for more info.
|
8
|
+
|
9
|
+
### Define
|
10
|
+
|
11
|
+
require 'redis/model'
|
12
|
+
|
13
|
+
class User < Redis::Model
|
14
|
+
value :name, :string
|
15
|
+
value :created, :datetime
|
16
|
+
value :profile, :json
|
17
|
+
|
18
|
+
list :posts, :json
|
19
|
+
|
20
|
+
set :followers, :int
|
21
|
+
end
|
22
|
+
|
23
|
+
### Write
|
24
|
+
|
25
|
+
u = User.with_key(1)
|
26
|
+
u.name = 'Joe' # set user:1:name Joe
|
27
|
+
u.created = DateTime.now # set user:1:created 2009-10-05T12:09:56+0400
|
28
|
+
u.profile = { # set user:1:profile {"sex":"M","about":"Lorem","age":23}
|
29
|
+
:age => 23,
|
30
|
+
:sex => 'M',
|
31
|
+
:about => 'Lorem'
|
32
|
+
}
|
33
|
+
u.posts << { # rpush user:1:posts {"title":"Hello world!","text":"lorem"}
|
34
|
+
:title => "Hello world!",
|
35
|
+
:text => "lorem"
|
36
|
+
}
|
37
|
+
u.followers << 2 # sadd user:1:followers 2
|
38
|
+
|
39
|
+
### Read
|
40
|
+
|
41
|
+
u = User.with_key(1)
|
42
|
+
p u.name # get user:1:name
|
43
|
+
p u.created.strftime('%m/%d/%Y') # get user:1:created
|
44
|
+
p u.posts[0,20] # lrange user:1:posts 0 20
|
45
|
+
p u.posts[0] # lindex user:1:posts 0
|
46
|
+
p u.followers.has_key?(2) # sismember user:1:followers 2
|
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'spec/rake/spectask'
|
3
|
+
|
4
|
+
task :default => :spec
|
5
|
+
|
6
|
+
desc "Run specs"
|
7
|
+
Spec::Rake::SpecTask.new do |t|
|
8
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
9
|
+
t.spec_opts = %w(-fs --color)
|
10
|
+
end
|
11
|
+
|
12
|
+
desc "Run all examples with RCov"
|
13
|
+
Spec::Rake::SpecTask.new(:rcov) do |t|
|
14
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
15
|
+
t.rcov = true
|
16
|
+
end
|
17
|
+
|
18
|
+
begin
|
19
|
+
require 'jeweler'
|
20
|
+
Jeweler::Tasks.new do |gemspec|
|
21
|
+
gemspec.name = "redis-model"
|
22
|
+
gemspec.summary = "Minimal models for Redis"
|
23
|
+
gemspec.description = "Minimal model support for redis-rb. Directly maps ruby properties to model_name:id:field_name keys in redis. Scalar, list and set properties are supported."
|
24
|
+
gemspec.email = "voloko@gmail.com"
|
25
|
+
gemspec.homepage = "http://github.com/voloko/redis-model"
|
26
|
+
gemspec.authors = ["Vladimir Kolesnikov"]
|
27
|
+
end
|
28
|
+
rescue LoadError
|
29
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
30
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/bench.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'benchmark'
|
3
|
+
$:.push File.join(File.dirname(__FILE__), 'lib')
|
4
|
+
require 'redis/model'
|
5
|
+
|
6
|
+
n = 20000
|
7
|
+
|
8
|
+
class TestModel < Redis::Model
|
9
|
+
value :foo
|
10
|
+
list :bar
|
11
|
+
end
|
12
|
+
|
13
|
+
@r = Redis.new#(:debug => true)
|
14
|
+
@r['foo'] = "The first line we sent to the server is some text"
|
15
|
+
|
16
|
+
Benchmark.bmbm do |x|
|
17
|
+
x.report("set (model)") do
|
18
|
+
n.times do |i|
|
19
|
+
m = TestModel.with_key(i)
|
20
|
+
m.foo = "The first line we sent to the server is some text";
|
21
|
+
m.foo
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
x.report("push+trim (model)") do
|
26
|
+
n.times do |i|
|
27
|
+
m = TestModel.with_key(i)
|
28
|
+
m.bar << i
|
29
|
+
m.bar.trim 0, 30
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
@r.keys('*').each do |k|
|
35
|
+
@r.delete k
|
36
|
+
end
|
data/examples/model.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
|
3
|
+
require 'redis/model'
|
4
|
+
|
5
|
+
|
6
|
+
class User < Redis::Model
|
7
|
+
value :name, :string
|
8
|
+
value :created, :datetime
|
9
|
+
value :profile, :json
|
10
|
+
|
11
|
+
list :posts, :json
|
12
|
+
|
13
|
+
set :followers, :int
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
|
18
|
+
u = User.with_key(1)
|
19
|
+
u.delete
|
20
|
+
u.name = 'Joe'
|
21
|
+
u.created = DateTime.now
|
22
|
+
u.profile = {
|
23
|
+
:age => 23,
|
24
|
+
:sex => 'M',
|
25
|
+
:about => 'Lorem'
|
26
|
+
}
|
27
|
+
u.posts << {
|
28
|
+
:title => "Hello world!",
|
29
|
+
:text => "lorem"
|
30
|
+
}
|
31
|
+
u.followers << 2
|
32
|
+
|
33
|
+
|
34
|
+
|
35
|
+
u = User.with_key(1)
|
36
|
+
p u.name
|
37
|
+
p u.created.strftime('%m/%d/%Y')
|
38
|
+
p u.posts[0,20]
|
39
|
+
p u.posts[0]
|
40
|
+
p u.followers.has_key?(2)
|
data/lib/redis/model.rb
ADDED
@@ -0,0 +1,364 @@
|
|
1
|
+
require "redis"
|
2
|
+
require "json"
|
3
|
+
|
4
|
+
# Simple models for redis-rb.
|
5
|
+
# It maps ruby properties to <tt>model_name:id:field_name</tt> keys in redis.
|
6
|
+
# It also adds marshaling for string fields and more OOP style access for sets and lists
|
7
|
+
#
|
8
|
+
# == Define
|
9
|
+
#
|
10
|
+
# require 'redis/model'
|
11
|
+
# class User < Redis::Model
|
12
|
+
# value :name, :string
|
13
|
+
# value :created, :datetime
|
14
|
+
# value :profile, :json
|
15
|
+
# list :posts
|
16
|
+
# set :followers
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# See Redis::Marshal for more types
|
20
|
+
#
|
21
|
+
#
|
22
|
+
# == Write
|
23
|
+
#
|
24
|
+
# u = User.with_key(1)
|
25
|
+
# u.name = 'Joe' # set user:1:name Joe
|
26
|
+
# u.created = DateTime.now # set user:1:created 2009-10-05T12:09:56+0400
|
27
|
+
# u.profile = { # set user:1:profile {"sex":"M","about":"Lorem","age":23}
|
28
|
+
# :age => 23,
|
29
|
+
# :sex => 'M',
|
30
|
+
# :about => 'Lorem'
|
31
|
+
# }
|
32
|
+
# u.posts << "Hello world!" # rpush user:1:posts 'Hello world!'
|
33
|
+
# u.followers << 2 # sadd user:1:followers 2
|
34
|
+
#
|
35
|
+
# == Read
|
36
|
+
#
|
37
|
+
# u = User.with_key(1)
|
38
|
+
# p u.name # get user:1:name
|
39
|
+
# p u.created.strftime('%m/%d/%Y') # get user:1:created
|
40
|
+
# p u.posts[0,20] # lrange user:1:posts 0 20
|
41
|
+
# p u.followers.has_key?(2) # sismember user:1:followers 2
|
42
|
+
#
|
43
|
+
|
44
|
+
|
45
|
+
class Redis::Model
|
46
|
+
attr_accessor :id
|
47
|
+
|
48
|
+
def initialize(id)
|
49
|
+
self.id = id
|
50
|
+
end
|
51
|
+
|
52
|
+
def redis #:nodoc:
|
53
|
+
self.class.redis
|
54
|
+
end
|
55
|
+
|
56
|
+
# Issues delete commands for all defined fields
|
57
|
+
def delete
|
58
|
+
self.class.fields.each do |field|
|
59
|
+
redis.delete field_key(field[:name])
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
protected
|
64
|
+
def prefix #:nodoc:
|
65
|
+
@prefix ||= self.class.prefix || self.class.to_s.
|
66
|
+
sub(%r{(.*::)}, '').
|
67
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
68
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
69
|
+
downcase
|
70
|
+
end
|
71
|
+
|
72
|
+
def field_key(name) #:nodoc:
|
73
|
+
"#{prefix}:#{id}:#{name}"
|
74
|
+
end
|
75
|
+
|
76
|
+
class << self
|
77
|
+
# Defaults to model_name.dasherize
|
78
|
+
attr_accessor :prefix
|
79
|
+
|
80
|
+
|
81
|
+
# Creates new model instance with new uniqid
|
82
|
+
# NOTE: "sequence:model_name:id" key is used
|
83
|
+
def create(values = {})
|
84
|
+
populate_model(self.new(next_id))
|
85
|
+
end
|
86
|
+
|
87
|
+
# Creates new model instance with given id
|
88
|
+
alias_method :with_key, :new
|
89
|
+
alias_method :with_next_key, :create
|
90
|
+
|
91
|
+
# Defines marshaled rw accessor for redis string value
|
92
|
+
def field(name, type = :string)
|
93
|
+
class_name = marshal_class_name(name, type)
|
94
|
+
|
95
|
+
fields << {:name => name.to_s, :type => :type}
|
96
|
+
if type == :string
|
97
|
+
class_eval "def #{name}; @#{name} ||= redis[field_key('#{name}')]; end"
|
98
|
+
class_eval "def #{name}=(value); @#{name} = redis[field_key('#{name}')] = value; end"
|
99
|
+
else
|
100
|
+
class_eval "def #{name}; @#{name} ||= Marshal::#{class_name}.load(redis[field_key('#{name}')]); end"
|
101
|
+
class_eval "def #{name}=(value); @#{name} = value; redis[field_key('#{name}')] = Marshal::#{class_name}.dump(value) end"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
alias_method :value, :field
|
105
|
+
|
106
|
+
# Defines accessor for redis list
|
107
|
+
def list(name, type = :string)
|
108
|
+
class_name = marshal_class_name(name, type)
|
109
|
+
|
110
|
+
fields << {:name => name.to_s, :type => :list}
|
111
|
+
class_eval "def #{name}; @#{name} ||= ListProxy.new(self.redis, field_key('#{name}'), Marshal::#{class_name}); end"
|
112
|
+
eval_writer(name)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Defines accessor for redis set
|
116
|
+
def set(name, type = :string)
|
117
|
+
class_name = marshal_class_name(name, type)
|
118
|
+
|
119
|
+
fields << {:name => name.to_s, :type => :set}
|
120
|
+
class_eval "def #{name}; @#{name} ||= SetProxy.new(self.redis, field_key('#{name}'), Marshal::#{class_name}); end"
|
121
|
+
eval_writer(name)
|
122
|
+
end
|
123
|
+
|
124
|
+
def marshal_class_name(name, type)
|
125
|
+
Marshal::TYPES[type] or raise ArgumentError.new("Unknown type #{type} for field #{name}")
|
126
|
+
end
|
127
|
+
|
128
|
+
# Redefine this to change connection options
|
129
|
+
def redis
|
130
|
+
@@redis ||= Redis.new
|
131
|
+
end
|
132
|
+
|
133
|
+
def fields #:nodoc:
|
134
|
+
@fields ||= []
|
135
|
+
end
|
136
|
+
|
137
|
+
protected
|
138
|
+
def eval_writer(name) #:nodoc:
|
139
|
+
class_eval <<-END
|
140
|
+
def #{name}=(value)
|
141
|
+
field = self.#{name};
|
142
|
+
if value.respond_to?(:each)
|
143
|
+
value.each {|v| field << v}
|
144
|
+
else
|
145
|
+
field << v
|
146
|
+
end
|
147
|
+
end
|
148
|
+
END
|
149
|
+
end
|
150
|
+
|
151
|
+
def next_id #:nodoc:
|
152
|
+
redis.incr "sequence:#{self.new.prefix}:id"
|
153
|
+
end
|
154
|
+
|
155
|
+
def populate_model(model, values) #:nodoc:
|
156
|
+
return model if values.empty?
|
157
|
+
@@fields.each do |field|
|
158
|
+
model.send(:"#{field[:name]}=", value) if values.has_key?(field[:name])
|
159
|
+
end
|
160
|
+
model
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
module Marshal
|
165
|
+
TYPES = {
|
166
|
+
:string => 'String',
|
167
|
+
:int => 'Integer',
|
168
|
+
:integer => 'Integer',
|
169
|
+
:float => 'Float',
|
170
|
+
:datetime => 'DateTime',
|
171
|
+
:json => 'JSON',
|
172
|
+
}
|
173
|
+
|
174
|
+
class String
|
175
|
+
def self.dump(v)
|
176
|
+
v
|
177
|
+
end
|
178
|
+
|
179
|
+
def self.load(v)
|
180
|
+
v
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
class Integer
|
185
|
+
def self.dump(v)
|
186
|
+
v.to_s
|
187
|
+
end
|
188
|
+
|
189
|
+
def self.load(v)
|
190
|
+
v && v.to_i
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
class Float
|
195
|
+
def self.dump(v)
|
196
|
+
v.to_s
|
197
|
+
end
|
198
|
+
|
199
|
+
def self.load(v)
|
200
|
+
v && v.to_f
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
class DateTime
|
205
|
+
def self.dump(v)
|
206
|
+
v.strftime('%FT%T%z')
|
207
|
+
end
|
208
|
+
|
209
|
+
def self.load(v)
|
210
|
+
v && ::DateTime.strptime(v, '%FT%T%z')
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
class JSON
|
215
|
+
def self.dump(v)
|
216
|
+
::JSON.dump(v)
|
217
|
+
end
|
218
|
+
|
219
|
+
def self.load(v)
|
220
|
+
v && ::JSON.load(v)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
|
226
|
+
|
227
|
+
class FieldProxy #:nodoc
|
228
|
+
def initialize(redis, name, marshal)
|
229
|
+
@redis = redis
|
230
|
+
@name = name
|
231
|
+
@marshal = marshal
|
232
|
+
end
|
233
|
+
|
234
|
+
def method_missing(method, *argv)
|
235
|
+
translated_method = translate_method_name(method)
|
236
|
+
raise NoMethodError.new("Method '#{method}' is not defined") unless translated_method
|
237
|
+
@redis.send translated_method, @name, *argv
|
238
|
+
end
|
239
|
+
|
240
|
+
protected
|
241
|
+
def translate_method_name(m)
|
242
|
+
m
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
|
247
|
+
|
248
|
+
class ListProxy < FieldProxy #:nodoc:
|
249
|
+
def <<(v)
|
250
|
+
@redis.rpush @name, @marshal.dump(v)
|
251
|
+
end
|
252
|
+
alias_method :push_tail, :<<
|
253
|
+
|
254
|
+
def push_head(v)
|
255
|
+
@redis.lpush @name, @marshal.dump(v)
|
256
|
+
end
|
257
|
+
|
258
|
+
def pop_tail
|
259
|
+
@marshal.load(@redis.rpop(@name))
|
260
|
+
end
|
261
|
+
|
262
|
+
def pop_head
|
263
|
+
@marshal.load(@redis.lpop(@name))
|
264
|
+
end
|
265
|
+
|
266
|
+
def [](from, to = nil)
|
267
|
+
if to.nil?
|
268
|
+
@marshal.load(@redis.lindex(@name, from))
|
269
|
+
else
|
270
|
+
@redis.lrange(@name, from, to).map! { |v| @marshal.load(v) }
|
271
|
+
end
|
272
|
+
end
|
273
|
+
alias_method :range, :[]
|
274
|
+
|
275
|
+
def []=(index, v)
|
276
|
+
@redis.lset(@name, index, @marshal.dump(v))
|
277
|
+
end
|
278
|
+
alias_method :set, :[]=
|
279
|
+
|
280
|
+
def include?(v)
|
281
|
+
@redis.exists(@name, @marshal.dump(v))
|
282
|
+
end
|
283
|
+
|
284
|
+
def remove(count, v)
|
285
|
+
@redis.lrem(@name, count, @marshal.dump(v))
|
286
|
+
end
|
287
|
+
|
288
|
+
def length
|
289
|
+
@redis.llen(@name)
|
290
|
+
end
|
291
|
+
|
292
|
+
def trim(from, to)
|
293
|
+
@redis.ltrim(@name, from, to)
|
294
|
+
end
|
295
|
+
|
296
|
+
def to_s
|
297
|
+
range(0, 100).join(', ')
|
298
|
+
end
|
299
|
+
|
300
|
+
protected
|
301
|
+
def translate_method_name(m)
|
302
|
+
COMMANDS[m]
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
|
307
|
+
|
308
|
+
class SetProxy < FieldProxy #:nodoc:
|
309
|
+
COMMANDS = {
|
310
|
+
:intersect_store => "sinterstore",
|
311
|
+
:union_store => "sunionstore",
|
312
|
+
:diff_store => "sdiffstore",
|
313
|
+
:move => "smove",
|
314
|
+
}
|
315
|
+
|
316
|
+
def <<(v)
|
317
|
+
@redis.sadd @name, @marshal.dump(v)
|
318
|
+
end
|
319
|
+
alias_method :add, :<<
|
320
|
+
|
321
|
+
def delete(v)
|
322
|
+
@redis.srem @name, @marshal.dump(v)
|
323
|
+
end
|
324
|
+
alias_method :remove, :delete
|
325
|
+
|
326
|
+
def include?(v)
|
327
|
+
@redis.sismember @name, @marshal.dump(v)
|
328
|
+
end
|
329
|
+
alias_method :has_key?, :include?
|
330
|
+
alias_method :member?, :include?
|
331
|
+
|
332
|
+
def members
|
333
|
+
@redis.smembers(@name).map { |v| @marshal.load(v) }
|
334
|
+
end
|
335
|
+
|
336
|
+
def intersect(*keys)
|
337
|
+
@redis.sinter(@name, *keys).map { |v| @marshal.load(v) }
|
338
|
+
end
|
339
|
+
|
340
|
+
def union(*keys)
|
341
|
+
@redis.sunion(@name, *keys).map { |v| @marshal.load(v) }
|
342
|
+
end
|
343
|
+
|
344
|
+
def diff(*keys)
|
345
|
+
@redis.sdiff(@name, *keys).map { |v| @marshal.load(v) }
|
346
|
+
end
|
347
|
+
|
348
|
+
def length
|
349
|
+
@redis.llen(@name)
|
350
|
+
end
|
351
|
+
|
352
|
+
def to_s
|
353
|
+
members.join(', ')
|
354
|
+
end
|
355
|
+
|
356
|
+
protected
|
357
|
+
def translate_method_name(m)
|
358
|
+
COMMANDS[m]
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
|
363
|
+
end
|
364
|
+
|
@@ -0,0 +1,188 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper.rb'
|
2
|
+
require 'redis/model'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
describe Redis::Model do
|
6
|
+
|
7
|
+
context "DSL" do
|
8
|
+
class TestDSL < Redis::Model
|
9
|
+
field :foo
|
10
|
+
list :bar
|
11
|
+
set :sloppy
|
12
|
+
end
|
13
|
+
|
14
|
+
before(:each) do
|
15
|
+
@x = TestDSL.with_key(1)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should define rw accessors for field" do
|
19
|
+
@x.should respond_to(:foo)
|
20
|
+
@x.should respond_to(:foo=)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should define r accessor for list" do
|
24
|
+
@x.should respond_to(:bar)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should define r accessor for set" do
|
28
|
+
@x.should respond_to(:sloppy)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should raise error on invalid type" do
|
32
|
+
lambda do
|
33
|
+
class TestInvalidType < Redis::Model
|
34
|
+
field :invalid, :invalid_type
|
35
|
+
end
|
36
|
+
end.should raise_error(ArgumentError, 'Unknown type invalid_type for field invalid')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context "field type cast" do
|
41
|
+
class TestType < Redis::Model
|
42
|
+
field :foo_string, :string
|
43
|
+
field :foo_json, :json
|
44
|
+
field :foo_date, :datetime
|
45
|
+
field :foo_int, :int
|
46
|
+
field :foo_float, :float
|
47
|
+
|
48
|
+
list :list_date, :datetime
|
49
|
+
set :set_date, :datetime
|
50
|
+
end
|
51
|
+
|
52
|
+
before(:each) do
|
53
|
+
@xRedisMock = Spec::Mocks::Mock.new
|
54
|
+
@yRedisMock = Spec::Mocks::Mock.new
|
55
|
+
@x = TestType.with_key(1)
|
56
|
+
@y = TestType.with_key(1)
|
57
|
+
@x.stub!(:redis).and_return(@xRedisMock)
|
58
|
+
@y.stub!(:redis).and_return(@yRedisMock)
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should save string as is" do
|
62
|
+
@xRedisMock.should_receive(:[]=).with('test_type:1:foo_string', 'xxx')
|
63
|
+
@yRedisMock.should_receive(:[]).with('test_type:1:foo_string').and_return('xxx')
|
64
|
+
@x.foo_string = 'xxx'
|
65
|
+
@y.foo_string.should be_instance_of(String)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should marshal integer fields" do
|
69
|
+
@xRedisMock.should_receive(:[]=).with('test_type:1:foo_int', '12')
|
70
|
+
@yRedisMock.should_receive(:[]).with('test_type:1:foo_int').and_return('12')
|
71
|
+
@x.foo_int = 12
|
72
|
+
@y.foo_int.should be_kind_of(Integer)
|
73
|
+
@y.foo_int.should == 12
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should marshal float fields" do
|
77
|
+
@xRedisMock.should_receive(:[]=).with('test_type:1:foo_float', '12.1')
|
78
|
+
@yRedisMock.should_receive(:[]).with('test_type:1:foo_float').and_return('12.1')
|
79
|
+
@x.foo_float = 12.1
|
80
|
+
@y.foo_float.should be_kind_of(Float)
|
81
|
+
@y.foo_float.should == 12.1
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should marshal datetime fields" do
|
85
|
+
time = DateTime.now
|
86
|
+
str = time.strftime('%FT%T%z')
|
87
|
+
@xRedisMock.should_receive(:[]=).with('test_type:1:foo_date', str)
|
88
|
+
@yRedisMock.should_receive(:[]).with('test_type:1:foo_date').and_return(str)
|
89
|
+
@x.foo_date = time
|
90
|
+
@y.foo_date.should be_kind_of(DateTime)
|
91
|
+
@y.foo_date.should.to_s == time.to_s
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should marshal json structs" do
|
95
|
+
data = {'foo' => 'bar', 'x' => 2}
|
96
|
+
str = JSON.dump(data)
|
97
|
+
@xRedisMock.should_receive(:[]=).with('test_type:1:foo_json', str)
|
98
|
+
@yRedisMock.should_receive(:[]).with('test_type:1:foo_json').and_return(str)
|
99
|
+
@x.foo_json = data
|
100
|
+
@y.foo_json.should be_kind_of(Hash)
|
101
|
+
@y.foo_json.should.inspect == data.inspect
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should return nil for empty fields" do
|
105
|
+
@xRedisMock.should_receive(:[]).with('test_type:1:foo_date').and_return(nil)
|
106
|
+
@x.foo_date.should be_nil
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should marshal list values" do
|
110
|
+
data = DateTime.now
|
111
|
+
str = data.strftime('%FT%T%z')
|
112
|
+
|
113
|
+
@xRedisMock.should_receive('rpush').with('test_type:1:list_date', str)
|
114
|
+
@xRedisMock.should_receive('lset').with('test_type:1:list_date', 1, str)
|
115
|
+
@xRedisMock.should_receive('exists').with('test_type:1:list_date', str)
|
116
|
+
@xRedisMock.should_receive('lrem').with('test_type:1:list_date', 0, str)
|
117
|
+
@xRedisMock.should_receive('lpush').with('test_type:1:list_date', str)
|
118
|
+
@xRedisMock.should_receive('lrange').with('test_type:1:list_date', 0, 1).and_return([str])
|
119
|
+
@xRedisMock.should_receive('rpop').with('test_type:1:list_date').and_return(str)
|
120
|
+
@xRedisMock.should_receive('lpop').with('test_type:1:list_date').and_return(str)
|
121
|
+
@xRedisMock.should_receive('lindex').with('test_type:1:list_date', 0).and_return(str)
|
122
|
+
@x.list_date << data
|
123
|
+
@x.list_date[1] = data
|
124
|
+
@x.list_date.include?(data)
|
125
|
+
@x.list_date.remove(0, data)
|
126
|
+
@x.list_date.push_head(data)
|
127
|
+
@x.list_date[0].should be_kind_of(DateTime)
|
128
|
+
@x.list_date[0, 1][0].should be_kind_of(DateTime)
|
129
|
+
@x.list_date.pop_tail.should be_kind_of(DateTime)
|
130
|
+
@x.list_date.pop_head.should be_kind_of(DateTime)
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should marshal set values" do
|
134
|
+
data = DateTime.now
|
135
|
+
str = data.strftime('%FT%T%z')
|
136
|
+
|
137
|
+
@xRedisMock.should_receive('sadd').with('test_type:1:set_date', str)
|
138
|
+
@xRedisMock.should_receive('srem').with('test_type:1:set_date', str)
|
139
|
+
@xRedisMock.should_receive('sismember').with('test_type:1:set_date', str)
|
140
|
+
@xRedisMock.should_receive('smembers').with('test_type:1:set_date').and_return([str])
|
141
|
+
@xRedisMock.should_receive('sinter').with('test_type:1:set_date', 'x').and_return([str])
|
142
|
+
@xRedisMock.should_receive('sunion').with('test_type:1:set_date', 'x').and_return([str])
|
143
|
+
@xRedisMock.should_receive('sdiff').with('test_type:1:set_date', 'x', 'y').and_return([str])
|
144
|
+
@x.set_date << data
|
145
|
+
@x.set_date.delete(data)
|
146
|
+
@x.set_date.include?(data)
|
147
|
+
@x.set_date.members[0].should be_kind_of(DateTime)
|
148
|
+
@x.set_date.intersect('x')[0].should be_kind_of(DateTime)
|
149
|
+
@x.set_date.union('x')[0].should be_kind_of(DateTime)
|
150
|
+
@x.set_date.diff('x', 'y')[0].should be_kind_of(DateTime)
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
context "redis commands" do
|
156
|
+
class TestCommands < Redis::Model
|
157
|
+
field :foo
|
158
|
+
list :bar
|
159
|
+
set :sloppy
|
160
|
+
end
|
161
|
+
|
162
|
+
before(:each) do
|
163
|
+
@redisMock = Spec::Mocks::Mock.new
|
164
|
+
@x = TestCommands.with_key(1)
|
165
|
+
@x.stub!(:redis).and_return(@redisMock)
|
166
|
+
end
|
167
|
+
|
168
|
+
it "should send GET on field read" do
|
169
|
+
@redisMock.should_receive(:[]).with('test_commands:1:foo')
|
170
|
+
@x.foo
|
171
|
+
end
|
172
|
+
|
173
|
+
it "should send SET on field write" do
|
174
|
+
@redisMock.should_receive(:[]=).with('test_commands:1:foo', 'bar')
|
175
|
+
@x.foo = 'bar'
|
176
|
+
end
|
177
|
+
|
178
|
+
it "should send RPUSH on list <<" do
|
179
|
+
@redisMock.should_receive(:rpush).with('test_commands:1:bar', 'bar')
|
180
|
+
@x.bar << 'bar'
|
181
|
+
end
|
182
|
+
|
183
|
+
it "should send SADD on set <<" do
|
184
|
+
@redisMock.should_receive(:sadd).with('test_commands:1:sloppy', 'bar')
|
185
|
+
@x.sloppy << 'bar'
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: redis-model
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Vladimir Kolesnikov
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-10-14 00:00:00 +04:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Minimal model support for redis-rb. Directly maps ruby properties to model_name:id:field_name keys in redis. Scalar, list and set properties are supported.
|
17
|
+
email: voloko@gmail.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README.markdown
|
24
|
+
files:
|
25
|
+
- .gitignore
|
26
|
+
- README.markdown
|
27
|
+
- Rakefile
|
28
|
+
- VERSION
|
29
|
+
- bench.rb
|
30
|
+
- examples/model.rb
|
31
|
+
- lib/redis/model.rb
|
32
|
+
- spec/redis/model_spec.rb
|
33
|
+
- spec/spec_helper.rb
|
34
|
+
has_rdoc: true
|
35
|
+
homepage: http://github.com/voloko/redis-model
|
36
|
+
licenses: []
|
37
|
+
|
38
|
+
post_install_message:
|
39
|
+
rdoc_options:
|
40
|
+
- --charset=UTF-8
|
41
|
+
require_paths:
|
42
|
+
- lib
|
43
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: "0"
|
48
|
+
version:
|
49
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: "0"
|
54
|
+
version:
|
55
|
+
requirements: []
|
56
|
+
|
57
|
+
rubyforge_project:
|
58
|
+
rubygems_version: 1.3.4
|
59
|
+
signing_key:
|
60
|
+
specification_version: 3
|
61
|
+
summary: Minimal models for Redis
|
62
|
+
test_files:
|
63
|
+
- spec/redis/model_spec.rb
|
64
|
+
- spec/spec_helper.rb
|
65
|
+
- examples/model.rb
|