ohm 0.0.26 → 0.0.27
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +21 -20
- data/Thorfile +27 -0
- data/lib/ohm.rb +119 -96
- data/test/indices_test.rb +41 -35
- data/test/model_test.rb +77 -4
- data/test/validations_test.rb +19 -7
- metadata +3 -3
- data/test/search_test.rb +0 -42
data/README.markdown
CHANGED
@@ -12,6 +12,12 @@ Ohm is a library for storing objects in
|
|
12
12
|
database. It includes an extensible list of validations and has very
|
13
13
|
good performance.
|
14
14
|
|
15
|
+
Community
|
16
|
+
---------
|
17
|
+
|
18
|
+
Join the mailing list: [http://groups.google.com/group/ohm-ruby](http://groups.google.com/group/ohm-ruby)
|
19
|
+
|
20
|
+
Meet us on IRC: [#ohm](irc://chat.freenode.net/#ohm) on [freenode.net](http://freenode.net/)
|
15
21
|
|
16
22
|
Getting started
|
17
23
|
---------------
|
@@ -157,9 +163,9 @@ An index is a set that's handled automatically by Ohm. For any index declared,
|
|
157
163
|
Ohm maintains different sets of objects ids for quick lookups.
|
158
164
|
|
159
165
|
For example, in the example above, the index on the name attribute will
|
160
|
-
allow for searches like Event.find(:
|
166
|
+
allow for searches like Event.find(name: "some value").
|
161
167
|
|
162
|
-
Note that the `assert_unique` validation and the methods `find
|
168
|
+
Note that the `assert_unique` validation and the methods `find` and `except` need a
|
163
169
|
corresponding index in order to work.
|
164
170
|
|
165
171
|
### Finding
|
@@ -169,27 +175,22 @@ You can find a collection of records with the `find` method:
|
|
169
175
|
# This returns a collection of users with the username "Albert"
|
170
176
|
User.find(username: "Albert")
|
171
177
|
|
172
|
-
###
|
178
|
+
### Filtering results
|
173
179
|
|
174
|
-
#
|
175
|
-
User.
|
176
|
-
@users = search_results.all
|
177
|
-
end
|
180
|
+
# Find all users from Argentina
|
181
|
+
User.find(country: "Argentina")
|
178
182
|
|
179
|
-
#
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
183
|
+
# Find all activated users from Argentina
|
184
|
+
User.find(country: "Argentina", status: "activated")
|
185
|
+
|
186
|
+
# Find all users from Argentina, except those with a suspended account.
|
187
|
+
User.find(country: "Argentina").except(status: "suspended")
|
188
|
+
|
189
|
+
Note that calling these methods results in new sets being created
|
190
|
+
on the fly. This is important so that you can perform further operations
|
191
|
+
before reading the items to the client.
|
186
192
|
|
187
|
-
|
188
|
-
to a block. This is important because the set of results is stored
|
189
|
-
for the duration of the block (to allow for chaining of searches and
|
190
|
-
filters), but is deleted once the block ends. The `.all` is necessary
|
191
|
-
for retrieving the actual instances, as keeping a reference to a set
|
192
|
-
that is going to be deleted would only return empty sets.
|
193
|
+
For more information, see (SINTERSTORE)[http://code.google.com/p/redis/wiki/SinterstoreCommand] and (SDIFFSTORE)[http://code.google.com/p/redis/wiki/SdiffstoreCommand].
|
193
194
|
|
194
195
|
Validations
|
195
196
|
-----------
|
data/Thorfile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
class Ohm < Thor
|
4
|
+
desc "doc", "Generate YARD documentation"
|
5
|
+
method_options :open => false
|
6
|
+
def doc
|
7
|
+
require "yard"
|
8
|
+
|
9
|
+
opts = ["--protected", "--title", "Ohm – Object-hash mapping library for Redis"]
|
10
|
+
|
11
|
+
YARD::CLI::Yardoc.run(*opts)
|
12
|
+
|
13
|
+
system "open doc/index.html" if options[:open]
|
14
|
+
end
|
15
|
+
|
16
|
+
desc "deploy", "Deploy documentation"
|
17
|
+
def deploy
|
18
|
+
system "rsync -az doc/* ohm.keyvalue.org:deploys/ohm.keyvalue.org/"
|
19
|
+
end
|
20
|
+
|
21
|
+
desc "test", "Run all tests"
|
22
|
+
def test
|
23
|
+
Dir["test/**/*_test.rb"].each do |file|
|
24
|
+
load file
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/ohm.rb
CHANGED
@@ -74,11 +74,14 @@ module Ohm
|
|
74
74
|
# @option options [#to_s] :order (ASC) Sorting order, which can be ASC or DESC.
|
75
75
|
# @option options [Integer] :limit (all) Number of items to return.
|
76
76
|
# @option options [Integer] :start (0) An offset from where the limit will be applied.
|
77
|
+
#
|
77
78
|
# @example Get the first ten users sorted alphabetically by name:
|
78
|
-
#
|
79
|
+
#
|
80
|
+
# @event.attendees.sort(:by => :name, :order => "ALPHA", :limit => 10)
|
79
81
|
#
|
80
82
|
# @example Get five posts sorted by number of votes and starting from the number 5 (zero based):
|
81
|
-
#
|
83
|
+
#
|
84
|
+
# @blog.posts.sort(:by => :votes, :start => 5, :limit => 10")
|
82
85
|
def sort(options = {})
|
83
86
|
return [] if empty?
|
84
87
|
options[:start] ||= 0
|
@@ -94,8 +97,9 @@ module Ohm
|
|
94
97
|
# User.create :name => "B"
|
95
98
|
# User.create :name => "A"
|
96
99
|
#
|
97
|
-
# user = User.all.sort_by
|
98
|
-
# user.name == "A"
|
100
|
+
# user = User.all.sort_by(:name, :order => "ALPHA").first
|
101
|
+
# user.name == "A"
|
102
|
+
# # => true
|
99
103
|
def sort_by(att, options = {})
|
100
104
|
sort(options.merge(:by => model.key("*", att)))
|
101
105
|
end
|
@@ -128,6 +132,29 @@ module Ohm
|
|
128
132
|
size.zero?
|
129
133
|
end
|
130
134
|
|
135
|
+
# Clears the values in the collection.
|
136
|
+
def clear
|
137
|
+
db.del(key)
|
138
|
+
self
|
139
|
+
end
|
140
|
+
|
141
|
+
# Appends the given values to the collection.
|
142
|
+
def concat(values)
|
143
|
+
values.each { |value| self << value }
|
144
|
+
self
|
145
|
+
end
|
146
|
+
|
147
|
+
# Replaces the collection with the passed values.
|
148
|
+
def replace(values)
|
149
|
+
clear
|
150
|
+
concat(values)
|
151
|
+
end
|
152
|
+
|
153
|
+
# @param value [Ohm::Model#id] Adds the id of the object if it's an Ohm::Model.
|
154
|
+
def add(model)
|
155
|
+
self << model.id
|
156
|
+
end
|
157
|
+
|
131
158
|
private
|
132
159
|
|
133
160
|
def instantiate(raw)
|
@@ -180,6 +207,10 @@ module Ohm
|
|
180
207
|
db.llen(key)
|
181
208
|
end
|
182
209
|
|
210
|
+
def include?(value)
|
211
|
+
raw.include?(value)
|
212
|
+
end
|
213
|
+
|
183
214
|
def inspect
|
184
215
|
"#<List: #{raw.inspect}>"
|
185
216
|
end
|
@@ -206,13 +237,6 @@ module Ohm
|
|
206
237
|
db.sadd(key, value)
|
207
238
|
end
|
208
239
|
|
209
|
-
# @param value [Ohm::Model#id] Adds the id of the object if it's an Ohm::Model.
|
210
|
-
def add model
|
211
|
-
raise ArgumentError unless model.kind_of?(Ohm::Model)
|
212
|
-
raise ArgumentError unless model.id
|
213
|
-
self << model.id
|
214
|
-
end
|
215
|
-
|
216
240
|
def delete(value)
|
217
241
|
db.srem(key, value)
|
218
242
|
end
|
@@ -230,70 +254,63 @@ module Ohm
|
|
230
254
|
db.scard(key)
|
231
255
|
end
|
232
256
|
|
257
|
+
def inspect
|
258
|
+
"#<Set: #{raw.inspect}>"
|
259
|
+
end
|
260
|
+
|
233
261
|
# Returns an intersection with the sets generated from the passed hash.
|
234
262
|
#
|
235
|
-
# @see Ohm::Model.
|
236
|
-
# @yield [results] Results of the filtering. Beware that the set of results is deleted from Redis when the block ends.
|
263
|
+
# @see Ohm::Model.find
|
237
264
|
# @example
|
238
|
-
# Event.
|
239
|
-
# @events = filter_results.all
|
240
|
-
# end
|
265
|
+
# @events = Event.find(public: true)
|
241
266
|
#
|
242
|
-
# # You can
|
243
|
-
#
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
raise ArgumentError, "filter expects a block" unless block_given?
|
250
|
-
apply(:sinterstore, keys(hash).push(key), &block)
|
251
|
-
end
|
252
|
-
|
253
|
-
# Returns a union with the sets generated from the passed hash.
|
267
|
+
# # You can combine the result with sort and other set operations:
|
268
|
+
# @events.sort_by(:name)
|
269
|
+
def find(hash)
|
270
|
+
apply(:sinterstore, hash, "+")
|
271
|
+
end
|
272
|
+
|
273
|
+
# Returns the difference between the receiver and the passed sets.
|
254
274
|
#
|
255
|
-
# @see Ohm::Model.search
|
256
|
-
# @yield [results] Results of the search. Beware that the set of results is deleted from Redis when the block ends.
|
257
275
|
# @example
|
258
|
-
# Event.
|
259
|
-
|
260
|
-
|
261
|
-
def search(hash, &block)
|
262
|
-
raise ArgumentError, "search expects a block" unless block_given?
|
263
|
-
apply(:sunionstore, keys(hash), &block)
|
276
|
+
# @events = Event.find(public: true).except(status: "sold_out")
|
277
|
+
def except(hash)
|
278
|
+
apply(:sdiffstore, hash, "-")
|
264
279
|
end
|
265
280
|
|
266
|
-
|
267
|
-
db.del(key)
|
268
|
-
end
|
281
|
+
private
|
269
282
|
|
270
|
-
# Apply a redis operation on a collection of sets.
|
271
|
-
|
272
|
-
|
273
|
-
target =
|
274
|
-
db.send(operation, target, *
|
275
|
-
|
276
|
-
block.call(set)
|
277
|
-
set.delete! if source.size > 1
|
283
|
+
# Apply a redis operation on a collection of sets.
|
284
|
+
def apply(operation, hash, glue)
|
285
|
+
indices = keys(hash).unshift(key).uniq
|
286
|
+
target = indices.join(glue)
|
287
|
+
db.send(operation, target, *indices)
|
288
|
+
self.class.new(db, target, model)
|
278
289
|
end
|
279
290
|
|
280
|
-
def inspect
|
281
|
-
"#<Set: #{raw.inspect}>"
|
282
|
-
end
|
283
|
-
|
284
|
-
private
|
285
|
-
|
286
291
|
# Transform a hash of attribute/values into an array of keys.
|
287
292
|
def keys(hash)
|
288
293
|
hash.inject([]) do |acc, t|
|
289
294
|
acc + Array(t[1]).map do |v|
|
290
|
-
model.
|
295
|
+
model.index_key_for(t[0], v)
|
291
296
|
end
|
292
297
|
end
|
293
298
|
end
|
294
299
|
end
|
300
|
+
|
301
|
+
class Index < Set
|
302
|
+
def inspect
|
303
|
+
"#<Index: #{raw.inspect}>"
|
304
|
+
end
|
305
|
+
|
306
|
+
def clear
|
307
|
+
raise Ohm::Model::CannotDeleteIndex
|
308
|
+
end
|
309
|
+
end
|
295
310
|
end
|
296
311
|
|
312
|
+
Error = Class.new(StandardError)
|
313
|
+
|
297
314
|
class Model
|
298
315
|
module Validations
|
299
316
|
include Ohm::Validations
|
@@ -307,20 +324,44 @@ module Ohm
|
|
307
324
|
# Validates that the :street and :city pair is unique.
|
308
325
|
def assert_unique(attrs)
|
309
326
|
result = db.sinter(*Array(attrs).map { |att| index_key_for(att, send(att)) })
|
310
|
-
assert
|
327
|
+
assert result.empty? || !new? && result.include?(id.to_s), [attrs, :not_unique]
|
311
328
|
end
|
312
329
|
end
|
313
330
|
|
314
331
|
include Validations
|
315
332
|
|
316
|
-
|
333
|
+
class MissingID < Error
|
334
|
+
def message
|
335
|
+
"You tried to perform an operation that needs the model ID, but it's not present."
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
class CannotDeleteIndex < Error
|
340
|
+
def message
|
341
|
+
"You tried to delete an internal index used by Ohm."
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
class IndexNotFound < Error
|
346
|
+
def initialize(att)
|
347
|
+
@att = att
|
348
|
+
end
|
349
|
+
|
350
|
+
def message
|
351
|
+
"Index #{@att.inspect} not found."
|
352
|
+
end
|
353
|
+
end
|
317
354
|
|
318
355
|
@@attributes = Hash.new { |hash, key| hash[key] = [] }
|
319
356
|
@@collections = Hash.new { |hash, key| hash[key] = [] }
|
320
357
|
@@counters = Hash.new { |hash, key| hash[key] = [] }
|
321
358
|
@@indices = Hash.new { |hash, key| hash[key] = [] }
|
322
359
|
|
323
|
-
|
360
|
+
attr_writer :id
|
361
|
+
|
362
|
+
def id
|
363
|
+
@id or raise MissingID
|
364
|
+
end
|
324
365
|
|
325
366
|
# Defines a string attribute for the model. This attribute will be persisted by Redis
|
326
367
|
# as a string. Any value stored here will be retrieved in its string representation.
|
@@ -381,7 +422,7 @@ module Ohm
|
|
381
422
|
# end
|
382
423
|
#
|
383
424
|
# # Now this is possible:
|
384
|
-
# User.find :
|
425
|
+
# User.find email: "ohm@example.com"
|
385
426
|
#
|
386
427
|
# @param name [Symbol] Name of the attribute to be indexed.
|
387
428
|
def self.index(att)
|
@@ -411,7 +452,7 @@ module Ohm
|
|
411
452
|
end
|
412
453
|
|
413
454
|
def self.all
|
414
|
-
@all ||= Attributes::
|
455
|
+
@all ||= Attributes::Index.new(db, key(:all), self)
|
415
456
|
end
|
416
457
|
|
417
458
|
def self.attributes
|
@@ -436,38 +477,16 @@ module Ohm
|
|
436
477
|
model
|
437
478
|
end
|
438
479
|
|
439
|
-
# Find all the records matching the specified attribute-value pair.
|
440
|
-
#
|
441
|
-
# @example
|
442
|
-
# Event.find(:starts_on, Date.today)
|
443
|
-
def self.find(attrs, value)
|
444
|
-
Attributes::Set.new(db, key(attrs, encode(value)), self)
|
445
|
-
end
|
446
|
-
|
447
480
|
# Search across multiple indices and return the intersection of the sets.
|
448
481
|
#
|
449
482
|
# @example Finds all the user events for the supplied days
|
450
483
|
# event1 = Event.create day: "2009-09-09", author: "Albert"
|
451
484
|
# event2 = Event.create day: "2009-09-09", author: "Benoit"
|
452
485
|
# event3 = Event.create day: "2009-09-10", author: "Albert"
|
453
|
-
# Event.filter(author: "Albert", day: "2009-09-09") do |events|
|
454
|
-
# assert_equal [event1], events
|
455
|
-
# end
|
456
|
-
def self.filter(hash, &block)
|
457
|
-
self.all.filter(hash, &block)
|
458
|
-
end
|
459
|
-
|
460
|
-
# Search across multiple indices and return the union of the sets.
|
461
486
|
#
|
462
|
-
#
|
463
|
-
|
464
|
-
|
465
|
-
# event3 = Event.create day: "2009-09-11"
|
466
|
-
# Event.search(day: ["2009-09-09", "2009-09-10", "2009-09-011"]) do |events|
|
467
|
-
# assert_equal [event1, event2, event3], events
|
468
|
-
# end
|
469
|
-
def self.search(hash, &block)
|
470
|
-
self.all.search(hash, &block)
|
487
|
+
# assert_equal [event1], Event.find(author: "Albert", day: "2009-09-09")
|
488
|
+
def self.find(hash)
|
489
|
+
all.find(hash)
|
471
490
|
end
|
472
491
|
|
473
492
|
def self.encode(value)
|
@@ -480,7 +499,7 @@ module Ohm
|
|
480
499
|
end
|
481
500
|
|
482
501
|
def new?
|
483
|
-
|
502
|
+
!@id
|
484
503
|
end
|
485
504
|
|
486
505
|
def create
|
@@ -524,19 +543,19 @@ module Ohm
|
|
524
543
|
self
|
525
544
|
end
|
526
545
|
|
527
|
-
# Increment the
|
546
|
+
# Increment the counter denoted by :att.
|
528
547
|
#
|
529
548
|
# @param att [Symbol] Attribute to increment.
|
530
549
|
def incr(att)
|
531
|
-
raise ArgumentError unless counters.include?(att)
|
550
|
+
raise ArgumentError, "#{att.inspect} is not a counter." unless counters.include?(att)
|
532
551
|
write_local(att, db.incr(key(att)))
|
533
552
|
end
|
534
553
|
|
535
|
-
# Decrement the
|
554
|
+
# Decrement the counter denoted by :att.
|
536
555
|
#
|
537
556
|
# @param att [Symbol] Attribute to decrement.
|
538
557
|
def decr(att)
|
539
|
-
raise ArgumentError unless counters.include?(att)
|
558
|
+
raise ArgumentError, "#{att.inspect} is not a counter." unless counters.include?(att)
|
540
559
|
write_local(att, db.decr(key(att)))
|
541
560
|
end
|
542
561
|
|
@@ -558,7 +577,7 @@ module Ohm
|
|
558
577
|
|
559
578
|
def ==(other)
|
560
579
|
other.kind_of?(self.class) && other.key == key
|
561
|
-
rescue
|
580
|
+
rescue MissingID
|
562
581
|
false
|
563
582
|
end
|
564
583
|
|
@@ -574,20 +593,19 @@ module Ohm
|
|
574
593
|
everything = (attributes + collections + counters).map do |att|
|
575
594
|
value = begin
|
576
595
|
send(att)
|
577
|
-
rescue
|
596
|
+
rescue MissingID
|
578
597
|
nil
|
579
598
|
end
|
580
599
|
|
581
600
|
[att, value.inspect]
|
582
601
|
end
|
583
602
|
|
584
|
-
"#<#{self.class}:#{
|
603
|
+
"#<#{self.class}:#{new? ? "?" : id} #{everything.map {|e| e.join("=") }.join(" ")}>"
|
585
604
|
end
|
586
605
|
|
587
606
|
protected
|
588
607
|
|
589
608
|
def key(*args)
|
590
|
-
raise ModelIsNew if new?
|
591
609
|
self.class.key(id, *args)
|
592
610
|
end
|
593
611
|
|
@@ -610,7 +628,7 @@ module Ohm
|
|
610
628
|
end
|
611
629
|
|
612
630
|
def initialize_id
|
613
|
-
self.id = db.incr(self.class.key("id"))
|
631
|
+
self.id = db.incr(self.class.key("id")).to_s
|
614
632
|
end
|
615
633
|
|
616
634
|
def db
|
@@ -673,7 +691,7 @@ module Ohm
|
|
673
691
|
end
|
674
692
|
|
675
693
|
def read_remote(att)
|
676
|
-
|
694
|
+
db.get(key(att)) unless new?
|
677
695
|
end
|
678
696
|
|
679
697
|
def write_remote(att, value)
|
@@ -694,8 +712,13 @@ module Ohm
|
|
694
712
|
end
|
695
713
|
end
|
696
714
|
|
715
|
+
def self.index_key_for(att, value)
|
716
|
+
raise IndexNotFound, att unless indices.include?(att)
|
717
|
+
key(att, encode(value))
|
718
|
+
end
|
719
|
+
|
697
720
|
def index_key_for(att, value)
|
698
|
-
self.class.
|
721
|
+
self.class.index_key_for(att, value)
|
699
722
|
end
|
700
723
|
|
701
724
|
# Lock the object so no other instances can modify it.
|
data/test/indices_test.rb
CHANGED
@@ -9,6 +9,7 @@ class IndicesTest < Test::Unit::TestCase
|
|
9
9
|
attribute :email
|
10
10
|
attribute :update
|
11
11
|
attribute :activation_code
|
12
|
+
attribute :sandunga
|
12
13
|
|
13
14
|
index :email
|
14
15
|
index :email_provider
|
@@ -38,30 +39,46 @@ class IndicesTest < Test::Unit::TestCase
|
|
38
39
|
end
|
39
40
|
|
40
41
|
should "be able to find by the given attribute" do
|
41
|
-
assert_equal @user1, User.find(:email
|
42
|
+
assert_equal @user1, User.find(:email => "foo").first
|
43
|
+
end
|
44
|
+
|
45
|
+
should "raise if the field is not indexed" do
|
46
|
+
assert_raises(Ohm::Model::IndexNotFound) do
|
47
|
+
User.find(:sandunga => "foo")
|
48
|
+
end
|
42
49
|
end
|
43
50
|
|
44
51
|
should "return nil if no results are found" do
|
45
|
-
assert User.find(:email
|
46
|
-
assert_equal nil, User.find(:email
|
52
|
+
assert User.find(:email => "foobar").empty?
|
53
|
+
assert_equal nil, User.find(:email => "foobar").first
|
47
54
|
end
|
48
55
|
|
49
56
|
should "update indices when changing attribute values" do
|
50
57
|
@user1.email = "baz"
|
51
58
|
@user1.save
|
52
59
|
|
53
|
-
assert_equal [], User.find(:email
|
54
|
-
assert_equal [@user1], User.find(:email
|
60
|
+
assert_equal [], User.find(:email => "foo")
|
61
|
+
assert_equal [@user1], User.find(:email => "baz")
|
55
62
|
end
|
56
63
|
|
57
64
|
should "remove from the index after deleting" do
|
58
65
|
@user2.delete
|
59
66
|
|
60
|
-
assert_equal [], User.find(:email
|
67
|
+
assert_equal [], User.find(:email => "bar")
|
61
68
|
end
|
62
69
|
|
63
70
|
should "work with attributes that contain spaces" do
|
64
|
-
assert_equal [@user3], User.find(:email
|
71
|
+
assert_equal [@user3], User.find(:email => "baz qux")
|
72
|
+
end
|
73
|
+
|
74
|
+
should "not allow to manually clear an index" do
|
75
|
+
assert_raise Ohm::Model::CannotDeleteIndex do
|
76
|
+
User.find(:email => "bar").clear
|
77
|
+
end
|
78
|
+
|
79
|
+
assert_raise Ohm::Model::CannotDeleteIndex do
|
80
|
+
User.find(:email => "bar").find(:email => "baz").clear
|
81
|
+
end
|
65
82
|
end
|
66
83
|
end
|
67
84
|
|
@@ -73,12 +90,12 @@ class IndicesTest < Test::Unit::TestCase
|
|
73
90
|
end
|
74
91
|
|
75
92
|
should "allow indexing by an arbitrary attribute" do
|
76
|
-
assert_equal [@user1, @user2], User.find(:email_provider
|
77
|
-
assert_equal [@user3], User.find(:email_provider
|
93
|
+
assert_equal [@user1, @user2], User.find(:email_provider => "gmail.com").to_a.sort_by { |u| u.id }
|
94
|
+
assert_equal [@user3], User.find(:email_provider => "yahoo.com")
|
78
95
|
end
|
79
96
|
|
80
97
|
should "allow indexing by an attribute that is lazily set" do
|
81
|
-
assert_equal [@user1], User.find(:activation_code
|
98
|
+
assert_equal [@user1], User.find(:activation_code => "user:1").to_a
|
82
99
|
end
|
83
100
|
end
|
84
101
|
|
@@ -97,7 +114,7 @@ class IndicesTest < Test::Unit::TestCase
|
|
97
114
|
end
|
98
115
|
|
99
116
|
should "index each item" do
|
100
|
-
assert_equal [@user1, @user2], User.find(:working_days
|
117
|
+
assert_equal [@user1, @user2], User.find(:working_days => "Mon").to_a.sort_by { |u| u.id }
|
101
118
|
end
|
102
119
|
|
103
120
|
# TODO If we deal with Ohm collections, the updates are atomic but the reindexing never happens.
|
@@ -105,11 +122,11 @@ class IndicesTest < Test::Unit::TestCase
|
|
105
122
|
should "remove the indices when the object changes" do
|
106
123
|
@user2.working_days.delete "Mon"
|
107
124
|
@user2.save
|
108
|
-
assert_equal [@user1], User.find(:working_days
|
125
|
+
assert_equal [@user1], User.find(:working_days => "Mon")
|
109
126
|
end
|
110
127
|
end
|
111
128
|
|
112
|
-
context "Intersection and
|
129
|
+
context "Intersection and difference" do
|
113
130
|
class Event < Ohm::Model
|
114
131
|
attr_writer :days
|
115
132
|
|
@@ -123,40 +140,29 @@ class IndicesTest < Test::Unit::TestCase
|
|
123
140
|
end
|
124
141
|
|
125
142
|
setup do
|
126
|
-
@event1 = Event.create(:timeline => 1)
|
127
|
-
@event2 = Event.create(:timeline => 1)
|
128
|
-
@event3 = Event.create(:timeline => 2)
|
129
|
-
@event1.days = [1, 2]
|
130
|
-
@event2.days = [2, 3]
|
131
|
-
@event3.days = [3, 4]
|
132
|
-
@event1.save
|
133
|
-
@event2.save
|
134
|
-
@event3.save
|
143
|
+
@event1 = Event.create(:timeline => 1).update(:days => [1, 2])
|
144
|
+
@event2 = Event.create(:timeline => 1).update(:days => [2, 3])
|
145
|
+
@event3 = Event.create(:timeline => 2).update(:days => [3, 4])
|
135
146
|
end
|
136
147
|
|
137
148
|
should "intersect multiple sets of results" do
|
138
|
-
Event.
|
139
|
-
|
140
|
-
end
|
149
|
+
assert_equal [@event1], Event.find(:timeline => 1, :days => [1, 2])
|
150
|
+
assert_equal [@event1], Event.find(:timeline => 1).find(:days => [1, 2])
|
141
151
|
end
|
142
152
|
|
143
|
-
should "
|
144
|
-
Event.
|
145
|
-
assert_equal [@event1, @event2], set
|
146
|
-
end
|
153
|
+
should "compute the difference between sets" do
|
154
|
+
assert_equal [@event2], Event.find(:timeline => 1).except(:days => 1)
|
147
155
|
end
|
148
156
|
|
149
|
-
should "
|
150
|
-
|
151
|
-
|
152
|
-
assert_equal [@event1, @event2], result
|
153
|
-
end
|
157
|
+
should "raise if the argument is not an index" do
|
158
|
+
assert_raises(Ohm::Model::IndexNotFound) do
|
159
|
+
Event.find(:timeline => 1).except(:not_an_index => 1)
|
154
160
|
end
|
155
161
|
end
|
156
162
|
|
157
163
|
should "work with strings that generate a new line when encoded" do
|
158
164
|
user = User.create(:email => "foo@bar", :update => "CORRECTED - UPDATE 2-Suspected US missile strike kills 5 in Pakistan")
|
159
|
-
assert_equal [user], User.find(:update
|
165
|
+
assert_equal [user], User.find(:update => "CORRECTED - UPDATE 2-Suspected US missile strike kills 5 in Pakistan")
|
160
166
|
end
|
161
167
|
end
|
162
168
|
end
|
data/test/model_test.rb
CHANGED
@@ -6,6 +6,7 @@ require "ostruct"
|
|
6
6
|
class Post < Ohm::Model
|
7
7
|
attribute :body
|
8
8
|
list :comments
|
9
|
+
list :related, Post
|
9
10
|
end
|
10
11
|
|
11
12
|
class User < Ohm::Model
|
@@ -15,10 +16,15 @@ end
|
|
15
16
|
|
16
17
|
class Person < Ohm::Model
|
17
18
|
attribute :name
|
19
|
+
index :initial
|
18
20
|
|
19
21
|
def validate
|
20
22
|
assert_present :name
|
21
23
|
end
|
24
|
+
|
25
|
+
def initial
|
26
|
+
name[0, 1].upcase
|
27
|
+
end
|
22
28
|
end
|
23
29
|
|
24
30
|
class Event < Ohm::Model
|
@@ -44,9 +50,13 @@ class TestRedis < Test::Unit::TestCase
|
|
44
50
|
|
45
51
|
context "An event created from a hash of attributes" do
|
46
52
|
should "assign an id and save the object" do
|
53
|
+
Ohm.flush
|
54
|
+
|
47
55
|
event1 = Event.create(:name => "Ruby Tuesday")
|
48
56
|
event2 = Event.create(:name => "Ruby Meetup")
|
49
|
-
|
57
|
+
|
58
|
+
assert_equal "1", event1.id
|
59
|
+
assert_equal "2", event2.id
|
50
60
|
end
|
51
61
|
|
52
62
|
should "return the unsaved object if validation fails" do
|
@@ -144,14 +154,19 @@ class TestRedis < Test::Unit::TestCase
|
|
144
154
|
|
145
155
|
context "Creating a new model" do
|
146
156
|
should "assign a new id to the event" do
|
157
|
+
Ohm.flush
|
158
|
+
|
147
159
|
event1 = Event.new
|
148
160
|
event1.create
|
149
161
|
|
150
162
|
event2 = Event.new
|
151
163
|
event2.create
|
152
164
|
|
153
|
-
assert event1.
|
154
|
-
|
165
|
+
assert !event1.new?
|
166
|
+
assert !event2.new?
|
167
|
+
|
168
|
+
assert_equal "1", event1.id
|
169
|
+
assert_equal "2", event2.id
|
155
170
|
end
|
156
171
|
end
|
157
172
|
|
@@ -265,6 +280,8 @@ class TestRedis < Test::Unit::TestCase
|
|
265
280
|
|
266
281
|
context "Loading attributes" do
|
267
282
|
setup do
|
283
|
+
Ohm.flush
|
284
|
+
|
268
285
|
event = Event.new
|
269
286
|
event.name = "Ruby Tuesday"
|
270
287
|
@id = event.create.id
|
@@ -291,7 +308,7 @@ class TestRedis < Test::Unit::TestCase
|
|
291
308
|
end
|
292
309
|
|
293
310
|
should "not be available if the model is new" do
|
294
|
-
assert_raise Ohm::Model::
|
311
|
+
assert_raise Ohm::Model::MissingID do
|
295
312
|
@event.attendees << 1
|
296
313
|
end
|
297
314
|
end
|
@@ -338,6 +355,34 @@ class TestRedis < Test::Unit::TestCase
|
|
338
355
|
@event.attendees << "3"
|
339
356
|
assert_equal 3, @event.attendees.size
|
340
357
|
end
|
358
|
+
|
359
|
+
should "empty the set" do
|
360
|
+
@event.create
|
361
|
+
@event.attendees << "1"
|
362
|
+
|
363
|
+
@event.attendees.clear
|
364
|
+
|
365
|
+
assert @event.attendees.empty?
|
366
|
+
end
|
367
|
+
|
368
|
+
should "replace the values in the set" do
|
369
|
+
@event.create
|
370
|
+
@event.attendees << "1"
|
371
|
+
|
372
|
+
@event.attendees.replace(["2", "3"])
|
373
|
+
|
374
|
+
assert_equal ["2", "3"], @event.attendees.raw.sort
|
375
|
+
end
|
376
|
+
|
377
|
+
should "filter elements" do
|
378
|
+
@event.create
|
379
|
+
@event.attendees.add(Person.create(:name => "Albert"))
|
380
|
+
@event.attendees.add(Person.create(:name => "Marie"))
|
381
|
+
|
382
|
+
assert_equal ["1"], @event.attendees.find(:initial => "A").raw
|
383
|
+
assert_equal ["2"], @event.attendees.find(:initial => "M").raw
|
384
|
+
assert_equal [], @event.attendees.find(:initial => "Z").raw
|
385
|
+
end
|
341
386
|
end
|
342
387
|
|
343
388
|
context "Attributes of type List" do
|
@@ -408,6 +453,34 @@ class TestRedis < Test::Unit::TestCase
|
|
408
453
|
assert_equal "2", @post.comments.pop
|
409
454
|
assert @post.comments.empty?
|
410
455
|
end
|
456
|
+
|
457
|
+
should "empty the list" do
|
458
|
+
@post.comments.unshift "1"
|
459
|
+
@post.comments.clear
|
460
|
+
|
461
|
+
assert @post.comments.empty?
|
462
|
+
end
|
463
|
+
|
464
|
+
should "replace the values in the list" do
|
465
|
+
@post.comments.replace(["1", "2"])
|
466
|
+
|
467
|
+
assert_equal ["1", "2"], @post.comments.raw
|
468
|
+
end
|
469
|
+
|
470
|
+
should "add models" do
|
471
|
+
@post.related.add(Post.create(:body => "Hello"))
|
472
|
+
|
473
|
+
assert_equal ["2"], @post.related.raw
|
474
|
+
end
|
475
|
+
|
476
|
+
should "find elements in the list" do
|
477
|
+
another_post = Post.create
|
478
|
+
|
479
|
+
@post.related.add(another_post)
|
480
|
+
|
481
|
+
assert @post.related.include?(another_post.id)
|
482
|
+
assert !@post.related.include?("-1")
|
483
|
+
end
|
411
484
|
end
|
412
485
|
|
413
486
|
context "Applying arbitrary transformations" do
|
data/test/validations_test.rb
CHANGED
@@ -22,13 +22,13 @@ class ValidationsTest < Test::Unit::TestCase
|
|
22
22
|
context "That must have a present name" do
|
23
23
|
should "not be created if the name is never assigned" do
|
24
24
|
@event.create
|
25
|
-
|
25
|
+
assert @event.new?
|
26
26
|
end
|
27
27
|
|
28
28
|
should "not be created if the name assigned is empty" do
|
29
29
|
@event.name = ""
|
30
30
|
@event.create
|
31
|
-
|
31
|
+
assert @event.new?
|
32
32
|
end
|
33
33
|
|
34
34
|
should "be created if the name assigned is not empty" do
|
@@ -41,7 +41,7 @@ class ValidationsTest < Test::Unit::TestCase
|
|
41
41
|
should "not be created if the name doesn't match /^\w+$/" do
|
42
42
|
@event.name = "hello-world"
|
43
43
|
@event.create
|
44
|
-
|
44
|
+
assert @event.new?
|
45
45
|
end
|
46
46
|
end
|
47
47
|
end
|
@@ -56,7 +56,7 @@ class ValidationsTest < Test::Unit::TestCase
|
|
56
56
|
@event.place = "bar"
|
57
57
|
@event.create
|
58
58
|
|
59
|
-
|
59
|
+
assert @event.new?
|
60
60
|
assert_equal [[:capacity, :not_numeric]], @event.errors
|
61
61
|
end
|
62
62
|
|
@@ -70,7 +70,7 @@ class ValidationsTest < Test::Unit::TestCase
|
|
70
70
|
@event.capacity = "baz"
|
71
71
|
@event.create
|
72
72
|
|
73
|
-
|
73
|
+
assert @event.new?
|
74
74
|
assert_equal [[:capacity, :not_numeric]], @event.errors
|
75
75
|
end
|
76
76
|
|
@@ -98,7 +98,7 @@ class ValidationsTest < Test::Unit::TestCase
|
|
98
98
|
@event.name = "foo"
|
99
99
|
@event.create
|
100
100
|
|
101
|
-
|
101
|
+
assert @event.new?
|
102
102
|
assert_equal [[:name, :not_unique]], @event.errors
|
103
103
|
end
|
104
104
|
end
|
@@ -114,7 +114,7 @@ class ValidationsTest < Test::Unit::TestCase
|
|
114
114
|
@event.place = "bar"
|
115
115
|
@event.create
|
116
116
|
|
117
|
-
|
117
|
+
assert @event.new?
|
118
118
|
assert_equal [[[:name, :place], :not_unique]], @event.errors
|
119
119
|
|
120
120
|
@event.place = "baz"
|
@@ -123,6 +123,18 @@ class ValidationsTest < Test::Unit::TestCase
|
|
123
123
|
assert @event.valid?
|
124
124
|
end
|
125
125
|
end
|
126
|
+
|
127
|
+
context "That defines a unique validation on a non indexed attribute" do
|
128
|
+
should "raise ArgumentError" do
|
129
|
+
def @event.validate
|
130
|
+
assert_unique :capacity
|
131
|
+
end
|
132
|
+
|
133
|
+
assert_raises(Ohm::Model::IndexNotFound) do
|
134
|
+
@event.valid?
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
126
138
|
end
|
127
139
|
|
128
140
|
context "An existing model with a valid name" do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ohm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.27
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michel Martens
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2009-
|
13
|
+
date: 2009-11-19 00:00:00 -03:00
|
14
14
|
default_executable:
|
15
15
|
dependencies: []
|
16
16
|
|
@@ -31,6 +31,7 @@ files:
|
|
31
31
|
- README.markdown
|
32
32
|
- LICENSE
|
33
33
|
- Rakefile
|
34
|
+
- Thorfile
|
34
35
|
- test/all_tests.rb
|
35
36
|
- test/benchmarks.rb
|
36
37
|
- test/connection_test.rb
|
@@ -39,7 +40,6 @@ files:
|
|
39
40
|
- test/model_test.rb
|
40
41
|
- test/mutex_test.rb
|
41
42
|
- test/redis_test.rb
|
42
|
-
- test/search_test.rb
|
43
43
|
- test/test_helper.rb
|
44
44
|
- test/validations_test.rb
|
45
45
|
- test/test.conf
|
data/test/search_test.rb
DELETED
@@ -1,42 +0,0 @@
|
|
1
|
-
require File.join(File.dirname(__FILE__), "test_helper")
|
2
|
-
|
3
|
-
class SearchTest < Test::Unit::TestCase
|
4
|
-
setup do
|
5
|
-
Ohm.flush
|
6
|
-
end
|
7
|
-
|
8
|
-
class Lane < Ohm::Model
|
9
|
-
attribute :lane_type
|
10
|
-
|
11
|
-
index :lane_type
|
12
|
-
|
13
|
-
def to_s
|
14
|
-
lane_type.capitalize
|
15
|
-
end
|
16
|
-
|
17
|
-
def validate
|
18
|
-
assert_unique :lane_type
|
19
|
-
end
|
20
|
-
|
21
|
-
def error_messages
|
22
|
-
errors.present do |e|
|
23
|
-
e.on [:lane_type, :not_unique], "The lane type #{lane_type} is already in use"
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
context "A model with an indexed attribute" do
|
29
|
-
setup do
|
30
|
-
@results = []
|
31
|
-
@subresults = []
|
32
|
-
Lane.search(:lane_type => "email") { |sr| @results << sr.size }
|
33
|
-
Lane.create(:lane_type => "email")
|
34
|
-
Lane.search(:lane_type => "email") { |sr| @results << sr.size }
|
35
|
-
Lane.search(:lane_type => "email") { |sr| @results << sr.size }
|
36
|
-
end
|
37
|
-
|
38
|
-
should "be able to find by the given attribute" do
|
39
|
-
assert_equal [0, 1, 1], @results
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|