ohm-sorted 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/ohm-sorted.png)](http://badge.fury.io/rb/ohm-sorted)
|
5
|
+
[![Build Status](https://travis-ci.org/educabilia/ohm-sorted.png?branch=master)](https://travis-ci.org/educabilia/ohm-sorted)
|
6
|
+
[![Code Climate](https://codeclimate.com/github/educabilia/ohm-sorted.png)](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
|