ohm 0.0.26 → 0.0.27
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/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
|