ranked-model 0.0.5 → 0.1.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/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
|