ohm 1.3.2 → 1.4.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG +1 -1
- data/README.md +6 -4
- data/examples/chaining.rb +56 -52
- data/lib/ohm.rb +37 -2
- data/lib/ohm/transaction.rb +1 -1
- data/ohm.gemspec +1 -1
- data/test/filtering.rb +8 -139
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c8fb32b51fcb6f8e322f0cdf25638936febbe980
|
4
|
+
data.tar.gz: 37875da64dde8f61fef65909900a9889cbf5a3c9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 94ad5d4c99d9f85212f0902627aa27836bcd23a61f22cef7bcd40b6e8b855656dfcb68dc274128825a04b4f4b46a6b0c6bc4dd9d678326374f18acc51d9e5f10
|
7
|
+
data.tar.gz: 6a8007f49f2e2fac0c06d22323659b9b7ec987c6a5c87fda67e77b6e181f5160f4d78db0d5ebf6b1fa38c8cd34a9833b1f9ae0ec21063e05cb8b87b37bf85e96
|
data/CHANGELOG
CHANGED
data/README.md
CHANGED
@@ -443,8 +443,7 @@ lookups.
|
|
443
443
|
In the `Event` example, the index on the name attribute will
|
444
444
|
allow for searches like `Event.find(:name => "some value")`.
|
445
445
|
|
446
|
-
Note that the {Ohm::Model::
|
447
|
-
validation and the methods {Ohm::Model::Set#find find} and
|
446
|
+
Note that the methods {Ohm::Model::Set#find find} and
|
448
447
|
{Ohm::Model::Set#except except} need a corresponding index in order to work.
|
449
448
|
|
450
449
|
### Finding records
|
@@ -462,8 +461,11 @@ User.find(:username => "Albert")
|
|
462
461
|
# Find all users from Argentina
|
463
462
|
User.find(:country => "Argentina")
|
464
463
|
|
465
|
-
# Find all
|
466
|
-
User.find(:country => "Argentina", :status => "
|
464
|
+
# Find all active users from Argentina
|
465
|
+
User.find(:country => "Argentina", :status => "active")
|
466
|
+
|
467
|
+
# Find all active users from Argentina and Uruguay
|
468
|
+
User.find(status: "active").combine(country: ["Argentina", "Uruguay"])
|
467
469
|
|
468
470
|
# Find all users from Argentina, except those with a suspended account.
|
469
471
|
User.find(:country => "Argentina").except(:status => "suspended")
|
data/examples/chaining.rb
CHANGED
@@ -21,7 +21,7 @@ require "ohm"
|
|
21
21
|
# end
|
22
22
|
#
|
23
23
|
class User < Ohm::Model
|
24
|
-
collection :orders, Order
|
24
|
+
collection :orders, :Order
|
25
25
|
end
|
26
26
|
|
27
27
|
# The product for our purposes will only contain a name.
|
@@ -41,8 +41,8 @@ class Order < Ohm::Model
|
|
41
41
|
attribute :state
|
42
42
|
index :state
|
43
43
|
|
44
|
-
reference :user, User
|
45
|
-
reference :product, Product
|
44
|
+
reference :user, :User
|
45
|
+
reference :product, :Product
|
46
46
|
end
|
47
47
|
|
48
48
|
##### Testing what we have so far.
|
@@ -58,15 +58,26 @@ prepare { Ohm.flush }
|
|
58
58
|
setup do
|
59
59
|
@user = User.create
|
60
60
|
|
61
|
-
@ipod = Product.create(:
|
62
|
-
@ipad = Product.create(:
|
61
|
+
@ipod = Product.create(name: "iPod")
|
62
|
+
@ipad = Product.create(name: "iPad")
|
63
63
|
|
64
|
-
@pending =
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
64
|
+
@pending = Order.create(
|
65
|
+
user: @user,
|
66
|
+
state: "pending",
|
67
|
+
product: @ipod
|
68
|
+
)
|
69
|
+
|
70
|
+
@authorized = Order.create(
|
71
|
+
user: @user,
|
72
|
+
state: "authorized",
|
73
|
+
product: @ipad
|
74
|
+
)
|
75
|
+
|
76
|
+
@captured = Order.create(
|
77
|
+
user: @user,
|
78
|
+
state: "captured",
|
79
|
+
product: @ipad
|
80
|
+
)
|
70
81
|
end
|
71
82
|
|
72
83
|
# Now let's try and grab all pending orders, and also pending
|
@@ -74,31 +85,23 @@ end
|
|
74
85
|
test "finding pending orders" do
|
75
86
|
assert @user.orders.find(state: "pending").include?(@pending)
|
76
87
|
|
77
|
-
assert @user.orders.find(:
|
78
|
-
:
|
88
|
+
assert @user.orders.find(state: "pending",
|
89
|
+
product_id: @ipod.id).include?(@pending)
|
79
90
|
|
80
|
-
assert @user.orders.find(:
|
81
|
-
:product_id => @ipad.id).empty?
|
91
|
+
assert @user.orders.find(state: "pending", product_id: @ipad.id).empty?
|
82
92
|
end
|
83
93
|
|
84
|
-
# Now we try and find captured and authorized orders.
|
85
|
-
#
|
86
|
-
#
|
87
|
-
# finder syntax.
|
94
|
+
# Now we try and find captured and authorized orders.
|
95
|
+
# Since now `Ohm` supports unions in its finder syntax,
|
96
|
+
# it's really easy to do so.
|
88
97
|
test "finding authorized and/or captured orders" do
|
89
|
-
assert @user.orders.find(:
|
90
|
-
assert @user.orders.find(:
|
98
|
+
assert @user.orders.find(state: "authorized").include?(@authorized)
|
99
|
+
assert @user.orders.find(state: "captured").include?(@captured)
|
91
100
|
|
92
|
-
|
101
|
+
auth_or_capt = @user.orders.find(state: "authorized").union(state: "captured")
|
93
102
|
|
94
|
-
auth_or_capt
|
95
|
-
auth_or_capt.
|
96
|
-
@user.orders.find(:state => "authorized").key,
|
97
|
-
@user.orders.find(:state => "captured").key
|
98
|
-
)
|
99
|
-
|
100
|
-
assert auth_or_capt.smembers.include?(@authorized.id)
|
101
|
-
assert auth_or_capt.smembers.include?(@captured.id)
|
103
|
+
assert auth_or_capt.include?(@authorized)
|
104
|
+
assert auth_or_capt.include?(@captured)
|
102
105
|
end
|
103
106
|
|
104
107
|
#### Creating shortcuts
|
@@ -106,11 +109,11 @@ end
|
|
106
109
|
# You can of course define methods to make that code more readable.
|
107
110
|
class User < Ohm::Model
|
108
111
|
def authorized_orders
|
109
|
-
orders.find(:
|
112
|
+
orders.find(state: "authorized")
|
110
113
|
end
|
111
114
|
|
112
115
|
def captured_orders
|
113
|
-
orders.find(:
|
116
|
+
orders.find(state: "captured")
|
114
117
|
end
|
115
118
|
end
|
116
119
|
|
@@ -125,29 +128,33 @@ end
|
|
125
128
|
|
126
129
|
#### Chaining Kung-Fu
|
127
130
|
|
128
|
-
# The `Ohm::
|
129
|
-
# for its arguments.
|
131
|
+
# The `Ohm::Set` takes a *Redis* key, a *namespace* and
|
132
|
+
# an *Ohm model* for its arguments.
|
130
133
|
#
|
131
|
-
# We can simply subclass it and define the
|
132
|
-
#
|
133
|
-
class UserOrders < Ohm::
|
134
|
+
# We can simply subclass it and define the arguments
|
135
|
+
# so we don't have to manually set them everytime.
|
136
|
+
class UserOrders < Ohm::Set
|
137
|
+
attr :model
|
138
|
+
|
134
139
|
def initialize(key)
|
135
|
-
|
140
|
+
@model = Order
|
141
|
+
|
142
|
+
super(key, key, @model)
|
136
143
|
end
|
137
144
|
|
138
145
|
# Here is the crux of the chaining pattern. Instead of
|
139
|
-
# just doing a straight up `find(:
|
146
|
+
# just doing a straight up `find(state: "pending")`, we return
|
140
147
|
# `UserOrders` again.
|
141
148
|
def pending
|
142
|
-
self.class.new(model.
|
149
|
+
self.class.new(model.key[:indices][:state]["pending"])
|
143
150
|
end
|
144
151
|
|
145
152
|
def authorized
|
146
|
-
self.class.new(model.
|
153
|
+
self.class.new(model.key[:indices][:state]["authorized"])
|
147
154
|
end
|
148
155
|
|
149
156
|
def captured
|
150
|
-
self.class.new(model.
|
157
|
+
self.class.new(model.key[:indices][:state]["captured"])
|
151
158
|
end
|
152
159
|
|
153
160
|
# Now we wrap the implementation of doing an `SUNIONSTORE` and also
|
@@ -156,27 +163,24 @@ class UserOrders < Ohm::Model::Set
|
|
156
163
|
# NOTE: `volatile` just returns the key prepended with a `~:`, so in
|
157
164
|
# this case it would be `~:Order:accepted`.
|
158
165
|
def accepted
|
159
|
-
model.key.
|
160
|
-
authorized.key, captured.key
|
161
|
-
)
|
162
|
-
|
163
|
-
self.class.new(model.key.volatile[:accepted])
|
166
|
+
Ohm::MultiSet.new(key, @model, Ohm::Command[:sunionstore, authorized.key, captured.key])
|
164
167
|
end
|
165
168
|
end
|
166
169
|
|
167
170
|
# Now let's re-open the `User` class and add a customized `orders` method.
|
168
171
|
class User < Ohm::Model
|
169
172
|
def orders
|
170
|
-
UserOrders.new(Order.
|
173
|
+
UserOrders.new(Order.key[:indices][:user_id][id])
|
171
174
|
end
|
172
175
|
end
|
173
176
|
|
174
177
|
# Ok! Let's put all of that chaining code to good use.
|
175
178
|
test "finding pending orders using a chainable style" do
|
176
179
|
assert @user.orders.pending.include?(@pending)
|
177
|
-
assert @user.orders.pending.find(:product_id => @ipod.id).include?(@pending)
|
178
180
|
|
179
|
-
assert @user.orders.pending.find(:
|
181
|
+
assert @user.orders.pending.find(product_id: @ipod.id).include?(@pending)
|
182
|
+
|
183
|
+
assert @user.orders.pending.find(product_id: @ipad.id).empty?
|
180
184
|
end
|
181
185
|
|
182
186
|
test "finding authorized and/or captured orders using a chainable style" do
|
@@ -188,8 +192,8 @@ test "finding authorized and/or captured orders using a chainable style" do
|
|
188
192
|
|
189
193
|
accepted = @user.orders.accepted
|
190
194
|
|
191
|
-
assert accepted.find(:
|
192
|
-
assert accepted.find(:
|
195
|
+
assert accepted.find(product_id: @ipad.id).include?(@authorized)
|
196
|
+
assert accepted.find(product_id: @ipad.id).include?(@captured)
|
193
197
|
end
|
194
198
|
|
195
199
|
#### Conclusion
|
data/lib/ohm.rb
CHANGED
@@ -149,7 +149,7 @@ module Ohm
|
|
149
149
|
fetch(slice).each { |e| yield(e) }
|
150
150
|
end
|
151
151
|
else
|
152
|
-
|
152
|
+
to_enum
|
153
153
|
end
|
154
154
|
end
|
155
155
|
|
@@ -467,6 +467,20 @@ module Ohm
|
|
467
467
|
MultiSet.new(namespace, model, key).except(dict)
|
468
468
|
end
|
469
469
|
|
470
|
+
# Perform an intersection between the existent set and
|
471
|
+
# the new set created by the union of the passed filters.
|
472
|
+
#
|
473
|
+
# Example:
|
474
|
+
#
|
475
|
+
# set = User.find(:status => "active")
|
476
|
+
# set.combine(:name => ["John", "Jane"])
|
477
|
+
#
|
478
|
+
# # The result will include all users with active status
|
479
|
+
# # and with names "John" or "Jane".
|
480
|
+
def combine(dict)
|
481
|
+
MultiSet.new(namespace, model, key).combine(dict)
|
482
|
+
end
|
483
|
+
|
470
484
|
# Do a union to the existing set using any number of filters.
|
471
485
|
#
|
472
486
|
# Example:
|
@@ -598,7 +612,23 @@ module Ohm
|
|
598
612
|
#
|
599
613
|
def except(dict)
|
600
614
|
MultiSet.new(
|
601
|
-
namespace, model, Command[:sdiffstore, command,
|
615
|
+
namespace, model, Command[:sdiffstore, command, unioned(dict)]
|
616
|
+
)
|
617
|
+
end
|
618
|
+
|
619
|
+
# Perform an intersection between the existent set and
|
620
|
+
# the new set created by the union of the passed filters.
|
621
|
+
#
|
622
|
+
# Example:
|
623
|
+
#
|
624
|
+
# set = User.find(:status => "active")
|
625
|
+
# set.combine(:name => ["John", "Jane"])
|
626
|
+
#
|
627
|
+
# # The result will include all users with active status
|
628
|
+
# # and with names "John" or "Jane".
|
629
|
+
def combine(dict)
|
630
|
+
MultiSet.new(
|
631
|
+
namespace, model, Command[:sinterstore, command, unioned(dict)]
|
602
632
|
)
|
603
633
|
end
|
604
634
|
|
@@ -627,6 +657,11 @@ module Ohm
|
|
627
657
|
Command[:sinterstore, *model.filters(dict)]
|
628
658
|
end
|
629
659
|
|
660
|
+
|
661
|
+
def unioned(dict)
|
662
|
+
Command[:sunionstore, *model.filters(dict)]
|
663
|
+
end
|
664
|
+
|
630
665
|
def execute
|
631
666
|
# namespace[:tmp] is where all the temp keys should be stored in.
|
632
667
|
# db will be where all the commands are executed against.
|
data/lib/ohm/transaction.rb
CHANGED
@@ -38,7 +38,7 @@ module Ohm
|
|
38
38
|
# t1.append(t2)
|
39
39
|
# t1.commit(redis)
|
40
40
|
#
|
41
|
-
# @see http://redis.io/
|
41
|
+
# @see http://redis.io/topics/transactions Transactions in Redis.
|
42
42
|
class Transaction
|
43
43
|
class Store
|
44
44
|
class EntryAlreadyExistsError < RuntimeError
|
data/ohm.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = "ohm"
|
3
|
-
s.version = "1.
|
3
|
+
s.version = "1.4.0"
|
4
4
|
s.summary = %{Object-hash mapping library for Redis.}
|
5
5
|
s.description = %Q{Ohm is a library that allows to store an object in Redis, a persistent key-value database. It includes an extensible list of validations and has very good performance.}
|
6
6
|
s.authors = ["Michel Martens", "Damian Janowski", "Cyril David"]
|
data/test/filtering.rb
CHANGED
@@ -95,6 +95,14 @@ test "#union" do |john, jane|
|
|
95
95
|
assert res.any? { |e| e.status == "inactive" }
|
96
96
|
end
|
97
97
|
|
98
|
+
test "#combine" do |john, jane|
|
99
|
+
res = User.find(:status => "active").combine(fname: ["John", "Jane"])
|
100
|
+
|
101
|
+
assert_equal 2, res.size
|
102
|
+
assert res.include?(john)
|
103
|
+
assert res.include?(jane)
|
104
|
+
end
|
105
|
+
|
98
106
|
# book author thing via @myobie
|
99
107
|
scope do
|
100
108
|
class Book < Ohm::Model
|
@@ -159,142 +167,3 @@ scope do
|
|
159
167
|
assert_equal 2, res.size
|
160
168
|
end
|
161
169
|
end
|
162
|
-
|
163
|
-
# test precision of filtering commands
|
164
|
-
require "logger"
|
165
|
-
require "stringio"
|
166
|
-
scope do
|
167
|
-
class Post < Ohm::Model
|
168
|
-
attribute :author
|
169
|
-
index :author
|
170
|
-
|
171
|
-
attribute :mood
|
172
|
-
index :mood
|
173
|
-
end
|
174
|
-
|
175
|
-
setup do
|
176
|
-
io = StringIO.new
|
177
|
-
|
178
|
-
Post.connect(:logger => Logger.new(io))
|
179
|
-
|
180
|
-
Post.create(author: "matz", mood: "happy")
|
181
|
-
Post.create(author: "rich", mood: "mad")
|
182
|
-
|
183
|
-
io
|
184
|
-
end
|
185
|
-
|
186
|
-
def read(io)
|
187
|
-
io.rewind
|
188
|
-
io.read
|
189
|
-
end
|
190
|
-
|
191
|
-
test "SINTERSTORE a b" do |io|
|
192
|
-
Post.find(author: "matz").find(mood: "happy").to_a
|
193
|
-
|
194
|
-
# This is the simple case. We should only do one SINTERSTORE
|
195
|
-
# given two direct keys. Anything more and we're performing badly.
|
196
|
-
expected = "SINTERSTORE Post:tmp:[a-f0-9]{64} " +
|
197
|
-
"Post:indices:author:matz Post:indices:mood:happy"
|
198
|
-
|
199
|
-
assert(read(io) =~ Regexp.new(expected))
|
200
|
-
end
|
201
|
-
|
202
|
-
test "SUNIONSTORE a b" do |io|
|
203
|
-
Post.find(author: "matz").union(mood: "happy").to_a
|
204
|
-
|
205
|
-
# Another simple case where we must only do one operation at maximum.
|
206
|
-
expected = "SUNIONSTORE Post:tmp:[a-f0-9]{64} " +
|
207
|
-
"Post:indices:author:matz Post:indices:mood:happy"
|
208
|
-
|
209
|
-
assert(read(io) =~ Regexp.new(expected))
|
210
|
-
end
|
211
|
-
|
212
|
-
test "SUNIONSTORE c (SINTERSTORE a b)" do |io|
|
213
|
-
Post.find(author: "matz").find(mood: "happy").union(author: "rich").to_a
|
214
|
-
|
215
|
-
# For this case we need an intermediate key. This will
|
216
|
-
# contain the intersection of matz + happy.
|
217
|
-
expected = "SINTERSTORE (Post:tmp:[a-f0-9]{64}) " +
|
218
|
-
"Post:indices:author:matz Post:indices:mood:happy"
|
219
|
-
|
220
|
-
assert(read(io) =~ Regexp.new(expected))
|
221
|
-
|
222
|
-
# The next operation is simply doing a UNION of the previously
|
223
|
-
# generated intermediate key and the additional single key.
|
224
|
-
expected = "SUNIONSTORE (Post:tmp:[a-f0-9]{64}) " +
|
225
|
-
"%s Post:indices:author:rich" % $1
|
226
|
-
|
227
|
-
assert(read(io) =~ Regexp.new(expected))
|
228
|
-
end
|
229
|
-
|
230
|
-
test "SUNIONSTORE (SINTERSTORE c d) (SINTERSTORE a b)" do |io|
|
231
|
-
Post.find(author: "matz").find(mood: "happy").
|
232
|
-
union(author: "rich", mood: "sad").to_a
|
233
|
-
|
234
|
-
# Similar to the previous case, we need to do an intermediate
|
235
|
-
# operation.
|
236
|
-
expected = "SINTERSTORE (Post:tmp:[a-f0-9]{64}) " +
|
237
|
-
"Post:indices:author:matz Post:indices:mood:happy"
|
238
|
-
|
239
|
-
match1 = read(io).match(Regexp.new(expected))
|
240
|
-
assert match1
|
241
|
-
|
242
|
-
# But now, we need to also hold another intermediate key for the
|
243
|
-
# condition of author: rich AND mood: sad.
|
244
|
-
expected = "SINTERSTORE (Post:tmp:[a-f0-9]{64}) " +
|
245
|
-
"Post:indices:author:rich Post:indices:mood:sad"
|
246
|
-
|
247
|
-
match2 = read(io).match(Regexp.new(expected))
|
248
|
-
assert match2
|
249
|
-
|
250
|
-
# Now we expect that it does a UNION of those two previous
|
251
|
-
# intermediate keys.
|
252
|
-
expected = sprintf(
|
253
|
-
"SUNIONSTORE (Post:tmp:[a-f0-9]{64}) %s %s",
|
254
|
-
match1[1], match2[1]
|
255
|
-
)
|
256
|
-
|
257
|
-
assert(read(io) =~ Regexp.new(expected))
|
258
|
-
end
|
259
|
-
|
260
|
-
test do |io|
|
261
|
-
Post.create(author: "kent", mood: "sad")
|
262
|
-
|
263
|
-
Post.find(author: "kent", mood: "sad").
|
264
|
-
union(author: "matz", mood: "happy").
|
265
|
-
except(mood: "sad", author: "rich").to_a
|
266
|
-
|
267
|
-
expected = "SINTERSTORE (Post:tmp:[a-f0-9]{64}) " +
|
268
|
-
"Post:indices:author:kent Post:indices:mood:sad"
|
269
|
-
|
270
|
-
match1 = read(io).match(Regexp.new(expected))
|
271
|
-
assert match1
|
272
|
-
|
273
|
-
expected = "SINTERSTORE (Post:tmp:[a-f0-9]{64}) " +
|
274
|
-
"Post:indices:author:matz Post:indices:mood:happy"
|
275
|
-
|
276
|
-
match2 = read(io).match(Regexp.new(expected))
|
277
|
-
assert match2
|
278
|
-
|
279
|
-
expected = sprintf(
|
280
|
-
"SUNIONSTORE (Post:tmp:[a-f0-9]{64}) %s %s",
|
281
|
-
match1[1], match2[1]
|
282
|
-
)
|
283
|
-
|
284
|
-
match3 = read(io).match(Regexp.new(expected))
|
285
|
-
assert match3
|
286
|
-
|
287
|
-
expected = "SINTERSTORE (Post:tmp:[a-f0-9]{64}) " +
|
288
|
-
"Post:indices:mood:sad Post:indices:author:rich"
|
289
|
-
|
290
|
-
match4 = read(io).match(Regexp.new(expected))
|
291
|
-
assert match4
|
292
|
-
|
293
|
-
expected = sprintf(
|
294
|
-
"SDIFFSTORE (Post:tmp:[a-f0-9]{64}) %s %s",
|
295
|
-
match3[1], match4[1]
|
296
|
-
)
|
297
|
-
|
298
|
-
assert(read(io) =~ Regexp.new(expected))
|
299
|
-
end
|
300
|
-
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: 1.
|
4
|
+
version: 1.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michel Martens
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2015-01-22 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: redis
|
@@ -143,7 +143,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
143
143
|
version: '0'
|
144
144
|
requirements: []
|
145
145
|
rubyforge_project: ohm
|
146
|
-
rubygems_version: 2.0.
|
146
|
+
rubygems_version: 2.0.14
|
147
147
|
signing_key:
|
148
148
|
specification_version: 4
|
149
149
|
summary: Object-hash mapping library for Redis.
|