ohm 0.0.18 → 0.0.19
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 +20 -25
- data/lib/ohm.rb +112 -26
- data/test/connection_test.rb +1 -1
- data/test/indices_test.rb +89 -4
- data/test/model_test.rb +8 -0
- data/test/validations_test.rb +3 -3
- metadata +2 -2
data/README.markdown
CHANGED
@@ -135,6 +135,21 @@ with an attribute name, which will determine the sorting order. Both
|
|
135
135
|
methods receive an options hash which is explained in the documentation
|
136
136
|
for {Ohm::Attributes::Collection#sort}.
|
137
137
|
|
138
|
+
Adding instances of `Person` to the attendees hash is done with the
|
139
|
+
`add` method:
|
140
|
+
|
141
|
+
@event.attendees.add(Person.create(name: "Albert"))
|
142
|
+
|
143
|
+
# And now...
|
144
|
+
@event.attendees.each do |person|
|
145
|
+
# ...do what you want with this person.
|
146
|
+
end
|
147
|
+
|
148
|
+
Just to clarify: when you add items to a set with `<<`, Ohm inserts
|
149
|
+
whatever you send without checking it. When you use `add`, it assumes
|
150
|
+
it's an instance of some `Ohm::Model` and stores its id.
|
151
|
+
|
152
|
+
|
138
153
|
Indexes
|
139
154
|
-------
|
140
155
|
|
@@ -144,14 +159,9 @@ Ohm maintains different sets of objects ids for quick lookups.
|
|
144
159
|
For example, in the example above, the index on the name attribute will
|
145
160
|
allow for searches like Event.find(:name, "some value").
|
146
161
|
|
147
|
-
You can also declare an index on multiple colums, like this:
|
148
|
-
|
149
|
-
index [:name, :company]
|
150
|
-
|
151
162
|
Note that the `find` method and the `assert_unique` validation need a
|
152
163
|
corresponding index to exist.
|
153
164
|
|
154
|
-
|
155
165
|
Validations
|
156
166
|
-----------
|
157
167
|
|
@@ -183,43 +193,28 @@ to false.
|
|
183
193
|
Checks that the given field is not nil or empty. The error code for this
|
184
194
|
assertion is :not_present.
|
185
195
|
|
186
|
-
|
187
|
-
assert(!send(att).to_s.empty?, error)
|
188
|
-
end
|
196
|
+
assert_present :name
|
189
197
|
|
190
198
|
### assert_format
|
191
199
|
|
192
200
|
Checks that the given field matches the provided format. The error code
|
193
201
|
for this assertion is :format.
|
194
202
|
|
195
|
-
|
196
|
-
if assert_present(att, error)
|
197
|
-
assert(send(att).to_s.match(format), error)
|
198
|
-
end
|
199
|
-
end
|
203
|
+
assert_format :username, /^\w+$/
|
200
204
|
|
201
205
|
### assert_numeric
|
202
206
|
|
203
207
|
Checks that the given field holds a number as a Fixnum or as a string
|
204
208
|
representation. The error code for this assertion is :not_numeric.
|
205
209
|
|
206
|
-
|
207
|
-
if assert_present(att, error)
|
208
|
-
assert_format(att, /^\d+$/, error)
|
209
|
-
end
|
210
|
-
end
|
210
|
+
assert_numeric :votes
|
211
211
|
|
212
212
|
### assert_unique
|
213
213
|
|
214
214
|
Validates that the attribute or array of attributes are unique.
|
215
|
-
For this, an index of the same kind must exist. The error code is
|
216
|
-
:not_unique.
|
217
|
-
|
218
|
-
def assert_unique(attrs)
|
219
|
-
index_key = index_key_for(Array(attrs), read_locals(Array(attrs)))
|
220
|
-
assert(db.scard(index_key).zero? || db.sismember(index_key, id), [Array(attrs), :not_unique])
|
221
|
-
end
|
215
|
+
For this, an index of the same kind must exist. The error code is :not_unique.
|
222
216
|
|
217
|
+
assert_unique :email
|
223
218
|
|
224
219
|
Errors
|
225
220
|
------
|
data/lib/ohm.rb
CHANGED
@@ -81,7 +81,8 @@ module Ohm
|
|
81
81
|
return [] if empty?
|
82
82
|
options[:start] ||= 0
|
83
83
|
options[:limit] = [options[:start], options[:limit]] if options[:limit]
|
84
|
-
|
84
|
+
result = db.sort(key, options)
|
85
|
+
options[:get] ? result : instantiate(result)
|
85
86
|
end
|
86
87
|
|
87
88
|
# Sort the model instances by the given attribute.
|
@@ -222,6 +223,57 @@ module Ohm
|
|
222
223
|
def size
|
223
224
|
db.scard(key)
|
224
225
|
end
|
226
|
+
|
227
|
+
# Returns an intersection with the sets generated from the passed hash.
|
228
|
+
#
|
229
|
+
# @see Ohm::Model.filter
|
230
|
+
# @yield [results] Results of the filtering. Beware that the set of results is deleted from Redis when the block ends.
|
231
|
+
# @example
|
232
|
+
# Event.search(day: "2009-09-11") do |search_results|
|
233
|
+
# events = search_results.all
|
234
|
+
# end
|
235
|
+
def filter(hash, &block)
|
236
|
+
apply(:sinterstore, keys(hash).push(key), &block)
|
237
|
+
end
|
238
|
+
|
239
|
+
# Returns a union with the sets generated from the passed hash.
|
240
|
+
#
|
241
|
+
# @see Ohm::Model.search
|
242
|
+
# @yield [results] Results of the search. Beware that the set of results is deleted from Redis when the block ends.
|
243
|
+
# @example
|
244
|
+
# Event.search(day: "2009-09-11") do |search_results|
|
245
|
+
# search_results.filter(public: true) do |filter_results|
|
246
|
+
# events = filter_results.all
|
247
|
+
# end
|
248
|
+
# end
|
249
|
+
def search(hash, &block)
|
250
|
+
apply(:sunionstore, keys(hash), &block)
|
251
|
+
end
|
252
|
+
|
253
|
+
def delete!
|
254
|
+
db.del(key)
|
255
|
+
end
|
256
|
+
|
257
|
+
# Apply a redis operation on a collection of sets. Note that
|
258
|
+
# the resulting set is removed inmediatly after use.
|
259
|
+
def apply(operation, source, &block)
|
260
|
+
target = source.join(operation)
|
261
|
+
db.send(operation, target, *source)
|
262
|
+
set = self.class.new(db, target, model)
|
263
|
+
block.call(set)
|
264
|
+
set.delete!
|
265
|
+
end
|
266
|
+
|
267
|
+
private
|
268
|
+
|
269
|
+
# Transform a hash of attribute/values into an array of keys.
|
270
|
+
def keys(hash)
|
271
|
+
hash.inject([]) do |acc, t|
|
272
|
+
acc + Array(t[1]).map do |v|
|
273
|
+
model.key(t[0], model.encode(v))
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
225
277
|
end
|
226
278
|
end
|
227
279
|
|
@@ -237,8 +289,8 @@ module Ohm
|
|
237
289
|
# @overload assert_unique [:street, :city]
|
238
290
|
# Validates that the :street and :city pair is unique.
|
239
291
|
def assert_unique(attrs)
|
240
|
-
|
241
|
-
assert(
|
292
|
+
result = db.sinter(*Array(attrs).map { |att| index_key_for(att, send(att)) })
|
293
|
+
assert(result.empty? || result.include?(id.to_s), [attrs, :not_unique])
|
242
294
|
end
|
243
295
|
end
|
244
296
|
|
@@ -305,9 +357,6 @@ module Ohm
|
|
305
357
|
# If you want to find a model instance by some attribute value, then an index for that
|
306
358
|
# attribute must exist.
|
307
359
|
#
|
308
|
-
# Each index declaration creates an index. It can be either an index on one particular attribute,
|
309
|
-
# or an index accross many attributes.
|
310
|
-
#
|
311
360
|
# @example
|
312
361
|
# class User < Ohm::Model
|
313
362
|
# attribute :email
|
@@ -317,12 +366,9 @@ module Ohm
|
|
317
366
|
# # Now this is possible:
|
318
367
|
# User.find :email, "ohm@example.com"
|
319
368
|
#
|
320
|
-
# @
|
321
|
-
|
322
|
-
|
323
|
-
# Creates a composite index for street and city.
|
324
|
-
def self.index(attrs)
|
325
|
-
indices << Array(attrs)
|
369
|
+
# @param name [Symbol] Name of the attribute to be indexed.
|
370
|
+
def self.index(att)
|
371
|
+
indices << att
|
326
372
|
end
|
327
373
|
|
328
374
|
def self.attr_list_reader(name, model = nil)
|
@@ -369,18 +415,42 @@ module Ohm
|
|
369
415
|
model
|
370
416
|
end
|
371
417
|
|
372
|
-
|
373
|
-
|
418
|
+
# Find all the records matching the specified attribute-value pair.
|
419
|
+
#
|
420
|
+
# @example
|
421
|
+
# Event.find(:starts_on, Date.today)
|
422
|
+
def self.find(attrs, value)
|
423
|
+
Attributes::Set.new(db, key(attrs, encode(value)), self)
|
424
|
+
end
|
425
|
+
|
426
|
+
# Search across multiple indices and return the intersection of the sets.
|
427
|
+
#
|
428
|
+
# @example Finds all the user events for the supplied days
|
429
|
+
# event1 = Event.create day: "2009-09-09", author: "Albert"
|
430
|
+
# event2 = Event.create day: "2009-09-09", author: "Benoit"
|
431
|
+
# event3 = Event.create day: "2009-09-10", author: "Albert"
|
432
|
+
# Event.filter(author: "Albert", day: "2009-09-09") do |events|
|
433
|
+
# assert_equal [event1], events
|
434
|
+
# end
|
435
|
+
def self.filter(hash, &block)
|
436
|
+
self.all.filter(hash, &block)
|
374
437
|
end
|
375
438
|
|
376
|
-
|
377
|
-
|
439
|
+
# Search across multiple indices and return the union of the sets.
|
440
|
+
#
|
441
|
+
# @example Finds all the events for the supplied days
|
442
|
+
# event1 = Event.create day: "2009-09-09"
|
443
|
+
# event2 = Event.create day: "2009-09-10"
|
444
|
+
# event3 = Event.create day: "2009-09-11"
|
445
|
+
# Event.search(day: ["2009-09-09", "2009-09-10", "2009-09-011"]) do |events|
|
446
|
+
# assert_equal [event1, event2, event3], events
|
447
|
+
# end
|
448
|
+
def self.search(hash, &block)
|
449
|
+
self.all.search(hash, &block)
|
378
450
|
end
|
379
451
|
|
380
|
-
def self.
|
381
|
-
|
382
|
-
encode(value)
|
383
|
-
end
|
452
|
+
def self.encode(value)
|
453
|
+
Base64.encode64(value.to_s).gsub("\n", "")
|
384
454
|
end
|
385
455
|
|
386
456
|
def initialize(attrs = {})
|
@@ -533,14 +603,30 @@ module Ohm
|
|
533
603
|
end
|
534
604
|
|
535
605
|
def add_to_indices
|
536
|
-
indices.each do |
|
537
|
-
|
606
|
+
indices.each do |att|
|
607
|
+
next add_to_index(att) unless collection?(send(att))
|
608
|
+
send(att).each { |value| add_to_index(att, value) }
|
538
609
|
end
|
539
610
|
end
|
540
611
|
|
612
|
+
def collection?(value)
|
613
|
+
self.class.collection?(value)
|
614
|
+
end
|
615
|
+
|
616
|
+
def self.collection?(value)
|
617
|
+
value.kind_of?(Enumerable) &&
|
618
|
+
value.kind_of?(String) == false
|
619
|
+
end
|
620
|
+
|
621
|
+
def add_to_index(att, value = send(att))
|
622
|
+
index = index_key_for(att, value)
|
623
|
+
db.sadd(index, id)
|
624
|
+
db.sadd(key(:_indices), index)
|
625
|
+
end
|
626
|
+
|
541
627
|
def delete_from_indices
|
542
|
-
|
543
|
-
db.srem(
|
628
|
+
db.smembers(key(:_indices)).each do |index|
|
629
|
+
db.srem(index, id)
|
544
630
|
end
|
545
631
|
end
|
546
632
|
|
@@ -572,8 +658,8 @@ module Ohm
|
|
572
658
|
end
|
573
659
|
end
|
574
660
|
|
575
|
-
def index_key_for(
|
576
|
-
self.class.key
|
661
|
+
def index_key_for(att, value)
|
662
|
+
self.class.key(att, self.class.encode(value))
|
577
663
|
end
|
578
664
|
|
579
665
|
# Lock the object so no other instances can modify it.
|
data/test/connection_test.rb
CHANGED
data/test/indices_test.rb
CHANGED
@@ -1,21 +1,30 @@
|
|
1
1
|
require File.join(File.dirname(__FILE__), "test_helper")
|
2
2
|
|
3
3
|
class IndicesTest < Test::Unit::TestCase
|
4
|
+
setup do
|
5
|
+
Ohm.flush
|
6
|
+
end
|
7
|
+
|
4
8
|
class User < Ohm::Model
|
5
9
|
attribute :email
|
10
|
+
attribute :update
|
6
11
|
|
7
12
|
index :email
|
8
13
|
index :email_provider
|
14
|
+
index :working_days
|
15
|
+
index :update
|
9
16
|
|
10
17
|
def email_provider
|
11
18
|
email.split("@").last
|
12
19
|
end
|
20
|
+
|
21
|
+
def working_days
|
22
|
+
@working_days ||= []
|
23
|
+
end
|
13
24
|
end
|
14
25
|
|
15
26
|
context "A model with an indexed attribute" do
|
16
27
|
setup do
|
17
|
-
Ohm.flush
|
18
|
-
|
19
28
|
@user1 = User.create(:email => "foo")
|
20
29
|
@user2 = User.create(:email => "bar")
|
21
30
|
@user3 = User.create(:email => "baz qux")
|
@@ -51,8 +60,6 @@ class IndicesTest < Test::Unit::TestCase
|
|
51
60
|
|
52
61
|
context "Indexing arbitrary attributes" do
|
53
62
|
setup do
|
54
|
-
Ohm.flush
|
55
|
-
|
56
63
|
@user1 = User.create(:email => "foo@gmail.com")
|
57
64
|
@user2 = User.create(:email => "bar@gmail.com")
|
58
65
|
@user3 = User.create(:email => "bazqux@yahoo.com")
|
@@ -63,4 +70,82 @@ class IndicesTest < Test::Unit::TestCase
|
|
63
70
|
assert_equal [@user3], User.find(:email_provider, "yahoo.com")
|
64
71
|
end
|
65
72
|
end
|
73
|
+
|
74
|
+
context "Indexing enumerables" do
|
75
|
+
setup do
|
76
|
+
@user1 = User.create(:email => "foo@gmail.com")
|
77
|
+
@user2 = User.create(:email => "bar@gmail.com")
|
78
|
+
|
79
|
+
@user1.working_days << "Mon"
|
80
|
+
@user1.working_days << "Tue"
|
81
|
+
@user2.working_days << "Mon"
|
82
|
+
@user2.working_days << "Wed"
|
83
|
+
|
84
|
+
@user1.save
|
85
|
+
@user2.save
|
86
|
+
end
|
87
|
+
|
88
|
+
should "index each item" do
|
89
|
+
assert_equal [@user1, @user2], User.find(:working_days, "Mon").to_a.sort_by { |u| u.id }
|
90
|
+
end
|
91
|
+
|
92
|
+
# TODO If we deal with Ohm collections, the updates are atomic but the reindexing never happens.
|
93
|
+
# One solution may be to reindex after inserts or deletes in collection.
|
94
|
+
should "remove the indices when the object changes" do
|
95
|
+
@user2.working_days.delete "Mon"
|
96
|
+
@user2.save
|
97
|
+
assert_equal [@user1], User.find(:working_days, "Mon")
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context "Intersection and and union" do
|
102
|
+
class Event < Ohm::Model
|
103
|
+
attr_writer :days
|
104
|
+
|
105
|
+
attribute :timeline
|
106
|
+
index :timeline
|
107
|
+
index :days
|
108
|
+
|
109
|
+
def days
|
110
|
+
@days ||= []
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
setup do
|
115
|
+
@event1 = Event.create(timeline: 1)
|
116
|
+
@event2 = Event.create(timeline: 1)
|
117
|
+
@event3 = Event.create(timeline: 2)
|
118
|
+
@event1.days = [1, 2]
|
119
|
+
@event2.days = [2, 3]
|
120
|
+
@event3.days = [3, 4]
|
121
|
+
@event1.save
|
122
|
+
@event2.save
|
123
|
+
@event3.save
|
124
|
+
end
|
125
|
+
|
126
|
+
should "intersect multiple sets of results" do
|
127
|
+
Event.filter(timeline: 1, days: [1, 2]) do |set|
|
128
|
+
assert_equal [@event1], set
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
should "group multiple sets of results" do
|
133
|
+
Event.search(days: [1, 2]) do |set|
|
134
|
+
assert_equal [@event1, @event2], set
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
should "combine intersections and unions" do
|
139
|
+
Event.search(days: [1, 2, 3]) do |events|
|
140
|
+
events.filter(timeline: 1) do |result|
|
141
|
+
assert_equal [@event1, @event2], result
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
should "work with strings that generate a new line when encoded" do
|
147
|
+
user = User.create(email: "foo@bar", update: "CORRECTED - UPDATE 2-Suspected US missile strike kills 5 in Pakistan")
|
148
|
+
assert_equal [user], User.find(:update, "CORRECTED - UPDATE 2-Suspected US missile strike kills 5 in Pakistan")
|
149
|
+
end
|
150
|
+
end
|
66
151
|
end
|
data/test/model_test.rb
CHANGED
@@ -221,6 +221,14 @@ class TestRedis < Test::Unit::TestCase
|
|
221
221
|
Person.create :name => "A"
|
222
222
|
assert_equal "A", Person.all.first(:by => :name, :order => "ALPHA").name
|
223
223
|
end
|
224
|
+
|
225
|
+
should "return attribute values when the get parameter is specified" do
|
226
|
+
Ohm.flush
|
227
|
+
Person.create :name => "B"
|
228
|
+
Person.create :name => "A"
|
229
|
+
|
230
|
+
assert_equal "A", Person.all.sort_by(:name, get: "Person:*:name", order: "ALPHA").first
|
231
|
+
end
|
224
232
|
end
|
225
233
|
|
226
234
|
context "Loading attributes" do
|
data/test/validations_test.rb
CHANGED
@@ -7,7 +7,7 @@ class ValidationsTest < Test::Unit::TestCase
|
|
7
7
|
attribute :capacity
|
8
8
|
|
9
9
|
index :name
|
10
|
-
index
|
10
|
+
index :place
|
11
11
|
|
12
12
|
def validate
|
13
13
|
assert_format(:name, /^\w+$/)
|
@@ -99,12 +99,12 @@ class ValidationsTest < Test::Unit::TestCase
|
|
99
99
|
@event.create
|
100
100
|
|
101
101
|
assert_nil @event.id
|
102
|
-
assert_equal [[
|
102
|
+
assert_equal [[:name, :not_unique]], @event.errors
|
103
103
|
end
|
104
104
|
end
|
105
105
|
|
106
106
|
context "That must have a unique name scoped by place" do
|
107
|
-
should "fail when the value already exists" do
|
107
|
+
should "fail when the value already exists for a scoped attribute" do
|
108
108
|
def @event.validate
|
109
109
|
assert_unique [:name, :place]
|
110
110
|
end
|
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.19
|
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-09-
|
13
|
+
date: 2009-09-15 00:00:00 -03:00
|
14
14
|
default_executable:
|
15
15
|
dependencies: []
|
16
16
|
|