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 CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- NDlkNzNmNzllNTJmMDEzODExNGQ5MDlhMWM5NTYyMjA5MDQwNGUwMA==
4
+ OGIxYjAxZmZiNTBlMmUwZTk0MDRkMTFlYTNjYjdmMGIyN2FmYTZhYQ==
5
5
  data.tar.gz: !binary |-
6
- ZjQ5YzdhNTg2NTYzMzY0YjljZjcxZDQ3Y2JjZjJhMmUzYWFmZmZlNw==
6
+ NjAzZjNkNWFiNTM0MjI1OWUyOTA4ZWQ0MGJkY2QzNGZlODU0ODg0MA==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- MmFmNjgzODkwNzMyYzRiMjNhZTM2MDI5N2Y4NjRhYzMyMzcyNDgxZTYzMWE2
10
- ZGZmNjI5M2E1ODdjYTE1MGYzMjlkZGFmYTI1MDE0ZDY1OTEyZTIyYzVhNzg4
11
- OTk3NWVkMmFkNjE5M2UwNDdmZTUzYWE4YTE1MGUxNGJmMDg2MmM=
9
+ MWE3NjZlMDc4ZDY2MDhlODIyZTU1NzZiZTBlMWQyYzI3ZjcyMmU5YzhkZTlk
10
+ M2EyNTc3MTRjOWUyMjU1MGFmZTE0OTVlOGU5MTNmMzFmMTZjYjkzNTgxYTYw
11
+ YzM4MGQ0MGQ0MDcyNmQ3ZmQ3OTgwZWI1OWUwMTg1MjU4MWMyZWI=
12
12
  data.tar.gz: !binary |-
13
- MzM3NDc5YWQzN2ZiN2EzOWQ3OTIxZDAxMDI5Y2YzZTEyMDlhMzcyNjVjMDdi
14
- Yzc3NTQ4ZTQ3NzA2ZmI5NmIyZjk1NDIxNjYwZjg4NGU0NTAzM2JlNTljNTE0
15
- ZWQ5ZGZjZDkwNmQyNmFhMjRhMDJjNTlmMjY3MmIxN2JiMDdmZWE=
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 :status, by: :ranking
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
- db.zrange(key, 0, -1).each { |id| block.call(model.to_proc[id]) }
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
- db.zrange(key, 0, -1).map(&model)
117
+ ids.map(&model)
67
118
  end
68
119
 
69
120
  def first
70
- db.zrange(key, 0, 1).map(&model).first
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(attr, options={})
90
- sorted_indices[attr] = options
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?(dict.keys.first, by: attribute)
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
- index = sorted_indices[attribute]
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", dict.keys.first, attribute, dict.values.first].join(":")
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(options[:by]).to_f
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
- key = self.class.sorted_index_key(
158
- options[:by], {attribute => send(attribute)})
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
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'ohm-sorted'
3
- s.version = '0.1.0'
3
+ s.version = '0.2.0'
4
4
  s.summary = "Sorted indices for Ohm."
5
5
  s.description = "An plugin for Ohm that lets you create sorted indices."
6
6
  s.author = "Federico Bond"
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 :status, by: :order
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 test_sorted_find_first
27
- post = Post.create(status: "draft", order: 1)
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
- assert_equal post, sorted_set.first
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) ? Ohm::IndexNotFound : Ohm::Model::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.1.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-05 00:00:00.000000000 Z
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