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 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