rankle 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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