ranked-model 0.1.1 → 0.2.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.
- 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
|
+
[](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
|