ohm 1.3.2 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
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.