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