redis-model 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|