ranked-model 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +4 -0
- data/Readme.mkd +5 -1
- data/lib/ranked-model.rb +1 -1
- data/lib/ranked-model/ranker.rb +28 -16
- data/lib/ranked-model/version.rb +1 -1
- data/spec/ranked-model/ranker_spec.rb +49 -3
- data/spec/sti-model/element_spec.rb +10 -10
- data/spec/sti-model/vehicle_spec.rb +75 -0
- data/spec/support/active_record.rb +36 -1
- metadata +8 -5
data/.travis.yml
ADDED
data/Readme.mkd
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
**ranked-model** is a modern row sorting library built for Rails 3. It uses ARel aggressively and is better optimized than most other libraries.
|
2
2
|
|
3
|
+
[![Build Status](https://travis-ci.org/mixonic/ranked-model.png)](https://travis-ci.org/mixonic/ranked-model)
|
4
|
+
|
3
5
|
Installation
|
4
6
|
------------
|
5
7
|
|
@@ -124,4 +126,6 @@ A hearty thanks to these contributors:
|
|
124
126
|
|
125
127
|
* [Harvest](http://getharvest.com) where this Gem started. They are great, great folks.
|
126
128
|
* [yabawock](https://github.com/yabawock)
|
127
|
-
* [AndrewRadev](https://github.com/AndrewRadev
|
129
|
+
* [AndrewRadev](https://github.com/AndrewRadev)
|
130
|
+
* [adheerajkumar](https://github.com/adheerajkumar)
|
131
|
+
* [mikeycgto](https://github.com/mikeycgto)
|
data/lib/ranked-model.rb
CHANGED
data/lib/ranked-model/ranker.rb
CHANGED
@@ -4,13 +4,14 @@ module RankedModel
|
|
4
4
|
class InvalidField < StandardError; end
|
5
5
|
|
6
6
|
class Ranker
|
7
|
-
attr_accessor :name, :column, :scope, :with_same
|
7
|
+
attr_accessor :name, :column, :scope, :with_same, :class_name, :unless
|
8
8
|
|
9
9
|
def initialize name, options={}
|
10
10
|
self.name = name.to_sym
|
11
11
|
self.column = options[:column] || name
|
12
|
+
self.class_name = options[:class_name]
|
12
13
|
|
13
|
-
[ :scope, :with_same ].each do |key|
|
14
|
+
[ :scope, :with_same, :unless ].each do |key|
|
14
15
|
self.send "#{key}=", options[key]
|
15
16
|
end
|
16
17
|
end
|
@@ -30,7 +31,7 @@ module RankedModel
|
|
30
31
|
end
|
31
32
|
|
32
33
|
def validate_ranker_for_instance!
|
33
|
-
if ranker.scope && !
|
34
|
+
if ranker.scope && !instance_class.respond_to?(ranker.scope)
|
34
35
|
raise RankedModel::InvalidScope, %Q{No scope called "#{ranker.scope}" found in model}
|
35
36
|
end
|
36
37
|
|
@@ -49,6 +50,13 @@ module RankedModel
|
|
49
50
|
end
|
50
51
|
|
51
52
|
def handle_ranking
|
53
|
+
case ranker.unless
|
54
|
+
when Proc
|
55
|
+
return if ranker.unless.call(instance)
|
56
|
+
when Symbol
|
57
|
+
return if instance.send(ranker.unless)
|
58
|
+
end
|
59
|
+
|
52
60
|
update_index_from_position
|
53
61
|
assure_unique_position
|
54
62
|
end
|
@@ -56,7 +64,7 @@ module RankedModel
|
|
56
64
|
def update_rank! value
|
57
65
|
# Bypass callbacks
|
58
66
|
#
|
59
|
-
|
67
|
+
instance_class.where(:id => instance.id).update_all ["#{ranker.column} = ?", value]
|
60
68
|
end
|
61
69
|
|
62
70
|
def position
|
@@ -75,6 +83,10 @@ module RankedModel
|
|
75
83
|
|
76
84
|
private
|
77
85
|
|
86
|
+
def instance_class
|
87
|
+
ranker.class_name.nil? ? instance.class : ranker.class_name.constantize
|
88
|
+
end
|
89
|
+
|
78
90
|
def position_at value
|
79
91
|
instance.send "#{ranker.name}_position=", value
|
80
92
|
update_index_from_position
|
@@ -140,19 +152,19 @@ module RankedModel
|
|
140
152
|
_scope = finder
|
141
153
|
unless instance.id.nil?
|
142
154
|
# Never update ourself, shift others around us.
|
143
|
-
_scope = _scope.where(
|
155
|
+
_scope = _scope.where( instance_class.arel_table[:id].not_eq(instance.id) )
|
144
156
|
end
|
145
157
|
if current_first.rank && current_first.rank > RankedModel::MIN_RANK_VALUE && rank == RankedModel::MAX_RANK_VALUE
|
146
158
|
_scope.
|
147
|
-
where(
|
159
|
+
where( instance_class.arel_table[ranker.column].lteq(rank) ).
|
148
160
|
update_all( "#{ranker.column} = #{ranker.column} - 1" )
|
149
161
|
elsif current_last.rank && current_last.rank < (RankedModel::MAX_RANK_VALUE - 1) && rank < current_last.rank
|
150
162
|
_scope.
|
151
|
-
where(
|
163
|
+
where( instance_class.arel_table[ranker.column].gteq(rank) ).
|
152
164
|
update_all( "#{ranker.column} = #{ranker.column} + 1" )
|
153
|
-
elsif current_first.rank && current_first.rank > RankedModel::MIN_RANK_VALUE && rank > current_first.rank
|
165
|
+
elsif current_first.rank && current_first.rank > RankedModel::MIN_RANK_VALUE && rank > current_first.rank
|
154
166
|
_scope.
|
155
|
-
where(
|
167
|
+
where( instance_class.arel_table[ranker.column].lt(rank) ).
|
156
168
|
update_all( "#{ranker.column} = #{ranker.column} - 1" )
|
157
169
|
rank_at( rank - 1 )
|
158
170
|
else
|
@@ -184,23 +196,23 @@ module RankedModel
|
|
184
196
|
|
185
197
|
def finder
|
186
198
|
@finder ||= begin
|
187
|
-
_finder =
|
199
|
+
_finder = instance_class
|
188
200
|
if ranker.scope
|
189
201
|
_finder = _finder.send ranker.scope
|
190
202
|
end
|
191
203
|
case ranker.with_same
|
192
204
|
when Symbol
|
193
205
|
_finder = _finder.where \
|
194
|
-
|
206
|
+
instance_class.arel_table[ranker.with_same].eq(instance.attributes["#{ranker.with_same}"])
|
195
207
|
when Array
|
196
208
|
_finder = _finder.where(
|
197
209
|
ranker.with_same[1..-1].inject(
|
198
|
-
|
210
|
+
instance_class.arel_table[ranker.with_same.first].eq(
|
199
211
|
instance.attributes["#{ranker.with_same.first}"]
|
200
212
|
)
|
201
213
|
) {|scoper, attr|
|
202
|
-
scoper.and(
|
203
|
-
|
214
|
+
scoper.and(
|
215
|
+
instance_class.arel_table[attr].eq(
|
204
216
|
instance.attributes["#{attr}"]
|
205
217
|
)
|
206
218
|
)
|
@@ -209,9 +221,9 @@ module RankedModel
|
|
209
221
|
end
|
210
222
|
if !new_record?
|
211
223
|
_finder = _finder.where \
|
212
|
-
|
224
|
+
instance_class.arel_table[:id].not_eq(instance.id)
|
213
225
|
end
|
214
|
-
_finder.order(
|
226
|
+
_finder.order(instance_class.arel_table[ranker.column].asc).select([instance_class.arel_table[:id], instance_class.arel_table[ranker.column]])
|
215
227
|
end
|
216
228
|
end
|
217
229
|
|
data/lib/ranked-model/version.rb
CHANGED
@@ -5,14 +5,60 @@ describe RankedModel::Ranker, 'initialized' do
|
|
5
5
|
subject {
|
6
6
|
RankedModel::Ranker.new \
|
7
7
|
:overview,
|
8
|
-
:column
|
9
|
-
:scope
|
10
|
-
:with_same
|
8
|
+
:column => :a_sorting_column,
|
9
|
+
:scope => :a_scope,
|
10
|
+
:with_same => :a_column,
|
11
|
+
:class_name => 'SomeClass',
|
12
|
+
:unless => :a_method
|
11
13
|
}
|
12
14
|
|
13
15
|
its(:name) { should == :overview }
|
14
16
|
its(:column) { should == :a_sorting_column }
|
15
17
|
its(:scope) { should == :a_scope }
|
16
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
|
17
60
|
|
61
|
+
subject.handle_ranking
|
62
|
+
}
|
63
|
+
end
|
18
64
|
end
|
@@ -30,9 +30,9 @@ describe Element do
|
|
30
30
|
subject { NobleGas.rank(:combination_order) }
|
31
31
|
|
32
32
|
its(:size) { should == 3 }
|
33
|
-
|
33
|
+
|
34
34
|
its(:first) { should == @elements[:xenon] }
|
35
|
-
|
35
|
+
|
36
36
|
its(:last) { should == @elements[:argon] }
|
37
37
|
|
38
38
|
end
|
@@ -57,9 +57,9 @@ describe Element do
|
|
57
57
|
subject { NobleGas.rank(:combination_order) }
|
58
58
|
|
59
59
|
its(:size) { should == 3 }
|
60
|
-
|
60
|
+
|
61
61
|
its(:first) { should == @elements[:argon] }
|
62
|
-
|
62
|
+
|
63
63
|
its(:last) { should == @elements[:helium] }
|
64
64
|
|
65
65
|
end
|
@@ -69,9 +69,9 @@ describe Element do
|
|
69
69
|
subject { TransitionMetal.rank(:combination_order) }
|
70
70
|
|
71
71
|
its(:size) { should == 2 }
|
72
|
-
|
72
|
+
|
73
73
|
its(:first) { should == @elements[:chromium] }
|
74
|
-
|
74
|
+
|
75
75
|
its(:last) { should == @elements[:manganese] }
|
76
76
|
|
77
77
|
end
|
@@ -98,9 +98,9 @@ describe Element do
|
|
98
98
|
subject { NobleGas.rank(:combination_order) }
|
99
99
|
|
100
100
|
its(:size) { should == 3 }
|
101
|
-
|
101
|
+
|
102
102
|
its(:first) { should == @elements[:argon] }
|
103
|
-
|
103
|
+
|
104
104
|
its(:last) { should == @elements[:helium] }
|
105
105
|
|
106
106
|
end
|
@@ -110,9 +110,9 @@ describe Element do
|
|
110
110
|
subject { TransitionMetal.rank(:combination_order) }
|
111
111
|
|
112
112
|
its(:size) { should == 2 }
|
113
|
-
|
113
|
+
|
114
114
|
its(:first) { should == @elements[:chromium] }
|
115
|
-
|
115
|
+
|
116
116
|
its(:last) { should == @elements[:manganese] }
|
117
117
|
|
118
118
|
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Vehicle do
|
4
|
+
|
5
|
+
before {
|
6
|
+
@vehicles = {
|
7
|
+
:ford => Car.create( :manufacturer => 'Ford' ),
|
8
|
+
:bmw => Car.create( :manufacturer => 'BMW' ),
|
9
|
+
:daimler => Truck.create( :manufacturer => 'Daimler' ),
|
10
|
+
:volvo => Truck.create( :manufacturer => 'Volvo' ),
|
11
|
+
:kenworth => Truck.create( :manufacturer => 'Kenworth' )
|
12
|
+
}
|
13
|
+
@vehicles.each { |name, vehicle|
|
14
|
+
vehicle.reload
|
15
|
+
vehicle.update_attribute :parking_order_position, 0
|
16
|
+
}
|
17
|
+
@vehicles.each {|name, vehicle| vehicle.reload }
|
18
|
+
}
|
19
|
+
|
20
|
+
describe "ranking by STI parent" do
|
21
|
+
|
22
|
+
before {
|
23
|
+
@vehicles[:volvo].update_attribute :parking_order_position, :first
|
24
|
+
@vehicles[:ford].update_attribute :parking_order_position, :first
|
25
|
+
}
|
26
|
+
|
27
|
+
describe "Vehicle" do
|
28
|
+
|
29
|
+
subject { Vehicle.rank(:parking_order) }
|
30
|
+
|
31
|
+
its(:size) { should == 5 }
|
32
|
+
|
33
|
+
its(:first) { should == @vehicles[:ford] }
|
34
|
+
|
35
|
+
its(:second) { should == @vehicles[:volvo] }
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "overriding parent's ranking" do
|
42
|
+
|
43
|
+
describe "Vehicle" do
|
44
|
+
|
45
|
+
it "should have one ranker object" do
|
46
|
+
Vehicle.rankers.count.should == 1
|
47
|
+
end
|
48
|
+
|
49
|
+
subject { Vehicle.rankers.first }
|
50
|
+
|
51
|
+
its(:class_name) { should == 'Vehicle' }
|
52
|
+
|
53
|
+
its(:column) { should == :parking_order }
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "MotorBike" do
|
58
|
+
|
59
|
+
it "should have one ranker object" do
|
60
|
+
MotorBike.rankers.count.should == 1
|
61
|
+
end
|
62
|
+
|
63
|
+
subject { MotorBike.rankers.first }
|
64
|
+
|
65
|
+
its(:class_name) { should == 'Vehicle' }
|
66
|
+
|
67
|
+
its(:column) { should == :parking_order }
|
68
|
+
|
69
|
+
its(:with_same) { should == :color }
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
@@ -39,6 +39,13 @@ ActiveRecord::Schema.define :version => 0 do
|
|
39
39
|
t.string :type
|
40
40
|
t.integer :combination_order
|
41
41
|
end
|
42
|
+
|
43
|
+
create_table :vehicles, :force => true do |t|
|
44
|
+
t.string :color
|
45
|
+
t.string :manufacturer
|
46
|
+
t.string :type
|
47
|
+
t.integer :parking_order
|
48
|
+
end
|
42
49
|
end
|
43
50
|
|
44
51
|
class Duck < ActiveRecord::Base
|
@@ -47,7 +54,7 @@ class Duck < ActiveRecord::Base
|
|
47
54
|
ranks :row
|
48
55
|
ranks :size, :scope => :in_shin_pond
|
49
56
|
ranks :age, :with_same => :pond
|
50
|
-
|
57
|
+
|
51
58
|
ranks :landing_order, :with_same => [:lake_id, :flock_id]
|
52
59
|
scope :in_lake_and_flock, lambda {|lake, flock| where(:lake_id => lake, :flock_id => flock) }
|
53
60
|
|
@@ -71,6 +78,8 @@ class WrongFieldDuck < ActiveRecord::Base
|
|
71
78
|
|
72
79
|
end
|
73
80
|
|
81
|
+
# Example for STI, ranking within each child class
|
82
|
+
|
74
83
|
class Element < ActiveRecord::Base
|
75
84
|
|
76
85
|
include RankedModel
|
@@ -85,3 +94,29 @@ end
|
|
85
94
|
class NobleGas < Element
|
86
95
|
|
87
96
|
end
|
97
|
+
|
98
|
+
# Example for STI, ranking within parent
|
99
|
+
|
100
|
+
class Vehicle < ActiveRecord::Base
|
101
|
+
|
102
|
+
include RankedModel
|
103
|
+
ranks :parking_order, :class_name => 'Vehicle'
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
class Car < Vehicle
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
class Truck < Vehicle
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
# Example for STI, overriding parent's ranking
|
116
|
+
|
117
|
+
class MotorBike < Vehicle
|
118
|
+
|
119
|
+
include RankedModel
|
120
|
+
ranks :parking_order, :class_name => 'Vehicle', :with_same => :color
|
121
|
+
|
122
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ranked-model
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-11-30 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
@@ -117,6 +117,7 @@ extra_rdoc_files: []
|
|
117
117
|
files:
|
118
118
|
- .gitignore
|
119
119
|
- .rspec
|
120
|
+
- .travis.yml
|
120
121
|
- Gemfile
|
121
122
|
- LICENSE
|
122
123
|
- Rakefile
|
@@ -134,6 +135,7 @@ files:
|
|
134
135
|
- spec/ranked-model/version_spec.rb
|
135
136
|
- spec/spec_helper.rb
|
136
137
|
- spec/sti-model/element_spec.rb
|
138
|
+
- spec/sti-model/vehicle_spec.rb
|
137
139
|
- spec/support/active_record.rb
|
138
140
|
- spec/support/database.yml
|
139
141
|
- tmp/.gitignore
|
@@ -151,7 +153,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
151
153
|
version: '0'
|
152
154
|
segments:
|
153
155
|
- 0
|
154
|
-
hash: -
|
156
|
+
hash: -3607802699364138868
|
155
157
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
156
158
|
none: false
|
157
159
|
requirements:
|
@@ -160,10 +162,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
160
162
|
version: '0'
|
161
163
|
segments:
|
162
164
|
- 0
|
163
|
-
hash: -
|
165
|
+
hash: -3607802699364138868
|
164
166
|
requirements: []
|
165
167
|
rubyforge_project:
|
166
|
-
rubygems_version: 1.8.
|
168
|
+
rubygems_version: 1.8.23
|
167
169
|
signing_key:
|
168
170
|
specification_version: 3
|
169
171
|
summary: An acts_as_sortable replacement built for Rails 3
|
@@ -175,5 +177,6 @@ test_files:
|
|
175
177
|
- spec/ranked-model/version_spec.rb
|
176
178
|
- spec/spec_helper.rb
|
177
179
|
- spec/sti-model/element_spec.rb
|
180
|
+
- spec/sti-model/vehicle_spec.rb
|
178
181
|
- spec/support/active_record.rb
|
179
182
|
- spec/support/database.yml
|