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 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