rankle 0.0.0.pre → 0.0.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 +4 -4
- data/.gitignore +3 -0
- data/README.md +57 -37
- data/Rakefile +5 -5
- data/lib/generators/rankle/install_generator.rb +1 -1
- data/lib/generators/rankle/templates/migration.rb +6 -6
- data/lib/rankle/ranker.rb +3 -12
- data/lib/rankle/version.rb +1 -1
- data/lib/rankle.rb +7 -32
- data/lib/rankle_index.rb +57 -0
- data/rankle.gemspec +0 -1
- data/test/default_behavior_test.rb +22 -0
- data/test/multiple_resources_test.rb +70 -0
- data/test/named_ranking_test.rb +75 -0
- data/test/scoped_ranking_test.rb +14 -0
- data/test/simple_usage_test.rb +40 -0
- data/test/support/models.rb +9 -0
- data/test/support/schema.rb +15 -0
- data/test/support/test_helper.rb +37 -0
- metadata +21 -46
- data/features/default_ranking.feature +0 -20
- data/features/get_position.feature +0 -19
- data/features/multi-resource_ranking.feature +0 -37
- data/features/named_ranking.feature +0 -49
- data/features/ranking.feature +0 -24
- data/features/set_position.feature +0 -41
- data/features/step_definitions/fruit_steps.rb +0 -29
- data/features/step_definitions/model_steps.rb +0 -101
- data/features/step_definitions/position_steps.rb +0 -24
- data/features/support/env.rb +0 -15
- data/features/support/factories/fruit.rb +0 -16
- data/features/support/factories/vegetable.rb +0 -36
- data/features/support/models.rb +0 -19
- data/features/support/schema.rb +0 -37
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 93e91d72d1ae47471f1be951fdf49bea1b5de6c0
|
4
|
+
data.tar.gz: e6cee95ce83201559b16638e08eefb73b1cf89ba
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 196913721f0a56bd267fc1f1b17c13e8e4a3dde6251fb22c3a6ffecc4896bb510a9d24ecceb73712d53f50e5a26ed52b699a1fb988a392949a0813020da59f00
|
7
|
+
data.tar.gz: e3919e144ebd2a1d86f4a42a0105c7bd946567e6caef4061e7d2bd75fb29266c50ed0b4fc3065ee10bb1d6766a380a3300578cdf41893fb58609f54f1c41b79d
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -1,10 +1,7 @@
|
|
1
|
-
# Rankle
|
1
|
+
# Rankle
|
2
2
|
|
3
3
|
Rankle provides multi-resource ranking. It uses a separate join table rather than a resource specific position column.
|
4
4
|
|
5
|
-
** Rankle is currently in a pre-release state. It is not optimized for performance and should not be used in
|
6
|
-
production applications. Future work will be tracked with issues. **
|
7
|
-
|
8
5
|
## Installation
|
9
6
|
|
10
7
|
Add this line to your application's Gemfile:
|
@@ -55,8 +52,8 @@ orange.position # 1
|
|
55
52
|
The ranked method provides an ordered ActiveRecord::Relation:
|
56
53
|
|
57
54
|
```ruby
|
58
|
-
Fruit.create! name
|
59
|
-
Fruit.create! name
|
55
|
+
Fruit.create! name: 'apple'
|
56
|
+
Fruit.create! name: 'orange'
|
60
57
|
|
61
58
|
Fruit.ranked.map(&:name) # ['apple', 'orange']
|
62
59
|
```
|
@@ -104,43 +101,43 @@ Fruit.ranked.map(&:name) # ['apple', 'banana', 'orange']
|
|
104
101
|
Passing a symbol to the rank method with a position will update the position to that named rank:
|
105
102
|
|
106
103
|
```ruby
|
107
|
-
|
108
|
-
|
104
|
+
apple = Fruit.create! name: 'apple'
|
105
|
+
orange = Fruit.create! name: 'orange'
|
109
106
|
|
110
|
-
|
111
|
-
|
107
|
+
apple.rank :reverse, 1
|
108
|
+
orange.rank :reverse, 0
|
112
109
|
|
113
|
-
|
114
|
-
|
110
|
+
apple.position # 0
|
111
|
+
orange.position # 1
|
115
112
|
|
116
|
-
|
117
|
-
|
113
|
+
apple.position :reverse # 1
|
114
|
+
orange.position :reverse # 0
|
118
115
|
|
119
|
-
|
120
|
-
|
116
|
+
Fruit.ranked.map(&:name) # ['apple', 'orange']
|
117
|
+
Fruit.ranked(:reverse).map(&:name) # ['orange', 'apple']
|
121
118
|
```
|
122
119
|
|
123
120
|
Since positions are not stored with an absolute value, the available positions increases by 1 with each call to the rank method:
|
124
121
|
|
125
122
|
```ruby
|
126
|
-
|
127
|
-
|
128
|
-
|
123
|
+
apple = Fruit.create! name: 'apple'
|
124
|
+
banana = Fruit.create! name: 'banana'
|
125
|
+
orange = Fruit.create! name: 'orange'
|
129
126
|
|
130
|
-
|
131
|
-
|
132
|
-
|
127
|
+
apple.rank :reverse, 2 # [apple]
|
128
|
+
banana.rank :reverse, 1 # [banana, apple]
|
129
|
+
orange.rank :reverse, 0 # [orange, banana, apple]
|
133
130
|
|
134
|
-
|
135
|
-
|
136
|
-
|
131
|
+
apple.position # 0
|
132
|
+
banana.position # 1
|
133
|
+
orange.position # 2
|
137
134
|
|
138
|
-
|
139
|
-
|
140
|
-
|
135
|
+
apple.position :reverse # 1
|
136
|
+
banana.position :reverse # 2
|
137
|
+
orange.position :reverse # 0
|
141
138
|
|
142
|
-
|
143
|
-
|
139
|
+
Fruit.ranked.map(&:name) # ['apple', 'banana', 'orange']
|
140
|
+
Fruit.ranked(:reverse).map(&:name) # ['orange', 'apple', 'banana']
|
144
141
|
```
|
145
142
|
|
146
143
|
You can bypass this issue by registering the ranking on the class:
|
@@ -150,9 +147,9 @@ class Fruit < ActiveRecord::Base
|
|
150
147
|
ranks :reverse
|
151
148
|
end
|
152
149
|
|
153
|
-
apple = Fruit.create!
|
154
|
-
banana = Fruit.create!
|
155
|
-
orange = Fruit.create!
|
150
|
+
apple = Fruit.create! name: 'apple'
|
151
|
+
banana = Fruit.create! name: 'banana'
|
152
|
+
orange = Fruit.create! name: 'orange'
|
156
153
|
|
157
154
|
apple.position # 0
|
158
155
|
banana.position # 1
|
@@ -189,8 +186,8 @@ end
|
|
189
186
|
class Vegetable < ActiveRecord::Base
|
190
187
|
end
|
191
188
|
|
192
|
-
apple = Fruit.create!
|
193
|
-
carrot = Vegetable.create!
|
189
|
+
apple = Fruit.create! name: 'apple'
|
190
|
+
carrot = Vegetable.create! name: 'carrot'
|
194
191
|
|
195
192
|
apple.rank :produce, 0
|
196
193
|
carrot.rank :produce, 1
|
@@ -227,8 +224,8 @@ Class Vegetable
|
|
227
224
|
ranks :produce
|
228
225
|
end
|
229
226
|
|
230
|
-
apple
|
231
|
-
carrot = Vegetable.create!
|
227
|
+
apple = Fruit.create! name: 'apple'
|
228
|
+
carrot = Vegetable.create! name: 'vegetable'
|
232
229
|
|
233
230
|
apple.position # 0
|
234
231
|
carrot.position # 0
|
@@ -237,6 +234,29 @@ apple.position :produce # 0
|
|
237
234
|
carrot.position :produce # 1
|
238
235
|
```
|
239
236
|
|
237
|
+
## Scoped Ranking
|
238
|
+
|
239
|
+
ActiveRecord scopes work in conjunction to further restrict the ranking:
|
240
|
+
|
241
|
+
```ruby
|
242
|
+
class Fruit
|
243
|
+
scope :berries, -> { where "name LIKE ?", '%berry' }
|
244
|
+
end
|
245
|
+
|
246
|
+
Fruit.create! name: 'apple'
|
247
|
+
Fruit.create! name: 'apricot'
|
248
|
+
Fruit.create! name: 'banana'
|
249
|
+
Fruit.create! name: 'bilberry'
|
250
|
+
Fruit.create! name: 'blackberry'
|
251
|
+
Fruit.create! name: 'blackcurrant'
|
252
|
+
Fruit.create! name: 'blueberry'
|
253
|
+
Fruit.create! name: 'boysenberry'
|
254
|
+
Fruit.create! name: 'cantaloupe'
|
255
|
+
|
256
|
+
Fruit.ranked.map(&:name) # ['apple', 'apricot', 'banana', 'bilberry', 'blackberry', 'blackcurrant', 'blueberry', 'boysenberry', 'cantaloupe']
|
257
|
+
Fruit.berries.ranked.map(&:name) # ['bilberry', 'blackberry', 'blueberry', 'boysenberry']
|
258
|
+
```
|
259
|
+
|
240
260
|
## Contributing
|
241
261
|
|
242
262
|
1. Fork it ( https://github.com/[my-github-username]/rankle/fork )
|
data/Rakefile
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
require 'bundler/gem_tasks'
|
2
|
-
require 'cucumber'
|
3
|
-
require 'cucumber/rake/task'
|
4
2
|
require 'yard'
|
3
|
+
require 'rake/testtask'
|
5
4
|
|
6
|
-
|
7
|
-
t.
|
5
|
+
Rake::TestTask.new do |t|
|
6
|
+
t.test_files = FileList['test/*_test.rb']
|
7
|
+
t.verbose = true
|
8
8
|
end
|
9
9
|
|
10
10
|
YARD::Rake::YardocTask.new do |t|
|
11
11
|
t.files = ['lib/rankle.rb']
|
12
12
|
end
|
13
13
|
|
14
|
-
task :default => [:
|
14
|
+
task :default => [:test, :yard]
|
@@ -7,7 +7,7 @@ module Rankle
|
|
7
7
|
source_root File.expand_path('../templates/', __FILE__)
|
8
8
|
|
9
9
|
def generate_migration
|
10
|
-
migration_template 'migration.rb', 'db/migrate/
|
10
|
+
migration_template 'migration.rb', 'db/migrate/create_rankle_indices.rb'
|
11
11
|
end
|
12
12
|
|
13
13
|
def self.next_migration_number(dir)
|
@@ -1,6 +1,6 @@
|
|
1
|
-
class
|
1
|
+
class CreateRankleIndices < ActiveRecord::Migration
|
2
2
|
def change
|
3
|
-
create_table
|
3
|
+
create_table :rankle_indices do |t|
|
4
4
|
t.string :indexable_name
|
5
5
|
t.integer :indexable_id
|
6
6
|
t.string :indexable_type
|
@@ -9,9 +9,9 @@ class CreateRankleIndex < ActiveRecord::Migration
|
|
9
9
|
t.timestamps null: false
|
10
10
|
end
|
11
11
|
|
12
|
-
add_index :
|
13
|
-
add_index :
|
14
|
-
add_index :
|
15
|
-
add_index :
|
12
|
+
add_index :rankle_indices, :indexable_name
|
13
|
+
add_index :rankle_indices, :indexable_id
|
14
|
+
add_index :rankle_indices, :indexable_type
|
15
|
+
add_index :rankle_indices, :indexable_position
|
16
16
|
end
|
17
17
|
end
|
data/lib/rankle/ranker.rb
CHANGED
@@ -1,18 +1,9 @@
|
|
1
1
|
module Rankle
|
2
2
|
class Ranker
|
3
|
-
|
4
|
-
@rankers ||= {}
|
5
|
-
@rankers[klass] = proc
|
6
|
-
end
|
7
|
-
|
8
|
-
def self.get klass
|
9
|
-
@rankers[klass] rescue nil
|
10
|
-
end
|
3
|
+
attr_accessor :strategy
|
11
4
|
|
12
|
-
def
|
13
|
-
|
14
|
-
first_index.update_attribute(:indexable_position, second_index.indexable_position)
|
15
|
-
second_index.update_attribute(:indexable_position, first_index_position)
|
5
|
+
def initialize strategy
|
6
|
+
@strategy = strategy
|
16
7
|
end
|
17
8
|
end
|
18
9
|
end
|
data/lib/rankle/version.rb
CHANGED
data/lib/rankle.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'active_record'
|
2
|
+
require 'rankle_index'
|
2
3
|
require 'rankle/ranker'
|
3
4
|
require 'rankle/version'
|
4
5
|
|
@@ -22,28 +23,19 @@ module Rankle
|
|
22
23
|
end
|
23
24
|
end
|
24
25
|
|
25
|
-
def ranks
|
26
|
-
|
26
|
+
def ranks strategy
|
27
|
+
RankleIndex.ranks self, Ranker.new(strategy)
|
27
28
|
end
|
28
29
|
end
|
29
30
|
|
30
31
|
def self.ranked name = :default
|
31
|
-
RankleIndex.
|
32
|
-
duck.indexable_type.classify.constantize.find(duck.indexable_id)
|
33
|
-
end
|
32
|
+
RankleIndex.ranked name
|
34
33
|
end
|
35
34
|
|
36
35
|
# instance methods added to ActiveRecord models
|
37
36
|
module InstanceMethods
|
38
37
|
def set_default_position
|
39
|
-
|
40
|
-
position = self.class.ranked.each_with_index { |record, index| break index if Ranker.get(self.class).call(self, record) }
|
41
|
-
end unless Ranker.get(self.class).is_a?(Symbol)
|
42
|
-
position = self.class.count - 1 if position.nil? || position.is_a?(Array)
|
43
|
-
rank position
|
44
|
-
if Ranker.get(self.class).is_a?(Symbol)
|
45
|
-
rank Ranker.get(self.class), RankleIndex.where(indexable_name: Ranker.get(self.class)).count
|
46
|
-
end
|
38
|
+
RankleIndex.set_default_position self
|
47
39
|
end
|
48
40
|
|
49
41
|
# Assigns an explicit position to the record
|
@@ -55,28 +47,11 @@ module Rankle
|
|
55
47
|
end
|
56
48
|
|
57
49
|
def rank name = :default, position
|
58
|
-
|
59
|
-
rankle_index_length = if name == :default
|
60
|
-
RankleIndex.where(indexable_name: name.to_s, indexable_type: self.class).count
|
61
|
-
else
|
62
|
-
RankleIndex.where(indexable_name: name.to_s).count
|
63
|
-
end
|
64
|
-
position = 0 if position < 0
|
65
|
-
position = rankle_index_length - 1 if position >= rankle_index_length
|
66
|
-
rankle_index.update_attribute(:indexable_position, rankle_index_length - 1) unless rankle_index.indexable_position
|
67
|
-
swap_distance = -1
|
68
|
-
swap_distance *= -1 if rankle_index.indexable_position < position
|
69
|
-
until rankle_index.indexable_position == position
|
70
|
-
if name == :default
|
71
|
-
Ranker.swap(rankle_index, RankleIndex.where(indexable_name: name.to_s, indexable_type: self.class, indexable_position: rankle_index.indexable_position + swap_distance).first)
|
72
|
-
else
|
73
|
-
Ranker.swap(rankle_index, RankleIndex.where(indexable_name: name.to_s, indexable_position: rankle_index.indexable_position + swap_distance).first)
|
74
|
-
end
|
75
|
-
end
|
50
|
+
RankleIndex.rank self, name, position
|
76
51
|
end
|
77
52
|
|
78
53
|
def position name = :default
|
79
|
-
RankleIndex.
|
54
|
+
RankleIndex.position self, name
|
80
55
|
end
|
81
56
|
end
|
82
57
|
end
|
data/lib/rankle_index.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
class RankleIndex < ActiveRecord::Base
|
2
|
+
belongs_to :indexable, polymorphic: true
|
3
|
+
|
4
|
+
@rankers = {}
|
5
|
+
|
6
|
+
def self.ranks klass, ranker
|
7
|
+
@rankers[klass] = ranker
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.set_default_position instance
|
11
|
+
if @rankers[instance.class] && @rankers[instance.class].strategy
|
12
|
+
position = instance.class.ranked.each_with_index { |record, index| break index if @rankers[instance.class].strategy.call(instance, record) } unless @rankers[instance.class].strategy.is_a?(Symbol)
|
13
|
+
end
|
14
|
+
position = instance.class.count - 1 if position.nil? || position.is_a?(Array)
|
15
|
+
instance.rank position
|
16
|
+
if @rankers[instance.class] && @rankers[instance.class].strategy.is_a?(Symbol)
|
17
|
+
instance.rank @rankers[instance.class].strategy, RankleIndex.where(indexable_name: @rankers[instance.class].strategy).count
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.rank instance, name, position
|
22
|
+
rankle_index = RankleIndex.where(indexable_name: name.to_s, indexable_id: instance.id, indexable_type: instance.class).first_or_create!
|
23
|
+
rankle_index_length = if name == :default
|
24
|
+
RankleIndex.where(indexable_name: name.to_s, indexable_type: instance.class).count
|
25
|
+
else
|
26
|
+
RankleIndex.where(indexable_name: name.to_s).count
|
27
|
+
end
|
28
|
+
position = 0 if position < 0
|
29
|
+
position = rankle_index_length - 1 if position >= rankle_index_length
|
30
|
+
rankle_index.update_attribute(:indexable_position, rankle_index_length - 1) unless rankle_index.indexable_position
|
31
|
+
swap_distance = -1
|
32
|
+
swap_distance *= -1 if rankle_index.indexable_position < position
|
33
|
+
until rankle_index.indexable_position == position
|
34
|
+
if name == :default
|
35
|
+
swap(rankle_index, RankleIndex.where(indexable_name: name.to_s, indexable_type: instance.class, indexable_position: rankle_index.indexable_position + swap_distance).first)
|
36
|
+
else
|
37
|
+
swap(rankle_index, RankleIndex.where(indexable_name: name.to_s, indexable_position: rankle_index.indexable_position + swap_distance).first)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.position instance, name
|
43
|
+
where(indexable_name: name.to_s, indexable_id: instance.id, indexable_type: instance.class).first_or_create!.indexable_position
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.ranked name
|
47
|
+
where(indexable_name: name).order(:indexable_position).map do |duck|
|
48
|
+
duck.indexable_type.classify.constantize.find(duck.indexable_id)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.swap(first_index, second_index)
|
53
|
+
first_index_position = first_index.indexable_position
|
54
|
+
first_index.update_attribute(:indexable_position, second_index.indexable_position)
|
55
|
+
second_index.update_attribute(:indexable_position, first_index_position)
|
56
|
+
end
|
57
|
+
end
|
data/rankle.gemspec
CHANGED
@@ -21,7 +21,6 @@ Gem::Specification.new do |spec|
|
|
21
21
|
spec.add_dependency "activerecord"
|
22
22
|
spec.add_development_dependency "bundler", "~> 1.7"
|
23
23
|
spec.add_development_dependency "rake", "~> 10.0"
|
24
|
-
spec.add_development_dependency "cucumber"
|
25
24
|
spec.add_development_dependency "sqlite3"
|
26
25
|
spec.add_development_dependency "rspec-expectations"
|
27
26
|
spec.add_development_dependency "database_cleaner"
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require_relative './support/test_helper'
|
2
|
+
|
3
|
+
class TestDefaultBehavior < Minitest::Test
|
4
|
+
def test_it_is_ineffectual
|
5
|
+
assert(Fruit.all.to_a == Fruit.ranked.to_a)
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_it_sets_position
|
9
|
+
apple = Fruit.create!
|
10
|
+
orange = Fruit.create!
|
11
|
+
|
12
|
+
assert_equal 0, apple.position
|
13
|
+
assert_equal 1, orange.position
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_it_ranks_on_insert_order
|
17
|
+
Fruit.create! name: 'apple'
|
18
|
+
Fruit.create! name: 'orange'
|
19
|
+
|
20
|
+
assert_equal ['apple', 'orange'], Fruit.ranked.map(&:name)
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require_relative './support/test_helper'
|
2
|
+
|
3
|
+
class TestNamedRanking < Minitest::Test
|
4
|
+
def test_it_ranks_multiple_resources_together
|
5
|
+
apple = Fruit.create! name: 'apple'
|
6
|
+
carrot = Vegetable.create! name: 'carrot'
|
7
|
+
|
8
|
+
apple.rank :produce, 0
|
9
|
+
carrot.rank :produce, 1
|
10
|
+
|
11
|
+
assert_equal 0, apple.position
|
12
|
+
assert_equal 0, carrot.position
|
13
|
+
|
14
|
+
assert_equal 0, apple.position(:produce)
|
15
|
+
assert_equal 1, carrot.position(:produce)
|
16
|
+
|
17
|
+
assert_equal ['apple'], Fruit.ranked.map(&:name)
|
18
|
+
assert_equal ['carrot'], Vegetable.ranked.map(&:name)
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_it_does_not_expand_scope
|
22
|
+
apple = Fruit.create! name: 'apple'
|
23
|
+
carrot = Vegetable.create! name: 'carrot'
|
24
|
+
|
25
|
+
apple.rank :produce, 0
|
26
|
+
carrot.rank :produce, 1
|
27
|
+
|
28
|
+
assert_equal 0, apple.position
|
29
|
+
assert_equal 0, carrot.position
|
30
|
+
|
31
|
+
assert_equal 0, apple.position(:produce)
|
32
|
+
assert_equal 1, carrot.position(:produce)
|
33
|
+
|
34
|
+
assert_equal ['apple'], Fruit.ranked(:produce).map(&:name)
|
35
|
+
assert_equal ['carrot'], Vegetable.ranked(:produce).map(&:name)
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_it_can_access_the_global_scope
|
39
|
+
apple = Fruit.create! name: 'apple'
|
40
|
+
carrot = Vegetable.create! name: 'carrot'
|
41
|
+
|
42
|
+
apple.rank :produce, 0
|
43
|
+
carrot.rank :produce, 1
|
44
|
+
|
45
|
+
assert_equal 0, apple.position
|
46
|
+
assert_equal 0, carrot.position
|
47
|
+
|
48
|
+
assert_equal 0, apple.position(:produce)
|
49
|
+
assert_equal 1, carrot.position(:produce)
|
50
|
+
|
51
|
+
assert_equal ['apple', 'carrot'], Rankle.ranked(:produce).map(&:name)
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_it_will_initialize_named_rankings_across_multiple_resources
|
55
|
+
Fruit.send :ranks, :produce
|
56
|
+
Vegetable.send :ranks, :produce
|
57
|
+
|
58
|
+
apple = Fruit.create! name: 'apple'
|
59
|
+
carrot = Vegetable.create! name: 'carrot'
|
60
|
+
|
61
|
+
assert_equal 0, apple.position
|
62
|
+
assert_equal 0, carrot.position
|
63
|
+
|
64
|
+
assert_equal 0, apple.position(:produce)
|
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
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require_relative './support/test_helper'
|
2
|
+
|
3
|
+
class TestNamedRanking < Minitest::Test
|
4
|
+
def test_it_sets_the_position_for_the_named_ranking
|
5
|
+
apple = Fruit.create! name: 'apple'
|
6
|
+
orange = Fruit.create! name: 'orange'
|
7
|
+
|
8
|
+
apple.rank :reverse, 1
|
9
|
+
orange.rank :reverse, 0
|
10
|
+
|
11
|
+
assert_equal 0, apple.position
|
12
|
+
assert_equal 1, orange.position
|
13
|
+
|
14
|
+
assert_equal 1, apple.position(:reverse)
|
15
|
+
assert_equal 0, orange.position(:reverse)
|
16
|
+
|
17
|
+
assert_equal ['apple', 'orange'], Fruit.ranked.map(&:name)
|
18
|
+
assert_equal ['orange', 'apple'], Fruit.ranked(:reverse).map(&:name)
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_it_does_not_initialize_named_rankings
|
22
|
+
apple = Fruit.create! name: 'apple'
|
23
|
+
banana = Fruit.create! name: 'banana'
|
24
|
+
orange = Fruit.create! name: 'orange'
|
25
|
+
|
26
|
+
apple.rank :reverse, 2
|
27
|
+
banana.rank :reverse, 1
|
28
|
+
orange.rank :reverse, 0
|
29
|
+
|
30
|
+
assert_equal 0, apple.position
|
31
|
+
assert_equal 1, banana.position
|
32
|
+
assert_equal 2, orange.position
|
33
|
+
|
34
|
+
assert_equal 1, apple.position(:reverse)
|
35
|
+
assert_equal 2, banana.position(:reverse)
|
36
|
+
assert_equal 0, orange.position(:reverse)
|
37
|
+
|
38
|
+
assert_equal ['apple', 'banana', 'orange'], Fruit.ranked.map(&:name)
|
39
|
+
assert_equal ['orange', 'apple', 'banana'], Fruit.ranked(:reverse).map(&:name)
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_it_initializes_registered_named_rankings
|
43
|
+
Fruit.send :ranks, :reverse
|
44
|
+
|
45
|
+
apple = Fruit.create! name: 'apple'
|
46
|
+
banana = Fruit.create! name: 'banana'
|
47
|
+
orange = Fruit.create! name: 'orange'
|
48
|
+
|
49
|
+
assert_equal 0, apple.position
|
50
|
+
assert_equal 1, banana.position
|
51
|
+
assert_equal 2, orange.position
|
52
|
+
|
53
|
+
assert_equal 0, apple.position(:reverse)
|
54
|
+
assert_equal 1, banana.position(:reverse)
|
55
|
+
assert_equal 2, orange.position(:reverse)
|
56
|
+
|
57
|
+
apple.rank :reverse, 2
|
58
|
+
banana.rank :reverse, 1
|
59
|
+
orange.rank :reverse, 0
|
60
|
+
|
61
|
+
assert_equal 0, apple.position
|
62
|
+
assert_equal 1, banana.position
|
63
|
+
assert_equal 2, orange.position
|
64
|
+
|
65
|
+
assert_equal 2, apple.position(:reverse)
|
66
|
+
assert_equal 1, banana.position(:reverse)
|
67
|
+
assert_equal 0, orange.position(:reverse)
|
68
|
+
|
69
|
+
assert_equal ['apple', 'banana', 'orange'], Fruit.ranked.map(&:name)
|
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
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require_relative './support/test_helper'
|
2
|
+
|
3
|
+
class TestScopedRanking < Minitest::Test
|
4
|
+
def test_it_ranks_relative_to_a_scope
|
5
|
+
Fruit.class_exec { scope :berries, -> { where "name LIKE ?", '%berry' } }
|
6
|
+
|
7
|
+
['apple', 'apricot', 'banana', 'bilberry', 'blackberry', 'blackcurrant', 'blueberry', 'boysenberry', 'cantaloupe'].each do |name|
|
8
|
+
Fruit.create! name: name
|
9
|
+
end
|
10
|
+
|
11
|
+
assert_equal ['apple', 'apricot', 'banana', 'bilberry', 'blackberry', 'blackcurrant', 'blueberry', 'boysenberry', 'cantaloupe'], Fruit.ranked.map(&:name)
|
12
|
+
assert_equal ['bilberry', 'blackberry', 'blueberry', 'boysenberry'], Fruit.berries.ranked.map(&:name)
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require_relative './support/test_helper'
|
2
|
+
|
3
|
+
class TestSimpleUsage < Minitest::Test
|
4
|
+
def test_it_assigns_an_explicit_ranking
|
5
|
+
apple = Fruit.create! name: 'apple'
|
6
|
+
orange = Fruit.create! name: 'orange'
|
7
|
+
|
8
|
+
apple.update_attribute :position, 1
|
9
|
+
|
10
|
+
assert_equal 1, apple.position
|
11
|
+
assert_equal 0, orange.position
|
12
|
+
|
13
|
+
assert_equal ['orange', 'apple'], Fruit.ranked.map(&:name)
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_it_sets_position_with_the_rank_method
|
17
|
+
apple = Fruit.create! name: 'apple'
|
18
|
+
orange = Fruit.create! name: 'orange'
|
19
|
+
|
20
|
+
apple.rank 1
|
21
|
+
|
22
|
+
assert_equal 1, apple.position
|
23
|
+
assert_equal 0, orange.position
|
24
|
+
|
25
|
+
assert_equal ['orange', 'apple'], Fruit.ranked.map(&:name)
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_it_maintains_rank_with_a_proc
|
29
|
+
Fruit.send :ranks, ->(a, b) { a.name < b.name }
|
30
|
+
|
31
|
+
Fruit.create! name: 'apple'
|
32
|
+
Fruit.create! name: 'orange'
|
33
|
+
Fruit.create! name: 'banana'
|
34
|
+
|
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
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
ActiveRecord::Schema.define do
|
2
|
+
self.verbose = false
|
3
|
+
|
4
|
+
create_table :fruits, :force => true do |t|
|
5
|
+
t.string :name
|
6
|
+
|
7
|
+
t.timestamps null: false
|
8
|
+
end
|
9
|
+
|
10
|
+
create_table :vegetables, :force => true do |t|
|
11
|
+
t.string :name
|
12
|
+
|
13
|
+
t.timestamps null: false
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'minitest/pride'
|
3
|
+
require 'rankle'
|
4
|
+
require 'database_cleaner'
|
5
|
+
require 'rails/generators'
|
6
|
+
require 'rake'
|
7
|
+
|
8
|
+
ActiveRecord::Base.establish_connection(
|
9
|
+
adapter: 'sqlite3',
|
10
|
+
database: 'rankle.sqlite3'
|
11
|
+
)
|
12
|
+
|
13
|
+
rake = Rake.application
|
14
|
+
rake.init
|
15
|
+
rake.load_rakefile
|
16
|
+
Dir.entries(File.dirname(__FILE__) + '/../../db/migrate').each do |filename|
|
17
|
+
File.delete(File.dirname(__FILE__) + '/../../db/migrate/' + filename) rescue nil
|
18
|
+
end
|
19
|
+
Rails::Generators.invoke 'rankle:install'
|
20
|
+
require File.dirname(__FILE__) + '/../../db/migrate/' + Dir.entries(File.dirname(__FILE__) + '/../../db/migrate')[0]
|
21
|
+
File.delete 'rankle.sqlite3'
|
22
|
+
CreateRankleIndices.new.migrate :up
|
23
|
+
|
24
|
+
load File.dirname(__FILE__) + '/schema.rb'
|
25
|
+
load File.dirname(__FILE__) + '/models.rb'
|
26
|
+
|
27
|
+
DatabaseCleaner.strategy = :truncation
|
28
|
+
|
29
|
+
class Minitest::Test
|
30
|
+
def setup
|
31
|
+
DatabaseCleaner.start
|
32
|
+
end
|
33
|
+
|
34
|
+
def teardown
|
35
|
+
DatabaseCleaner.clean
|
36
|
+
end
|
37
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rankle
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.0
|
4
|
+
version: 0.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Wil
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-06-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -52,20 +52,6 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '10.0'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: cucumber
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - ">="
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '0'
|
62
|
-
type: :development
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - ">="
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '0'
|
69
55
|
- !ruby/object:Gem::Dependency
|
70
56
|
name: sqlite3
|
71
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -149,26 +135,21 @@ files:
|
|
149
135
|
- LICENSE.txt
|
150
136
|
- README.md
|
151
137
|
- Rakefile
|
152
|
-
- features/default_ranking.feature
|
153
|
-
- features/get_position.feature
|
154
|
-
- features/multi-resource_ranking.feature
|
155
|
-
- features/named_ranking.feature
|
156
|
-
- features/ranking.feature
|
157
|
-
- features/set_position.feature
|
158
|
-
- features/step_definitions/fruit_steps.rb
|
159
|
-
- features/step_definitions/model_steps.rb
|
160
|
-
- features/step_definitions/position_steps.rb
|
161
|
-
- features/support/env.rb
|
162
|
-
- features/support/factories/fruit.rb
|
163
|
-
- features/support/factories/vegetable.rb
|
164
|
-
- features/support/models.rb
|
165
|
-
- features/support/schema.rb
|
166
138
|
- lib/generators/rankle/install_generator.rb
|
167
139
|
- lib/generators/rankle/templates/migration.rb
|
168
140
|
- lib/rankle.rb
|
169
141
|
- lib/rankle/ranker.rb
|
170
142
|
- lib/rankle/version.rb
|
143
|
+
- lib/rankle_index.rb
|
171
144
|
- rankle.gemspec
|
145
|
+
- test/default_behavior_test.rb
|
146
|
+
- test/multiple_resources_test.rb
|
147
|
+
- test/named_ranking_test.rb
|
148
|
+
- test/scoped_ranking_test.rb
|
149
|
+
- test/simple_usage_test.rb
|
150
|
+
- test/support/models.rb
|
151
|
+
- test/support/schema.rb
|
152
|
+
- test/support/test_helper.rb
|
172
153
|
homepage: ''
|
173
154
|
licenses:
|
174
155
|
- MIT
|
@@ -184,9 +165,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
184
165
|
version: '0'
|
185
166
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
186
167
|
requirements:
|
187
|
-
- - "
|
168
|
+
- - ">="
|
188
169
|
- !ruby/object:Gem::Version
|
189
|
-
version:
|
170
|
+
version: '0'
|
190
171
|
requirements: []
|
191
172
|
rubyforge_project:
|
192
173
|
rubygems_version: 2.2.2
|
@@ -194,18 +175,12 @@ signing_key:
|
|
194
175
|
specification_version: 4
|
195
176
|
summary: Rankle provides multi-resource ranking.
|
196
177
|
test_files:
|
197
|
-
-
|
198
|
-
-
|
199
|
-
-
|
200
|
-
-
|
201
|
-
-
|
202
|
-
-
|
203
|
-
-
|
204
|
-
-
|
205
|
-
- features/step_definitions/position_steps.rb
|
206
|
-
- features/support/env.rb
|
207
|
-
- features/support/factories/fruit.rb
|
208
|
-
- features/support/factories/vegetable.rb
|
209
|
-
- features/support/models.rb
|
210
|
-
- features/support/schema.rb
|
178
|
+
- test/default_behavior_test.rb
|
179
|
+
- test/multiple_resources_test.rb
|
180
|
+
- test/named_ranking_test.rb
|
181
|
+
- test/scoped_ranking_test.rb
|
182
|
+
- test/simple_usage_test.rb
|
183
|
+
- test/support/models.rb
|
184
|
+
- test/support/schema.rb
|
185
|
+
- test/support/test_helper.rb
|
211
186
|
has_rdoc:
|
@@ -1,20 +0,0 @@
|
|
1
|
-
Feature: Default ranking
|
2
|
-
In order to not surprise new customers
|
3
|
-
As a consumer of rankle
|
4
|
-
I want the default behavior to match existing behavior
|
5
|
-
|
6
|
-
Scenario: Empty fruit model
|
7
|
-
Given an empty fruit model
|
8
|
-
Then ranking all has no effect
|
9
|
-
And the ranked fruit array is []
|
10
|
-
|
11
|
-
Scenario: Fruit model with several fruits
|
12
|
-
Given several fruits
|
13
|
-
Then ranking all has no effect
|
14
|
-
And the ranked fruit array is [Apple, Apricot, Banana, Bilberry, Blackberry, Blackcurrant, Blueberry, Boysenberry, Cantaloupe, Currant]
|
15
|
-
|
16
|
-
Scenario: Ranked fruit
|
17
|
-
Given an empty fruit model
|
18
|
-
And an apple
|
19
|
-
And an orange
|
20
|
-
Then the ranked fruit array is [apple, orange]
|
@@ -1,19 +0,0 @@
|
|
1
|
-
Feature: Get position
|
2
|
-
In order to inspect a ranking
|
3
|
-
As a developer
|
4
|
-
I want to retrieve an element's position
|
5
|
-
|
6
|
-
Scenario: Default ranking
|
7
|
-
Given an apple
|
8
|
-
And an orange
|
9
|
-
Then the apple is in position 0
|
10
|
-
And the orange is in position 1
|
11
|
-
And the ranked fruit array is [apple, orange]
|
12
|
-
|
13
|
-
Scenario: Custom ranking
|
14
|
-
Given an apple
|
15
|
-
And an orange
|
16
|
-
When I assign the apple's position to 1
|
17
|
-
Then the apple is in position 1
|
18
|
-
And the orange is in position 0
|
19
|
-
And the ranked fruit array is [orange, apple]
|
@@ -1,37 +0,0 @@
|
|
1
|
-
Feature: Multi-resource ranking
|
2
|
-
In order to rank multiple resources
|
3
|
-
As a consumer of rankle
|
4
|
-
I want to create a multi-resource ranking
|
5
|
-
|
6
|
-
Scenario: Basic ranking
|
7
|
-
Given a 'fruit' model
|
8
|
-
And a 'vegetable' model
|
9
|
-
And an 'apple' fruit
|
10
|
-
And a 'carrot' vegetable
|
11
|
-
When I assign the 'apple' fruit's 'produce' rank to '0'
|
12
|
-
And I assign the 'carrot' vegetable's 'produce' rank to '1'
|
13
|
-
Then the 'apple' fruit's 'default' rank is '0'
|
14
|
-
And the 'carrot' vegetable's 'default' rank is '0'
|
15
|
-
And the 'apple' fruit's 'produce' rank is '0'
|
16
|
-
And the 'carrot' vegetable's 'produce' rank is '1'
|
17
|
-
And the default ranked fruit array is [apple]
|
18
|
-
And the default ranked vegetable array is [carrot]
|
19
|
-
And the produce ranked fruit array is [apple]
|
20
|
-
And the produce ranked vegetable array is [carrot]
|
21
|
-
And the produce ranked rankle array is [apple, carrot]
|
22
|
-
|
23
|
-
|
24
|
-
Scenario: Default ranking
|
25
|
-
Given a 'fruit' model with a 'produce' ranking
|
26
|
-
And a 'vegetable' model with a 'produce' ranking
|
27
|
-
And an 'apple' fruit
|
28
|
-
And a 'carrot' vegetable
|
29
|
-
Then the 'apple' fruit's 'default' rank is '0'
|
30
|
-
And the 'carrot' vegetable's 'default' rank is '0'
|
31
|
-
And the 'apple' fruit's 'produce' rank is '0'
|
32
|
-
And the 'carrot' vegetable's 'produce' rank is '1'
|
33
|
-
And the default ranked fruit array is [apple]
|
34
|
-
And the default ranked vegetable array is [carrot]
|
35
|
-
And the produce ranked fruit array is [apple]
|
36
|
-
And the produce ranked vegetable array is [carrot]
|
37
|
-
And the produce ranked rankle array is [apple, carrot]
|
@@ -1,49 +0,0 @@
|
|
1
|
-
Feature: Named ranking
|
2
|
-
In order to maintain multiple rankings on a single class
|
3
|
-
As a developer
|
4
|
-
I want to create named rankings
|
5
|
-
|
6
|
-
Scenario: Reverse ranking
|
7
|
-
Given an apple
|
8
|
-
And an orange
|
9
|
-
When I assign the 'apple' fruit's 'reverse' rank to '1'
|
10
|
-
When I assign the 'orange' fruit's 'reverse' rank to '0'
|
11
|
-
Then the 'apple' fruit's 'default' rank is '0'
|
12
|
-
And the 'orange' fruit's 'default' rank is '1'
|
13
|
-
And the 'apple' fruit's 'reverse' rank is '1'
|
14
|
-
And the 'orange' fruit's 'reverse' rank is '0'
|
15
|
-
And the default ranked fruit array is [apple, orange]
|
16
|
-
And the reverse ranked fruit array is [orange, apple]
|
17
|
-
|
18
|
-
Scenario: Growing ranking
|
19
|
-
Given an apple
|
20
|
-
And a banana
|
21
|
-
And an orange
|
22
|
-
When I assign the 'apple' fruit's 'reverse' rank to '2'
|
23
|
-
And I assign the 'banana' fruit's 'reverse' rank to '1'
|
24
|
-
And I assign the 'orange' fruit's 'reverse' rank to '0'
|
25
|
-
Then the 'apple' fruit's 'default' rank is '0'
|
26
|
-
And the 'banana' fruit's 'default' rank is '1'
|
27
|
-
And the 'orange' fruit's 'default' rank is '2'
|
28
|
-
And the 'apple' fruit's 'reverse' rank is '1'
|
29
|
-
And the 'banana' fruit's 'reverse' rank is '2'
|
30
|
-
And the 'orange' fruit's 'reverse' rank is '0'
|
31
|
-
And the default ranked fruit array is [apple, banana, orange]
|
32
|
-
And the reverse ranked fruit array is [orange, apple, banana]
|
33
|
-
|
34
|
-
Scenario: Registered ranking
|
35
|
-
Given a 'fruit' class with a 'reverse' ranking
|
36
|
-
And an apple
|
37
|
-
And a banana
|
38
|
-
And an orange
|
39
|
-
When I assign the 'apple' fruit's 'reverse' rank to '2'
|
40
|
-
And I assign the 'banana' fruit's 'reverse' rank to '1'
|
41
|
-
And I assign the 'orange' fruit's 'reverse' rank to '0'
|
42
|
-
Then the 'apple' fruit's 'default' rank is '0'
|
43
|
-
And the 'banana' fruit's 'default' rank is '1'
|
44
|
-
And the 'orange' fruit's 'default' rank is '2'
|
45
|
-
And the 'apple' fruit's 'reverse' rank is '2'
|
46
|
-
And the 'banana' fruit's 'reverse' rank is '1'
|
47
|
-
And the 'orange' fruit's 'reverse' rank is '0'
|
48
|
-
And the default ranked fruit array is [apple, banana, orange]
|
49
|
-
And the reverse ranked fruit array is [orange, banana, apple]
|
data/features/ranking.feature
DELETED
@@ -1,24 +0,0 @@
|
|
1
|
-
Feature: Ranks
|
2
|
-
In order to provide an explicit ordering
|
3
|
-
As a developer
|
4
|
-
I want to rank on position
|
5
|
-
|
6
|
-
Scenario: Reverse ranking
|
7
|
-
Given 10 rows
|
8
|
-
When I rank them in reverse order
|
9
|
-
Then ranking is equivalent to all reversed
|
10
|
-
|
11
|
-
Scenario: Update ranking
|
12
|
-
Given 10 rows in default order
|
13
|
-
When I move row 9 to row 0
|
14
|
-
Then ranking is equivalent to all rotated -1
|
15
|
-
|
16
|
-
Scenario: Negative rank
|
17
|
-
Given 10 rows in default order
|
18
|
-
When I move row 9 to row -10
|
19
|
-
Then row 9 is in position 0
|
20
|
-
|
21
|
-
Scenario: Out-of-bounds rank
|
22
|
-
Given 10 rows in default order
|
23
|
-
When I move row 0 to row 30
|
24
|
-
Then row 0 is in position 9
|
@@ -1,41 +0,0 @@
|
|
1
|
-
Feature: Set position
|
2
|
-
In order to assign a ranking
|
3
|
-
As a developer
|
4
|
-
I want to set an element's position
|
5
|
-
|
6
|
-
Scenario: Update position attribute
|
7
|
-
Given an empty fruit model
|
8
|
-
And an 'apple' fruit
|
9
|
-
And an 'orange' fruit
|
10
|
-
When I update the apple's position attribute to 1
|
11
|
-
Then the apple is in position 1
|
12
|
-
And the orange is in position 0
|
13
|
-
Then the ranked fruit array is [orange, apple]
|
14
|
-
|
15
|
-
Scenario: Update position attribute with rank method
|
16
|
-
Given an apple
|
17
|
-
And an orange
|
18
|
-
When I assign the apple's rank to 1
|
19
|
-
Then the apple is in position 1
|
20
|
-
And the orange is in position 0
|
21
|
-
Then the ranked fruit array is [orange, apple]
|
22
|
-
|
23
|
-
|
24
|
-
Scenario: Override default with stabby proc
|
25
|
-
Given a fruit class with a reverse alphabetical default ranking on name
|
26
|
-
And an apple
|
27
|
-
And an orange
|
28
|
-
Then the apple is in position 1
|
29
|
-
And the orange is in position 0
|
30
|
-
Then the ranked fruit array is [orange, apple]
|
31
|
-
|
32
|
-
|
33
|
-
Scenario: Override default with stabby proc (documentation)
|
34
|
-
Given a fruit class with an alphabetical default ranking on name
|
35
|
-
And an apple
|
36
|
-
And an orange
|
37
|
-
And a banana
|
38
|
-
Then the apple is in position 0
|
39
|
-
And the banana is in position 1
|
40
|
-
And the orange is in position 2
|
41
|
-
Then the ranked fruit array is [apple, banana, orange]
|
@@ -1,29 +0,0 @@
|
|
1
|
-
Given(/^an apple$/) do
|
2
|
-
DatabaseCleaner.clean
|
3
|
-
@fruit ||= {}
|
4
|
-
@fruit[:apple] = create :fruit, name: 'apple'
|
5
|
-
end
|
6
|
-
|
7
|
-
Given(/^an orange$/) do
|
8
|
-
@fruit[:orange] = create :fruit, name: 'orange'
|
9
|
-
end
|
10
|
-
|
11
|
-
Given(/^a banana$/) do
|
12
|
-
@fruit[:banana] = create :fruit, name: 'banana'
|
13
|
-
end
|
14
|
-
|
15
|
-
Then(/^the apple is in position (\d+)$/) do |position|
|
16
|
-
expect(@fruit[:apple].position).to eq(position.to_i)
|
17
|
-
end
|
18
|
-
|
19
|
-
Then(/^the orange is in position (\d+)$/) do |position|
|
20
|
-
expect(@fruit[:orange].position).to eq(position.to_i)
|
21
|
-
end
|
22
|
-
|
23
|
-
Then(/^the banana is in position (\d+)$/) do |position|
|
24
|
-
expect(@fruit[:banana].position).to eq(position.to_i)
|
25
|
-
end
|
26
|
-
|
27
|
-
When(/^I assign the apple's position to (\d+)$/) do |position|
|
28
|
-
@fruit[:apple].update_attribute(:position, position.to_i)
|
29
|
-
end
|
@@ -1,101 +0,0 @@
|
|
1
|
-
Given(/^a '(.*)' model$/) do |name|
|
2
|
-
DatabaseCleaner.clean
|
3
|
-
name.classify.constantize.delete_all
|
4
|
-
end
|
5
|
-
|
6
|
-
Given(/^a '(.*)' model with a '(.*)' ranking$/) do |klass, ranking|
|
7
|
-
step "a '#{klass}' model"
|
8
|
-
klass.classify.constantize.send :ranks, ranking.to_sym
|
9
|
-
end
|
10
|
-
|
11
|
-
|
12
|
-
Given(/^an '(.*)' fruit$/) do |name|
|
13
|
-
@fruit ||= {}
|
14
|
-
@fruit[name.to_sym] = create :fruit, name: name
|
15
|
-
end
|
16
|
-
|
17
|
-
Given(/^a '(.*)' vegetable$/) do |name|
|
18
|
-
@vegetable ||= {}
|
19
|
-
@vegetable[name.to_sym] = create :vegetable, name: name
|
20
|
-
end
|
21
|
-
|
22
|
-
Given(/^an empty (.+) model$/) do |klass|
|
23
|
-
DatabaseCleaner.clean
|
24
|
-
klass.classify.constantize.delete_all
|
25
|
-
end
|
26
|
-
|
27
|
-
Given(/^several fruits$/) do
|
28
|
-
DatabaseCleaner.clean
|
29
|
-
@fruit ||= {}
|
30
|
-
10.times.each do |index|
|
31
|
-
fruit = create :fruit
|
32
|
-
@fruit[fruit.name.to_sym] = fruit
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
Given(/^several points$/) do
|
37
|
-
DatabaseCleaner.clean
|
38
|
-
10.times.each { |index| Point.create!(x: index, y: index) }
|
39
|
-
end
|
40
|
-
|
41
|
-
Given(/^(\d+) rows$/) do |count|
|
42
|
-
DatabaseCleaner.clean
|
43
|
-
count.to_i.times.each { |index| Row.create!(text: index) }
|
44
|
-
end
|
45
|
-
|
46
|
-
Given(/^(\d+) rows in default order$/) do |count|
|
47
|
-
DatabaseCleaner.clean
|
48
|
-
count.to_i.times.each { |index| Row.create!(text: index).update_attribute(:position, index) }
|
49
|
-
end
|
50
|
-
|
51
|
-
Given(/^(\d+) even and odd rows$/) do |count|
|
52
|
-
DatabaseCleaner.clean
|
53
|
-
count.to_i.times.each { |index| Row.create!(text: index.even? ? 'even' : 'odd').update_attribute(:position, index) }
|
54
|
-
end
|
55
|
-
|
56
|
-
When(/^I rank them in reverse order$/) do
|
57
|
-
Row.all.reverse.each_with_index { |row, index| row.update_attribute(:position, index) }
|
58
|
-
end
|
59
|
-
|
60
|
-
When(/^I move row (\d+) to row (-?\d+)$/) do |start_position, end_position|
|
61
|
-
Row.all[start_position.to_i].update_attribute(:position, end_position.to_i)
|
62
|
-
end
|
63
|
-
|
64
|
-
When(/^I reverse rank the even rows$/) do
|
65
|
-
Row.rank(:even)
|
66
|
-
end
|
67
|
-
|
68
|
-
Then(/^ranking is equivalent to all reversed$/) do
|
69
|
-
expect(Row.ranked.to_a).to eq(Row.all.to_a.reverse)
|
70
|
-
end
|
71
|
-
|
72
|
-
Then(/^ranking is equivalent to all rotated (\-\d+)$/) do |positions|
|
73
|
-
expect(Row.ranked.to_a).to eq(Row.all.to_a.rotate(positions.to_i))
|
74
|
-
end
|
75
|
-
|
76
|
-
Then(/^ranking all has no effect$/) do
|
77
|
-
expect(Point.ranked.to_a).to eq(Point.all.to_a)
|
78
|
-
end
|
79
|
-
|
80
|
-
Then(/^row (\d+) is in position (\d+)$/) do |row, position|
|
81
|
-
expect(Row.ranked[position.to_i].id).to eq(row.to_i + 1)
|
82
|
-
end
|
83
|
-
|
84
|
-
Given(/^a fruit class with an alphabetical default ranking on name$/) do
|
85
|
-
Fruit.send :ranks, ->(a, b) { a.name < b.name }
|
86
|
-
end
|
87
|
-
|
88
|
-
Given(/^a fruit class with a reverse alphabetical default ranking on name$/) do
|
89
|
-
Fruit.send :ranks, ->(a, b) { a.name > b.name }
|
90
|
-
end
|
91
|
-
|
92
|
-
Given(/^a 'fruit' class with a 'reverse' ranking$/) do
|
93
|
-
Fruit.send :ranks, :reverse
|
94
|
-
end
|
95
|
-
|
96
|
-
Then(/^the (.*)ranked (.*) array is \[(.*)\]$/) do |ranker, klass, names|
|
97
|
-
ranker = 'default' if ranker.blank?
|
98
|
-
expect(klass.classify.constantize.ranked(ranker.strip.to_sym).map(&:name)).to eq(names.split(',').map do |name|
|
99
|
-
@fruit[name.strip.to_sym].name rescue @vegetable[name.strip.to_sym].name
|
100
|
-
end)
|
101
|
-
end
|
@@ -1,24 +0,0 @@
|
|
1
|
-
When(/^I update the apple's position attribute to (\d+)$/) do |position|
|
2
|
-
@fruit[:apple].update_attribute(:position, position.to_i)
|
3
|
-
end
|
4
|
-
|
5
|
-
When(/^I assign the apple's rank to (\d+)$/) do |position|
|
6
|
-
@fruit[:apple].rank position.to_i
|
7
|
-
end
|
8
|
-
|
9
|
-
When(/^I assign the '(.*)' fruit's '(.*)' rank to '(\d+)'$/) do |type, name, position|
|
10
|
-
@fruit[type.to_sym].rank name.to_sym, position.to_i
|
11
|
-
end
|
12
|
-
|
13
|
-
When(/^I assign the '(.*)' vegetable's '(.*)' rank to '(\d+)'$/) do |type, name, position|
|
14
|
-
@vegetable[type.to_sym].rank name.to_sym, position.to_i
|
15
|
-
end
|
16
|
-
|
17
|
-
|
18
|
-
Then(/^the '(.*)' fruit's '(.*)' rank is '(\d+)'$/) do |type, name, position|
|
19
|
-
expect(@fruit[type.to_sym].position name).to eq(position.to_i)
|
20
|
-
end
|
21
|
-
|
22
|
-
Then(/^the '(.*)' vegetable's '(.*)' rank is '(\d+)'$/) do |type, name, position|
|
23
|
-
expect(@vegetable[type.to_sym].position name).to eq(position.to_i)
|
24
|
-
end
|
data/features/support/env.rb
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
require 'rankle'
|
2
|
-
require 'database_cleaner'
|
3
|
-
require 'factory_girl'
|
4
|
-
|
5
|
-
DatabaseCleaner.strategy = :truncation
|
6
|
-
|
7
|
-
ActiveRecord::Base.establish_connection(
|
8
|
-
adapter: 'sqlite3',
|
9
|
-
database: 'rankle.sqlite3'
|
10
|
-
)
|
11
|
-
|
12
|
-
load File.dirname(__FILE__) + '/schema.rb'
|
13
|
-
load File.dirname(__FILE__) + '/models.rb'
|
14
|
-
|
15
|
-
World FactoryGirl::Syntax::Methods
|
@@ -1,16 +0,0 @@
|
|
1
|
-
FactoryGirl.define do
|
2
|
-
factory :fruit do
|
3
|
-
sequence(:name) { |n| FRUITS[n-1] }
|
4
|
-
end
|
5
|
-
end
|
6
|
-
|
7
|
-
FRUITS = ['Apple', 'Apricot', 'Banana', 'Bilberry', 'Blackberry', 'Blackcurrant', 'Blueberry', 'Boysenberry',
|
8
|
-
'Cantaloupe', 'Currant', 'Cherry', 'Cherimoya', 'Cloudberry', 'Coconut', 'Cranberry', 'Damson', 'Date',
|
9
|
-
'Dragonfruit', 'Durian', 'Elderberry', 'Feijoa', 'Fig', 'Goji berry', 'Gooseberry', 'Grape', 'Raisin',
|
10
|
-
'Grapefruit', 'Guava', 'Huckleberry', 'Jackfruit', 'Jambul', 'Jujube', 'Kiwi fruit', 'Kumquat', 'Lemon',
|
11
|
-
'Lime', 'Loquat', 'Lychee', 'Mango', 'Marion berry', 'Melon', 'Cantaloupe', 'Honeydew', 'Watermelon',
|
12
|
-
'Rock melon', 'Miracle fruit', 'Mulberry', 'Nectarine', 'Olive', 'Orange', 'Clementine', 'Mandarine',
|
13
|
-
'Tangerine', 'Papaya', 'Passionfruit', 'Peach', 'Pear', 'Williams pear', 'Bartlett pear', 'Persimmon',
|
14
|
-
'Physalis', 'Plum/prune (dried plum)', 'Pineapple', 'Pomegranate', 'Pomelo', 'Purple Mangosteen', 'Quince',
|
15
|
-
'Raspberry', 'Salmon berry', 'Black raspberry', 'Rambutan', 'Redcurrant', 'Salal berry', 'Satsuma',
|
16
|
-
'Star fruit', 'Strawberry', 'Tamarillo', 'Ugli fruit']
|
@@ -1,36 +0,0 @@
|
|
1
|
-
FactoryGirl.define do
|
2
|
-
factory :vegetable do
|
3
|
-
sequence(:name) { |n| VEGETABLES[n-1] }
|
4
|
-
end
|
5
|
-
|
6
|
-
#factory :legume, class: Vegetable do
|
7
|
-
# sequence(:name) { |n| LEGUMES[n-1] }
|
8
|
-
#end
|
9
|
-
end
|
10
|
-
|
11
|
-
VEGETABLES = ['Artichoke', 'Arugula', 'Asparagus', 'Amaranth', 'Bok choy', 'Broccoflower', 'Broccoli',
|
12
|
-
'Brussels sprouts', 'Cabbage', 'Calabrese', 'Cannabis', 'Carrots', 'Cauliflower', 'Celery', 'Chard',
|
13
|
-
'Collard greens', 'Corn salad', 'Eggplant', 'Endive', 'Fiddleheads', 'Frisee', 'Kale', 'Kohlrabi',
|
14
|
-
'Lettuce Lactuca sativa', 'Corn', 'Mushrooms', 'Mustard greens', 'Nettles', 'New Zealand spinach', 'Okra',
|
15
|
-
'Parsley', 'Radicchio', 'Rhubarb', 'Salsify', 'Skirret', 'Spinach', 'Topinambur', 'Tat soi', 'Tomato',
|
16
|
-
'Water chestnut', 'Watercress']
|
17
|
-
|
18
|
-
LEGUMES = ['Alfalfa sprouts', 'Azuki beans', 'Bean sprouts', 'Black beans', 'Black-eyed peas', 'Borlotti bean',
|
19
|
-
'Broad beans', 'Chickpeas', 'Green beans', 'Kidney beans', 'Lentils', 'Lima beans', 'Mung beans',
|
20
|
-
'Navy beans', 'Pinto beans', 'Runner beans', 'Soy beans', 'Snap peas']
|
21
|
-
|
22
|
-
HERBS_AND_SPICES = ['Anise', 'Basil', 'Caraway', 'Cilantro', 'Coriander', 'Chamomile', 'Dill', 'Fennel', 'Lavender',
|
23
|
-
'Lemon Grass', 'Marjoram', 'Oregano', 'Parsley', 'Rosemary', 'Sage', 'Thyme']
|
24
|
-
|
25
|
-
ONIONS = ['Chives', 'Garlic', 'Leek Allium porrum', 'Onion', 'Shallot', 'Scallion']
|
26
|
-
|
27
|
-
PEPPERS = ['Bell pepper', 'Chili pepper', 'Jalapeno', 'Habanero', 'Paprika', 'Tabasco pepper', 'Cayenne pepper']
|
28
|
-
|
29
|
-
ROOT_VEGETABLES = ['Beet', 'Carrot', 'Celeriac', 'Daikon', 'Ginger', 'Parsnip', 'Rutabaga', 'Turnip']
|
30
|
-
|
31
|
-
RADISH = ['Rutabaga', 'Turnip', 'Wasabi', 'Horseradish', 'White radish']
|
32
|
-
|
33
|
-
SQUASHES = ['Acorn squash', 'Butternut squash', 'Banana squash', 'Zucchini', 'Cucumber', 'Delicata', 'Gem squash',
|
34
|
-
'Hubbard squash', 'Squash', 'Patty pans', 'Pumpkin', 'Spaghetti squash']
|
35
|
-
|
36
|
-
TUBERS = ['Jicama', 'Jerusalem artichoke', 'Potato', 'Sweet potato', 'Taro', 'Yam']
|
data/features/support/models.rb
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
class RankleIndex < ActiveRecord::Base
|
2
|
-
belongs_to :indexable, polymorphic: true
|
3
|
-
end
|
4
|
-
|
5
|
-
class Fruit < ActiveRecord::Base
|
6
|
-
has_many :rankle_indices, as: :indexable
|
7
|
-
end
|
8
|
-
|
9
|
-
class Vegetable < ActiveRecord::Base
|
10
|
-
has_many :rankle_indices, as: :indexable
|
11
|
-
end
|
12
|
-
|
13
|
-
class Point < ActiveRecord::Base
|
14
|
-
has_many :rankle_indices, as: :indexable
|
15
|
-
end
|
16
|
-
|
17
|
-
class Row < ActiveRecord::Base
|
18
|
-
has_many :rankle_indices, as: :indexable
|
19
|
-
end
|
data/features/support/schema.rb
DELETED
@@ -1,37 +0,0 @@
|
|
1
|
-
ActiveRecord::Schema.define do
|
2
|
-
self.verbose = false
|
3
|
-
|
4
|
-
create_table :fruits, :force => true do |t|
|
5
|
-
t.string :name
|
6
|
-
|
7
|
-
t.timestamps null: false
|
8
|
-
end
|
9
|
-
|
10
|
-
create_table :vegetables, :force => true do |t|
|
11
|
-
t.string :name
|
12
|
-
|
13
|
-
t.timestamps null: false
|
14
|
-
end
|
15
|
-
|
16
|
-
create_table :points, :force => true do |t|
|
17
|
-
t.integer :x
|
18
|
-
t.integer :y
|
19
|
-
|
20
|
-
t.timestamps null: false
|
21
|
-
end
|
22
|
-
|
23
|
-
create_table :rows, :force => true do |t|
|
24
|
-
t.string :text
|
25
|
-
|
26
|
-
t.timestamps null: false
|
27
|
-
end
|
28
|
-
|
29
|
-
create_table :rankle_indices, :force => true do |t|
|
30
|
-
t.string :indexable_name
|
31
|
-
t.integer :indexable_id
|
32
|
-
t.string :indexable_type
|
33
|
-
t.integer :indexable_position
|
34
|
-
|
35
|
-
t.timestamps null: false
|
36
|
-
end
|
37
|
-
end
|