rankle 0.0.1 → 0.0.2

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,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d2a00ba713d656ff500838729e9ea39ed91d71c5
4
- data.tar.gz: 1b07a9638a677ca8265d6a146674d222d80b0470
3
+ metadata.gz: 074be4978d90fa4f0456dc867138970a59d0aaf4
4
+ data.tar.gz: 9c6742f8b6f14098c5c62ee2748d5f46c8241f34
5
5
  SHA512:
6
- metadata.gz: a48c2ed7910ffc5d4e0c8d23fcfec501e220c82ea7db87f8d88a30dbb500251b4e67dc8c2743d0380182fb75b47a4bbb204cb7440803925f375f9ecdbf0ba26a
7
- data.tar.gz: 1bb2cee9412e484908a9e94d4095ecda288223b868b487259bdfa0c2d40b1cb8d2d0a4c50258d949d2ba1bc295003e987c98856134698a116e011724cf8dbc79
6
+ metadata.gz: 431ea786772022a565750d33713834bdb8c4f4828f8dc99f05cc70fb92fd291e5b08b90a0700368d2ff6443e2e62e97236581fc9e39e9bc5a8887b3faadb2427
7
+ data.tar.gz: 6797af0d99cc9aa1a85be6de905606ca84b864f9546d7cf667225f295a92db5c8adcf97528418aad01f802aa6ed2c5e09d566eff5e3fab646523fc1a39f6c089
data/lib/rankle/ranker.rb CHANGED
@@ -1,36 +1,9 @@
1
1
  module Rankle
2
2
  class Ranker
3
- MIN_INDEX = -2147483648
4
- MAX_INDEX = 2147483647
5
-
6
3
  attr_accessor :strategy
7
4
 
8
5
  def initialize strategy
9
6
  @strategy = strategy
10
7
  end
11
-
12
- def self.insert target_position, existing_elements
13
- if existing_elements.count > MAX_INDEX - MIN_INDEX
14
- raise IndexError
15
- elsif existing_elements.empty?
16
- return 0, []
17
- elsif target_position <= 0
18
- return (MIN_INDEX + existing_elements.first) / 2, existing_elements
19
- elsif target_position >= existing_elements.count
20
- return (MAX_INDEX + existing_elements.last) / 2, existing_elements
21
- elsif existing_elements[target_position] - existing_elements[target_position - 1] > 1
22
- return (existing_elements[target_position] + existing_elements[target_position - 1]) / 2, existing_elements
23
- else
24
- existing_elements = balance existing_elements
25
- insert target_position, existing_elements
26
- end
27
- end
28
-
29
- def self.balance indices, options = {}
30
- min_index = options[:min_index] || MIN_INDEX
31
- max_index = options[:max_index] || MAX_INDEX
32
- offset = (max_index - min_index) / (indices.count + 1)
33
- indices.count.times.map { |index| min_index + (offset * (index + 1)) + index }
34
- end
35
8
  end
36
9
  end
@@ -1,3 +1,3 @@
1
1
  module Rankle
2
- VERSION = '0.0.1'
2
+ VERSION = '0.0.2'
3
3
  end
data/lib/rankle_index.rb CHANGED
@@ -1,4 +1,10 @@
1
+ require 'ranked-model'
2
+
1
3
  class RankleIndex < ActiveRecord::Base
4
+ include RankedModel
5
+
6
+ ranks :row_order, column: :indexable_position, :with_same => :indexable_name
7
+
2
8
  belongs_to :indexable, polymorphic: true
3
9
 
4
10
  @rankers = {}
@@ -26,20 +32,16 @@ class RankleIndex < ActiveRecord::Base
26
32
  end
27
33
  position = existing_indices.length - 1 if position > existing_indices.length
28
34
  position = 0 if position < 0
29
- index = RankleIndex.where(indexable_name: name.to_s, indexable_id: instance.id, indexable_type: instance.class).first_or_initialize
30
- existing_positions = existing_indices.pluck(:indexable_position).compact
31
- existing_positions -= [index.indexable_position] unless index.new_record?
32
- indexable_position, existing_positions = Rankle::Ranker.insert(position, existing_positions)
33
- existing_positions.each_with_index do |position, index|
34
- existing_indices[index].update_attribute(:indexable_position, position) unless existing_indices[index].indexable_position = position
35
- end
36
- index.indexable_position = indexable_position
37
- index.save!
35
+ index = RankleIndex.where(indexable_name: name.to_s, indexable_id: instance.id, indexable_type: instance.class).first_or_create!
36
+ index.update_attribute(:row_order_position, position)
38
37
  end
39
38
 
40
39
  def self.position instance, name
41
40
  indexable_position = where(indexable_name: name.to_s, indexable_id: instance.id, indexable_type: instance.class).first_or_create!.indexable_position
42
- where(indexable_name: name.to_s).where('indexable_position < ?', indexable_position).count
41
+ indexable_scope = where(indexable_name: name.to_s)
42
+ indexable_scope = indexable_scope.where(indexable_type: instance.class) if name == :default
43
+ indexable_scope = indexable_scope.where('indexable_position < ?', indexable_position)
44
+ indexable_scope.count
43
45
  end
44
46
 
45
47
  def self.ranked name
@@ -47,10 +49,4 @@ class RankleIndex < ActiveRecord::Base
47
49
  duck.indexable_type.classify.constantize.find(duck.indexable_id)
48
50
  end
49
51
  end
50
-
51
- def self.swap(first_index, second_index)
52
- first_index_position = first_index.indexable_position
53
- first_index.update_attribute(:indexable_position, second_index.indexable_position)
54
- second_index.update_attribute(:indexable_position, first_index_position)
55
- end
56
52
  end
data/rankle.gemspec CHANGED
@@ -19,6 +19,7 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ["lib"]
20
20
 
21
21
  spec.add_dependency "activerecord"
22
+ spec.add_dependency "ranked-model"
22
23
  spec.add_development_dependency "bundler", "~> 1.7"
23
24
  spec.add_development_dependency "rake", "~> 10.0"
24
25
  spec.add_development_dependency "sqlite3"
@@ -30,9 +30,5 @@ class BenchmarkRank < Minitest::Benchmark
30
30
  n.times { |name| Fruit.create! name: name }
31
31
  end
32
32
  end
33
-
34
- # FIXME: This unfortunate hack reaches into the internals of RankleIndex to reset the test state
35
- RankleIndex.instance_variable_set(:@rankers, {})
36
- DatabaseCleaner.clean
37
33
  end
38
34
  end
@@ -63,8 +63,5 @@ class TestNamedRanking < Minitest::Test
63
63
 
64
64
  assert_equal 0, apple.position(:produce)
65
65
  assert_equal 1, carrot.position(:produce)
66
-
67
- # FIXME: This unfortunate hack reaches into the internals of RankleIndex to reset the test state
68
- RankleIndex.instance_variable_set(:@rankers, {})
69
66
  end
70
67
  end
@@ -68,8 +68,5 @@ class TestNamedRanking < Minitest::Test
68
68
 
69
69
  assert_equal ['apple', 'banana', 'orange'], Fruit.ranked.map(&:name)
70
70
  assert_equal ['orange', 'banana', 'apple'], Fruit.ranked(:reverse).map(&:name)
71
-
72
- # FIXME: This unfortunate hack reaches into the internals of RankleIndex to reset the test state
73
- RankleIndex.instance_variable_set(:@rankers, {})
74
71
  end
75
72
  end
@@ -0,0 +1,11 @@
1
+ require_relative '../support/test_helper'
2
+
3
+ class TestGetPosition < Minitest::Test
4
+ def test_retrieving_position_makes_only_one_query
5
+ apple = Fruit.create! name: 'apple'
6
+
7
+ assert_queries(2) do
8
+ apple.position
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,33 @@
1
+ require_relative '../support/test_helper'
2
+
3
+ class TestSetPosition < Minitest::Test
4
+ def test_initializing_position_makes_six_queries
5
+ assert_queries(8) do
6
+ apple = Fruit.create! name: 'apple'
7
+ end
8
+ end
9
+
10
+ def test_update_position_makes_three_queries
11
+ apple = Fruit.create! name: 'apple'
12
+
13
+ assert_queries(4) do
14
+ apple.update_attribute :position, 1
15
+ end
16
+ end
17
+
18
+ def test_update_rank_makes_three_queries
19
+ apple = Fruit.create! name: 'apple'
20
+
21
+ assert_queries(4) do
22
+ apple.rank 1
23
+ end
24
+ end
25
+
26
+ def test_initializing_position_with_a_proc_makes_eight_queries
27
+ Fruit.send :ranks, ->(a, b) { a.name < b.name }
28
+
29
+ assert_queries(10) do
30
+ Fruit.create! name: 'apple'
31
+ end
32
+ end
33
+ end
@@ -33,8 +33,5 @@ class TestSimpleUsage < Minitest::Test
33
33
  Fruit.create! name: 'banana'
34
34
 
35
35
  assert_equal ['apple', 'banana', 'orange'], Fruit.ranked.map(&:name)
36
-
37
- # FIXME: This unfortunate hack reaches into the internals of RankleIndex to reset the test state
38
- RankleIndex.instance_variable_set(:@rankers, {})
39
36
  end
40
37
  end
@@ -6,8 +6,8 @@ require 'rails/generators'
6
6
  require 'rake'
7
7
 
8
8
  ActiveRecord::Base.establish_connection(
9
- adapter: 'sqlite3',
10
- database: 'rankle.sqlite3'
9
+ adapter: 'sqlite3',
10
+ database: 'rankle.sqlite3'
11
11
  )
12
12
 
13
13
  rake = Rake.application
@@ -32,4 +32,20 @@ class Minitest::Test
32
32
  def setup
33
33
  DatabaseCleaner.clean
34
34
  end
35
+
36
+ def teardown
37
+ # FIXME: This unfortunate hack reaches into the internals of RankleIndex to reset the test state
38
+ RankleIndex.instance_variable_set(:@rankers, {})
39
+ end
40
+ end
41
+
42
+ def assert_queries(num = 1, &block)
43
+ queries = []
44
+ callback = lambda { |name, start, finish, id, payload|
45
+ queries << payload[:sql] if payload[:sql] =~ /^SELECT|UPDATE|INSERT/
46
+ }
47
+
48
+ ActiveSupport::Notifications.subscribed(callback, "sql.active_record", &block)
49
+ ensure
50
+ assert_equal num, queries.size, "#{queries.size} instead of #{num} queries were executed.#{queries.size == 0 ? '' : "\nQueries:\n#{queries.join("\n")}"}"
35
51
  end
metadata CHANGED
@@ -1,125 +1,139 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rankle
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wil
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-06-15 00:00:00.000000000 Z
11
+ date: 2015-06-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - '>='
18
18
  - !ruby/object:Gem::Version
19
19
  version: '0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: ranked-model
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
25
39
  - !ruby/object:Gem::Version
26
40
  version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: bundler
29
43
  requirement: !ruby/object:Gem::Requirement
30
44
  requirements:
31
- - - "~>"
45
+ - - ~>
32
46
  - !ruby/object:Gem::Version
33
47
  version: '1.7'
34
48
  type: :development
35
49
  prerelease: false
36
50
  version_requirements: !ruby/object:Gem::Requirement
37
51
  requirements:
38
- - - "~>"
52
+ - - ~>
39
53
  - !ruby/object:Gem::Version
40
54
  version: '1.7'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: rake
43
57
  requirement: !ruby/object:Gem::Requirement
44
58
  requirements:
45
- - - "~>"
59
+ - - ~>
46
60
  - !ruby/object:Gem::Version
47
61
  version: '10.0'
48
62
  type: :development
49
63
  prerelease: false
50
64
  version_requirements: !ruby/object:Gem::Requirement
51
65
  requirements:
52
- - - "~>"
66
+ - - ~>
53
67
  - !ruby/object:Gem::Version
54
68
  version: '10.0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: sqlite3
57
71
  requirement: !ruby/object:Gem::Requirement
58
72
  requirements:
59
- - - ">="
73
+ - - '>='
60
74
  - !ruby/object:Gem::Version
61
75
  version: '0'
62
76
  type: :development
63
77
  prerelease: false
64
78
  version_requirements: !ruby/object:Gem::Requirement
65
79
  requirements:
66
- - - ">="
80
+ - - '>='
67
81
  - !ruby/object:Gem::Version
68
82
  version: '0'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: rspec-expectations
71
85
  requirement: !ruby/object:Gem::Requirement
72
86
  requirements:
73
- - - ">="
87
+ - - '>='
74
88
  - !ruby/object:Gem::Version
75
89
  version: '0'
76
90
  type: :development
77
91
  prerelease: false
78
92
  version_requirements: !ruby/object:Gem::Requirement
79
93
  requirements:
80
- - - ">="
94
+ - - '>='
81
95
  - !ruby/object:Gem::Version
82
96
  version: '0'
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: database_cleaner
85
99
  requirement: !ruby/object:Gem::Requirement
86
100
  requirements:
87
- - - ">="
101
+ - - '>='
88
102
  - !ruby/object:Gem::Version
89
103
  version: '0'
90
104
  type: :development
91
105
  prerelease: false
92
106
  version_requirements: !ruby/object:Gem::Requirement
93
107
  requirements:
94
- - - ">="
108
+ - - '>='
95
109
  - !ruby/object:Gem::Version
96
110
  version: '0'
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: factory_girl_rails
99
113
  requirement: !ruby/object:Gem::Requirement
100
114
  requirements:
101
- - - ">="
115
+ - - '>='
102
116
  - !ruby/object:Gem::Version
103
117
  version: '0'
104
118
  type: :development
105
119
  prerelease: false
106
120
  version_requirements: !ruby/object:Gem::Requirement
107
121
  requirements:
108
- - - ">="
122
+ - - '>='
109
123
  - !ruby/object:Gem::Version
110
124
  version: '0'
111
125
  - !ruby/object:Gem::Dependency
112
126
  name: yard
113
127
  requirement: !ruby/object:Gem::Requirement
114
128
  requirements:
115
- - - ">="
129
+ - - '>='
116
130
  - !ruby/object:Gem::Version
117
131
  version: '0'
118
132
  type: :development
119
133
  prerelease: false
120
134
  version_requirements: !ruby/object:Gem::Requirement
121
135
  requirements:
122
- - - ">="
136
+ - - '>='
123
137
  - !ruby/object:Gem::Version
124
138
  version: '0'
125
139
  description: Rankle provides multi-resource ranking.
@@ -129,7 +143,7 @@ executables: []
129
143
  extensions: []
130
144
  extra_rdoc_files: []
131
145
  files:
132
- - ".gitignore"
146
+ - .gitignore
133
147
  - Gemfile
134
148
  - LICENSE
135
149
  - LICENSE.txt
@@ -142,13 +156,14 @@ files:
142
156
  - lib/rankle/version.rb
143
157
  - lib/rankle_index.rb
144
158
  - rankle.gemspec
159
+ - test/benchmark/rank_benchmark.rb
145
160
  - test/default_behavior_test.rb
146
161
  - test/multiple_resources_test.rb
147
162
  - test/named_ranking_test.rb
148
- - test/performance/rank_benchmark.rb
163
+ - test/performance/get_position_test.rb
164
+ - test/performance/set_position_test.rb
149
165
  - test/scoped_ranking_test.rb
150
166
  - test/simple_usage_test.rb
151
- - test/spec/ranker_test.rb
152
167
  - test/spec/rankle_index_test.rb
153
168
  - test/support/models.rb
154
169
  - test/support/schema.rb
@@ -163,12 +178,12 @@ require_paths:
163
178
  - lib
164
179
  required_ruby_version: !ruby/object:Gem::Requirement
165
180
  requirements:
166
- - - ">="
181
+ - - '>='
167
182
  - !ruby/object:Gem::Version
168
183
  version: '0'
169
184
  required_rubygems_version: !ruby/object:Gem::Requirement
170
185
  requirements:
171
- - - ">="
186
+ - - '>='
172
187
  - !ruby/object:Gem::Version
173
188
  version: '0'
174
189
  requirements: []
@@ -178,13 +193,14 @@ signing_key:
178
193
  specification_version: 4
179
194
  summary: Rankle provides multi-resource ranking.
180
195
  test_files:
196
+ - test/benchmark/rank_benchmark.rb
181
197
  - test/default_behavior_test.rb
182
198
  - test/multiple_resources_test.rb
183
199
  - test/named_ranking_test.rb
184
- - test/performance/rank_benchmark.rb
200
+ - test/performance/get_position_test.rb
201
+ - test/performance/set_position_test.rb
185
202
  - test/scoped_ranking_test.rb
186
203
  - test/simple_usage_test.rb
187
- - test/spec/ranker_test.rb
188
204
  - test/spec/rankle_index_test.rb
189
205
  - test/support/models.rb
190
206
  - test/support/schema.rb
@@ -1,115 +0,0 @@
1
- require_relative '../support/test_helper'
2
-
3
- describe Rankle::Ranker do
4
- describe '.insert into' do
5
- describe 'empty array' do
6
- describe 'at position 0' do
7
- it{ assert_equal [0, []], Rankle::Ranker.insert(0, []) }
8
- end
9
-
10
- describe 'at position -100' do
11
- it{ assert_equal [0, []], Rankle::Ranker.insert(-100, []) }
12
- end
13
-
14
- describe 'at position 100' do
15
- it{ assert_equal [0, []], Rankle::Ranker.insert(100, []) }
16
- end
17
- end
18
-
19
- describe 'singleton array' do
20
- describe 'at position 0' do
21
- it{ assert_equal [-1073741824, [0]], Rankle::Ranker.insert(0, [0]) }
22
- end
23
-
24
- describe 'at position 1' do
25
- it{ assert_equal [1073741823, [0]], Rankle::Ranker.insert(1, [0]) }
26
- end
27
-
28
- describe 'at position -100' do
29
- it{ assert_equal [-1073741824, [0]], Rankle::Ranker.insert(-100, [0]) }
30
- end
31
-
32
- describe 'at position 100' do
33
- it{ assert_equal [1073741823, [0]], Rankle::Ranker.insert(100, [0]) }
34
- end
35
- end
36
-
37
- describe 'between non-adjacent elements' do
38
- it{ assert_equal [0, [-1, 1]], Rankle::Ranker.insert(1, [-1, 1]) }
39
- it{ assert_equal [0, [-1, 2]], Rankle::Ranker.insert(1, [-1, 2]) }
40
- end
41
-
42
- describe 'saturated' do
43
- before do
44
- @min_index = Rankle::Ranker::MIN_INDEX
45
- @max_index = Rankle::Ranker::MAX_INDEX
46
- Rankle::Ranker.send :remove_const, :MIN_INDEX
47
- Rankle::Ranker.send :remove_const, :MAX_INDEX
48
- Rankle::Ranker::MIN_INDEX = -2
49
- Rankle::Ranker::MAX_INDEX = 2
50
- end
51
-
52
- it{ assert_raises(IndexError) { Rankle::Ranker.insert(0, [-2, -1, 0, 1, 2]) } }
53
-
54
- after do
55
- Rankle::Ranker.send :remove_const, :MIN_INDEX
56
- Rankle::Ranker.send :remove_const, :MAX_INDEX
57
- Rankle::Ranker::MIN_INDEX = @min_index
58
- Rankle::Ranker::MAX_INDEX = @max_index
59
- end
60
- end
61
-
62
- describe 'collision' do
63
- it{ assert_equal [0, [-715827883, 715827883]], Rankle::Ranker.insert(1, [-1, 0]) }
64
- it{ assert_equal [0, [-715827883, 715827883]], Rankle::Ranker.insert(1, [ 0, 1]) }
65
- end
66
-
67
- describe 'cascading collision' do
68
- it{ assert_equal [-858993459, [-1288490189, -429496729, 429496731, 1288490191]], Rankle::Ranker.insert(1, [-2, -1, 0, 1]) }
69
- it{ assert_equal [-858993459, [-1288490189, -429496729, 429496731, 1288490191]], Rankle::Ranker.insert(1, [-1, 0, 1, 2]) }
70
- end
71
-
72
- describe 'half-saturated' do
73
- before do
74
- @min_index = Rankle::Ranker::MIN_INDEX
75
- @max_index = Rankle::Ranker::MAX_INDEX
76
- Rankle::Ranker.send :remove_const, :MIN_INDEX
77
- Rankle::Ranker.send :remove_const, :MAX_INDEX
78
- Rankle::Ranker::MIN_INDEX = -2
79
- Rankle::Ranker::MAX_INDEX = 2
80
- end
81
-
82
- it{ assert_equal [-1, [-2, 0, 2]], Rankle::Ranker.insert(1, [-2, 0, 2]) }
83
- it{ assert_equal [1, [-2, 0, 2]], Rankle::Ranker.insert(2, [-2, 0, 2]) }
84
-
85
- after do
86
- Rankle::Ranker.send :remove_const, :MIN_INDEX
87
- Rankle::Ranker.send :remove_const, :MAX_INDEX
88
- Rankle::Ranker::MIN_INDEX = @min_index
89
- Rankle::Ranker::MAX_INDEX = @max_index
90
- end
91
- end
92
- end
93
-
94
- describe '.balance' do
95
- describe 'with default range' do
96
- before do
97
- @indices = {
98
- [-1073741824] => [-1],
99
- [1073741823] => [-1],
100
- [-1, 1] => [-715827883, 715827883]
101
- }
102
- end
103
-
104
- it do
105
- @indices.each do |indices, expected|
106
- assert_equal expected, Rankle::Ranker.balance(indices)
107
- end
108
- end
109
- end
110
-
111
- describe 'with custom range' do
112
- it{ assert_equal [3, 7], Rankle::Ranker.balance([0, 1], min_index: 0, max_index: 10) }
113
- end
114
- end
115
- end