ranked-model-rails2 0.4.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.
@@ -0,0 +1,169 @@
1
+ require 'spec_helper'
2
+
3
+ describe Duck do
4
+
5
+ before {
6
+ 200.times do |i|
7
+ Duck.create \
8
+ :name => "Duck #{i}"
9
+ end
10
+ }
11
+
12
+ describe "setting and fetching by position" do
13
+
14
+ describe '137' do
15
+
16
+ before {
17
+ @last = Duck.last
18
+ @last.update_attribute :row_position, 137
19
+ }
20
+
21
+ subject { Duck.ranker(:row).with(Duck.new).current_at_position(137).instance }
22
+
23
+ its(:id) { should == @last.id }
24
+
25
+ end
26
+
27
+ describe '2' do
28
+
29
+ before {
30
+ @last = Duck.last
31
+ @last.update_attribute :row_position, 2
32
+ }
33
+
34
+ subject { Duck.ranker(:row).with(Duck.new).current_at_position(2).instance }
35
+
36
+ its(:id) { should == @last.id }
37
+
38
+ end
39
+
40
+ describe 'last' do
41
+
42
+ before {
43
+ @last = Duck.last
44
+ @last.update_attribute :row_position, :last
45
+ }
46
+
47
+ subject { Duck.rank(:row).last }
48
+
49
+ its(:id) { should == @last.id }
50
+
51
+ end
52
+
53
+ describe 'first' do
54
+
55
+ before {
56
+ @last = Duck.last
57
+ @last.update_attribute :row_position, :first
58
+ }
59
+
60
+ subject { Duck.rank(:row).first }
61
+
62
+ its(:id) { should == @last.id }
63
+
64
+ end
65
+
66
+ end
67
+
68
+ describe "a rearrangement" do
69
+
70
+ describe "with max value" do
71
+
72
+ before {
73
+ @first = Duck.first
74
+ @second = Duck.offset(1).first
75
+ @ordered = Duck.rank(:row).where(%Q{"id" NOT IN (#{@first.id}, #{@second.id})}).collect {|d| d.id }
76
+ @first.update_attribute :row, RankedModel::MAX_RANK_VALUE
77
+ @second.update_attribute :row, RankedModel::MAX_RANK_VALUE
78
+ }
79
+
80
+ context {
81
+
82
+ subject { Duck.rank(:row).collect {|d| d.id } }
83
+
84
+ it { should == (@ordered[0..-2] + [@ordered[-1], @first.id, @second.id]) }
85
+
86
+ }
87
+
88
+ end
89
+
90
+ describe "with max value and with_same pond" do
91
+
92
+ before {
93
+ Duck.order("id ASC").limit(50).each_with_index do |d, index|
94
+ d.update_attributes :age => index % 10, :pond => "Pond #{index / 10}"
95
+ end
96
+ @duck_11 = Duck.offset(10).first
97
+ @duck_12 = Duck.offset(11).first
98
+ @ordered = Duck.where(:pond => 'Pond 1').rank(:age).where(%Q{"id" NOT IN (#{@duck_11.id}, #{@duck_12.id})}).collect {|d| d.id }
99
+ @duck_11.update_attribute :age, RankedModel::MAX_RANK_VALUE
100
+ @duck_12.update_attribute :age, RankedModel::MAX_RANK_VALUE
101
+ }
102
+
103
+ context {
104
+ subject { Duck.where(:pond => 'Pond 1').rank(:age).collect {|d| d.id } }
105
+
106
+ it { should == (@ordered[0..-2] + [@ordered[-1], @duck_11.id, @duck_12.id]) }
107
+ }
108
+
109
+ context {
110
+ subject { Duck.first.age }
111
+ it { should == 0}
112
+ }
113
+
114
+ end
115
+
116
+ describe "with min value" do
117
+
118
+ before {
119
+ @first = Duck.first
120
+ @second = Duck.offset(1).first
121
+ @ordered = Duck.rank(:row).where(%Q{"id" NOT IN (#{@first.id}, #{@second.id})}).collect {|d| d.id }
122
+ @first.update_attribute :row, RankedModel::MIN_RANK_VALUE
123
+ @second.update_attribute :row, RankedModel::MIN_RANK_VALUE
124
+ }
125
+
126
+ context {
127
+
128
+ subject { Duck.rank(:row).collect {|d| d.id } }
129
+
130
+ it { should == ([@second.id, @first.id] + @ordered) }
131
+
132
+ }
133
+
134
+ end
135
+
136
+ describe "with no more gaps" do
137
+
138
+ before {
139
+ @first = Duck.first
140
+ @second = Duck.where(:row => RankedModel::MAX_RANK_VALUE).first || Duck.offset(1).first
141
+ @third = Duck.offset(2).first
142
+ @fourth = Duck.offset(4).first
143
+ @lower = Duck.rank(:row).
144
+ where(%Q{"id" NOT IN (#{@first.id}, #{@second.id}, #{@third.id}, #{@fourth.id})}).
145
+ where(%Q{"row" < ?}, RankedModel::MAX_RANK_VALUE / 2).
146
+ collect {|d| d.id }
147
+ @upper = Duck.rank(:row).
148
+ where(%Q{"id" NOT IN (#{@first.id}, #{@second.id}, #{@third.id}, #{@fourth.id})}).
149
+ where(%Q{"row" >= ?}, RankedModel::MAX_RANK_VALUE / 2).
150
+ collect {|d| d.id }
151
+ @first.update_attribute :row, RankedModel::MIN_RANK_VALUE
152
+ @second.update_attribute :row, RankedModel::MAX_RANK_VALUE
153
+ @third.update_attribute :row, (RankedModel::MAX_RANK_VALUE / 2)
154
+ @fourth.update_attribute :row, @third.row
155
+ }
156
+
157
+ context {
158
+
159
+ subject { Duck.rank(:row).collect {|d| d.id } }
160
+
161
+ it { should == ([@first.id] + @lower + [@fourth.id, @third.id] + @upper + [@second.id]) }
162
+
163
+ }
164
+
165
+ end
166
+
167
+ end
168
+
169
+ end
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+
3
+ describe WrongScopeDuck do
4
+
5
+ it "should raise an error because of an unknown scope" do
6
+
7
+ expect {
8
+ WrongScopeDuck.create(:name => 'Quocky', :pond => 'Shin')
9
+ }.to raise_error(RankedModel::InvalidScope, 'No scope called "non_existant_scope" found in model')
10
+
11
+ end
12
+
13
+ end
14
+
15
+ describe WrongFieldDuck do
16
+
17
+ it "should raise an error because of an unknown field" do
18
+
19
+ expect {
20
+ WrongFieldDuck.create(:name => 'Quicky', :pond => 'Shin')
21
+ }.to raise_error(RankedModel::InvalidField, 'No field called "non_existant_field" found in model')
22
+
23
+ end
24
+
25
+ end
26
+
27
+ describe ReallyWrongFieldDuck do
28
+
29
+ it "should raise an error because of a specific unknown field" do
30
+
31
+ expect {
32
+ ReallyWrongFieldDuck.create(:name => 'Quicky', :pond => 'Shin')
33
+ }.to raise_error(RankedModel::InvalidField, 'No field called "non_existant_field" found in model')
34
+ end
35
+
36
+ end
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ego do
4
+
5
+ before {
6
+ @egos = {
7
+ :bob => Ego.create(:name => 'Bob'),
8
+ :nick => Ego.create(:name => 'Nick'),
9
+ :sally => Ego.create(:name => 'Sally')
10
+ }
11
+ @egos.each { |name, ego|
12
+ ego.reload
13
+ ego.update_attribute :size_position, 0
14
+ ego.save!
15
+ }
16
+ @egos.each {|name, ego| ego.reload }
17
+ }
18
+
19
+ describe "sorting on size alternative primary key" do
20
+
21
+ before {
22
+ @egos[:nick].update_attribute :size_position, 0
23
+ @egos[:sally].update_attribute :size_position, 2
24
+ }
25
+
26
+ subject { Ego.rank(:size).to_a }
27
+
28
+ its(:size) { should == 3 }
29
+
30
+ its(:first) { should == @egos[:nick] }
31
+
32
+ its(:last) { should == @egos[:sally] }
33
+
34
+ end
35
+
36
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe Player do
4
+
5
+ before {
6
+ @players = {}
7
+ @players[:dave] = Player.create!(:name => "Dave", :city => "Detroit")
8
+ @players[:bob] = Player.create!(:name => "Bob", :city => "Portland")
9
+ @players[:nigel] = Player.create!(:name => "Nigel", :city => "New York")
10
+ Player.class_eval do
11
+ include RankedModel
12
+ ranks :score
13
+ end
14
+
15
+ }
16
+
17
+ describe "setting the position of a record that already exists" do
18
+ it "sets the rank without error" do
19
+ expect{@players[:bob].update_attributes! :score_position => 1}.to_not raise_error
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,64 @@
1
+ require 'spec_helper'
2
+
3
+ describe RankedModel::Ranker, 'initialized' do
4
+
5
+ subject {
6
+ RankedModel::Ranker.new \
7
+ :overview,
8
+ :column => :a_sorting_column,
9
+ :scope => :a_scope,
10
+ :with_same => :a_column,
11
+ :class_name => 'SomeClass',
12
+ :unless => :a_method
13
+ }
14
+
15
+ its(:name) { should == :overview }
16
+ its(:column) { should == :a_sorting_column }
17
+ its(:scope) { should == :a_scope }
18
+ its(:with_same) { should == :a_column }
19
+ its(:class_name) { should == 'SomeClass' }
20
+ its(:unless) { should == :a_method }
21
+ end
22
+
23
+ describe RankedModel::Ranker, 'unless as Symbol' do
24
+ let(:receiver) { mock('model') }
25
+
26
+ subject {
27
+ RankedModel::Ranker.new(:overview, :unless => :a_method).with(receiver)
28
+ }
29
+
30
+ context 'returns true' do
31
+ before { receiver.expects(:a_method).once.returns(true) }
32
+
33
+ its(:handle_ranking) { should == nil }
34
+ end
35
+
36
+ context 'returns false' do
37
+ before { receiver.expects(:a_method).once.returns(false) }
38
+
39
+ it {
40
+ subject.expects(:update_index_from_position).once
41
+ subject.expects(:assure_unique_position).once
42
+
43
+ subject.handle_ranking
44
+ }
45
+ end
46
+ end
47
+
48
+ describe RankedModel::Ranker, 'unless as Proc' do
49
+ context 'returns true' do
50
+ subject { RankedModel::Ranker.new(:overview, :unless => Proc.new { true }).with(Class.new) }
51
+ its(:handle_ranking) { should == nil }
52
+ end
53
+
54
+ context 'returns false' do
55
+ subject { RankedModel::Ranker.new(:overview, :unless => Proc.new { false }).with(Class.new) }
56
+
57
+ it {
58
+ subject.expects(:update_index_from_position).once
59
+ subject.expects(:assure_unique_position).once
60
+
61
+ subject.handle_ranking
62
+ }
63
+ end
64
+ end
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+
3
+ describe RankedModel do
4
+
5
+ it { should define_constant(:VERSION) }
6
+
7
+ end
@@ -0,0 +1,31 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require 'ranked-model'
5
+
6
+ Dir[File.join(File.dirname(__FILE__), 'support/**/*.rb')].each {|f| require f}
7
+
8
+ # After the DB connection is setup
9
+ require 'database_cleaner'
10
+
11
+ RSpec.configure do |config|
12
+ config.mock_with :mocha
13
+
14
+ config.before(:suite) do
15
+ DatabaseCleaner.strategy = :transaction
16
+ DatabaseCleaner.clean_with(:truncation)
17
+ end
18
+
19
+ config.before(:each) do
20
+ DatabaseCleaner.start
21
+ end
22
+
23
+ config.after(:each) do
24
+ DatabaseCleaner.clean
25
+ end
26
+
27
+ end
28
+
29
+ RSpec::Matchers.define :define_constant do |expected|
30
+ match { |actual| actual.const_defined?(expected) }
31
+ end
@@ -0,0 +1,122 @@
1
+ require 'spec_helper'
2
+
3
+ describe Element do
4
+
5
+ before {
6
+ @elements = {
7
+ :chromium => TransitionMetal.create( :symbol => 'Cr' ),
8
+ :manganese => TransitionMetal.create( :symbol => 'Mn' ),
9
+ :argon => NobleGas.create( :symbol => 'Ar' ),
10
+ :helium => NobleGas.create( :symbol => 'He' ),
11
+ :xenon => NobleGas.create( :symbol => 'Xe' )
12
+ }
13
+ @elements.each { |name, element|
14
+ element.reload
15
+ element.update_attribute :combination_order_position, 0
16
+ }
17
+ @elements.each {|name, element| element.reload }
18
+ }
19
+
20
+ describe "rebalancing on an STI class should not affect the other class" do
21
+
22
+ before {
23
+ @elements[:helium].update_attribute :combination_order_position, :first
24
+ @elements[:xenon].update_attribute :combination_order_position, :first
25
+ @elements[:argon].update_attribute :combination_order_position, :last
26
+
27
+ TransitionMetal.ranker(:combination_order).with(@elements[:chromium]).instance_eval { rebalance_ranks }
28
+ }
29
+
30
+ subject { NobleGas.rank(:combination_order) }
31
+
32
+ its(:size) { should == 3 }
33
+
34
+ its(:first) { should == @elements[:xenon] }
35
+
36
+ its(:last) { should == @elements[:argon] }
37
+
38
+ end
39
+
40
+ describe "setting positions on STI classes" do
41
+
42
+ before {
43
+ @elements[:helium].update_attribute :combination_order_position, :first
44
+ @elements[:xenon].update_attribute :combination_order_position, :first
45
+ @elements[:argon].update_attribute :combination_order_position, :first
46
+
47
+ @elements[:chromium].update_attribute :combination_order_position, 1
48
+ @elements[:manganese].update_attribute :combination_order_position, 1
49
+ @elements[:manganese].update_attribute :combination_order_position, 0
50
+ @elements[:chromium].update_attribute :combination_order_position, 0
51
+ @elements[:manganese].update_attribute :combination_order_position, 0
52
+ @elements[:chromium].update_attribute :combination_order_position, 0
53
+ }
54
+
55
+ describe "NobleGas" do
56
+
57
+ subject { NobleGas.rank(:combination_order) }
58
+
59
+ its(:size) { should == 3 }
60
+
61
+ its(:first) { should == @elements[:argon] }
62
+
63
+ its(:last) { should == @elements[:helium] }
64
+
65
+ end
66
+
67
+ describe "TransitionMetal" do
68
+
69
+ subject { TransitionMetal.rank(:combination_order) }
70
+
71
+ its(:size) { should == 2 }
72
+
73
+ its(:first) { should == @elements[:chromium] }
74
+
75
+ its(:last) { should == @elements[:manganese] }
76
+
77
+ end
78
+
79
+ end
80
+
81
+ describe "setting positions on STI classes" do
82
+
83
+ before {
84
+ @elements[:helium].update_attribute :combination_order_position, :first
85
+ @elements[:xenon].update_attribute :combination_order_position, :first
86
+ @elements[:argon].update_attribute :combination_order_position, :first
87
+
88
+ @elements[:chromium].update_attribute :combination_order_position, 1
89
+ @elements[:manganese].update_attribute :combination_order_position, 1
90
+ @elements[:manganese].update_attribute :combination_order_position, 0
91
+ @elements[:chromium].update_attribute :combination_order_position, 0
92
+ @elements[:manganese].update_attribute :combination_order_position, 0
93
+ @elements[:chromium].update_attribute :combination_order_position, 0
94
+ }
95
+
96
+ describe "NobleGas" do
97
+
98
+ subject { NobleGas.rank(:combination_order) }
99
+
100
+ its(:size) { should == 3 }
101
+
102
+ its(:first) { should == @elements[:argon] }
103
+
104
+ its(:last) { should == @elements[:helium] }
105
+
106
+ end
107
+
108
+ describe "TransitionMetal" do
109
+
110
+ subject { TransitionMetal.rank(:combination_order) }
111
+
112
+ its(:size) { should == 2 }
113
+
114
+ its(:first) { should == @elements[:chromium] }
115
+
116
+ its(:last) { should == @elements[:manganese] }
117
+
118
+ end
119
+
120
+ end
121
+
122
+ end