ohm 2.1.0 → 2.2.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/.gems +3 -2
- data/CHANGELOG.md +12 -0
- data/examples/activity-feed.rb +11 -16
- data/examples/one-to-many.rb +7 -13
- data/lib/ohm.rb +131 -249
- data/makefile +6 -1
- data/ohm.gemspec +2 -1
- data/test/indices.rb +0 -6
- metadata +16 -5
- data/lib/ohm/command.rb +0 -51
- data/test/command.rb +0 -55
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9c7c0f6876c2563f59ad8e20dcbab465e5cffcb8
|
4
|
+
data.tar.gz: e4f06f406172d073ff4ae25ac2d4f9ebad74c034
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9a55ef1b2043b4950a617ffa984a01a410a2aea17d656072c4732a995d7dcff550b38437a60ad382b9f30f9b87a2541064597ccb40c3e70043cf38f2cb77638e
|
7
|
+
data.tar.gz: ef38056726cf8956480bf728150168a1d2dd1337b5987b9ce6845c771c05f1c53fc38d84d92bab4c8fa015e3069ddfb141f548eee6d68b051417608a9dd2cc23
|
data/.gems
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
## 2.2.0
|
2
|
+
|
3
|
+
- Use Stal for set operations
|
4
|
+
|
5
|
+
## 2.1.0
|
6
|
+
|
7
|
+
- Add `combine` filter
|
8
|
+
|
1
9
|
## 2.0.0
|
2
10
|
|
3
11
|
- Lists now respond to range.
|
@@ -228,6 +236,10 @@
|
|
228
236
|
[scrivener]: https://github.com/soveran/scrivener
|
229
237
|
[redic]: https://github.com/amakawa/redic
|
230
238
|
|
239
|
+
## 1.4.0
|
240
|
+
|
241
|
+
- Add `combine` filter
|
242
|
+
|
231
243
|
## 1.3.2
|
232
244
|
|
233
245
|
- Fetching a batch of objects is now done in batches of 1000 objects at
|
data/examples/activity-feed.rb
CHANGED
@@ -29,18 +29,18 @@ class User < Ohm::Model
|
|
29
29
|
# Because a `User` literally has a `list` of activities, using a Redis
|
30
30
|
# `list` to model the activities would be a good choice. We default to
|
31
31
|
# getting the first 100 activities, and use
|
32
|
-
# [lrange](http://
|
32
|
+
# [lrange](http://redis.io/commands/lrange) directly.
|
33
33
|
def activities(start = 0, limit = 100)
|
34
|
-
key[:activities]
|
34
|
+
redis.call 'LRANGE', key[:activities], start, start + limit
|
35
35
|
end
|
36
36
|
|
37
37
|
# Broadcasting a message to all the `followers` of a user would simply
|
38
38
|
# be prepending the message for each if his `followers`. We also use
|
39
39
|
# the Redis command
|
40
|
-
# [lpush](http://
|
40
|
+
# [lpush](http://redis.io/commands/lpush) directly.
|
41
41
|
def broadcast(str)
|
42
42
|
followers.each do |user|
|
43
|
-
user.key[:activities]
|
43
|
+
redis.call 'LPUSH', user.key[:activities], str
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
@@ -81,8 +81,8 @@ end
|
|
81
81
|
test "jane following john" do |john, jane|
|
82
82
|
jane.follow(john)
|
83
83
|
|
84
|
-
|
85
|
-
|
84
|
+
assert_equal [john], jane.following.to_a
|
85
|
+
assert_equal [jane], john.followers.to_a
|
86
86
|
end
|
87
87
|
|
88
88
|
# Broadcasting a message should simply notify all the followers of the
|
@@ -125,17 +125,12 @@ class User
|
|
125
125
|
# We define a constant where we set the maximum number of activity entries.
|
126
126
|
MAX = 10
|
127
127
|
|
128
|
-
# Using `MAX` as the reference, we
|
129
|
-
#
|
130
|
-
# [ltrim](http://code.google.com/p/redis/wiki/LtrimCommand) to truncate
|
131
|
-
# the activities.
|
128
|
+
# Using `MAX` as the reference, we truncate the activities feed using
|
129
|
+
# [ltrim](http://redis.io/commands/ltrim).
|
132
130
|
def broadcast(str)
|
133
131
|
followers.each do |user|
|
134
|
-
user.key[:activities]
|
135
|
-
|
136
|
-
if user.key[:activities].llen > MAX
|
137
|
-
user.key[:activities].ltrim(0, MAX - 1)
|
138
|
-
end
|
132
|
+
redis.call 'LPUSH', user.key[:activities], str
|
133
|
+
redis.call 'LTRIM', user.key[:activities], 0, MAX - 1
|
139
134
|
end
|
140
135
|
end
|
141
136
|
end
|
@@ -158,5 +153,5 @@ end
|
|
158
153
|
#
|
159
154
|
# As a final note, keep in mind that the Ohm solution would still need
|
160
155
|
# sharding for large datasets, but that would be again trivial to implement
|
161
|
-
# using [redis-rb](http://github.com/
|
156
|
+
# using [redis-rb](http://github.com/redis/redis-rb)'s distributed support
|
162
157
|
# and sharding it against the *user_id*.
|
data/examples/one-to-many.rb
CHANGED
@@ -29,11 +29,11 @@ require "ohm"
|
|
29
29
|
|
30
30
|
# We define both a `Video` and `Audio` model, with a `list` of *comments*.
|
31
31
|
class Video < Ohm::Model
|
32
|
-
list :comments, Comment
|
32
|
+
list :comments, :Comment
|
33
33
|
end
|
34
34
|
|
35
35
|
class Audio < Ohm::Model
|
36
|
-
list :comments, Comment
|
36
|
+
list :comments, :Comment
|
37
37
|
end
|
38
38
|
|
39
39
|
# The `Comment` model for this example will just contain one attribute called
|
@@ -65,10 +65,10 @@ test "adding all sorts of comments" do
|
|
65
65
|
audio.comments.push(audio_comment)
|
66
66
|
|
67
67
|
assert video.comments.include?(video_comment)
|
68
|
-
|
68
|
+
assert_equal video.comments.size, 1
|
69
69
|
|
70
70
|
assert audio.comments.include?(audio_comment)
|
71
|
-
|
71
|
+
assert_equal audio.comments.size, 1
|
72
72
|
end
|
73
73
|
|
74
74
|
|
@@ -100,14 +100,8 @@ test "getting paged chunks of comments" do
|
|
100
100
|
|
101
101
|
20.times { |i| video.comments.push(Comment.create(:body => "C#{i + 1}")) }
|
102
102
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
# ** Range style is also supported.
|
107
|
-
assert %w(C11 C12 C13 C14 C15) == video.comments[10..14].map(&:body)
|
108
|
-
|
109
|
-
# ** Also you can just pass in a single number.
|
110
|
-
assert "C16" == video.comments[15].body
|
103
|
+
assert_equal %w(C1 C2 C3 C4 C5), video.comments.range(0, 4).map(&:body)
|
104
|
+
assert_equal %w(C6 C7 C8 C9 C10), video.comments.range(5, 9).map(&:body)
|
111
105
|
end
|
112
106
|
|
113
107
|
#### Caveats
|
@@ -121,4 +115,4 @@ end
|
|
121
115
|
# `SORTED SET`, and to use the timestamp (or the negative of the timestamp) as
|
122
116
|
# the score to maintain the desired order. Deleting a comment from a
|
123
117
|
# `SORTED SET` would be a simple
|
124
|
-
# [ZREM](http://
|
118
|
+
# [ZREM](http://redis.io/commands/zrem) call.
|
data/lib/ohm.rb
CHANGED
@@ -3,8 +3,7 @@
|
|
3
3
|
require "msgpack"
|
4
4
|
require "nido"
|
5
5
|
require "redic"
|
6
|
-
require "
|
7
|
-
require_relative "ohm/command"
|
6
|
+
require "stal"
|
8
7
|
|
9
8
|
module Ohm
|
10
9
|
LUA_CACHE = Hash.new { |h, k| h[k] = Hash.new }
|
@@ -76,7 +75,7 @@ module Ohm
|
|
76
75
|
Hash[*arr]
|
77
76
|
end
|
78
77
|
|
79
|
-
def self.
|
78
|
+
def self.sort_options(options)
|
80
79
|
args = []
|
81
80
|
|
82
81
|
args.concat(["BY", options[:by]]) if options[:by]
|
@@ -85,7 +84,7 @@ module Ohm
|
|
85
84
|
args.concat(options[:order].split(" ")) if options[:order]
|
86
85
|
args.concat(["STORE", options[:store]]) if options[:store]
|
87
86
|
|
88
|
-
|
87
|
+
return args
|
89
88
|
end
|
90
89
|
end
|
91
90
|
|
@@ -285,6 +284,7 @@ module Ohm
|
|
285
284
|
# # => true
|
286
285
|
#
|
287
286
|
def delete(model)
|
287
|
+
|
288
288
|
# LREM key 0 <id> means remove all elements matching <id>
|
289
289
|
# @see http://redis.io/commands/lrem
|
290
290
|
redis.call("LREM", key, 0, model.id)
|
@@ -321,27 +321,103 @@ module Ohm
|
|
321
321
|
end
|
322
322
|
end
|
323
323
|
|
324
|
-
|
325
|
-
class BasicSet
|
324
|
+
class Set
|
326
325
|
include Collection
|
327
326
|
|
328
|
-
|
329
|
-
|
327
|
+
attr :key
|
328
|
+
attr :model
|
329
|
+
attr :namespace
|
330
|
+
|
331
|
+
def initialize(model, namespace, key)
|
332
|
+
@model = model
|
333
|
+
@namespace = namespace
|
334
|
+
@key = key
|
335
|
+
end
|
336
|
+
|
337
|
+
# Retrieve a specific element using an ID from this set.
|
338
|
+
#
|
339
|
+
# Example:
|
340
|
+
#
|
341
|
+
# # Let's say we got the ID 1 from a request parameter.
|
342
|
+
# id = 1
|
343
|
+
#
|
344
|
+
# # Retrieve the post if it's included in the user's posts.
|
345
|
+
# post = user.posts[id]
|
346
|
+
#
|
347
|
+
def [](id)
|
348
|
+
model[id] if exists?(id)
|
349
|
+
end
|
350
|
+
|
351
|
+
# Returns an array with all the ID's of the set.
|
352
|
+
#
|
353
|
+
# class Post < Ohm::Model
|
354
|
+
# end
|
330
355
|
#
|
331
356
|
# class User < Ohm::Model
|
332
357
|
# attribute :name
|
358
|
+
# index :name
|
359
|
+
#
|
360
|
+
# set :posts, :Post
|
333
361
|
# end
|
334
362
|
#
|
335
|
-
# User.
|
336
|
-
# User.
|
337
|
-
# User.all.sort_by(:name, :order => "ALPHA DESC", :limit => [0, 10])
|
363
|
+
# User.create(name: "John")
|
364
|
+
# User.create(name: "Jane")
|
338
365
|
#
|
339
|
-
#
|
340
|
-
#
|
341
|
-
# them.
|
366
|
+
# User.all.ids
|
367
|
+
# # => ["1", "2"]
|
342
368
|
#
|
343
|
-
|
344
|
-
|
369
|
+
# User.find(name: "John").union(name: "Jane").ids
|
370
|
+
# # => ["1", "2"]
|
371
|
+
#
|
372
|
+
def ids
|
373
|
+
if Array === key
|
374
|
+
Stal.solve(redis, key)
|
375
|
+
else
|
376
|
+
redis.call("SMEMBERS", key)
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
# Returns the total size of the set using SCARD.
|
381
|
+
def size
|
382
|
+
Stal.solve(redis, ["SCARD", key])
|
383
|
+
end
|
384
|
+
|
385
|
+
# Returns +true+ if +id+ is included in the set. Otherwise, returns +false+.
|
386
|
+
#
|
387
|
+
# Example:
|
388
|
+
#
|
389
|
+
# class Post < Ohm::Model
|
390
|
+
# end
|
391
|
+
#
|
392
|
+
# class User < Ohm::Model
|
393
|
+
# set :posts, :Post
|
394
|
+
# end
|
395
|
+
#
|
396
|
+
# user = User.create
|
397
|
+
# post = Post.create
|
398
|
+
# user.posts.add(post)
|
399
|
+
#
|
400
|
+
# user.posts.exists?('nonexistent') # => false
|
401
|
+
# user.posts.exists?(post.id) # => true
|
402
|
+
#
|
403
|
+
def exists?(id)
|
404
|
+
Stal.solve(redis, ["SISMEMBER", key, id]) == 1
|
405
|
+
end
|
406
|
+
|
407
|
+
# Check if a model is included in this set.
|
408
|
+
#
|
409
|
+
# Example:
|
410
|
+
#
|
411
|
+
# u = User.create
|
412
|
+
#
|
413
|
+
# User.all.include?(u)
|
414
|
+
# # => true
|
415
|
+
#
|
416
|
+
# Note: Ohm simply checks that the model's ID is included in the
|
417
|
+
# set. It doesn't do any form of type checking.
|
418
|
+
#
|
419
|
+
def include?(model)
|
420
|
+
exists?(model.id)
|
345
421
|
end
|
346
422
|
|
347
423
|
# Allows you to sort your models using their IDs. This is much
|
@@ -370,31 +446,30 @@ module Ohm
|
|
370
446
|
def sort(options = {})
|
371
447
|
if options.has_key?(:get)
|
372
448
|
options[:get] = to_key(options[:get])
|
373
|
-
return execute { |key| Utils.sort(redis, key, options) }
|
374
|
-
end
|
375
449
|
|
376
|
-
|
450
|
+
Stal.solve(redis, ["SORT", key, *Utils.sort_options(options)])
|
451
|
+
else
|
452
|
+
fetch(Stal.solve(redis, ["SORT", key, *Utils.sort_options(options)]))
|
453
|
+
end
|
377
454
|
end
|
378
455
|
|
379
|
-
#
|
380
|
-
#
|
381
|
-
# Example:
|
456
|
+
# Allows you to sort by any attribute in the hash, this doesn't include
|
457
|
+
# the +id+. If you want to sort by ID, use #sort.
|
382
458
|
#
|
383
|
-
#
|
459
|
+
# class User < Ohm::Model
|
460
|
+
# attribute :name
|
461
|
+
# end
|
384
462
|
#
|
385
|
-
# User.all.
|
386
|
-
#
|
463
|
+
# User.all.sort_by(:name, :order => "ALPHA")
|
464
|
+
# User.all.sort_by(:name, :order => "ALPHA DESC")
|
465
|
+
# User.all.sort_by(:name, :order => "ALPHA DESC", :limit => [0, 10])
|
387
466
|
#
|
388
|
-
# Note:
|
389
|
-
#
|
467
|
+
# Note: This is slower compared to just doing `sort`, specifically
|
468
|
+
# because Redis has to read each individual hash in order to sort
|
469
|
+
# them.
|
390
470
|
#
|
391
|
-
def
|
392
|
-
|
393
|
-
end
|
394
|
-
|
395
|
-
# Returns the total size of the set using SCARD.
|
396
|
-
def size
|
397
|
-
execute { |key| redis.call("SCARD", key) }
|
471
|
+
def sort_by(att, options = {})
|
472
|
+
sort(options.merge(:by => to_key(att)))
|
398
473
|
end
|
399
474
|
|
400
475
|
# Syntactic sugar for `sort_by` or `sort` when you only need the
|
@@ -419,88 +494,6 @@ module Ohm
|
|
419
494
|
end
|
420
495
|
end
|
421
496
|
|
422
|
-
# Returns an array with all the ID's of the set.
|
423
|
-
#
|
424
|
-
# class Post < Ohm::Model
|
425
|
-
# end
|
426
|
-
#
|
427
|
-
# class User < Ohm::Model
|
428
|
-
# attribute :name
|
429
|
-
# index :name
|
430
|
-
#
|
431
|
-
# set :posts, :Post
|
432
|
-
# end
|
433
|
-
#
|
434
|
-
# User.create(name: "John")
|
435
|
-
# User.create(name: "Jane")
|
436
|
-
#
|
437
|
-
# User.all.ids
|
438
|
-
# # => ["1", "2"]
|
439
|
-
#
|
440
|
-
# User.find(name: "John").union(name: "Jane").ids
|
441
|
-
# # => ["1", "2"]
|
442
|
-
#
|
443
|
-
def ids
|
444
|
-
execute { |key| redis.call("SMEMBERS", key) }
|
445
|
-
end
|
446
|
-
|
447
|
-
# Retrieve a specific element using an ID from this set.
|
448
|
-
#
|
449
|
-
# Example:
|
450
|
-
#
|
451
|
-
# # Let's say we got the ID 1 from a request parameter.
|
452
|
-
# id = 1
|
453
|
-
#
|
454
|
-
# # Retrieve the post if it's included in the user's posts.
|
455
|
-
# post = user.posts[id]
|
456
|
-
#
|
457
|
-
def [](id)
|
458
|
-
model[id] if exists?(id)
|
459
|
-
end
|
460
|
-
|
461
|
-
# Returns +true+ if +id+ is included in the set. Otherwise, returns +false+.
|
462
|
-
#
|
463
|
-
# Example:
|
464
|
-
#
|
465
|
-
# class Post < Ohm::Model
|
466
|
-
# end
|
467
|
-
#
|
468
|
-
# class User < Ohm::Model
|
469
|
-
# set :posts, :Post
|
470
|
-
# end
|
471
|
-
#
|
472
|
-
# user = User.create
|
473
|
-
# post = Post.create
|
474
|
-
# user.posts.add(post)
|
475
|
-
#
|
476
|
-
# user.posts.exists?('nonexistent') # => false
|
477
|
-
# user.posts.exists?(post.id) # => true
|
478
|
-
#
|
479
|
-
def exists?(id)
|
480
|
-
execute { |key| redis.call("SISMEMBER", key, id) == 1 }
|
481
|
-
end
|
482
|
-
|
483
|
-
private
|
484
|
-
def to_key(att)
|
485
|
-
if model.counters.include?(att)
|
486
|
-
namespace["*:counters->%s" % att]
|
487
|
-
else
|
488
|
-
namespace["*->%s" % att]
|
489
|
-
end
|
490
|
-
end
|
491
|
-
end
|
492
|
-
|
493
|
-
class Set < BasicSet
|
494
|
-
attr :key
|
495
|
-
attr :namespace
|
496
|
-
attr :model
|
497
|
-
|
498
|
-
def initialize(key, namespace, model)
|
499
|
-
@key = key
|
500
|
-
@namespace = namespace
|
501
|
-
@model = model
|
502
|
-
end
|
503
|
-
|
504
497
|
# Chain new fiters on an existing set.
|
505
498
|
#
|
506
499
|
# Example:
|
@@ -509,8 +502,8 @@ module Ohm
|
|
509
502
|
# set.find(:age => 30)
|
510
503
|
#
|
511
504
|
def find(dict)
|
512
|
-
|
513
|
-
|
505
|
+
Ohm::Set.new(
|
506
|
+
model, namespace, [:SINTER, key, *model.filters(dict)]
|
514
507
|
)
|
515
508
|
end
|
516
509
|
|
@@ -525,7 +518,9 @@ module Ohm
|
|
525
518
|
# User.find(:name => "John").except(:country => "US")
|
526
519
|
#
|
527
520
|
def except(dict)
|
528
|
-
|
521
|
+
Ohm::Set.new(
|
522
|
+
model, namespace, [:SDIFF, key, [:SUNION, *model.filters(dict)]]
|
523
|
+
)
|
529
524
|
end
|
530
525
|
|
531
526
|
# Perform an intersection between the existent set and
|
@@ -539,7 +534,9 @@ module Ohm
|
|
539
534
|
# # The result will include all users with active status
|
540
535
|
# # and with names "John" or "Jane".
|
541
536
|
def combine(dict)
|
542
|
-
|
537
|
+
Ohm::Set.new(
|
538
|
+
model, namespace, [:SINTER, key, [:SUNION, *model.filters(dict)]]
|
539
|
+
)
|
543
540
|
end
|
544
541
|
|
545
542
|
# Do a union to the existing set using any number of filters.
|
@@ -553,12 +550,18 @@ module Ohm
|
|
553
550
|
# User.find(:name => "John").union(:name => "Jane")
|
554
551
|
#
|
555
552
|
def union(dict)
|
556
|
-
|
553
|
+
Ohm::Set.new(
|
554
|
+
model, namespace, [:SUNION, key, [:SINTER, *model.filters(dict)]]
|
555
|
+
)
|
557
556
|
end
|
558
557
|
|
559
558
|
private
|
560
|
-
def
|
561
|
-
|
559
|
+
def to_key(att)
|
560
|
+
if model.counters.include?(att)
|
561
|
+
namespace["*:counters->%s" % att]
|
562
|
+
else
|
563
|
+
namespace["*->%s" % att]
|
564
|
+
end
|
562
565
|
end
|
563
566
|
|
564
567
|
def redis
|
@@ -567,6 +570,7 @@ module Ohm
|
|
567
570
|
end
|
568
571
|
|
569
572
|
class MutableSet < Set
|
573
|
+
|
570
574
|
# Add a model directly to the set.
|
571
575
|
#
|
572
576
|
# Example:
|
@@ -624,128 +628,6 @@ module Ohm
|
|
624
628
|
end
|
625
629
|
end
|
626
630
|
|
627
|
-
# Anytime you filter a set with more than one requirement, you
|
628
|
-
# internally use a `MultiSet`. `MultiSet` is a bit slower than just
|
629
|
-
# a `Set` because it has to `SINTERSTORE` all the keys prior to
|
630
|
-
# retrieving the members, size, etc.
|
631
|
-
#
|
632
|
-
# Example:
|
633
|
-
#
|
634
|
-
# User.all.kind_of?(Ohm::Set)
|
635
|
-
# # => true
|
636
|
-
#
|
637
|
-
# User.find(:name => "John").kind_of?(Ohm::Set)
|
638
|
-
# # => true
|
639
|
-
#
|
640
|
-
# User.find(:name => "John", :age => 30).kind_of?(Ohm::MultiSet)
|
641
|
-
# # => true
|
642
|
-
#
|
643
|
-
class MultiSet < BasicSet
|
644
|
-
attr :namespace
|
645
|
-
attr :model
|
646
|
-
attr :command
|
647
|
-
|
648
|
-
def initialize(namespace, model, command)
|
649
|
-
@namespace = namespace
|
650
|
-
@model = model
|
651
|
-
@command = command
|
652
|
-
end
|
653
|
-
|
654
|
-
# Chain new fiters on an existing set.
|
655
|
-
#
|
656
|
-
# Example:
|
657
|
-
#
|
658
|
-
# set = User.find(:name => "John", :age => 30)
|
659
|
-
# set.find(:status => 'pending')
|
660
|
-
#
|
661
|
-
def find(dict)
|
662
|
-
MultiSet.new(
|
663
|
-
namespace, model, Command[:sinterstore, command, intersected(dict)]
|
664
|
-
)
|
665
|
-
end
|
666
|
-
|
667
|
-
# Reduce the set using any number of filters.
|
668
|
-
#
|
669
|
-
# Example:
|
670
|
-
#
|
671
|
-
# set = User.find(:name => "John")
|
672
|
-
# set.except(:country => "US")
|
673
|
-
#
|
674
|
-
# # You can also do it in one line.
|
675
|
-
# User.find(:name => "John").except(:country => "US")
|
676
|
-
#
|
677
|
-
def except(dict)
|
678
|
-
MultiSet.new(
|
679
|
-
namespace, model, Command[:sdiffstore, command, unioned(dict)]
|
680
|
-
)
|
681
|
-
end
|
682
|
-
|
683
|
-
# Perform an intersection between the existent set and
|
684
|
-
# the new set created by the union of the passed filters.
|
685
|
-
#
|
686
|
-
# Example:
|
687
|
-
#
|
688
|
-
# set = User.find(:status => "active")
|
689
|
-
# set.combine(:name => ["John", "Jane"])
|
690
|
-
#
|
691
|
-
# # The result will include all users with active status
|
692
|
-
# # and with names "John" or "Jane".
|
693
|
-
def combine(dict)
|
694
|
-
MultiSet.new(
|
695
|
-
namespace, model, Command[:sinterstore, command, unioned(dict)]
|
696
|
-
)
|
697
|
-
end
|
698
|
-
|
699
|
-
# Do a union to the existing set using any number of filters.
|
700
|
-
#
|
701
|
-
# Example:
|
702
|
-
#
|
703
|
-
# set = User.find(:name => "John")
|
704
|
-
# set.union(:name => "Jane")
|
705
|
-
#
|
706
|
-
# # You can also do it in one line.
|
707
|
-
# User.find(:name => "John").union(:name => "Jane")
|
708
|
-
#
|
709
|
-
def union(dict)
|
710
|
-
MultiSet.new(
|
711
|
-
namespace, model, Command[:sunionstore, command, intersected(dict)]
|
712
|
-
)
|
713
|
-
end
|
714
|
-
|
715
|
-
private
|
716
|
-
def redis
|
717
|
-
model.redis
|
718
|
-
end
|
719
|
-
|
720
|
-
def intersected(dict)
|
721
|
-
Command[:sinterstore, *model.filters(dict)]
|
722
|
-
end
|
723
|
-
|
724
|
-
def unioned(dict)
|
725
|
-
Command[:sunionstore, *model.filters(dict)]
|
726
|
-
end
|
727
|
-
|
728
|
-
def execute
|
729
|
-
# namespace[:tmp] is where all the temp keys should be stored in.
|
730
|
-
# redis will be where all the commands are executed against.
|
731
|
-
response = command.call(namespace[:tmp], redis)
|
732
|
-
|
733
|
-
begin
|
734
|
-
|
735
|
-
# At this point, we have the final aggregated set, which we yield
|
736
|
-
# to the caller. the caller can do all the normal set operations,
|
737
|
-
# i.e. SCARD, SMEMBERS, etc.
|
738
|
-
yield response
|
739
|
-
|
740
|
-
ensure
|
741
|
-
|
742
|
-
# We have to make sure we clean up the temporary keys to avoid
|
743
|
-
# memory leaks and the unintended explosion of memory usage.
|
744
|
-
command.clean
|
745
|
-
end
|
746
|
-
end
|
747
|
-
end
|
748
|
-
|
749
631
|
# The base class for all your models. In order to better understand
|
750
632
|
# it, here is a semi-realtime explanation of the details involved
|
751
633
|
# when creating a User instance.
|
@@ -927,9 +809,9 @@ module Ohm
|
|
927
809
|
keys = filters(dict)
|
928
810
|
|
929
811
|
if keys.size == 1
|
930
|
-
Ohm::Set.new(
|
812
|
+
Ohm::Set.new(self, key, keys.first)
|
931
813
|
else
|
932
|
-
Ohm::
|
814
|
+
Ohm::Set.new(self, key, [:SINTER, *keys])
|
933
815
|
end
|
934
816
|
end
|
935
817
|
|
@@ -980,7 +862,7 @@ module Ohm
|
|
980
862
|
define_method name do
|
981
863
|
model = Utils.const(self.class, model)
|
982
864
|
|
983
|
-
Ohm::MutableSet.new(
|
865
|
+
Ohm::MutableSet.new(model, model.key, key[name])
|
984
866
|
end
|
985
867
|
end
|
986
868
|
|
@@ -1190,7 +1072,7 @@ module Ohm
|
|
1190
1072
|
|
1191
1073
|
# An Ohm::Set wrapper for Model.key[:all].
|
1192
1074
|
def self.all
|
1193
|
-
Set.new(key[:all]
|
1075
|
+
Ohm::Set.new(self, key, key[:all])
|
1194
1076
|
end
|
1195
1077
|
|
1196
1078
|
# Syntactic sugar for Model.new(atts).save
|
data/makefile
CHANGED
data/ohm.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = "ohm"
|
3
|
-
s.version = "2.
|
3
|
+
s.version = "2.2.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 has very good performance.}
|
6
6
|
s.authors = ["Michel Martens", "Damian Janowski", "Cyril David"]
|
@@ -14,6 +14,7 @@ Gem::Specification.new do |s|
|
|
14
14
|
|
15
15
|
s.add_dependency "redic"
|
16
16
|
s.add_dependency "nido"
|
17
|
+
s.add_dependency "stal"
|
17
18
|
s.add_dependency "msgpack"
|
18
19
|
|
19
20
|
s.add_development_dependency "cutest"
|
data/test/indices.rb
CHANGED
@@ -54,12 +54,6 @@ test "avoid intersections with the all collection" do
|
|
54
54
|
assert_equal "User:indices:email:foo", User.find(:email => "foo").key
|
55
55
|
end
|
56
56
|
|
57
|
-
test "cleanup the temporary key after use" do
|
58
|
-
assert User.find(:email => "foo", :activation_code => "bar").to_a
|
59
|
-
|
60
|
-
assert Ohm.redis.call("KEYS", "User:temp:*").empty?
|
61
|
-
end
|
62
|
-
|
63
57
|
test "allow multiple chained finds" do
|
64
58
|
assert 1 == User.find(:email => "foo").find(:activation_code => "bar").find(:update => "baz").size
|
65
59
|
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: 2.
|
4
|
+
version: 2.2.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: 2015-
|
13
|
+
date: 2015-03-03 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: redic
|
@@ -40,6 +40,20 @@ dependencies:
|
|
40
40
|
- - '>='
|
41
41
|
- !ruby/object:Gem::Version
|
42
42
|
version: '0'
|
43
|
+
- !ruby/object:Gem::Dependency
|
44
|
+
name: stal
|
45
|
+
requirement: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - '>='
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
type: :runtime
|
51
|
+
prerelease: false
|
52
|
+
version_requirements: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - '>='
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
43
57
|
- !ruby/object:Gem::Dependency
|
44
58
|
name: msgpack
|
45
59
|
requirement: !ruby/object:Gem::Requirement
|
@@ -95,14 +109,12 @@ files:
|
|
95
109
|
- examples/slug.rb
|
96
110
|
- examples/tagging.rb
|
97
111
|
- lib/ohm.rb
|
98
|
-
- lib/ohm/command.rb
|
99
112
|
- lib/ohm/json.rb
|
100
113
|
- lib/ohm/lua/delete.lua
|
101
114
|
- lib/ohm/lua/save.lua
|
102
115
|
- makefile
|
103
116
|
- ohm.gemspec
|
104
117
|
- test/association.rb
|
105
|
-
- test/command.rb
|
106
118
|
- test/connection.rb
|
107
119
|
- test/core.rb
|
108
120
|
- test/counters.rb
|
@@ -143,4 +155,3 @@ signing_key:
|
|
143
155
|
specification_version: 4
|
144
156
|
summary: Object-hash mapping library for Redis.
|
145
157
|
test_files: []
|
146
|
-
has_rdoc:
|
data/lib/ohm/command.rb
DELETED
@@ -1,51 +0,0 @@
|
|
1
|
-
module Ohm
|
2
|
-
class Command
|
3
|
-
def self.[](operation, head, *tail)
|
4
|
-
return head if tail.empty?
|
5
|
-
|
6
|
-
new(operation, head, *tail)
|
7
|
-
end
|
8
|
-
|
9
|
-
attr :operation
|
10
|
-
attr :args
|
11
|
-
attr :keys
|
12
|
-
|
13
|
-
def initialize(operation, *args)
|
14
|
-
@operation = operation
|
15
|
-
@args = args
|
16
|
-
@keys = []
|
17
|
-
end
|
18
|
-
|
19
|
-
def call(nido, redis)
|
20
|
-
newkey(nido, redis) do |key|
|
21
|
-
redis.call(@operation, key, *params(nido, redis))
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
def clean
|
26
|
-
keys.each do |key, redis|
|
27
|
-
redis.call("DEL", key)
|
28
|
-
end
|
29
|
-
|
30
|
-
subcommands.each { |cmd| cmd.clean }
|
31
|
-
end
|
32
|
-
|
33
|
-
private
|
34
|
-
def subcommands
|
35
|
-
args.select { |arg| arg.respond_to?(:call) }
|
36
|
-
end
|
37
|
-
|
38
|
-
def params(nido, redis)
|
39
|
-
args.map { |arg| arg.respond_to?(:call) ? arg.call(nido, redis) : arg }
|
40
|
-
end
|
41
|
-
|
42
|
-
def newkey(nido, redis)
|
43
|
-
key = nido[SecureRandom.hex(32)]
|
44
|
-
keys << [key, redis]
|
45
|
-
|
46
|
-
yield key
|
47
|
-
|
48
|
-
return key
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
data/test/command.rb
DELETED
@@ -1,55 +0,0 @@
|
|
1
|
-
require_relative "helper"
|
2
|
-
|
3
|
-
scope do
|
4
|
-
setup do
|
5
|
-
redis = Redic.new
|
6
|
-
redis.call("FLUSHDB")
|
7
|
-
|
8
|
-
nido = Nido.new("User:tmp")
|
9
|
-
|
10
|
-
[1, 2, 3].each { |i| redis.call("SADD", "A", i) }
|
11
|
-
[1, 4, 5].each { |i| redis.call("SADD", "B", i) }
|
12
|
-
|
13
|
-
[10, 11, 12].each { |i| redis.call("SADD", "C", i) }
|
14
|
-
[11, 12, 13].each { |i| redis.call("SADD", "D", i) }
|
15
|
-
[12, 13, 14].each { |i| redis.call("SADD", "E", i) }
|
16
|
-
|
17
|
-
[10, 11, 12].each { |i| redis.call("SADD", "F", i) }
|
18
|
-
[11, 12, 13].each { |i| redis.call("SADD", "G", i) }
|
19
|
-
[12, 13, 14].each { |i| redis.call("SADD", "H", i) }
|
20
|
-
|
21
|
-
[redis, nido]
|
22
|
-
end
|
23
|
-
|
24
|
-
test "special condition: single argument returns that arg" do
|
25
|
-
assert_equal "A", Ohm::Command[:sinterstore, "A"]
|
26
|
-
end
|
27
|
-
|
28
|
-
test "full stack test" do |redis, nido|
|
29
|
-
cmd1 = Ohm::Command[:sinterstore, "A", "B"]
|
30
|
-
|
31
|
-
res = cmd1.call(nido, redis)
|
32
|
-
assert_equal ["1"], redis.call("SMEMBERS", res)
|
33
|
-
|
34
|
-
cmd1.clean
|
35
|
-
assert_equal 0, redis.call("EXISTS", res)
|
36
|
-
|
37
|
-
cmd2 = Ohm::Command[:sinterstore, "C", "D", "E"]
|
38
|
-
cmd3 = Ohm::Command[:sunionstore, cmd1, cmd2]
|
39
|
-
|
40
|
-
res = cmd3.call(nido, redis)
|
41
|
-
assert_equal ["1", "12"], redis.call("SMEMBERS", res)
|
42
|
-
|
43
|
-
cmd3.clean
|
44
|
-
assert redis.call("KEYS", nido["*"]).empty?
|
45
|
-
|
46
|
-
cmd4 = Ohm::Command[:sinterstore, "F", "G", "H"]
|
47
|
-
cmd5 = Ohm::Command[:sdiffstore, cmd3, cmd4]
|
48
|
-
|
49
|
-
res = cmd5.call(nido, redis)
|
50
|
-
assert_equal ["1"], redis.call("SMEMBERS", res)
|
51
|
-
|
52
|
-
cmd5.clean
|
53
|
-
assert redis.call("KEYS", nido["*"]).empty?
|
54
|
-
end
|
55
|
-
end
|