ohm 0.1.5 → 1.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
@@ -33,7 +33,7 @@ test "export a hash with the errors" do
33
33
  person = Venue.new
34
34
  person.valid?
35
35
 
36
- assert Hash[:errors => [[:name, :not_present]]] == person.to_hash
36
+ assert_equal({ errors: { name: [:not_present] }}, person.to_hash)
37
37
  end
38
38
 
39
39
  test "export a hash with the its id" do
@@ -46,8 +46,7 @@ test "export a hash with its id and the errors" do
46
46
  person.name = nil
47
47
  person.valid?
48
48
 
49
- expected_hash = { :id => '1', :errors => [[:name, :not_present]] }
50
-
49
+ expected_hash = { id: '1', errors: { name: [:not_present] }}
51
50
  assert expected_hash == person.to_hash
52
51
  end
53
52
 
@@ -65,3 +64,11 @@ test "just be the to_hash of a model" do
65
64
  assert "1" == json["id"]
66
65
  assert "Ruby" == json["language"]
67
66
  end
67
+
68
+ test "export an array of records to json" do
69
+ Programmer.create(language: "Ruby")
70
+ Programmer.create(language: "Python")
71
+
72
+ expected = [{ id: "1", language: "Ruby" }, { id: "2", language: "Python"}].to_json
73
+ assert_equal expected, Programmer.all.to_a.to_json
74
+ end
data/test/lua-save.rb ADDED
@@ -0,0 +1,193 @@
1
+ require File.expand_path("./helper", File.dirname(__FILE__))
2
+
3
+ def redis
4
+ Ohm.redis
5
+ end
6
+
7
+ begin
8
+ Ohm.redis.script("flush")
9
+ rescue RuntimeError
10
+ # We're running on Redis < 2.6, so we
11
+ # skip all the test.
12
+ else
13
+ setup do
14
+ Ohm.redis.script("flush")
15
+
16
+ redis.sadd("User:uniques", "email")
17
+ redis.sadd("User:indices", "fname")
18
+ redis.sadd("User:indices", "lname")
19
+ redis.hset("User:uniques:email", "foo@bar.com", 1)
20
+
21
+ Ohm::Lua.new("./test/lua", redis)
22
+ end
23
+
24
+ test "empty email doesn't choke" do |lua|
25
+ res = lua.run_file("save",
26
+ keys: ["User"],
27
+ argv: ["email", nil])
28
+
29
+ assert_equal [200, ["id", "1"]], res
30
+ assert_equal "1", redis.hget("User:uniques:email", nil)
31
+ end
32
+
33
+ test "empty fname / lname doesn't choke" do |lua|
34
+ res = lua.run_file("save",
35
+ keys: ["User"],
36
+ argv: ["email", nil, "fname", nil, "lname", nil])
37
+
38
+ assert_equal [200, ["id", "1"]], res
39
+ assert redis.sismember("User:indices:fname:", 1)
40
+ assert redis.sismember("User:indices:lname:", 1)
41
+ end
42
+
43
+ test "returns the unique constraint error" do |lua|
44
+ res = lua.run_file("save",
45
+ keys: ["User"],
46
+ argv: ["email", "foo@bar.com"])
47
+
48
+ assert_equal [500, ["email", "not_unique"]], res
49
+ end
50
+
51
+ test "persists the unique entry properly" do |lua|
52
+ lua.run_file("save",
53
+ keys: ["User"],
54
+ argv: ["email", "bar@baz.com"])
55
+
56
+ assert_equal "1", redis.hget("User:uniques:email", "bar@baz.com")
57
+ end
58
+
59
+ test "adds the entry to User:all" do |lua|
60
+ lua.run_file("save",
61
+ keys: ["User"],
62
+ argv: ["email", "bar@baz.com"])
63
+
64
+ assert_equal 1, redis.scard("User:all")
65
+ end
66
+
67
+
68
+ test "saves the attributes" do |lua|
69
+ lua.run_file("save",
70
+ keys: ["User"],
71
+ argv: ["email", "bar@baz.com", "fname", "John", "lname", "Doe"])
72
+
73
+ assert_equal "bar@baz.com", redis.hget("User:1", "email")
74
+ assert_equal "John", redis.hget("User:1", "fname")
75
+ assert_equal "Doe", redis.hget("User:1", "lname")
76
+ end
77
+
78
+ test "indexes fname / lname" do |lua|
79
+ lua.run_file("save",
80
+ keys: ["User"],
81
+ argv: ["email", "bar@baz.com", "fname", "John", "lname", "Doe"])
82
+
83
+ assert redis.sismember("User:indices:fname:John", 1)
84
+ assert redis.sismember("User:indices:lname:Doe", 1)
85
+ end
86
+
87
+ test "unique constraint during update" do |lua|
88
+ lua.run_file("save",
89
+ keys: ["User"],
90
+ argv: ["email", "bar@baz.com", "fname", "John", "lname", "Doe"])
91
+
92
+ res = lua.run_file("save",
93
+ keys: ["User", "User:1"],
94
+ argv: ["email", "bar@baz.com", "fname", "John", "lname", "Doe"])
95
+
96
+ assert_equal [200, ["id", "1"]], res
97
+
98
+ res = lua.run_file("save",
99
+ keys: ["User", "User:1"],
100
+ argv: ["email", "foo@bar.com", "fname", "Jane", "lname", "Doe"])
101
+
102
+ assert_equal [200, ["id", "1"]], res
103
+ end
104
+
105
+ test "cleanup of existing indices during update" do |lua|
106
+ lua.run_file("save",
107
+ keys: ["User"],
108
+ argv: ["email", "bar@baz.com", "fname", "John", "lname", "Doe"])
109
+
110
+ res = lua.run_file("save",
111
+ keys: ["User", "User:1"],
112
+ argv: ["email", "foo@bar.com", "fname", "Jane", "lname", "Smith"])
113
+
114
+ assert ! redis.sismember("User:indices:fname:John", 1)
115
+ assert ! redis.sismember("User:indices:fname:Doe", 1)
116
+ end
117
+
118
+ test "cleanup of existing uniques during update" do |lua|
119
+ lua.run_file("save",
120
+ keys: ["User"],
121
+ argv: ["email", "bar@baz.com", "fname", "John", "lname", "Doe"])
122
+
123
+ res = lua.run_file("save",
124
+ keys: ["User", "User:1"],
125
+ argv: ["email", "foo@bar.com", "fname", "Jane", "lname", "Smith"])
126
+
127
+ assert_equal nil, redis.hget("User:uniques:email", "bar@baz.com")
128
+ end
129
+ end
130
+
131
+ __END__
132
+ $VERBOSE = false
133
+
134
+ test "stress test for lua scripting" do |lua|
135
+ require "benchmark"
136
+
137
+ class User < Ohm::Model
138
+ attribute :email
139
+ attribute :fname
140
+ attribute :lname
141
+
142
+ index :email
143
+ index :fname
144
+ index :lname
145
+ end
146
+
147
+ t = Benchmark.measure do
148
+ threads = 100.times.map do |i|
149
+ Thread.new do
150
+ User.create(email: "foo#{i}@bar.com",
151
+ fname: "Jane#{i}",
152
+ lname: "Smith#{i}")
153
+ end
154
+ end
155
+
156
+ threads.each(&:join)
157
+ end
158
+
159
+ puts t
160
+ end
161
+
162
+ test "stress test for postgres + sequel (as a comparison)" do
163
+ require "sequel"
164
+
165
+ DB = Sequel.connect("postgres://cyx@localhost/postgres")
166
+ DB[:users].truncate
167
+ t = Benchmark.measure do
168
+ threads = 100.times.map do |i|
169
+ Thread.new do
170
+ DB[:users].insert(email: "foo#{i}@bar.com",
171
+ fname: "John#{i}",
172
+ lname: "Doe#{i}")
173
+ end
174
+ end
175
+
176
+ threads.each(&:join)
177
+ end
178
+
179
+ puts t
180
+ end
181
+
182
+ ## Result for 100 threads:
183
+ # 0.040000 0.010000 0.050000 ( 0.061512) - lua script
184
+ # 0.150000 0.180000 0.330000 ( 0.259676) - postgres
185
+ #
186
+ ## Result for 100 linear executions:
187
+ #
188
+ # 0.010000 0.010000 0.020000 ( 0.032064) - lua script
189
+ # 0.010000 0.010000 0.020000 ( 0.059540) - postgres
190
+ #
191
+ ## It's also important to note that with 1K concurrent threads,
192
+ # postgres throws a Sequel::PoolTimeout
193
+ end
data/test/lua.rb ADDED
@@ -0,0 +1,47 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.expand_path("./helper", File.dirname(__FILE__))
4
+
5
+ begin
6
+ Ohm.redis.script("flush")
7
+ rescue RuntimeError
8
+ # We're running on Redis < 2.6, so we
9
+ # skip all the test.
10
+ else
11
+ setup do
12
+ Ohm::Lua.new("./test/lua", Ohm.redis)
13
+ end
14
+
15
+ test do |lua|
16
+ lua.redis.set("foo", "baz")
17
+
18
+ res = lua.run_file("getset", keys: ["foo"], argv: ["bar"])
19
+ assert_equal ["baz", "bar"], res
20
+ end
21
+
22
+ test do |lua|
23
+ res = lua.run_file("ohm-save",
24
+ keys: ["User"],
25
+ argv: ["fname", "John", "lname", "Doe"])
26
+
27
+ assert lua.redis.sismember("User:all", 1)
28
+ assert_equal({ "fname" => "John", "lname" => "Doe" },
29
+ lua.redis.hgetall("User:1"))
30
+ end
31
+
32
+ test do |lua|
33
+ lua.redis.sadd("User:indices", "fname")
34
+ lua.redis.sadd("User:indices", "lname")
35
+
36
+ res = lua.run_file("save-with-indices",
37
+ keys: ["User:1", "User:all", "User:indices"],
38
+ argv: ["fname", "John", "lname", "Doe"])
39
+
40
+ assert lua.redis.sismember("User:all", 1)
41
+
42
+ assert lua.redis.sismember("User:fname:John", 1)
43
+ assert lua.redis.sismember("User:lname:Doe", 1)
44
+ assert lua.redis.sismember("User:1:_indices", "User:fname:John")
45
+ assert lua.redis.sismember("User:1:_indices", "User:lname:Doe")
46
+ end
47
+ end
@@ -6,7 +6,7 @@ require "ostruct"
6
6
 
7
7
  class Post < Ohm::Model
8
8
  attribute :body
9
- list :related, Post
9
+ set :related, Post
10
10
  end
11
11
 
12
12
  class User < Ohm::Model
@@ -18,12 +18,8 @@ class Person < Ohm::Model
18
18
  attribute :name
19
19
  index :initial
20
20
 
21
- def validate
22
- assert_present :name
23
- end
24
-
25
21
  def initial
26
- name[0, 1].upcase
22
+ name[0, 1].upcase if name
27
23
  end
28
24
  end
29
25
 
@@ -34,7 +30,7 @@ class Event < Ohm::Model
34
30
 
35
31
  attribute :slug
36
32
 
37
- def write
33
+ def save
38
34
  self.slug = name.to_s.downcase
39
35
  super
40
36
  end
@@ -55,6 +51,48 @@ class Meetup < Ohm::Model
55
51
  end
56
52
  end
57
53
 
54
+ test "counters are cleaned up during deletion" do
55
+ e = Event.create(name: "Foo")
56
+ e.incr :votes, 10
57
+
58
+ assert_equal 10, e.votes
59
+
60
+ e.delete
61
+ assert ! e.key[:counters].exists
62
+ end
63
+
64
+ test "return the unsaved object if validation fails" do
65
+ assert Person.create(:name => nil).kind_of?(Person)
66
+ end
67
+
68
+ test "return false if the validation fails" do
69
+ event = Meetup.create(:name => "Ruby Tuesday")
70
+ assert !event.update(:name => nil)
71
+ end
72
+
73
+ test "get" do
74
+ m = Meetup.create(name: "Foo")
75
+ m.name = "Bar"
76
+
77
+ assert_equal "Foo", m.get(:name)
78
+ assert_equal "Foo", m.name
79
+ end
80
+
81
+ test "set" do
82
+ m = Meetup.create(name: "Foo")
83
+
84
+ m.set :name, "Bar"
85
+ assert_equal "Bar", m.name
86
+
87
+ m = Meetup[m.id]
88
+ assert_equal "Bar", m.name
89
+
90
+ # Deletes when value is nil.
91
+ m.set :name, nil
92
+ m = Meetup[m.id]
93
+ assert ! m.key.hexists(:name)
94
+ end
95
+
58
96
  test "assign attributes from the hash" do
59
97
  event = Event.new(:name => "Ruby Tuesday")
60
98
  assert event.name == "Ruby Tuesday"
@@ -68,21 +106,12 @@ test "assign an ID and save the object" do
68
106
  assert "2" == event2.id
69
107
  end
70
108
 
71
- test "return the unsaved object if validation fails" do
72
- assert Person.create(:name => nil).kind_of?(Person)
73
- end
74
-
75
109
  test "updates attributes" do
76
110
  event = Meetup.create(:name => "Ruby Tuesday")
77
111
  event.update(:name => "Ruby Meetup")
78
112
  assert "Ruby Meetup" == event.name
79
113
  end
80
114
 
81
- test "return false if the validation fails" do
82
- event = Meetup.create(:name => "Ruby Tuesday")
83
- assert !event.update(:name => nil)
84
- end
85
-
86
115
  test "save the attributes in UTF8" do
87
116
  event = Meetup.create(:name => "32° Kisei-sen")
88
117
  assert "32° Kisei-sen" == Meetup[event.id].name
@@ -92,7 +121,7 @@ test "delete the attribute if set to nil" do
92
121
  event = Meetup.create(:name => "Ruby Tuesday", :location => "Los Angeles")
93
122
  assert "Los Angeles" == Meetup[event.id].location
94
123
  assert event.update(:location => nil)
95
- assert nil == Meetup[event.id].location
124
+ assert_equal nil, Meetup[event.id].location
96
125
  end
97
126
 
98
127
  test "delete the attribute if set to an empty string" do
@@ -122,16 +151,6 @@ test "not raise if a counter is redefined" do
122
151
  end
123
152
  end
124
153
 
125
- test "not raise if a list is redefined" do
126
- class RedefinedModel < Ohm::Model
127
- list :todo, lambda { }
128
-
129
- silence_warnings do
130
- list :todo, lambda { }
131
- end
132
- end
133
- end
134
-
135
154
  test "not raise if a set is redefined" do
136
155
  class RedefinedModel < Ohm::Model
137
156
  set :friends, lambda { }
@@ -144,7 +163,7 @@ end
144
163
 
145
164
  test "not raise if a collection is redefined" do
146
165
  class RedefinedModel < Ohm::Model
147
- list :toys, lambda { }
166
+ set :toys, lambda { }
148
167
 
149
168
  silence_warnings do
150
169
  set :toys, lambda { }
@@ -225,10 +244,10 @@ end
225
244
 
226
245
  test "assign a new id to the event" do
227
246
  event1 = Event.new
228
- event1.create
247
+ event1.save
229
248
 
230
249
  event2 = Event.new
231
- event2.create
250
+ event2.save
232
251
 
233
252
  assert !event1.new?
234
253
  assert !event2.new?
@@ -246,7 +265,7 @@ end
246
265
  test "save it only if it was previously created" do
247
266
  event = Event.new
248
267
  event.name = "Lorem ipsum"
249
- event.create
268
+ event.save
250
269
 
251
270
  event.name = "Lorem"
252
271
  event.save
@@ -254,7 +273,7 @@ test "save it only if it was previously created" do
254
273
  assert "Lorem" == Event[event.id].name
255
274
  end
256
275
 
257
- test "allow to hook into write" do
276
+ test "allow to hook into save" do
258
277
  event = Event.create(:name => "Foo")
259
278
 
260
279
  assert "foo" == event.slug
@@ -266,7 +285,7 @@ test "save counters" do
266
285
  event.incr(:votes)
267
286
  event.save
268
287
 
269
- assert 1 == Event[event.id].votes
288
+ assert_equal 1, Event[event.id].votes
270
289
  end
271
290
 
272
291
  # Delete
@@ -274,13 +293,13 @@ test "delete an existing model" do
274
293
  class ModelToBeDeleted < Ohm::Model
275
294
  attribute :name
276
295
  set :foos, Post
277
- list :bars, Post
296
+ set :bars, Post
278
297
  end
279
298
 
280
299
  @model = ModelToBeDeleted.create(:name => "Lorem")
281
300
 
282
- @model.foos << Post.create
283
- @model.bars << Post.create
301
+ @model.foos.add(Post.create)
302
+ @model.bars.add(Post.create)
284
303
 
285
304
  id = @model.id
286
305
 
@@ -303,14 +322,13 @@ test "be no leftover keys" do
303
322
  index :name
304
323
  end
305
324
 
306
- assert [] == Ohm.redis.keys("*")
325
+ assert_equal [], Ohm.redis.keys("*")
307
326
 
308
327
  Foo.create(:name => "Bar")
309
-
310
- assert ["Foo:1:_indices", "Foo:1", "Foo:all", "Foo:id", "Foo:name:QmFy"].sort == Ohm.redis.keys("*").sort
328
+ expected = %w[Foo:1 Foo:all Foo:id Foo:indices:name:Bar]
329
+ assert expected.sort == Ohm.redis.keys("*").sort
311
330
 
312
331
  Foo[1].delete
313
-
314
332
  assert ["Foo:id"] == Ohm.redis.keys("*")
315
333
  end
316
334
 
@@ -318,14 +336,13 @@ end
318
336
  test "find all" do
319
337
  event1 = Event.new
320
338
  event1.name = "Ruby Meetup"
321
- event1.create
339
+ event1.save
322
340
 
323
341
  event2 = Event.new
324
342
  event2.name = "Ruby Tuesday"
325
- event2.create
343
+ event2.save
326
344
 
327
345
  all = Event.all
328
-
329
346
  assert all.detect {|e| e.name == "Ruby Meetup" }
330
347
  assert all.detect {|e| e.name == "Ruby Tuesday" }
331
348
  end
@@ -337,7 +354,8 @@ test "sort all" do
337
354
  Person.create :name => "B"
338
355
  Person.create :name => "A"
339
356
 
340
- assert %w[A B C D] == Person.all.sort_by(:name, :order => "ALPHA").map { |person| person.name }
357
+ names = Person.all.sort_by(:name, :order => "ALPHA").map { |p| p.name }
358
+ assert %w[A B C D] == names
341
359
  end
342
360
 
343
361
  test "return an empty array if there are no elements to sort" do
@@ -360,60 +378,58 @@ test "return attribute values when the get parameter is specified" do
360
378
  Person.create :name => "B"
361
379
  Person.create :name => "A"
362
380
 
363
- assert "A" == Person.all.sort_by(:name, :get => :name, :order => "ALPHA").first
381
+ res = Person.all.sort_by(:name, :get => :name, :order => "ALPHA")
382
+
383
+ assert_equal ["A", "B"], res
364
384
  end
365
385
 
366
386
  test "work on lists" do
367
- @post = Post.create :body => "Hello world!"
368
- @post.related << Post.create(:body => "C")
369
- @post.related << Post.create(:body => "B")
370
- @post.related << Post.create(:body => "A")
387
+ post = Post.create :body => "Hello world!"
388
+ post.related.key.rpush(Post.create(:body => "C").id)
389
+ post.related.key.rpush(Post.create(:body => "B").id)
390
+ post.related.key.rpush(Post.create(:body => "A").id)
371
391
 
372
- assert ["A", "B", "C"] == @post.related.sort_by(:body, :order => "ALPHA ASC").map { |model| model.body }
392
+ res = post.related.sort_by(:body, order: "ALPHA ASC").map { |r| r.body }
393
+ assert_equal ["A", "B", "C"], res
373
394
  end
374
395
 
375
396
  # Loading attributes
376
397
  setup do
377
398
  event = Event.new
378
399
  event.name = "Ruby Tuesday"
379
- @id = event.create.id
400
+ event.save.id
380
401
  end
381
402
 
382
- def monitor
383
- log = []
403
+ test "load attributes as a strings" do
404
+ event = Event.create(:name => 1)
384
405
 
385
- monitor = Thread.new do
386
- Redis.connect.monitor do |line|
387
- break if line =~ /ping/
388
- log << line
389
- end
390
- end
406
+ assert "1" == Event[event.id].name
407
+ end
391
408
 
392
- sleep 0.01
409
+ # Enumerable indices
410
+ class Entry < Ohm::Model
411
+ attribute :tags
412
+ index :tag
393
413
 
394
- log.clear.tap do
395
- yield
396
- Ohm.redis.ping
397
- monitor.join
414
+ def tag
415
+ tags.split(/\s+/)
398
416
  end
399
417
  end
400
418
 
401
- test "load attributes lazily" do
402
- event = Event[@id]
403
-
404
- log = monitor { event.name }
405
-
406
- assert !log.empty?
407
-
408
- log = monitor { event.name }
409
-
410
- assert log.empty?
419
+ setup do
420
+ Entry.create(tags: "foo bar baz")
411
421
  end
412
422
 
413
- test "load attributes as a strings" do
414
- event = Event.create(:name => 1)
423
+ test "finding by one entry in the enumerable" do |entry|
424
+ assert Entry.find(tag: "foo").include?(entry)
425
+ assert Entry.find(tag: "bar").include?(entry)
426
+ assert Entry.find(tag: "baz").include?(entry)
427
+ end
415
428
 
416
- assert "1" == Event[event.id].name
429
+ test "finding by multiple entries in the enumerable" do |entry|
430
+ assert Entry.find(tag: ["foo", "bar"]).include?(entry)
431
+ assert Entry.find(tag: ["bar", "baz"]).include?(entry)
432
+ assert Entry.find(tag: ["baz", "oof"]).empty?
417
433
  end
418
434
 
419
435
  # Attributes of type Set
@@ -426,314 +442,84 @@ setup do
426
442
  @event.name = "Ruby Tuesday"
427
443
  end
428
444
 
445
+ test "filter elements" do
446
+ @event.save
447
+ @event.attendees.add(@person1)
448
+ @event.attendees.add(@person2)
449
+
450
+ assert [@person1] == @event.attendees.find(:initial => "A").to_a
451
+ assert [@person2] == @event.attendees.find(:initial => "B").to_a
452
+ assert [] == @event.attendees.find(:initial => "Z").to_a
453
+ end
454
+
429
455
  test "not be available if the model is new" do
430
- assert_raise Ohm::Model::MissingID do
431
- @event.attendees << Person.new
456
+ assert_raise Ohm::MissingID do
457
+ @event.attendees
432
458
  end
433
459
  end
434
460
 
435
461
  test "remove an element if sent delete" do
436
- @event.create
437
- @event.attendees << @person1
438
- @event.attendees << @person2
439
- @event.attendees << @person3
440
- assert ["1", "2", "3"] == @event.attendees.key.sort
441
- @event.attendees.delete(@person2)
442
- assert ["1", "3"] == Event[@event.id].attendees.key.sort
462
+ @event.save
463
+ @event.attendees.add(@person1)
464
+ @event.attendees.add(@person2)
465
+ @event.attendees.add(@person3)
466
+
467
+ assert_equal ["1", "2", "3"], @event.attendees.key.sort
468
+
469
+ @event.attendees.key.srem(@person2.id)
470
+ assert_equal ["1", "3"], Event[@event.id].attendees.key.sort
443
471
  end
444
472
 
445
473
  test "return true if the set includes some member" do
446
- @event.create
447
- @event.attendees << @person1
448
- @event.attendees << @person2
474
+ @event.save
475
+ @event.attendees.add(@person1)
476
+ @event.attendees.add(@person2)
449
477
  assert @event.attendees.include?(@person2)
450
478
  assert !@event.attendees.include?(@person3)
451
479
  end
452
480
 
453
481
  test "return instances of the passed model" do
454
- @event.create
455
- @event.attendees << @person1
482
+ @event.save
483
+ @event.attendees.add(@person1)
456
484
 
457
- assert [@person1] == @event.attendees.all
485
+ assert [@person1] == @event.attendees.to_a
458
486
  assert @person1 == @event.attendees[@person1.id]
459
487
  end
460
488
 
461
489
  test "return the size of the set" do
462
- @event.create
463
- @event.attendees << @person1
464
- @event.attendees << @person2
465
- @event.attendees << @person3
490
+ @event.save
491
+ @event.attendees.add(@person1)
492
+ @event.attendees.add(@person2)
493
+ @event.attendees.add(@person3)
466
494
  assert 3 == @event.attendees.size
467
495
  end
468
496
 
469
497
  test "empty the set" do
470
- @event.create
471
- @event.attendees << @person1
472
-
473
- @event.attendees.clear
498
+ @event.save
499
+ @event.attendees.add(@person1)
500
+ @event.attendees.key.del
474
501
 
475
502
  assert @event.attendees.empty?
476
503
  end
477
504
 
478
505
  test "replace the values in the set" do
479
- @event.create
480
- @event.attendees << @person1
481
-
482
- assert [@person1] == @event.attendees.all
483
-
484
- @event.attendees.replace([@person2, @person3])
485
-
486
- assert [@person2, @person3] == @event.attendees.all.sort_by(&:id)
487
- end
488
-
489
- test "filter elements" do
490
- @event.create
506
+ @event.save
491
507
  @event.attendees.add(@person1)
492
- @event.attendees.add(@person2)
493
508
 
494
- assert [@person1] == @event.attendees.find(:initial => "A").all
495
- assert [@person2] == @event.attendees.find(:initial => "B").all
496
- assert [] == @event.attendees.find(:initial => "Z").all
497
- end
498
-
499
- # Attributes of type List
500
- setup do
501
- @post = Post.new
502
- @post.body = "Hello world!"
503
- @post.create
504
- end
505
-
506
- test "return an array" do
507
- assert @post.related.all.kind_of?(Array)
508
- end
509
+ assert [@person1] == @event.attendees.to_a
509
510
 
510
- test "append elements with push" do
511
- @post.related.push Post.create
512
- @post.related << Post.create
513
-
514
- assert ["2", "3"] == @post.related.all.map { |model| model.id }
515
- end
516
-
517
- test "keep the inserting order" do
518
- @post.related << Post.create
519
- @post.related << Post.create
520
- @post.related << Post.create
521
- assert ["2", "3", "4"] == @post.related.all.map { |model| model.id }
522
- end
523
-
524
- test "keep the inserting order after saving" do
525
- @post.related << Post.create
526
- @post.related << Post.create
527
- @post.related << Post.create
528
- @post.save
529
- assert ["2", "3", "4"] == Post[@post.id].related.map { |model| model.id }
530
- end
531
-
532
- test "allow slicing the list" do
533
- post1 = Post.create
534
- post2 = Post.create
535
- post3 = Post.create
536
-
537
- @post.related << post1
538
- @post.related << post2
539
- @post.related << post3
540
-
541
- assert post1 == @post.related[0]
542
- assert post2 == @post.related[1]
543
- assert post3 == @post.related[-1]
544
-
545
- assert nil == @post.related[3]
546
-
547
- assert [post2, post3] == @post.related[1, 2]
548
- assert [post2, post3] == @post.related[1, -1]
549
-
550
- assert [] == @post.related[4, 5]
551
-
552
- assert [post2, post3] == @post.related[1..2]
553
- assert [post2, post3] == @post.related[1..5]
554
-
555
- assert [] == @post.related[4..5]
556
- end
557
-
558
- test "respond to each" do
559
- @post.related << Post.create
560
- @post.related << Post.create
561
- @post.related << Post.create
562
-
563
- i = 2
564
- @post.related.each do |c|
565
- assert i == c.id.to_i
566
- i += 1
567
- end
568
- end
569
-
570
- test "return the size of the list" do
571
- @post.related << Post.create
572
- @post.related << Post.create
573
- @post.related << Post.create
574
- assert 3 == @post.related.size
575
- end
576
-
577
- test "return the last element with pop" do
578
- @post.related << Post.create
579
- @post.related << Post.create
580
- assert "3" == @post.related.pop.id
581
- assert "2" == @post.related.pop.id
582
- assert @post.related.empty?
583
- end
584
-
585
- test "return the first element with shift" do
586
- @post.related << Post.create
587
- @post.related << Post.create
588
- assert "2" == @post.related.shift.id
589
- assert "3" == @post.related.shift.id
590
- assert @post.related.empty?
591
- end
592
-
593
- test "push to the head of the list with unshift" do
594
- @post.related.unshift Post.create
595
- @post.related.unshift Post.create
596
- assert "2" == @post.related.pop.id
597
- assert "3" == @post.related.pop.id
598
- assert @post.related.empty?
599
- end
600
-
601
- test "empty the list" do
602
- @post.related.unshift Post.create
603
- @post.related.clear
604
-
605
- assert @post.related.empty?
606
- end
607
-
608
- test "replace the values in the list" do
609
- @post.related.replace([Post.create, Post.create])
610
-
611
- assert ["2", "3"] == @post.related.map { |model| model.id }
612
- end
613
-
614
- test "add models" do
615
- @post.related.add(Post.create(:body => "Hello"))
616
- assert ["2"] == @post.related.map { |model| model.id }
617
- end
618
-
619
- test "find elements in the list" do
620
- another_post = Post.create
621
-
622
- @post.related.add(another_post)
623
-
624
- assert @post.related.include?(another_post)
625
- assert !@post.related.include?(Post.create)
626
- end
627
-
628
- test "unshift models" do
629
- @post.related.unshift(Post.create(:body => "Hello"))
630
- @post.related.unshift(Post.create(:body => "Goodbye"))
631
-
632
- assert ["3", "2"] == @post.related.map { |model| model.id }
633
-
634
- assert "3" == @post.related.shift.id
635
-
636
- assert "2" == @post.related.pop.id
637
-
638
- assert @post.related.pop.nil?
639
- end
640
-
641
- # Applying arbitrary transformations
642
- require "date"
643
-
644
- class MyActiveRecordModel
645
- def self.find(id)
646
- return new if id.to_i == 1
647
- end
648
-
649
- def id
650
- 1
651
- end
652
-
653
- def ==(other)
654
- id == other.id
655
- end
656
- end
657
-
658
- class ::Calendar < Ohm::Model
659
- list :holidays, lambda { |v| Date.parse(v) }
660
- list :subscribers, lambda { |id| MyActiveRecordModel.find(id) }
661
- list :appointments, Appointment
662
-
663
- set :events, lambda { |id| MyActiveRecordModel.find(id) }
664
- end
665
-
666
- class ::Appointment < Ohm::Model
667
- attribute :text
668
- reference :subscriber, lambda { |id| MyActiveRecordModel.find(id) }
669
- end
670
-
671
- setup do
672
- @calendar = Calendar.create
673
-
674
- @calendar.holidays.key.rpush "2009-05-25"
675
- @calendar.holidays.key.rpush "2009-07-09"
676
-
677
- @calendar.subscribers << MyActiveRecordModel.find(1)
678
-
679
- @calendar.events << MyActiveRecordModel.find(1)
680
- end
681
-
682
- test "apply a transformation" do
683
- assert [Date.new(2009, 5, 25), Date.new(2009, 7, 9)] == @calendar.holidays.all
684
-
685
- assert [1] == @calendar.subscribers.all.map { |model| model.id }
686
- assert [MyActiveRecordModel.find(1)] == @calendar.subscribers.all
687
- end
688
-
689
- test "doing an each on lists" do
690
- arr = []
691
- @calendar.subscribers.each do |sub|
692
- arr << sub
693
- end
694
-
695
- assert [MyActiveRecordModel.find(1)] == arr
696
- end
697
-
698
- test "doing an each on sets" do
699
- arr = []
700
- @calendar.events.each do |sub|
701
- arr << sub
702
- end
703
-
704
- assert [MyActiveRecordModel.find(1)] == arr
705
- end
706
-
707
- test "allow lambdas in references" do
708
- appointment = Appointment.create(:subscriber => MyActiveRecordModel.find(1))
709
- assert MyActiveRecordModel.find(1) == appointment.subscriber
710
- end
711
-
712
- test "work with models too" do
713
- @calendar.appointments.add(Appointment.create(:text => "Meet with Bertrand"))
714
-
715
- assert [Appointment[1]] == Calendar[1].appointments.sort
716
- end
717
-
718
- # Sorting lists and sets
719
- setup do
720
- @post = Post.create(:body => "Lorem")
721
- @post.related << Post.create
722
- @post.related << Post.create
723
- @post.related << Post.create
724
- end
511
+ @event.attendees.replace([@person2, @person3])
725
512
 
726
- test "sort values" do
727
- assert %w{2 3 4} == @post.related.sort.map { |model| model.id }
513
+ assert [@person2, @person3] == @event.attendees.to_a.sort_by(&:id)
728
514
  end
729
515
 
730
516
  # Sorting lists and sets by model attributes
731
517
  setup do
732
518
  @event = Event.create(:name => "Ruby Tuesday")
733
- @event.attendees << Person.create(:name => "D")
734
- @event.attendees << Person.create(:name => "C")
735
- @event.attendees << Person.create(:name => "B")
736
- @event.attendees << Person.create(:name => "A")
519
+ @event.attendees.add(Person.create(:name => "D"))
520
+ @event.attendees.add(Person.create(:name => "C"))
521
+ @event.attendees.add(Person.create(:name => "B"))
522
+ @event.attendees.add(Person.create(:name => "A"))
737
523
  end
738
524
 
739
525
  test "sort the model instances by the values provided" do
@@ -742,39 +528,49 @@ test "sort the model instances by the values provided" do
742
528
  end
743
529
 
744
530
  test "accept a number in the limit parameter" do
745
- people = @event.attendees.sort_by(:name, :limit => 2, :order => "ALPHA")
531
+ people = @event.attendees.sort_by(:name, limit: [0, 2], order: "ALPHA")
746
532
  assert %w[A B] == people.map { |person| person.name }
747
533
  end
748
534
 
749
535
  test "use the start parameter as an offset if the limit is provided" do
750
- people = @event.attendees.sort_by(:name, :limit => 2, :start => 1, :order => "ALPHA")
536
+ people = @event.attendees.sort_by(:name, limit: [1, 2], order: "ALPHA")
751
537
  assert %w[B C] == people.map { |person| person.name }
752
538
  end
753
539
 
754
540
  # Collections initialized with a Model parameter
755
541
  setup do
756
542
  @user = User.create(:email => "albert@example.com")
757
- @user.posts.add Post.create(:body => "D")
758
- @user.posts.add Post.create(:body => "C")
759
- @user.posts.add Post.create(:body => "B")
760
- @user.posts.add Post.create(:body => "A")
543
+ @user.posts.add(Post.create(:body => "D"))
544
+ @user.posts.add(Post.create(:body => "C"))
545
+ @user.posts.add(Post.create(:body => "B"))
546
+ @user.posts.add(Post.create(:body => "A"))
761
547
  end
762
548
 
763
549
  test "return instances of the passed model" do
764
550
  assert Post == @user.posts.first.class
765
551
  end
766
552
 
553
+ test "remove an object from the set" do
554
+ post = @user.posts.first
555
+ assert @user.posts.include?(post)
556
+
557
+ @user.posts.key.srem(post.id)
558
+ assert !@user.posts.include?(post)
559
+ end
560
+
561
+ test "remove an object id from the set" do
562
+ post = @user.posts.first
563
+ assert @user.posts.include?(post)
564
+
565
+ @user.posts.key.srem(post.id)
566
+ assert !@user.posts.include?(post)
567
+ end
568
+
767
569
  # Counters
768
570
  setup do
769
571
  @event = Event.create(:name => "Ruby Tuesday")
770
572
  end
771
573
 
772
- test "raise ArgumentError if the attribute is not a counter" do
773
- assert_raise ArgumentError do
774
- @event.incr(:name)
775
- end
776
- end
777
-
778
574
  test "be zero if not initialized" do
779
575
  assert 0 == @event.votes
780
576
  end
@@ -824,7 +620,7 @@ class ::Bar < Ohm::Model
824
620
  attribute :name
825
621
  counter :visits
826
622
  set :friends, self
827
- list :comments, self
623
+ set :comments, self
828
624
 
829
625
  def foo
830
626
  bar.foo
@@ -839,56 +635,6 @@ class ::Bar < Ohm::Model
839
635
  end
840
636
  end
841
637
 
842
- test "provide a meaningful inspect" do
843
- bar = Bar.new
844
-
845
- assert "#<Bar:? name=nil friends=nil comments=nil visits=0>" == bar.inspect
846
-
847
- bar.update(:name => "Albert")
848
- bar.friends << Bar.create
849
- bar.comments << Bar.create
850
- bar.incr(:visits)
851
-
852
- assert %Q{#<Bar:#{bar.id} name="Albert" friends=#<Set (Bar): ["2"]> comments=#<List (Bar): ["3"]> visits=1>} == Bar[bar.id].inspect
853
- end
854
-
855
- def assert_wrapper_exception(&block)
856
- begin
857
- block.call
858
- rescue NoMethodError => exception_raised
859
- end
860
-
861
- assert exception_raised.message =~
862
- /You tried to call SomeMissingConstant#\w+, but SomeMissingConstant is not defined on #{__FILE__}:\d+:in `bar'/
863
- end
864
-
865
- test "inform about a miscatch by Wrapper when calling class methods" do
866
- assert_wrapper_exception { Bar.new.baz }
867
- end
868
-
869
- test "inform about a miscatch by Wrapper when calling instance methods" do
870
- assert_wrapper_exception { Bar.new.foo }
871
- end
872
-
873
- # Overwriting write
874
- class ::Baz < Ohm::Model
875
- attribute :name
876
-
877
- def write
878
- self.name = "Foobar"
879
- super
880
- end
881
- end
882
-
883
- test "work properly" do
884
- baz = Baz.new
885
- baz.name = "Foo"
886
- baz.save
887
- baz.name = "Foo"
888
- baz.save
889
- assert "Foobar" == Baz[baz.id].name
890
- end
891
-
892
638
  # Models connected to different databases
893
639
  class ::Car < Ohm::Model
894
640
  attribute :name
@@ -924,11 +670,10 @@ end
924
670
 
925
671
  test "allow changing the database" do
926
672
  Car.create(:name => "Twingo")
673
+ assert_equal ["1"], Car.all.key.smembers
927
674
 
928
- assert ["1"] == Car.all.key.smembers
929
-
930
- Car.connect
931
- assert [] == Car.all.key.smembers
675
+ Car.connect({})
676
+ assert_equal [], Car.all.key.smembers
932
677
 
933
678
  Car.connect :db => 15
934
679
  assert ["1"] == Car.all.key.smembers
@@ -941,41 +686,182 @@ test "persist attributes to a hash" do
941
686
 
942
687
  assert "hash" == Ohm.redis.type("Event:1")
943
688
 
944
- assert [
945
- "Event:1", "Event:all", "Event:id"
946
- ].sort == Ohm.redis.keys("Event:*").sort
689
+ expected= %w[Event:1 Event:1:counters Event:all Event:id]
690
+ assert_equal expected, Ohm.redis.keys("Event:*").sort
947
691
 
948
692
  assert "Redis Meetup" == Event[1].name
949
693
  assert 1 == Event[1].votes
950
694
  end
951
695
 
952
- # a Model with individual attribute writing needs
953
- class Order < Ohm::Model
954
- attribute :state
696
+ # namespaced models
697
+ test "be persisted" do
698
+ SomeNamespace::Foo.create(:name => "foo")
699
+
700
+ assert "hash" == Ohm.redis.type("SomeNamespace::Foo:1")
701
+
702
+ assert "foo" == SomeNamespace::Foo[1].name
703
+ end
955
704
 
956
- def authorize!
957
- write_remote :state, 'authorized'
705
+ test "typecast attributes" do
706
+ class Option < Ohm::Model
707
+ attribute :votes, lambda { |x| x.to_i }
958
708
  end
709
+
710
+ option = Option.create :votes => 20
711
+ option.update(votes: option.votes + 1)
712
+
713
+ assert_equal 21, option.votes
959
714
  end
960
715
 
961
- test "writes locally" do
962
- order = Order.create(:state => "pending")
963
- order.authorize!
964
- assert 'authorized' == order.state
716
+ test "poster-example for overriding writers" do
717
+ silence_warnings do
718
+ class Advertiser < Ohm::Model
719
+ attribute :email
720
+
721
+ def email=(e)
722
+ attributes[:email] = e.to_s.downcase.strip
723
+ end
724
+ end
725
+ end
726
+
727
+ a = Advertiser.new(email: " FOO@BAR.COM ")
728
+ assert_equal "foo@bar.com", a.email
965
729
  end
966
730
 
967
- test "writes remotely" do
968
- order = Order.create(:state => "pending")
969
- order.authorize!
970
- order = Order[order.id]
971
- assert 'authorized' == order.state
731
+ __END__
732
+
733
+ These are the vestigial tests for future reference
734
+
735
+ def monitor
736
+ log = []
737
+
738
+ monitor = Thread.new do
739
+ Redis.connect.monitor do |line|
740
+ break if line =~ /ping/
741
+ log << line
742
+ end
743
+ end
744
+
745
+ sleep 0.01
746
+
747
+ log.clear.tap do
748
+ yield
749
+ Ohm.redis.ping
750
+ monitor.join
751
+ end
972
752
  end
973
753
 
974
- # namespaced models
975
- test "be persisted" do
976
- SomeNamespace::Foo.create(:name => "foo")
754
+ test "load attributes lazily" do |id|
755
+ event = Event[id]
977
756
 
978
- assert "hash" == Ohm.redis.type("SomeNamespace::Foo:1")
757
+ log = monitor { event.name }
979
758
 
980
- assert "foo" == SomeNamespace::Foo[1].name
981
- end
759
+ assert !log.empty?
760
+
761
+ log = monitor { event.name }
762
+
763
+ assert log.empty?
764
+ end
765
+
766
+ test "allow slicing the list" do
767
+ post1 = Post.create
768
+ post2 = Post.create
769
+ post3 = Post.create
770
+
771
+ @post.related << post1
772
+ @post.related << post2
773
+ @post.related << post3
774
+
775
+ assert post1 == @post.related[0]
776
+ assert post2 == @post.related[1]
777
+ assert post3 == @post.related[-1]
778
+
779
+ assert nil == @post.related[3]
780
+
781
+ assert [post2, post3] == @post.related[1, 2]
782
+ assert [post2, post3] == @post.related[1, -1]
783
+
784
+ assert [] == @post.related[4, 5]
785
+
786
+ assert [post2, post3] == @post.related[1..2]
787
+ assert [post2, post3] == @post.related[1..5]
788
+
789
+ assert [] == @post.related[4..5]
790
+ end
791
+
792
+ # Applying arbitrary transformations
793
+ require "date"
794
+
795
+ class MyActiveRecordModel
796
+ def self.find(id)
797
+ return new if id.to_i == 1
798
+ end
799
+
800
+ def id
801
+ 1
802
+ end
803
+
804
+ def ==(other)
805
+ id == other.id
806
+ end
807
+ end
808
+
809
+ class ::Calendar < Ohm::Model
810
+ list :holidays, lambda { |v| Date.parse(v) }
811
+ list :subscribers, lambda { |id| MyActiveRecordModel.find(id) }
812
+ list :appointments, :Appointment
813
+
814
+ set :events, lambda { |id| MyActiveRecordModel.find(id) }
815
+ end
816
+
817
+ class ::Appointment < Ohm::Model
818
+ attribute :text
819
+ reference :subscriber, lambda { |id| MyActiveRecordModel.find(id) }
820
+ end
821
+
822
+ setup do
823
+ @calendar = Calendar.create
824
+
825
+ @calendar.holidays.key.rpush "2009-05-25"
826
+ @calendar.holidays.key.rpush "2009-07-09"
827
+
828
+ @calendar.subscribers << MyActiveRecordModel.find(1)
829
+
830
+ @calendar.events << MyActiveRecordModel.find(1)
831
+ end
832
+
833
+ test "apply a transformation" do
834
+ assert [Date.new(2009, 5, 25), Date.new(2009, 7, 9)] == @calendar.holidays.all
835
+
836
+ assert [1] == @calendar.subscribers.all.map { |model| model.id }
837
+ assert [MyActiveRecordModel.find(1)] == @calendar.subscribers.all
838
+ end
839
+
840
+ test "doing an each on lists" do
841
+ arr = []
842
+ @calendar.subscribers.each do |sub|
843
+ arr << sub
844
+ end
845
+
846
+ assert [MyActiveRecordModel.find(1)] == arr
847
+ end
848
+
849
+ test "doing an each on sets" do
850
+ arr = []
851
+ @calendar.events.each do |sub|
852
+ arr << sub
853
+ end
854
+
855
+ assert [MyActiveRecordModel.find(1)] == arr
856
+ end
857
+
858
+ test "allow lambdas in references" do
859
+ appointment = Appointment.create(:subscriber => MyActiveRecordModel.find(1))
860
+ assert MyActiveRecordModel.find(1) == appointment.subscriber
861
+ end
862
+
863
+ test "work with models too" do
864
+ @calendar.appointments.add(Appointment.create(:text => "Meet with Bertrand"))
865
+
866
+ assert [Appointment[1]] == Calendar[1].appointments.sort
867
+ end