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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a0015a7962f27903ee712ca027655b3f01722f64
4
- data.tar.gz: 06c1b5a4101dae29fbabd19d6804a0eafbb3ccd3
3
+ metadata.gz: c8fb32b51fcb6f8e322f0cdf25638936febbe980
4
+ data.tar.gz: 37875da64dde8f61fef65909900a9889cbf5a3c9
5
5
  SHA512:
6
- metadata.gz: 6ea9f1a1f47e0863607e97f6c6d3e5c2673a355fadca8baa88f30636028b404a08dce69a26d9489272896abd734ccdcd3535eebe4807ae740a8a4b37a6b8a8b0
7
- data.tar.gz: 88ee32f78b6ce100d028ecfc1dfe5d61e4e9ec6933c0261df4828d4c63835108aba23b30ea69259717e0cae5f06df8aa31ac1aadc0f1200d8d2bac255a9c249e
6
+ metadata.gz: 94ad5d4c99d9f85212f0902627aa27836bcd23a61f22cef7bcd40b6e8b855656dfcb68dc274128825a04b4f4b46a6b0c6bc4dd9d678326374f18acc51d9e5f10
7
+ data.tar.gz: 6a8007f49f2e2fac0c06d22323659b9b7ec987c6a5c87fda67e77b6e181f5160f4d78db0d5ebf6b1fa38c8cd34a9833b1f9ae0ec21063e05cb8b87b37bf85e96
data/CHANGELOG CHANGED
@@ -1,4 +1,4 @@
1
- (unreleased)
1
+ 1.3.2
2
2
 
3
3
  - Fetching a batch of objects is now done in batches of 1000 objects at
4
4
  a time. If you are iterating over large collections, this change should
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::Validations#assert_unique assert_unique}
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 activated users from Argentina
466
- User.find(:country => "Argentina", :status => "activated")
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")
@@ -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(:name => "iPod")
62
- @ipad = Product.create(:name => "iPad")
61
+ @ipod = Product.create(name: "iPod")
62
+ @ipad = Product.create(name: "iPad")
63
63
 
64
- @pending = Order.create(:user => @user, :state => "pending",
65
- :product => @ipod)
66
- @authorized = Order.create(:user => @user, :state => "authorized",
67
- :product => @ipad)
68
- @captured = Order.create(:user => @user, :state => "captured",
69
- :product => @ipad)
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(:state => "pending",
78
- :product_id => @ipod.id).include?(@pending)
88
+ assert @user.orders.find(state: "pending",
89
+ product_id: @ipod.id).include?(@pending)
79
90
 
80
- assert @user.orders.find(:state => "pending",
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. The tricky part
85
- # is trying to find an order that is either *captured* or *authorized*,
86
- # since `Ohm` as of this writing doesn't support unions in its
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(:state => "authorized").include?(@authorized)
90
- assert @user.orders.find(:state => "captured").include?(@captured)
98
+ assert @user.orders.find(state: "authorized").include?(@authorized)
99
+ assert @user.orders.find(state: "captured").include?(@captured)
91
100
 
92
- assert @user.orders.find(:state => ["authorized", "captured"]).empty?
101
+ auth_or_capt = @user.orders.find(state: "authorized").union(state: "captured")
93
102
 
94
- auth_or_capt = @user.orders.key.volatile[:auth_or_capt]
95
- auth_or_capt.sunionstore(
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(:state => "authorized")
112
+ orders.find(state: "authorized")
110
113
  end
111
114
 
112
115
  def captured_orders
113
- orders.find(:state => "captured")
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::Model::Set` takes a *Redis* key and a *class monad*
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 monad to always be an
132
- # `Order` so we don't have to manually set it everytime.
133
- class UserOrders < Ohm::Model::Set
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
- super key, Ohm::Model::Wrapper.wrap(Order)
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(:state => "pending")`, we return
146
+ # just doing a straight up `find(state: "pending")`, we return
140
147
  # `UserOrders` again.
141
148
  def pending
142
- self.class.new(model.index_key_for(:state, "pending"))
149
+ self.class.new(model.key[:indices][:state]["pending"])
143
150
  end
144
151
 
145
152
  def authorized
146
- self.class.new(model.index_key_for(:state, "authorized"))
153
+ self.class.new(model.key[:indices][:state]["authorized"])
147
154
  end
148
155
 
149
156
  def captured
150
- self.class.new(model.index_key_for(:state, "captured"))
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.volatile[:accepted].sunionstore(
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.index_key_for(:user_id, id))
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(:product_id => @ipad.id).empty?
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(:product_id => @ipad.id).include?(@authorized)
192
- assert accepted.find(:product_id => @ipad.id).include?(@captured)
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
- Enumerator.new(self, :each)
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, intersected(dict)]
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.
@@ -38,7 +38,7 @@ module Ohm
38
38
  # t1.append(t2)
39
39
  # t1.commit(redis)
40
40
  #
41
- # @see http://redis.io/topic/transactions Transactions in Redis.
41
+ # @see http://redis.io/topics/transactions Transactions in Redis.
42
42
  class Transaction
43
43
  class Store
44
44
  class EntryAlreadyExistsError < RuntimeError
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "ohm"
3
- s.version = "1.3.2"
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"]
@@ -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.3.2
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: 2013-04-20 00:00:00.000000000 Z
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.3
146
+ rubygems_version: 2.0.14
147
147
  signing_key:
148
148
  specification_version: 4
149
149
  summary: Object-hash mapping library for Redis.