sohm 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gems +4 -0
- data/.gitignore +3 -0
- data/CHANGELOG.md +312 -0
- data/LICENSE +19 -0
- data/README.md +10 -0
- data/benchmarks/common.rb +33 -0
- data/benchmarks/create.rb +21 -0
- data/benchmarks/delete.rb +13 -0
- data/examples/activity-feed.rb +162 -0
- data/examples/chaining.rb +162 -0
- data/examples/json-hash.rb +75 -0
- data/examples/one-to-many.rb +124 -0
- data/examples/philosophy.rb +137 -0
- data/examples/redis-logging.txt +179 -0
- data/examples/slug.rb +149 -0
- data/examples/tagging.rb +237 -0
- data/lib/sample.rb +14 -0
- data/lib/sohm/command.rb +51 -0
- data/lib/sohm/json.rb +17 -0
- data/lib/sohm/lua/delete.lua +72 -0
- data/lib/sohm/lua/save.lua +13 -0
- data/lib/sohm.rb +1576 -0
- data/makefile +4 -0
- data/sohm.gemspec +18 -0
- data/test/association.rb +33 -0
- data/test/command.rb +55 -0
- data/test/connection.rb +16 -0
- data/test/core.rb +24 -0
- data/test/counters.rb +67 -0
- data/test/enumerable.rb +79 -0
- data/test/filtering.rb +185 -0
- data/test/hash_key.rb +31 -0
- data/test/helper.rb +23 -0
- data/test/indices.rb +133 -0
- data/test/json.rb +62 -0
- data/test/list.rb +83 -0
- data/test/model.rb +789 -0
- data/test/set.rb +37 -0
- data/test/thread_safety.rb +67 -0
- data/test/to_hash.rb +29 -0
- data/test/uniques.rb +98 -0
- metadata +142 -0
data/test/model.rb
ADDED
@@ -0,0 +1,789 @@
|
|
1
|
+
require_relative "helper"
|
2
|
+
require "ostruct"
|
3
|
+
|
4
|
+
class Post < Ohm::Model
|
5
|
+
attribute :body
|
6
|
+
attribute :published
|
7
|
+
set :related, :Post
|
8
|
+
end
|
9
|
+
|
10
|
+
class User < Ohm::Model
|
11
|
+
attribute :email
|
12
|
+
set :posts, :Post
|
13
|
+
end
|
14
|
+
|
15
|
+
class Person < Ohm::Model
|
16
|
+
attribute :name
|
17
|
+
counter :logins
|
18
|
+
index :initial
|
19
|
+
|
20
|
+
def initial
|
21
|
+
name[0, 1].upcase if name
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Event < Ohm::Model
|
26
|
+
attribute :name
|
27
|
+
counter :votes
|
28
|
+
set :attendees, :Person
|
29
|
+
|
30
|
+
attribute :slug
|
31
|
+
|
32
|
+
def save
|
33
|
+
self.slug = name.to_s.downcase
|
34
|
+
super
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
module SomeNamespace
|
39
|
+
class Foo < Ohm::Model
|
40
|
+
attribute :name
|
41
|
+
end
|
42
|
+
|
43
|
+
class Bar < Ohm::Model
|
44
|
+
reference :foo, 'SomeNamespace::Foo'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class Meetup < Ohm::Model
|
49
|
+
attribute :name
|
50
|
+
attribute :location
|
51
|
+
end
|
52
|
+
|
53
|
+
test "booleans" do
|
54
|
+
post = Post.new(body: true, published: false)
|
55
|
+
|
56
|
+
post.save
|
57
|
+
|
58
|
+
assert_equal true, post.body
|
59
|
+
assert_equal false, post.published
|
60
|
+
|
61
|
+
post = Post[post.id]
|
62
|
+
|
63
|
+
assert_equal "true", post.body
|
64
|
+
assert_equal nil, post.published
|
65
|
+
end
|
66
|
+
|
67
|
+
test "empty model is ok" do
|
68
|
+
class Foo < Ohm::Model
|
69
|
+
end
|
70
|
+
|
71
|
+
Foo.create
|
72
|
+
end
|
73
|
+
|
74
|
+
test "counters are cleaned up during deletion" do
|
75
|
+
e = Event.create(:name => "Foo")
|
76
|
+
e.incr :votes, 10
|
77
|
+
|
78
|
+
assert_equal 10, e.votes
|
79
|
+
|
80
|
+
e.delete
|
81
|
+
assert_equal 0, Event.redis.call("EXISTS", e.key[:counters])
|
82
|
+
end
|
83
|
+
|
84
|
+
test "get" do
|
85
|
+
m = Meetup.create(:name => "Foo")
|
86
|
+
m.name = "Bar"
|
87
|
+
|
88
|
+
assert_equal "Foo", m.get(:name)
|
89
|
+
assert_equal "Foo", m.name
|
90
|
+
end
|
91
|
+
|
92
|
+
test "set" do
|
93
|
+
m = Meetup.create(:name => "Foo")
|
94
|
+
|
95
|
+
m.set :name, "Bar"
|
96
|
+
assert_equal "Bar", m.name
|
97
|
+
|
98
|
+
m = Meetup[m.id]
|
99
|
+
assert_equal "Bar", m.name
|
100
|
+
|
101
|
+
# Deletes when value is nil.
|
102
|
+
m.set :name, nil
|
103
|
+
m = Meetup[m.id]
|
104
|
+
assert_equal 0, Meetup.redis.call("HEXISTS", m.key, :name)
|
105
|
+
end
|
106
|
+
|
107
|
+
test "assign attributes from the hash" do
|
108
|
+
event = Event.new(:name => "Ruby Tuesday")
|
109
|
+
assert event.name == "Ruby Tuesday"
|
110
|
+
end
|
111
|
+
|
112
|
+
test "assign an ID and save the object" do
|
113
|
+
event1 = Event.create(name: "Ruby Tuesday")
|
114
|
+
event2 = Event.create(name: "Ruby Meetup")
|
115
|
+
|
116
|
+
assert_equal "1", event1.id
|
117
|
+
assert_equal "2", event2.id
|
118
|
+
end
|
119
|
+
|
120
|
+
test "updates attributes" do
|
121
|
+
event = Meetup.create(:name => "Ruby Tuesday")
|
122
|
+
event.update(:name => "Ruby Meetup")
|
123
|
+
assert "Ruby Meetup" == event.name
|
124
|
+
end
|
125
|
+
|
126
|
+
test "save the attributes in UTF8" do
|
127
|
+
event = Meetup.create(:name => "32° Kisei-sen")
|
128
|
+
assert "32° Kisei-sen" == Meetup[event.id].name
|
129
|
+
end
|
130
|
+
|
131
|
+
test "delete the attribute if set to nil" do
|
132
|
+
event = Meetup.create(:name => "Ruby Tuesday", :location => "Los Angeles")
|
133
|
+
assert "Los Angeles" == Meetup[event.id].location
|
134
|
+
assert event.update(:location => nil)
|
135
|
+
assert_equal nil, Meetup[event.id].location
|
136
|
+
end
|
137
|
+
|
138
|
+
test "not raise if an attribute is redefined" do
|
139
|
+
class RedefinedModel < Ohm::Model
|
140
|
+
attribute :name
|
141
|
+
|
142
|
+
silence_warnings do
|
143
|
+
attribute :name
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
test "not raise if a counter is redefined" do
|
149
|
+
class RedefinedModel < Ohm::Model
|
150
|
+
counter :age
|
151
|
+
|
152
|
+
silence_warnings do
|
153
|
+
counter :age
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
test "not raise if a set is redefined" do
|
159
|
+
class RedefinedModel < Ohm::Model
|
160
|
+
set :friends, lambda { }
|
161
|
+
|
162
|
+
silence_warnings do
|
163
|
+
set :friends, lambda { }
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
test "not raise if a collection is redefined" do
|
169
|
+
class RedefinedModel < Ohm::Model
|
170
|
+
set :toys, lambda { }
|
171
|
+
|
172
|
+
silence_warnings do
|
173
|
+
set :toys, lambda { }
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
test "not raise if a index is redefined" do
|
179
|
+
class RedefinedModel < Ohm::Model
|
180
|
+
attribute :color
|
181
|
+
index :color
|
182
|
+
index :color
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
test "allow arbitrary IDs" do
|
187
|
+
Event.create(:id => "abc123", :name => "Concert")
|
188
|
+
|
189
|
+
assert Event.all.size == 1
|
190
|
+
assert Event["abc123"].name == "Concert"
|
191
|
+
end
|
192
|
+
|
193
|
+
test "forbid assignment of IDs on a new object" do
|
194
|
+
event = Event.new(:name => "Concert")
|
195
|
+
|
196
|
+
assert_raise(NoMethodError) do
|
197
|
+
event.id = "abc123"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
setup do
|
202
|
+
Ohm.redis.call("SADD", "Event:all", 1)
|
203
|
+
Ohm.redis.call("HSET", "Event:1", "name", "Concert")
|
204
|
+
end
|
205
|
+
|
206
|
+
test "return an instance of Event" do
|
207
|
+
assert Event[1].kind_of?(Event)
|
208
|
+
assert 1 == Event[1].id
|
209
|
+
assert "Concert" == Event[1].name
|
210
|
+
end
|
211
|
+
|
212
|
+
setup do
|
213
|
+
Ohm.redis.call("SADD", "User:all", 1)
|
214
|
+
Ohm.redis.call("HSET", "User:1", "email", "albert@example.com")
|
215
|
+
end
|
216
|
+
|
217
|
+
test "return an instance of User" do
|
218
|
+
assert User[1].kind_of?(User)
|
219
|
+
assert 1 == User[1].id
|
220
|
+
assert "albert@example.com" == User[1].email
|
221
|
+
end
|
222
|
+
|
223
|
+
test "allow to map key to models" do
|
224
|
+
assert [User[1]] == [1].map(&User)
|
225
|
+
end
|
226
|
+
|
227
|
+
setup do
|
228
|
+
Ohm.redis.call("SADD", "User:all", 1)
|
229
|
+
Ohm.redis.call("SET", "User:1:email", "albert@example.com")
|
230
|
+
|
231
|
+
@user = User[1]
|
232
|
+
end
|
233
|
+
|
234
|
+
test "change its attributes" do
|
235
|
+
@user.email = "maria@example.com"
|
236
|
+
assert "maria@example.com" == @user.email
|
237
|
+
end
|
238
|
+
|
239
|
+
test "save the new values" do
|
240
|
+
@user.email = "maria@example.com"
|
241
|
+
@user.save
|
242
|
+
|
243
|
+
@user.email = "maria@example.com"
|
244
|
+
@user.save
|
245
|
+
|
246
|
+
assert "maria@example.com" == User[1].email
|
247
|
+
end
|
248
|
+
|
249
|
+
test "assign a new id to the event" do
|
250
|
+
event1 = Event.new
|
251
|
+
event1.save
|
252
|
+
|
253
|
+
event2 = Event.new
|
254
|
+
event2.save
|
255
|
+
|
256
|
+
assert !event1.new?
|
257
|
+
assert !event2.new?
|
258
|
+
|
259
|
+
assert_equal "1", event1.id
|
260
|
+
assert_equal "2", event2.id
|
261
|
+
end
|
262
|
+
|
263
|
+
# Saving a model
|
264
|
+
test "create the model if it is new" do
|
265
|
+
event = Event.new(:name => "Foo").save
|
266
|
+
assert "Foo" == Event[event.id].name
|
267
|
+
end
|
268
|
+
|
269
|
+
test "save it only if it was previously created" do
|
270
|
+
event = Event.new
|
271
|
+
event.name = "Lorem ipsum"
|
272
|
+
event.save
|
273
|
+
|
274
|
+
event.name = "Lorem"
|
275
|
+
event.save
|
276
|
+
|
277
|
+
assert "Lorem" == Event[event.id].name
|
278
|
+
end
|
279
|
+
|
280
|
+
test "allow to hook into save" do
|
281
|
+
event = Event.create(:name => "Foo")
|
282
|
+
|
283
|
+
assert "foo" == event.slug
|
284
|
+
end
|
285
|
+
|
286
|
+
test "save counters" do
|
287
|
+
event = Event.create(:name => "Foo")
|
288
|
+
|
289
|
+
event.incr(:votes)
|
290
|
+
event.save
|
291
|
+
|
292
|
+
assert_equal 1, Event[event.id].votes
|
293
|
+
end
|
294
|
+
|
295
|
+
# Delete
|
296
|
+
test "delete an existing model" do
|
297
|
+
class ModelToBeDeleted < Ohm::Model
|
298
|
+
attribute :name
|
299
|
+
set :foos, :Post
|
300
|
+
set :bars, :Post
|
301
|
+
end
|
302
|
+
|
303
|
+
@model = ModelToBeDeleted.create(:name => "Lorem")
|
304
|
+
|
305
|
+
@model.foos.add(Post.create)
|
306
|
+
@model.bars.add(Post.create)
|
307
|
+
|
308
|
+
id = @model.id
|
309
|
+
|
310
|
+
@model.delete
|
311
|
+
|
312
|
+
assert Ohm.redis.call("GET", ModelToBeDeleted.key[id]).nil?
|
313
|
+
assert Ohm.redis.call("GET", ModelToBeDeleted.key[id][:name]).nil?
|
314
|
+
assert Array.new == Ohm.redis.call("SMEMBERS", ModelToBeDeleted.key[id][:foos])
|
315
|
+
assert Array.new == Ohm.redis.call("LRANGE", ModelToBeDeleted.key[id][:bars], 0, -1)
|
316
|
+
|
317
|
+
assert ModelToBeDeleted.all.empty?
|
318
|
+
end
|
319
|
+
|
320
|
+
setup do
|
321
|
+
end
|
322
|
+
|
323
|
+
test "no leftover keys" do
|
324
|
+
class ::Foo < Ohm::Model
|
325
|
+
attribute :name
|
326
|
+
index :name
|
327
|
+
track :notes
|
328
|
+
end
|
329
|
+
|
330
|
+
assert_equal [], Ohm.redis.call("KEYS", "*")
|
331
|
+
|
332
|
+
Foo.create(:name => "Bar")
|
333
|
+
expected = %w[Foo:1:_indices Foo:1 Foo:all Foo:id Foo:indices:name:Bar]
|
334
|
+
|
335
|
+
assert_equal expected.sort, Ohm.redis.call("KEYS", "*").sort
|
336
|
+
|
337
|
+
Foo[1].delete
|
338
|
+
assert ["Foo:id"] == Ohm.redis.call("KEYS", "*")
|
339
|
+
|
340
|
+
Foo.create(:name => "Baz")
|
341
|
+
|
342
|
+
Ohm.redis.call("SET", Foo[2].key[:notes], "something")
|
343
|
+
|
344
|
+
expected = %w[Foo:2:_indices Foo:2 Foo:all Foo:id
|
345
|
+
Foo:indices:name:Baz Foo:2:notes]
|
346
|
+
|
347
|
+
assert_equal expected.sort, Ohm.redis.call("KEYS", "*").sort
|
348
|
+
|
349
|
+
Foo[2].delete
|
350
|
+
assert ["Foo:id"] == Ohm.redis.call("KEYS", "*")
|
351
|
+
end
|
352
|
+
|
353
|
+
# Listing
|
354
|
+
test "find all" do
|
355
|
+
event1 = Event.new
|
356
|
+
event1.name = "Ruby Meetup"
|
357
|
+
event1.save
|
358
|
+
|
359
|
+
event2 = Event.new
|
360
|
+
event2.name = "Ruby Tuesday"
|
361
|
+
event2.save
|
362
|
+
|
363
|
+
all = Event.all
|
364
|
+
assert all.detect {|e| e.name == "Ruby Meetup" }
|
365
|
+
assert all.detect {|e| e.name == "Ruby Tuesday" }
|
366
|
+
end
|
367
|
+
|
368
|
+
# Fetching
|
369
|
+
test "fetch ids" do
|
370
|
+
event1 = Event.create(:name => "A")
|
371
|
+
event2 = Event.create(:name => "B")
|
372
|
+
|
373
|
+
assert_equal [event1, event2], Event.fetch([event1.id, event2.id])
|
374
|
+
end
|
375
|
+
|
376
|
+
# Sorting
|
377
|
+
test "sort all" do
|
378
|
+
Person.create :name => "D"
|
379
|
+
Person.create :name => "C"
|
380
|
+
Person.create :name => "B"
|
381
|
+
Person.create :name => "A"
|
382
|
+
|
383
|
+
names = Person.all.sort_by(:name, :order => "ALPHA").map { |p| p.name }
|
384
|
+
assert %w[A B C D] == names
|
385
|
+
end
|
386
|
+
|
387
|
+
test "return an empty array if there are no elements to sort" do
|
388
|
+
assert [] == Person.all.sort_by(:name)
|
389
|
+
end
|
390
|
+
|
391
|
+
test "return the first element sorted by id when using first" do
|
392
|
+
Person.create :name => "A"
|
393
|
+
Person.create :name => "B"
|
394
|
+
assert "A" == Person.all.first.name
|
395
|
+
end
|
396
|
+
|
397
|
+
test "return the first element sorted by name if first receives a sorting option" do
|
398
|
+
Person.create :name => "B"
|
399
|
+
Person.create :name => "A"
|
400
|
+
assert "A" == Person.all.first(:by => :name, :order => "ALPHA").name
|
401
|
+
end
|
402
|
+
|
403
|
+
test "return attribute values when the get parameter is specified" do
|
404
|
+
Person.create :name => "B"
|
405
|
+
Person.create :name => "A"
|
406
|
+
|
407
|
+
res = Person.all.sort_by(:name, :get => :name, :order => "ALPHA")
|
408
|
+
|
409
|
+
assert_equal ["A", "B"], res
|
410
|
+
end
|
411
|
+
|
412
|
+
test "work on lists" do
|
413
|
+
post = Post.create :body => "Hello world!"
|
414
|
+
|
415
|
+
redis = Post.redis
|
416
|
+
|
417
|
+
redis.call("RPUSH", post.related.key, Post.create(:body => "C").id)
|
418
|
+
redis.call("RPUSH", post.related.key, Post.create(:body => "B").id)
|
419
|
+
redis.call("RPUSH", post.related.key, Post.create(:body => "A").id)
|
420
|
+
|
421
|
+
res = post.related.sort_by(:body, :order => "ALPHA ASC").map { |r| r.body }
|
422
|
+
assert_equal ["A", "B", "C"], res
|
423
|
+
end
|
424
|
+
|
425
|
+
# Loading attributes
|
426
|
+
setup do
|
427
|
+
event = Event.new
|
428
|
+
event.name = "Ruby Tuesday"
|
429
|
+
event.save.id
|
430
|
+
end
|
431
|
+
|
432
|
+
test "load attributes as a strings" do
|
433
|
+
event = Event.create(:name => 1)
|
434
|
+
|
435
|
+
assert "1" == Event[event.id].name
|
436
|
+
end
|
437
|
+
|
438
|
+
# Enumerable indices
|
439
|
+
class Entry < Ohm::Model
|
440
|
+
attribute :tags
|
441
|
+
index :tag
|
442
|
+
|
443
|
+
def tag
|
444
|
+
tags.split(/\s+/)
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
setup do
|
449
|
+
Entry.create(:tags => "foo bar baz")
|
450
|
+
end
|
451
|
+
|
452
|
+
test "finding by one entry in the enumerable" do |entry|
|
453
|
+
assert Entry.find(:tag => "foo").include?(entry)
|
454
|
+
assert Entry.find(:tag => "bar").include?(entry)
|
455
|
+
assert Entry.find(:tag => "baz").include?(entry)
|
456
|
+
end
|
457
|
+
|
458
|
+
test "finding by multiple entries in the enumerable" do |entry|
|
459
|
+
assert Entry.find(:tag => ["foo", "bar"]).include?(entry)
|
460
|
+
assert Entry.find(:tag => ["bar", "baz"]).include?(entry)
|
461
|
+
assert Entry.find(:tag => ["baz", "oof"]).empty?
|
462
|
+
end
|
463
|
+
|
464
|
+
# Attributes of type Set
|
465
|
+
setup do
|
466
|
+
@person1 = Person.create(:name => "Albert")
|
467
|
+
@person2 = Person.create(:name => "Bertrand")
|
468
|
+
@person3 = Person.create(:name => "Charles")
|
469
|
+
|
470
|
+
@event = Event.new
|
471
|
+
@event.name = "Ruby Tuesday"
|
472
|
+
end
|
473
|
+
|
474
|
+
test "filter elements" do
|
475
|
+
@event.save
|
476
|
+
@event.attendees.add(@person1)
|
477
|
+
@event.attendees.add(@person2)
|
478
|
+
|
479
|
+
assert [@person1] == @event.attendees.find(:initial => "A").to_a
|
480
|
+
assert [@person2] == @event.attendees.find(:initial => "B").to_a
|
481
|
+
assert [] == @event.attendees.find(:initial => "Z").to_a
|
482
|
+
end
|
483
|
+
|
484
|
+
test "delete elements" do
|
485
|
+
@event.save
|
486
|
+
@event.attendees.add(@person1)
|
487
|
+
@event.attendees.add(@person2)
|
488
|
+
|
489
|
+
assert_equal 2, @event.attendees.size
|
490
|
+
|
491
|
+
@event.attendees.delete(@person2)
|
492
|
+
assert_equal 1, @event.attendees.size
|
493
|
+
end
|
494
|
+
|
495
|
+
test "not be available if the model is new" do
|
496
|
+
assert_raise Ohm::MissingID do
|
497
|
+
@event.attendees
|
498
|
+
end
|
499
|
+
end
|
500
|
+
|
501
|
+
test "remove an element if sent delete" do
|
502
|
+
@event.save
|
503
|
+
@event.attendees.add(@person1)
|
504
|
+
@event.attendees.add(@person2)
|
505
|
+
@event.attendees.add(@person3)
|
506
|
+
|
507
|
+
assert_equal ["1", "2", "3"], Event.redis.call("SORT", @event.attendees.key)
|
508
|
+
|
509
|
+
Event.redis.call("SREM", @event.attendees.key, @person2.id)
|
510
|
+
assert_equal ["1", "3"], Event.redis.call("SORT", Event[@event.id].attendees.key)
|
511
|
+
end
|
512
|
+
|
513
|
+
test "return true if the set includes some member" do
|
514
|
+
@event.save
|
515
|
+
@event.attendees.add(@person1)
|
516
|
+
@event.attendees.add(@person2)
|
517
|
+
assert @event.attendees.include?(@person2)
|
518
|
+
|
519
|
+
@event.attendees.include?(@person3)
|
520
|
+
assert !@event.attendees.include?(@person3)
|
521
|
+
end
|
522
|
+
|
523
|
+
test "return instances of the passed model" do
|
524
|
+
@event.save
|
525
|
+
@event.attendees.add(@person1)
|
526
|
+
|
527
|
+
assert [@person1] == @event.attendees.to_a
|
528
|
+
assert @person1 == @event.attendees[@person1.id]
|
529
|
+
end
|
530
|
+
|
531
|
+
test "return the size of the set" do
|
532
|
+
@event.save
|
533
|
+
@event.attendees.add(@person1)
|
534
|
+
@event.attendees.add(@person2)
|
535
|
+
@event.attendees.add(@person3)
|
536
|
+
assert 3 == @event.attendees.size
|
537
|
+
end
|
538
|
+
|
539
|
+
test "empty the set" do
|
540
|
+
@event.save
|
541
|
+
@event.attendees.add(@person1)
|
542
|
+
|
543
|
+
Event.redis.call("DEL", @event.attendees.key)
|
544
|
+
|
545
|
+
assert @event.attendees.empty?
|
546
|
+
end
|
547
|
+
|
548
|
+
test "replace the values in the set" do
|
549
|
+
@event.save
|
550
|
+
@event.attendees.add(@person1)
|
551
|
+
|
552
|
+
assert [@person1] == @event.attendees.to_a
|
553
|
+
|
554
|
+
@event.attendees.replace([@person2, @person3])
|
555
|
+
|
556
|
+
assert [@person2, @person3] == @event.attendees.to_a.sort_by(&:id)
|
557
|
+
end
|
558
|
+
|
559
|
+
# Sorting lists and sets by model attributes
|
560
|
+
setup do
|
561
|
+
@event = Event.create(:name => "Ruby Tuesday")
|
562
|
+
{'D' => 4, 'C' => 2, 'B' => 5, 'A' => 3}.each_pair do |name, logins|
|
563
|
+
person = Person.create(:name => name)
|
564
|
+
person.incr :logins, logins
|
565
|
+
@event.attendees.add(person)
|
566
|
+
end
|
567
|
+
end
|
568
|
+
|
569
|
+
test "sort the model instances by the values provided" do
|
570
|
+
people = @event.attendees.sort_by(:name, :order => "ALPHA")
|
571
|
+
assert %w[A B C D] == people.map(&:name)
|
572
|
+
end
|
573
|
+
|
574
|
+
test "accept a number in the limit parameter" do
|
575
|
+
people = @event.attendees.sort_by(:name, :limit => [0, 2], :order => "ALPHA")
|
576
|
+
assert %w[A B] == people.map(&:name)
|
577
|
+
end
|
578
|
+
|
579
|
+
test "use the start parameter as an offset if the limit is provided" do
|
580
|
+
people = @event.attendees.sort_by(:name, :limit => [1, 2], :order => "ALPHA")
|
581
|
+
assert %w[B C] == people.map(&:name)
|
582
|
+
end
|
583
|
+
|
584
|
+
test "use counter attributes for sorting" do
|
585
|
+
people = @event.attendees.sort_by(:logins, :limit => [0, 3], :order => "ALPHA")
|
586
|
+
assert %w[C A D] == people.map(&:name)
|
587
|
+
end
|
588
|
+
|
589
|
+
test "use counter attributes for sorting with key option" do
|
590
|
+
people = @event.attendees.sort_by(:logins, :get => :logins, :limit => [0, 3], :order => "ALPHA")
|
591
|
+
assert %w[2 3 4] == people
|
592
|
+
end
|
593
|
+
|
594
|
+
# Collections initialized with a Model parameter
|
595
|
+
setup do
|
596
|
+
@user = User.create(:email => "albert@example.com")
|
597
|
+
@user.posts.add(Post.create(:body => "D"))
|
598
|
+
@user.posts.add(Post.create(:body => "C"))
|
599
|
+
@user.posts.add(Post.create(:body => "B"))
|
600
|
+
@user.posts.add(Post.create(:body => "A"))
|
601
|
+
end
|
602
|
+
|
603
|
+
test "return instances of the passed model" do
|
604
|
+
assert Post == @user.posts.first.class
|
605
|
+
end
|
606
|
+
|
607
|
+
test "remove an object from the set" do
|
608
|
+
post = @user.posts.first
|
609
|
+
assert @user.posts.include?(post)
|
610
|
+
|
611
|
+
User.redis.call("SREM", @user.posts.key, post.id)
|
612
|
+
assert !@user.posts.include?(post)
|
613
|
+
end
|
614
|
+
|
615
|
+
test "remove an object id from the set" do
|
616
|
+
post = @user.posts.first
|
617
|
+
assert @user.posts.include?(post)
|
618
|
+
|
619
|
+
User.redis.call("SREM", @user.posts.key, post.id)
|
620
|
+
assert !@user.posts.include?(post)
|
621
|
+
end
|
622
|
+
|
623
|
+
# Counters
|
624
|
+
setup do
|
625
|
+
@event = Event.create(:name => "Ruby Tuesday")
|
626
|
+
end
|
627
|
+
|
628
|
+
test "be zero if not initialized" do
|
629
|
+
assert 0 == @event.votes
|
630
|
+
end
|
631
|
+
|
632
|
+
test "be able to increment a counter" do
|
633
|
+
@event.incr(:votes)
|
634
|
+
assert 1 == @event.votes
|
635
|
+
|
636
|
+
@event.incr(:votes, 2)
|
637
|
+
assert 3 == @event.votes
|
638
|
+
end
|
639
|
+
|
640
|
+
test "be able to decrement a counter" do
|
641
|
+
@event.decr(:votes)
|
642
|
+
assert @event.votes == -1
|
643
|
+
|
644
|
+
@event.decr(:votes, 2)
|
645
|
+
assert @event.votes == -3
|
646
|
+
end
|
647
|
+
|
648
|
+
# Comparison
|
649
|
+
setup do
|
650
|
+
@user = User.create(:email => "foo")
|
651
|
+
end
|
652
|
+
|
653
|
+
test "be comparable to other instances" do
|
654
|
+
assert @user == User[@user.id]
|
655
|
+
|
656
|
+
assert @user != User.create
|
657
|
+
assert User.new != User.new
|
658
|
+
end
|
659
|
+
|
660
|
+
test "not be comparable to instances of other models" do
|
661
|
+
assert @user != Event.create(:name => "Ruby Tuesday")
|
662
|
+
end
|
663
|
+
|
664
|
+
test "be comparable to non-models" do
|
665
|
+
assert @user != 1
|
666
|
+
assert @user != true
|
667
|
+
|
668
|
+
# Not equal although the other object responds to #key.
|
669
|
+
assert @user != OpenStruct.new(:key => @user.send(:key))
|
670
|
+
end
|
671
|
+
|
672
|
+
# Debugging
|
673
|
+
class ::Bar < Ohm::Model
|
674
|
+
attribute :name
|
675
|
+
counter :visits
|
676
|
+
set :friends, self
|
677
|
+
set :comments, self
|
678
|
+
|
679
|
+
def foo
|
680
|
+
bar.foo
|
681
|
+
end
|
682
|
+
|
683
|
+
def baz
|
684
|
+
bar.new.foo
|
685
|
+
end
|
686
|
+
|
687
|
+
def bar
|
688
|
+
SomeMissingConstant
|
689
|
+
end
|
690
|
+
end
|
691
|
+
|
692
|
+
# Models connected to different databases
|
693
|
+
class ::Car < Ohm::Model
|
694
|
+
attribute :name
|
695
|
+
|
696
|
+
self.redis = Redic.new
|
697
|
+
end
|
698
|
+
|
699
|
+
class ::Make < Ohm::Model
|
700
|
+
attribute :name
|
701
|
+
end
|
702
|
+
|
703
|
+
setup do
|
704
|
+
Car.redis.call("SELECT", 15)
|
705
|
+
end
|
706
|
+
|
707
|
+
test "save to the selected database" do
|
708
|
+
car = Car.create(:name => "Twingo")
|
709
|
+
make = Make.create(:name => "Renault")
|
710
|
+
|
711
|
+
redis = Redic.new
|
712
|
+
|
713
|
+
assert ["1"] == redis.call("SMEMBERS", "Make:all")
|
714
|
+
assert [] == redis.call("SMEMBERS", "Car:all")
|
715
|
+
|
716
|
+
assert ["1"] == Car.redis.call("SMEMBERS", "Car:all")
|
717
|
+
assert [] == Car.redis.call("SMEMBERS", "Make:all")
|
718
|
+
|
719
|
+
assert car == Car[1]
|
720
|
+
assert make == Make[1]
|
721
|
+
|
722
|
+
Make.redis.call("FLUSHDB")
|
723
|
+
|
724
|
+
assert car == Car[1]
|
725
|
+
assert Make[1].nil?
|
726
|
+
end
|
727
|
+
|
728
|
+
test "allow changing the database" do
|
729
|
+
Car.create(:name => "Twingo")
|
730
|
+
assert_equal ["1"], Car.redis.call("SMEMBERS", Car.all.key)
|
731
|
+
|
732
|
+
Car.redis = Redic.new("redis://127.0.0.1:6379")
|
733
|
+
assert_equal [], Car.redis.call("SMEMBERS", Car.all.key)
|
734
|
+
|
735
|
+
Car.redis.call("SELECT", 15)
|
736
|
+
assert_equal ["1"], Car.redis.call("SMEMBERS", Car.all.key)
|
737
|
+
end
|
738
|
+
|
739
|
+
# Persistence
|
740
|
+
test "persist attributes to a hash" do
|
741
|
+
event = Event.create(:name => "Redis Meetup")
|
742
|
+
event.incr(:votes)
|
743
|
+
|
744
|
+
assert "hash" == Ohm.redis.call("TYPE", "Event:1")
|
745
|
+
|
746
|
+
expected= %w[Event:1 Event:1:counters Event:all Event:id]
|
747
|
+
assert_equal expected, Ohm.redis.call("KEYS", "Event:*").sort
|
748
|
+
|
749
|
+
assert "Redis Meetup" == Event[1].name
|
750
|
+
assert 1 == Event[1].votes
|
751
|
+
end
|
752
|
+
|
753
|
+
# namespaced models
|
754
|
+
test "be persisted" do
|
755
|
+
SomeNamespace::Foo.create(:name => "foo")
|
756
|
+
|
757
|
+
SomeNamespace::Bar.create(:foo => SomeNamespace::Foo[1])
|
758
|
+
|
759
|
+
assert "hash" == Ohm.redis.call("TYPE", "SomeNamespace::Foo:1")
|
760
|
+
|
761
|
+
assert "foo" == SomeNamespace::Foo[1].name
|
762
|
+
assert "foo" == SomeNamespace::Bar[1].foo.name
|
763
|
+
end if RUBY_VERSION >= "2.0.0"
|
764
|
+
|
765
|
+
test "typecast attributes" do
|
766
|
+
class Option < Ohm::Model
|
767
|
+
attribute :votes, lambda { |x| x.to_i }
|
768
|
+
end
|
769
|
+
|
770
|
+
option = Option.create :votes => 20
|
771
|
+
option.update(:votes => option.votes + 1)
|
772
|
+
|
773
|
+
assert_equal 21, option.votes
|
774
|
+
end
|
775
|
+
|
776
|
+
test "poster-example for overriding writers" do
|
777
|
+
silence_warnings do
|
778
|
+
class Advertiser < Ohm::Model
|
779
|
+
attribute :email
|
780
|
+
|
781
|
+
def email=(e)
|
782
|
+
attributes[:email] = e.to_s.downcase.strip
|
783
|
+
end
|
784
|
+
end
|
785
|
+
end
|
786
|
+
|
787
|
+
a = Advertiser.new(:email => " FOO@BAR.COM ")
|
788
|
+
assert_equal "foo@bar.com", a.email
|
789
|
+
end
|