ranked-model 0.0.5 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Readme.mkd +66 -44
- data/lib/ranked-model.rb +1 -1
- data/lib/ranked-model/ranker.rb +12 -10
- data/lib/ranked-model/version.rb +1 -1
- data/spec/duck-model/duck_spec.rb +41 -0
- data/spec/duck-model/lots_of_ducks_spec.rb +26 -0
- metadata +105 -106
data/Readme.mkd
CHANGED
@@ -5,11 +5,13 @@ Installation
|
|
5
5
|
|
6
6
|
To install ranked-model, just add it to your `Gemfile`:
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
8
|
+
``` ruby
|
9
|
+
gem 'ranked-model'
|
10
|
+
|
11
|
+
# Or pin ranked-model to git
|
12
|
+
# gem 'ranked-model',
|
13
|
+
# :git => 'git@github.com:mixonic/ranked-model.git'
|
14
|
+
```
|
13
15
|
|
14
16
|
Then use `bundle install` to update your `Gemfile.lock`.
|
15
17
|
|
@@ -18,66 +20,83 @@ Simple Use
|
|
18
20
|
|
19
21
|
Use of ranked-model is straight ahead. Get some ducks:
|
20
22
|
|
21
|
-
|
22
|
-
|
23
|
+
``` ruby
|
24
|
+
class Duck < ActiveRecord::Base
|
25
|
+
end
|
26
|
+
```
|
23
27
|
|
24
28
|
Put your ducks in a row:
|
25
29
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
30
|
+
``` ruby
|
31
|
+
class Duck < ActiveRecord::Base
|
32
|
+
|
33
|
+
include RankedModel
|
34
|
+
ranks :row_order
|
35
|
+
|
36
|
+
end
|
37
|
+
```
|
32
38
|
|
33
39
|
This simple example assumes an integer column called `row_order`. To order Ducks by this order:
|
34
40
|
|
35
|
-
|
41
|
+
``` ruby
|
42
|
+
Duck.rank(:row_order).all
|
43
|
+
```
|
36
44
|
|
37
45
|
The ranking integers stored in the `row_order` column will be big and spaced apart. When you
|
38
46
|
implement a sorting UI, just update the resource with the position instead:
|
39
47
|
|
40
|
-
|
48
|
+
``` ruby
|
49
|
+
@duck.update_attribute :row_order_position, 0 # or 1, 2, 37. :first and :last are also valid
|
50
|
+
```
|
51
|
+
|
52
|
+
Position numbers begin at zero. A position number greater than the number of records acts the
|
53
|
+
same as :last.
|
41
54
|
|
42
55
|
So using a normal json controller where `@duck.attributes = params[:duck]; @duck.save`, JS can
|
43
56
|
look pretty elegant:
|
44
57
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
58
|
+
``` javascript
|
59
|
+
$.ajax({
|
60
|
+
type: 'PUT',
|
61
|
+
url: '/ducks',
|
62
|
+
dataType: 'json',
|
63
|
+
data: { duck: { row_order_position: 0 } }, // or whatever your new position is
|
64
|
+
});
|
65
|
+
```
|
51
66
|
|
52
67
|
Complex Use
|
53
68
|
-----------
|
54
69
|
|
55
70
|
The `ranks` method takes serveral arguments:
|
56
71
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
72
|
+
``` ruby
|
73
|
+
class Duck < ActiveRecord::Base
|
74
|
+
|
75
|
+
include RankedModel
|
76
|
+
|
77
|
+
ranks :row_order, # Name this ranker, used with rank()
|
78
|
+
:column => :sort_order # Override the default column, which defaults to the name
|
79
|
+
|
80
|
+
belongs_to :pond
|
81
|
+
ranks :swimming_order,
|
82
|
+
:with_same => :pond_id # Ducks belong_to Ponds, make the ranker scoped to one pond
|
83
|
+
|
84
|
+
scope :walking, where(:walking => true )
|
85
|
+
ranks :walking_order,
|
86
|
+
:scope => :walking # Narrow this ranker to a scope
|
87
|
+
|
88
|
+
end
|
89
|
+
```
|
73
90
|
|
74
91
|
When you make a query, add the rank:
|
75
92
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
93
|
+
``` ruby
|
94
|
+
Duck.rank(:row_order)
|
95
|
+
|
96
|
+
Pond.first.ducks.rank(:swimming_order)
|
97
|
+
|
98
|
+
Duck.walking.rank(:walking)
|
99
|
+
```
|
81
100
|
|
82
101
|
Internals
|
83
102
|
---------
|
@@ -96,10 +115,13 @@ Contributing
|
|
96
115
|
|
97
116
|
Fork, clone, write a test, write some code, commit, push, send a pull request. Github FTW!
|
98
117
|
|
99
|
-
|
118
|
+
RankedModel is mostly the handiwork of Matthew Beale:
|
119
|
+
|
120
|
+
* [madhatted.com](http://madhatted.com) is where I blog. Also [@mixonic](http://twitter.com/mixonic).
|
121
|
+
* [Spinto](http://www.spintoapp.com) is a product I'm bootstrapping.
|
100
122
|
|
101
123
|
A hearty thanks to these contributors:
|
102
124
|
|
125
|
+
* [Harvest](http://getharvest.com) where this Gem started. They are great, great folks.
|
103
126
|
* [yabawock](https://github.com/yabawock)
|
104
127
|
* [AndrewRadev](https://github.com/AndrewRadev/ranked-model)
|
105
|
-
|
data/lib/ranked-model.rb
CHANGED
data/lib/ranked-model/ranker.rb
CHANGED
@@ -94,19 +94,19 @@ module RankedModel
|
|
94
94
|
|
95
95
|
def update_index_from_position
|
96
96
|
case position
|
97
|
-
when :first
|
97
|
+
when :first, 'first'
|
98
98
|
if current_first && current_first.rank
|
99
99
|
rank_at( ( ( RankedModel::MIN_RANK_VALUE - current_first.rank ).to_f / 2 ).ceil + current_first.rank)
|
100
100
|
else
|
101
101
|
position_at :middle
|
102
102
|
end
|
103
|
-
when :last
|
103
|
+
when :last, 'last'
|
104
104
|
if current_last && current_last.rank
|
105
105
|
rank_at( ( ( RankedModel::MAX_RANK_VALUE - current_last.rank ).to_f / 2 ).ceil + current_last.rank )
|
106
106
|
else
|
107
107
|
position_at :middle
|
108
108
|
end
|
109
|
-
when :middle
|
109
|
+
when :middle, 'middle'
|
110
110
|
rank_at( ( ( RankedModel::MAX_RANK_VALUE - RankedModel::MIN_RANK_VALUE ).to_f / 2 ).ceil + RankedModel::MIN_RANK_VALUE )
|
111
111
|
when String
|
112
112
|
position_at position.to_i
|
@@ -137,19 +137,21 @@ module RankedModel
|
|
137
137
|
end
|
138
138
|
|
139
139
|
def rearrange_ranks
|
140
|
+
_scope = finder
|
141
|
+
unless instance.id.nil?
|
142
|
+
# Never update ourself, shift others around us.
|
143
|
+
_scope = _scope.where( instance.class.arel_table[:id].not_eq(instance.id) )
|
144
|
+
end
|
140
145
|
if current_first.rank && current_first.rank > RankedModel::MIN_RANK_VALUE && rank == RankedModel::MAX_RANK_VALUE
|
141
|
-
|
142
|
-
where( instance.class.arel_table[:id].not_eq(instance.id) ).
|
146
|
+
_scope.
|
143
147
|
where( instance.class.arel_table[ranker.column].lteq(rank) ).
|
144
148
|
update_all( "#{ranker.column} = #{ranker.column} - 1" )
|
145
149
|
elsif current_last.rank && current_last.rank < (RankedModel::MAX_RANK_VALUE - 1) && rank < current_last.rank
|
146
|
-
|
147
|
-
where( instance.class.arel_table[:id].not_eq(instance.id) ).
|
150
|
+
_scope.
|
148
151
|
where( instance.class.arel_table[ranker.column].gteq(rank) ).
|
149
152
|
update_all( "#{ranker.column} = #{ranker.column} + 1" )
|
150
153
|
elsif current_first.rank && current_first.rank > RankedModel::MIN_RANK_VALUE && rank > current_first.rank
|
151
|
-
|
152
|
-
where( instance.class.arel_table[:id].not_eq(instance.id) ).
|
154
|
+
_scope.
|
153
155
|
where( instance.class.arel_table[ranker.column].lt(rank) ).
|
154
156
|
update_all( "#{ranker.column} = #{ranker.column} - 1" )
|
155
157
|
rank_at( rank - 1 )
|
@@ -209,7 +211,7 @@ module RankedModel
|
|
209
211
|
_finder = _finder.where \
|
210
212
|
instance.class.arel_table[:id].not_eq(instance.id)
|
211
213
|
end
|
212
|
-
_finder.order(instance.class.arel_table[ranker.column].asc).select([:id, ranker.column])
|
214
|
+
_finder.order(instance.class.arel_table[ranker.column].asc).select([instance.class.arel_table[:id], instance.class.arel_table[ranker.column]])
|
213
215
|
end
|
214
216
|
end
|
215
217
|
|
data/lib/ranked-model/version.rb
CHANGED
@@ -286,6 +286,47 @@ describe Duck do
|
|
286
286
|
}
|
287
287
|
|
288
288
|
end
|
289
|
+
|
290
|
+
describe "at the end with string" do
|
291
|
+
|
292
|
+
before {
|
293
|
+
@ordered = Duck.rank(:row).where(Duck.arel_table[:id].not_eq @ducks[:wingy].id).collect {|duck| duck.id }
|
294
|
+
@ducks[:wingy].update_attribute :row_position, 'last'
|
295
|
+
}
|
296
|
+
|
297
|
+
context {
|
298
|
+
|
299
|
+
subject { Duck.ranker(:row).with(Duck.new).current_at_position(@ducks.size - 1).instance }
|
300
|
+
|
301
|
+
its(:id) { should == @ducks[:wingy].id }
|
302
|
+
|
303
|
+
}
|
304
|
+
|
305
|
+
context {
|
306
|
+
|
307
|
+
subject { Duck.rank(:row).last }
|
308
|
+
|
309
|
+
its(:id) { should == @ducks[:wingy].id }
|
310
|
+
|
311
|
+
}
|
312
|
+
|
313
|
+
context {
|
314
|
+
|
315
|
+
subject { Duck.ranker(:row).with(Duck.new).instance_eval { current_last }.instance }
|
316
|
+
|
317
|
+
its(:id) { should == @ducks[:wingy].id }
|
318
|
+
|
319
|
+
}
|
320
|
+
|
321
|
+
context {
|
322
|
+
|
323
|
+
subject { Duck.rank(:row).collect {|duck| duck.id } }
|
324
|
+
|
325
|
+
it { subject[0..-2].should == @ordered }
|
326
|
+
|
327
|
+
}
|
328
|
+
|
329
|
+
end
|
289
330
|
|
290
331
|
end
|
291
332
|
|
@@ -87,6 +87,32 @@ describe Duck do
|
|
87
87
|
|
88
88
|
end
|
89
89
|
|
90
|
+
describe "with max value and with_same pond" do
|
91
|
+
|
92
|
+
before {
|
93
|
+
Duck.first(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(Duck.arel_table[: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
|
+
|
90
116
|
describe "with min value" do
|
91
117
|
|
92
118
|
before {
|
metadata
CHANGED
@@ -1,118 +1,120 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: ranked-model
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 0
|
9
|
-
- 5
|
10
|
-
version: 0.0.5
|
11
6
|
platform: ruby
|
12
|
-
authors:
|
7
|
+
authors:
|
13
8
|
- Matthew Beale
|
14
9
|
autorequire:
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
requirement: &id001 !ruby/object:Gem::Requirement
|
12
|
+
date: 2012-06-26 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activerecord
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
23
17
|
none: false
|
24
|
-
requirements:
|
25
|
-
- -
|
26
|
-
- !ruby/object:Gem::Version
|
27
|
-
hash: 1
|
28
|
-
segments:
|
29
|
-
- 3
|
30
|
-
- 0
|
31
|
-
- 3
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
32
21
|
version: 3.0.3
|
33
22
|
type: :runtime
|
34
|
-
version_requirements: *id001
|
35
|
-
name: activerecord
|
36
|
-
- !ruby/object:Gem::Dependency
|
37
23
|
prerelease: false
|
38
|
-
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
39
25
|
none: false
|
40
|
-
requirements:
|
41
|
-
- -
|
42
|
-
- !ruby/object:Gem::Version
|
43
|
-
|
44
|
-
|
45
|
-
- 0
|
46
|
-
version: "0"
|
47
|
-
type: :development
|
48
|
-
version_requirements: *id002
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 3.0.3
|
30
|
+
- !ruby/object:Gem::Dependency
|
49
31
|
name: rspec
|
50
|
-
|
51
|
-
prerelease: false
|
52
|
-
requirement: &id003 !ruby/object:Gem::Requirement
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
53
33
|
none: false
|
54
|
-
requirements:
|
55
|
-
- -
|
56
|
-
- !ruby/object:Gem::Version
|
57
|
-
|
58
|
-
segments:
|
59
|
-
- 0
|
60
|
-
version: "0"
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
61
38
|
type: :development
|
62
|
-
version_requirements: *id003
|
63
|
-
name: rspec-rails
|
64
|
-
- !ruby/object:Gem::Dependency
|
65
39
|
prerelease: false
|
66
|
-
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec-rails
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
67
49
|
none: false
|
68
|
-
requirements:
|
69
|
-
- -
|
70
|
-
- !ruby/object:Gem::Version
|
71
|
-
|
72
|
-
segments:
|
73
|
-
- 0
|
74
|
-
version: "0"
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
75
54
|
type: :development
|
76
|
-
version_requirements: *id004
|
77
|
-
name: sqlite3
|
78
|
-
- !ruby/object:Gem::Dependency
|
79
55
|
prerelease: false
|
80
|
-
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
81
57
|
none: false
|
82
|
-
requirements:
|
83
|
-
- -
|
84
|
-
- !ruby/object:Gem::Version
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: sqlite3
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
89
70
|
type: :development
|
90
|
-
version_requirements: *id005
|
91
|
-
name: genspec
|
92
|
-
- !ruby/object:Gem::Dependency
|
93
71
|
prerelease: false
|
94
|
-
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
95
73
|
none: false
|
96
|
-
requirements:
|
97
|
-
- -
|
98
|
-
- !ruby/object:Gem::Version
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: genspec
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
103
86
|
type: :development
|
104
|
-
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
105
95
|
name: mocha
|
106
|
-
|
107
|
-
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
description: ranked-model is a modern row sorting library built for Rails 3. It uses
|
111
|
+
ARel aggressively and is better optimized than most other libraries.
|
112
|
+
email:
|
108
113
|
- matt.beale@madhatted.com
|
109
114
|
executables: []
|
110
|
-
|
111
115
|
extensions: []
|
112
|
-
|
113
116
|
extra_rdoc_files: []
|
114
|
-
|
115
|
-
files:
|
117
|
+
files:
|
116
118
|
- .gitignore
|
117
119
|
- .rspec
|
118
120
|
- Gemfile
|
@@ -137,38 +139,35 @@ files:
|
|
137
139
|
- tmp/.gitignore
|
138
140
|
homepage: https://github.com/harvesthq/ranked-model
|
139
141
|
licenses: []
|
140
|
-
|
141
142
|
post_install_message:
|
142
143
|
rdoc_options: []
|
143
|
-
|
144
|
-
require_paths:
|
144
|
+
require_paths:
|
145
145
|
- lib
|
146
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
146
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
147
147
|
none: false
|
148
|
-
requirements:
|
149
|
-
- -
|
150
|
-
- !ruby/object:Gem::Version
|
151
|
-
|
152
|
-
segments:
|
148
|
+
requirements:
|
149
|
+
- - ! '>='
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: '0'
|
152
|
+
segments:
|
153
153
|
- 0
|
154
|
-
|
155
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
154
|
+
hash: -3628524900195041491
|
155
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
156
156
|
none: false
|
157
|
-
requirements:
|
158
|
-
- -
|
159
|
-
- !ruby/object:Gem::Version
|
160
|
-
|
161
|
-
segments:
|
157
|
+
requirements:
|
158
|
+
- - ! '>='
|
159
|
+
- !ruby/object:Gem::Version
|
160
|
+
version: '0'
|
161
|
+
segments:
|
162
162
|
- 0
|
163
|
-
|
163
|
+
hash: -3628524900195041491
|
164
164
|
requirements: []
|
165
|
-
|
166
165
|
rubyforge_project:
|
167
|
-
rubygems_version: 1.8.
|
166
|
+
rubygems_version: 1.8.24
|
168
167
|
signing_key:
|
169
168
|
specification_version: 3
|
170
169
|
summary: An acts_as_sortable replacement built for Rails 3
|
171
|
-
test_files:
|
170
|
+
test_files:
|
172
171
|
- spec/duck-model/duck_spec.rb
|
173
172
|
- spec/duck-model/lots_of_ducks_spec.rb
|
174
173
|
- spec/duck-model/wrong_ducks_spec.rb
|