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