ohm 0.1.5 → 1.0.0.alpha1

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.
@@ -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