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 ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.9.2"
4
+ - "1.9.3"
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/ranked-model)
129
+ * [AndrewRadev](https://github.com/AndrewRadev)
130
+ * [adheerajkumar](https://github.com/adheerajkumar)
131
+ * [mikeycgto](https://github.com/mikeycgto)
data/lib/ranked-model.rb CHANGED
@@ -11,7 +11,7 @@ module RankedModel
11
11
  def self.included base
12
12
 
13
13
  base.class_eval do
14
- cattr_accessor :rankers
14
+ class_attribute :rankers
15
15
 
16
16
  extend RankedModel::ClassMethods
17
17
 
@@ -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 && !instance.class.respond_to?(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
- instance.class.where(:id => instance.id).update_all ["#{ranker.column} = ?", value]
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( instance.class.arel_table[:id].not_eq(instance.id) )
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( instance.class.arel_table[ranker.column].lteq(rank) ).
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( instance.class.arel_table[ranker.column].gteq(rank) ).
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( instance.class.arel_table[ranker.column].lt(rank) ).
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 = instance.class
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
- instance.class.arel_table[ranker.with_same].eq(instance.attributes["#{ranker.with_same}"])
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
- instance.class.arel_table[ranker.with_same.first].eq(
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
- instance.class.arel_table[attr].eq(
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
- instance.class.arel_table[:id].not_eq(instance.id)
224
+ instance_class.arel_table[:id].not_eq(instance.id)
213
225
  end
214
- _finder.order(instance.class.arel_table[ranker.column].asc).select([instance.class.arel_table[:id], instance.class.arel_table[ranker.column]])
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
 
@@ -1,3 +1,3 @@
1
1
  module RankedModel
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -5,14 +5,60 @@ describe RankedModel::Ranker, 'initialized' do
5
5
  subject {
6
6
  RankedModel::Ranker.new \
7
7
  :overview,
8
- :column => :a_sorting_column,
9
- :scope => :a_scope,
10
- :with_same => :a_column
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.1.1
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-09-04 00:00:00.000000000 Z
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: -4485783404028753486
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: -4485783404028753486
165
+ hash: -3607802699364138868
164
166
  requirements: []
165
167
  rubyforge_project:
166
- rubygems_version: 1.8.24
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