ohm-sorted 0.1.0 → 0.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 +8 -8
- data/README.md +6 -1
- data/lib/ohm/sorted.rb +112 -19
- data/ohm-sorted.gemspec +1 -1
- data/test/sorted_test.rb +62 -7
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
OGIxYjAxZmZiNTBlMmUwZTk0MDRkMTFlYTNjYjdmMGIyN2FmYTZhYQ==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
NjAzZjNkNWFiNTM0MjI1OWUyOTA4ZWQ0MGJkY2QzNGZlODU0ODg0MA==
|
7
7
|
!binary "U0hBNTEy":
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
MWE3NjZlMDc4ZDY2MDhlODIyZTU1NzZiZTBlMWQyYzI3ZjcyMmU5YzhkZTlk
|
10
|
+
M2EyNTc3MTRjOWUyMjU1MGFmZTE0OTVlOGU5MTNmMzFmMTZjYjkzNTgxYTYw
|
11
|
+
YzM4MGQ0MGQ0MDcyNmQ3ZmQ3OTgwZWI1OWUwMTg1MjU4MWMyZWI=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
MzUxODc5YzgzZDgzZjdmZDQ1ZjRiYmJkMWJlZGM1N2I4YzNlYjRkNTNjYWU0
|
14
|
+
ZDEyNDIzMjM3MjRkMmU1ZDhhYTU5OGUwYTEzOTM5YzkyNGZkNGExZWJiMWIx
|
15
|
+
MDA3NDUyZGUwZWQxYTdhMDlmOTg0NWFkNGQxNWViYjZlMTc0YTE=
|
data/README.md
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
ohm-sorted
|
2
2
|
==========
|
3
3
|
|
4
|
+
[](http://badge.fury.io/rb/ohm-sorted)
|
5
|
+
[](https://travis-ci.org/educabilia/ohm-sorted)
|
6
|
+
[](https://codeclimate.com/github/educabilia/ohm-sorted)
|
7
|
+
|
4
8
|
Sorted indexes for Ohm
|
5
9
|
|
6
10
|
|
@@ -14,7 +18,7 @@ Setup
|
|
14
18
|
|
15
19
|
2. Add a sorted index to your model with the following line:
|
16
20
|
|
17
|
-
sorted :
|
21
|
+
sorted :ranking, group_by: :status
|
18
22
|
|
19
23
|
You will need to resave every model if they already exist.
|
20
24
|
|
@@ -25,6 +29,7 @@ To query the sorted index, use the `sorted_find` class method.
|
|
25
29
|
|
26
30
|
>> Post.sorted_find(:ranking, status: "draft")
|
27
31
|
|
32
|
+
|
28
33
|
This returns an Ohm::SortedSet, which is just a subclass of Ohm::BasicSet
|
29
34
|
backed by a sorted set.
|
30
35
|
|
data/lib/ohm/sorted.rb
CHANGED
@@ -3,8 +3,57 @@ require 'ohm/contrib'
|
|
3
3
|
|
4
4
|
module Ohm
|
5
5
|
|
6
|
+
module SortedMethods
|
7
|
+
attr_accessor :limit
|
8
|
+
attr_accessor :offset
|
9
|
+
attr_accessor :range
|
10
|
+
|
11
|
+
def limit(n)
|
12
|
+
set = dup
|
13
|
+
set.limit = n
|
14
|
+
set
|
15
|
+
end
|
16
|
+
|
17
|
+
def offset(n)
|
18
|
+
set = dup
|
19
|
+
set.offset = n
|
20
|
+
set
|
21
|
+
end
|
22
|
+
|
23
|
+
def start
|
24
|
+
@start ||= (@offset || 0)
|
25
|
+
end
|
26
|
+
|
27
|
+
def stop
|
28
|
+
@stop ||= (@limit.nil? ? -1 : start + @limit - 1)
|
29
|
+
end
|
30
|
+
|
31
|
+
def range(range)
|
32
|
+
set = dup
|
33
|
+
set.range = range
|
34
|
+
set
|
35
|
+
end
|
36
|
+
|
37
|
+
def ids
|
38
|
+
if @range.nil?
|
39
|
+
execute { |key| db.zrange(key, start, stop) }
|
40
|
+
else
|
41
|
+
execute { |key| db.zrangebyscore(key, @range.begin.to_f, @range.end.to_f, offset: [start, stop]) }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
unless instance_methods.include?(:execute)
|
46
|
+
def execute
|
47
|
+
yield key
|
48
|
+
end
|
49
|
+
private :execute
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
6
53
|
if defined?(BasicSet)
|
7
54
|
class SortedSet < BasicSet
|
55
|
+
include SortedMethods
|
56
|
+
|
8
57
|
attr :key
|
9
58
|
attr :namespace
|
10
59
|
attr :model
|
@@ -15,14 +64,14 @@ module Ohm
|
|
15
64
|
@model = model
|
16
65
|
end
|
17
66
|
|
18
|
-
def ids
|
19
|
-
execute { |key| db.zrange(key, 0, -1) }
|
20
|
-
end
|
21
|
-
|
22
67
|
def size
|
23
68
|
execute { |key| db.zcard(key) }
|
24
69
|
end
|
25
70
|
|
71
|
+
def first
|
72
|
+
fetch(execute { |key| db.zrange(key, 0, 1) }).first
|
73
|
+
end
|
74
|
+
|
26
75
|
private
|
27
76
|
def exists?(id)
|
28
77
|
execute { |key| !!db.zscore(key, id) }
|
@@ -38,6 +87,8 @@ module Ohm
|
|
38
87
|
end
|
39
88
|
else
|
40
89
|
class SortedSet < Model::Collection
|
90
|
+
include Ohm::SortedMethods
|
91
|
+
|
41
92
|
attr :key
|
42
93
|
attr :model
|
43
94
|
|
@@ -51,7 +102,7 @@ module Ohm
|
|
51
102
|
end
|
52
103
|
|
53
104
|
def each(&block)
|
54
|
-
|
105
|
+
ids.each { |id| block.call(model.to_proc[id]) }
|
55
106
|
end
|
56
107
|
|
57
108
|
def [](id)
|
@@ -63,11 +114,16 @@ module Ohm
|
|
63
114
|
end
|
64
115
|
|
65
116
|
def all
|
66
|
-
|
117
|
+
ids.map(&model)
|
67
118
|
end
|
68
119
|
|
69
120
|
def first
|
70
|
-
|
121
|
+
if @range.nil?
|
122
|
+
ids = db.zrange(key, start, 1)
|
123
|
+
else
|
124
|
+
ids = db.zrangebyscore(key, @range.begin.to_f, @range.end.to_f, offset: [start, 1])
|
125
|
+
end
|
126
|
+
ids.map(&model).first
|
71
127
|
end
|
72
128
|
|
73
129
|
def include?(model)
|
@@ -86,16 +142,16 @@ module Ohm
|
|
86
142
|
end
|
87
143
|
|
88
144
|
module ClassMethods
|
89
|
-
def sorted(
|
90
|
-
sorted_indices[
|
145
|
+
def sorted(attribute, options={})
|
146
|
+
sorted_indices << [attribute, options]
|
91
147
|
end
|
92
148
|
|
93
149
|
def sorted_indices
|
94
|
-
@sorted_indices ||=
|
150
|
+
@sorted_indices ||= []
|
95
151
|
end
|
96
152
|
|
97
|
-
def sorted_find(attribute, dict)
|
98
|
-
unless sorted_index_exists?(
|
153
|
+
def sorted_find(attribute, dict={})
|
154
|
+
unless sorted_index_exists?(attribute, to_options(dict))
|
99
155
|
raise index_not_found(attribute)
|
100
156
|
end
|
101
157
|
|
@@ -104,12 +160,18 @@ module Ohm
|
|
104
160
|
end
|
105
161
|
|
106
162
|
def sorted_index_exists?(attribute, options=nil)
|
107
|
-
|
108
|
-
!!(index && (options.nil? || options == index))
|
163
|
+
!!sorted_indices.detect { |i| i == [attribute, options] }
|
109
164
|
end
|
110
165
|
|
111
|
-
def sorted_index_key(attribute, dict)
|
112
|
-
[key, "sorted",
|
166
|
+
def sorted_index_key(attribute, dict={})
|
167
|
+
index_key = [key, "sorted", attribute]
|
168
|
+
if dict.keys.size == 1
|
169
|
+
index_key << dict.keys.first
|
170
|
+
index_key << dict.values.first
|
171
|
+
elsif dict.keys.size > 1
|
172
|
+
raise ArgumentError
|
173
|
+
end
|
174
|
+
index_key.join(":")
|
113
175
|
end
|
114
176
|
|
115
177
|
protected
|
@@ -120,6 +182,11 @@ module Ohm
|
|
120
182
|
Model::IndexNotFound.new(attribute)
|
121
183
|
end
|
122
184
|
end
|
185
|
+
|
186
|
+
def to_options(dict)
|
187
|
+
return {} if dict.empty?
|
188
|
+
{group_by: dict.keys.first}
|
189
|
+
end
|
123
190
|
end
|
124
191
|
|
125
192
|
protected
|
@@ -128,6 +195,10 @@ module Ohm
|
|
128
195
|
super
|
129
196
|
end
|
130
197
|
|
198
|
+
def before_update
|
199
|
+
prune_sorted_indices
|
200
|
+
end
|
201
|
+
|
131
202
|
def after_update
|
132
203
|
add_sorted_indices unless new?
|
133
204
|
super
|
@@ -140,7 +211,7 @@ module Ohm
|
|
140
211
|
|
141
212
|
def add_sorted_indices
|
142
213
|
update_sorted_indices do |key, attribute, options|
|
143
|
-
score = send(
|
214
|
+
score = send(attribute).to_f
|
144
215
|
db.zadd(key, score, id)
|
145
216
|
end
|
146
217
|
end
|
@@ -151,11 +222,33 @@ module Ohm
|
|
151
222
|
end
|
152
223
|
end
|
153
224
|
|
225
|
+
def prune_sorted_indices
|
226
|
+
return if new?
|
227
|
+
update_sorted_indices do |key, attribute, options|
|
228
|
+
return unless options.include?(:group_by)
|
229
|
+
|
230
|
+
old_value = db.hget(self.key, options[:group_by])
|
231
|
+
new_value = send(options[:group_by])
|
232
|
+
|
233
|
+
if old_value != new_value
|
234
|
+
opts = {options[:group_by] => old_value}
|
235
|
+
key = self.class.sorted_index_key(attribute, opts)
|
236
|
+
db.zrem(key, id)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
154
241
|
def update_sorted_indices
|
155
242
|
self.class.sorted_indices.each do |args|
|
156
243
|
attribute, options = *args
|
157
|
-
|
158
|
-
|
244
|
+
|
245
|
+
opts = {}
|
246
|
+
if options.include?(:group_by)
|
247
|
+
group_by = options[:group_by]
|
248
|
+
opts[group_by] = send(group_by)
|
249
|
+
end
|
250
|
+
key = self.class.sorted_index_key(attribute, opts)
|
251
|
+
|
159
252
|
yield(key, attribute, options)
|
160
253
|
end
|
161
254
|
end
|
data/ohm-sorted.gemspec
CHANGED
data/test/sorted_test.rb
CHANGED
@@ -7,8 +7,9 @@ class Post < Ohm::Model
|
|
7
7
|
|
8
8
|
attribute :order
|
9
9
|
attribute :status
|
10
|
-
|
11
|
-
sorted :
|
10
|
+
|
11
|
+
sorted :order, group_by: :status
|
12
|
+
sorted :order
|
12
13
|
end
|
13
14
|
|
14
15
|
class SortedTest < Test::Unit::TestCase
|
@@ -20,14 +21,22 @@ class SortedTest < Test::Unit::TestCase
|
|
20
21
|
Post.create(status: "draft", order: 1)
|
21
22
|
sorted_set = Post.sorted_find(:order, status: "draft")
|
22
23
|
assert_equal Ohm::SortedSet, sorted_set.class
|
23
|
-
assert_equal "Post:sorted:status:order:draft", sorted_set.key
|
24
24
|
end
|
25
25
|
|
26
|
-
def
|
27
|
-
|
26
|
+
def test_sorted_find_set_key
|
27
|
+
Post.create(status: "draft", order: 1)
|
28
28
|
sorted_set = Post.sorted_find(:order, status: "draft")
|
29
|
+
assert_equal "Post:sorted:order:status:draft", sorted_set.key
|
29
30
|
|
30
|
-
|
31
|
+
sorted_set = Post.sorted_find(:order)
|
32
|
+
assert_equal "Post:sorted:order", sorted_set.key
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_sorted_find_all
|
36
|
+
posts = []
|
37
|
+
posts << Post.create(order: 1)
|
38
|
+
posts << Post.create(order: 2)
|
39
|
+
assert_equal posts, Post.sorted_find(:order).to_a
|
31
40
|
end
|
32
41
|
|
33
42
|
def test_sorted_find_order
|
@@ -39,6 +48,41 @@ class SortedTest < Test::Unit::TestCase
|
|
39
48
|
assert_equal [post_3, post_1, post_2], sorted_set.to_a
|
40
49
|
end
|
41
50
|
|
51
|
+
def test_sorted_find_with_limit
|
52
|
+
posts = []
|
53
|
+
posts << Post.create(order: 1)
|
54
|
+
posts << Post.create(order: 2)
|
55
|
+
Post.create(status: "draft", order: 3)
|
56
|
+
assert_equal posts, Post.sorted_find(:order).limit(2).to_a
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_sorted_find_with_offset
|
60
|
+
Post.create(order: 1)
|
61
|
+
posts = []
|
62
|
+
posts << Post.create(order: 2)
|
63
|
+
posts << Post.create(order: 3)
|
64
|
+
assert_equal posts, Post.sorted_find(:order).offset(1).to_a
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_sorted_find_with_range
|
68
|
+
posts = []
|
69
|
+
posts << Post.create(status: "draft", order: 1)
|
70
|
+
posts << Post.create(status: "draft", order: 2)
|
71
|
+
posts << Post.create(status: "draft", order: 3)
|
72
|
+
posts << Post.create(status: "published", order: 4)
|
73
|
+
posts << Post.create(status: "draft", order: 5)
|
74
|
+
assert_equal posts.slice(1, 2), Post.sorted_find(:order).range(2..3).to_a
|
75
|
+
assert_equal [posts[3]], Post.sorted_find(:order, status: "published").range(2..4).to_a
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_sorted_find_first
|
79
|
+
Post.create(status: "draft", order: 2)
|
80
|
+
post = Post.create(status: "draft", order: 1)
|
81
|
+
sorted_set = Post.sorted_find(:order, status: "draft")
|
82
|
+
|
83
|
+
assert_equal post, sorted_set.first
|
84
|
+
end
|
85
|
+
|
42
86
|
def test_update
|
43
87
|
post_1 = Post.create(status: "draft", order: 1)
|
44
88
|
post_2 = Post.create(status: "draft", order: 2)
|
@@ -50,6 +94,13 @@ class SortedTest < Test::Unit::TestCase
|
|
50
94
|
|
51
95
|
sorted_set = Post.sorted_find(:order, status: "draft")
|
52
96
|
assert_equal [post_2, post_1], sorted_set.to_a
|
97
|
+
|
98
|
+
post_1.update(status: "published")
|
99
|
+
|
100
|
+
sorted_set = Post.sorted_find(:order, status: "draft")
|
101
|
+
assert_equal [post_2], sorted_set.to_a
|
102
|
+
sorted_set = Post.sorted_find(:order, status: "published")
|
103
|
+
assert_equal [post_1], sorted_set.to_a
|
53
104
|
end
|
54
105
|
|
55
106
|
def test_delete
|
@@ -67,7 +118,11 @@ class SortedTest < Test::Unit::TestCase
|
|
67
118
|
end
|
68
119
|
|
69
120
|
def test_sorted_find_invalid
|
70
|
-
exception_class = defined?(Ohm::IndexNotFound)
|
121
|
+
exception_class = if defined?(Ohm::IndexNotFound)
|
122
|
+
Ohm::IndexNotFound
|
123
|
+
else
|
124
|
+
Ohm::Model::IndexNotFound
|
125
|
+
end
|
71
126
|
|
72
127
|
Post.create(status: "draft", order: 1)
|
73
128
|
assert_raises(exception_class) do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ohm-sorted
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Federico Bond
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-09-
|
11
|
+
date: 2013-09-09 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: An plugin for Ohm that lets you create sorted indices.
|
14
14
|
email: federico@educabilia.com
|