ranked-model-rails2 0.4.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.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/.rbenv-vars +1 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +82 -0
- data/Gemfile +32 -0
- data/LICENSE +19 -0
- data/Rakefile +7 -0
- data/Readme.mkd +205 -0
- data/lib/ranked-model.rb +69 -0
- data/lib/ranked-model/railtie.rb +14 -0
- data/lib/ranked-model/ranker.rb +327 -0
- data/lib/ranked-model/version.rb +3 -0
- data/rails/init.rb +1 -0
- data/ranked-model-rails2.gemspec +29 -0
- data/spec/duck-model/duck_spec.rb +770 -0
- data/spec/duck-model/lots_of_ducks_spec.rb +169 -0
- data/spec/duck-model/wrong_ducks_spec.rb +36 -0
- data/spec/ego-model/ego_spec.rb +36 -0
- data/spec/player-model/records_already_exist_spec.rb +22 -0
- data/spec/ranked-model/ranker_spec.rb +64 -0
- data/spec/ranked-model/version_spec.rb +7 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/sti-model/element_spec.rb +122 -0
- data/spec/sti-model/vehicle_spec.rb +75 -0
- data/spec/support/active_record.rb +150 -0
- data/spec/support/database.yml +30 -0
- metadata +171 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 279b6761e2dd5ecf3adff73676890df2d2a16ad5
|
4
|
+
data.tar.gz: 296a49802a22f29242ff84159f77c20bdc809064
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 33bc41b5ac0209bd59d9220b489622a93663abd11156eeda31ccb3705b54f433f33924f1e86a28046d5024e90475fb51ef555913418052b0d284a45d9ed96ab7
|
7
|
+
data.tar.gz: 428bde2434df014565a085c1abcb1e6020059eb546f91fdc027c58a52ce0bcbe77d017f98a54d231799c437f307337aa709bd5383998aef91f77d2897c093fdd
|
data/.gitignore
ADDED
data/.rbenv-vars
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
JRUBY_OPTS=--1.8
|
data/.rspec
ADDED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ranked-model
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
jruby-1.7.11
|
data/.travis.yml
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
language: ruby
|
2
|
+
before_script:
|
3
|
+
- mysql -e 'create database ranked_model_test;'
|
4
|
+
- psql -c 'create database ranked_model_test;' -U postgres
|
5
|
+
rvm:
|
6
|
+
- "1.9.2"
|
7
|
+
- "1.9.3"
|
8
|
+
- "2.0.0"
|
9
|
+
- "2.1.0"
|
10
|
+
- "jruby-19mode"
|
11
|
+
- "rbx"
|
12
|
+
env:
|
13
|
+
- "ACTIVERECORD_VERSION=4.1.0.beta1"
|
14
|
+
- "ACTIVERECORD_VERSION=4.1.0.beta1 DB=mysql_travis"
|
15
|
+
- "ACTIVERECORD_VERSION=4.1.0.beta1 DB=postgresql_travis"
|
16
|
+
- "ACTIVERECORD_VERSION=4.0.2"
|
17
|
+
- "ACTIVERECORD_VERSION=4.0.2 DB=mysql_travis"
|
18
|
+
- "ACTIVERECORD_VERSION=4.0.2 DB=postgresql_travis"
|
19
|
+
- "ACTIVERECORD_VERSION=3.2.16"
|
20
|
+
- "ACTIVERECORD_VERSION=3.2.16 DB=mysql_travis"
|
21
|
+
- "ACTIVERECORD_VERSION=3.2.16 DB=postgresql_travis"
|
22
|
+
- "ACTIVERECORD_VERSION=3.1.12"
|
23
|
+
- "ACTIVERECORD_VERSION=3.1.12 DB=mysql_travis"
|
24
|
+
- "ACTIVERECORD_VERSION=3.1.12 DB=postgresql_travis"
|
25
|
+
- "ACTIVERECORD_VERSION=master"
|
26
|
+
- "ACTIVERECORD_VERSION=master DB=mysql_travis"
|
27
|
+
- "ACTIVERECORD_VERSION=master DB=postgresql_travis"
|
28
|
+
matrix:
|
29
|
+
exclude:
|
30
|
+
- env: "ACTIVERECORD_VERSION=4.1.0.beta1"
|
31
|
+
rvm: "1.9.2"
|
32
|
+
- env: "ACTIVERECORD_VERSION=4.1.0.beta1 DB=mysql_travis"
|
33
|
+
rvm: "1.9.2"
|
34
|
+
- env: "ACTIVERECORD_VERSION=4.1.0.beta1 DB=postgresql_travis"
|
35
|
+
rvm: "1.9.2"
|
36
|
+
- env: "ACTIVERECORD_VERSION=4.1.0.beta1"
|
37
|
+
rvm: "1.9.3"
|
38
|
+
- env: "ACTIVERECORD_VERSION=4.1.0.beta1 DB=mysql_travis"
|
39
|
+
rvm: "1.9.3"
|
40
|
+
- env: "ACTIVERECORD_VERSION=4.1.0.beta1 DB=postgresql_travis"
|
41
|
+
rvm: "1.9.3"
|
42
|
+
- env: "ACTIVERECORD_VERSION=4.1.0.beta1"
|
43
|
+
rvm: "jruby-19mode"
|
44
|
+
- env: "ACTIVERECORD_VERSION=4.1.0.beta1 DB=mysql_travis"
|
45
|
+
rvm: "jruby-19mode"
|
46
|
+
- env: "ACTIVERECORD_VERSION=4.1.0.beta1 DB=postgresql_travis"
|
47
|
+
rvm: "jruby-19mode"
|
48
|
+
- env: "ACTIVERECORD_VERSION=master"
|
49
|
+
rvm: "jruby-19mode"
|
50
|
+
- env: "ACTIVERECORD_VERSION=master DB=mysql_travis"
|
51
|
+
rvm: "jruby-19mode"
|
52
|
+
- env: "ACTIVERECORD_VERSION=master DB=postgresql_travis"
|
53
|
+
rvm: "jruby-19mode"
|
54
|
+
- env: "ACTIVERECORD_VERSION=4.0.2"
|
55
|
+
rvm: "1.9.2"
|
56
|
+
- env: "ACTIVERECORD_VERSION=4.0.2 DB=mysql_travis"
|
57
|
+
rvm: "1.9.2"
|
58
|
+
- env: "ACTIVERECORD_VERSION=4.0.2 DB=postgresql_travis"
|
59
|
+
rvm: "1.9.2"
|
60
|
+
- env: "ACTIVERECORD_VERSION=master"
|
61
|
+
rvm: "1.9.2"
|
62
|
+
- env: "ACTIVERECORD_VERSION=master DB=mysql_travis"
|
63
|
+
rvm: "1.9.2"
|
64
|
+
- env: "ACTIVERECORD_VERSION=master DB=postgresql_travis"
|
65
|
+
rvm: "1.9.2"
|
66
|
+
- env: "ACTIVERECORD_VERSION=master"
|
67
|
+
rvm: "1.9.3"
|
68
|
+
- env: "ACTIVERECORD_VERSION=master DB=mysql_travis"
|
69
|
+
rvm: "1.9.3"
|
70
|
+
- env: "ACTIVERECORD_VERSION=master DB=postgresql_travis"
|
71
|
+
rvm: "1.9.3"
|
72
|
+
allow_failures:
|
73
|
+
# Master may or may not pass
|
74
|
+
- env: "ACTIVERECORD_VERSION=master"
|
75
|
+
- env: "ACTIVERECORD_VERSION=master DB=mysql_travis"
|
76
|
+
- env: "ACTIVERECORD_VERSION=master DB=postgresql_travis"
|
77
|
+
# Rails 3.1 is incompatible with database cleaner 1.2
|
78
|
+
- env: "ACTIVERECORD_VERSION=3.1.12 DB=mysql_travis"
|
79
|
+
- env: "ACTIVERECORD_VERSION=3.1.12 DB=postgresql_travis"
|
80
|
+
# Postgres is not supported before Rails 4.0.
|
81
|
+
- env: "ACTIVERECORD_VERSION=3.1.12 DB=postgresql_travis"
|
82
|
+
- env: "ACTIVERECORD_VERSION=3.2.16 DB=postgresql_travis"
|
data/Gemfile
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
source "https://rubygems.org"
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in ranked-model.gemspec
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
ar_version = ENV["ACTIVERECORD_VERSION"] || "default"
|
7
|
+
|
8
|
+
ar_gem_version = case ar_version
|
9
|
+
when "master"
|
10
|
+
gem "activerecord", {:github => "rails/rails"}
|
11
|
+
when "default"
|
12
|
+
# Allow the gemspec to specify
|
13
|
+
else
|
14
|
+
gem "activerecord", "~> #{ar_version}"
|
15
|
+
end
|
16
|
+
|
17
|
+
platforms :rbx do
|
18
|
+
gem 'rubysl', '~> 2.0'
|
19
|
+
gem 'rubinius-developer_tools'
|
20
|
+
end
|
21
|
+
|
22
|
+
# SQLite
|
23
|
+
gem "activerecord-jdbcsqlite3-adapter", ">= 1.3.0", :platforms => :jruby
|
24
|
+
gem "sqlite3", :platforms => :ruby
|
25
|
+
|
26
|
+
# Postgres
|
27
|
+
gem "activerecord-jdbcpostgresql-adapter", :platforms => :jruby
|
28
|
+
gem "pg", :platforms => :ruby
|
29
|
+
|
30
|
+
# MySQL
|
31
|
+
gem "activerecord-jdbcmysql-adapter", :platforms => :jruby
|
32
|
+
gem "mysql", :platforms => :ruby
|
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2011 Iridesco LLC (support@harvestapp.com)
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/Rakefile
ADDED
data/Readme.mkd
ADDED
@@ -0,0 +1,205 @@
|
|
1
|
+
**ranked-model** is a modern row sorting library built for Rails 3 & 4. It uses ARel aggressively and is better optimized than most other libraries.
|
2
|
+
|
3
|
+
[](https://travis-ci.org/mixonic/ranked-model)
|
4
|
+
|
5
|
+
Installation
|
6
|
+
------------
|
7
|
+
|
8
|
+
ranked-model passes specs with Rails 3.1, 3.2, 4.0, and 4.1-beta for MySQL, Postgres, and SQLite on Ruby 1.9.2, 1.9.3, 2.0, 2.1, jruby-19mode, and rubinius where Rails supports the platform. This is with the exception of Postgres before Rails 4.0 on all platforms, which is unsupported (I'd gladly accept a PR to fix this).
|
9
|
+
|
10
|
+
TL;DR, if you are using Rails 4 and up you are 100% good to go. Before Rails 4, be wary of Postgres.
|
11
|
+
|
12
|
+
To install ranked-model, just add it to your `Gemfile`:
|
13
|
+
|
14
|
+
``` ruby
|
15
|
+
gem 'ranked-model'
|
16
|
+
|
17
|
+
# Or pin ranked-model to git
|
18
|
+
# gem 'ranked-model',
|
19
|
+
# :git => 'git@github.com:mixonic/ranked-model.git'
|
20
|
+
```
|
21
|
+
|
22
|
+
Then use `bundle install` to update your `Gemfile.lock`.
|
23
|
+
|
24
|
+
Simple Use
|
25
|
+
----------
|
26
|
+
|
27
|
+
Use of ranked-model is straight ahead. Get some ducks:
|
28
|
+
|
29
|
+
``` ruby
|
30
|
+
class Duck < ActiveRecord::Base
|
31
|
+
end
|
32
|
+
```
|
33
|
+
|
34
|
+
Put your ducks in a row:
|
35
|
+
|
36
|
+
``` ruby
|
37
|
+
class Duck < ActiveRecord::Base
|
38
|
+
|
39
|
+
include RankedModel
|
40
|
+
ranks :row_order
|
41
|
+
|
42
|
+
end
|
43
|
+
```
|
44
|
+
|
45
|
+
This simple example assumes an integer column called `row_order`. To order Ducks by this order:
|
46
|
+
|
47
|
+
``` ruby
|
48
|
+
Duck.rank(:row_order).all
|
49
|
+
```
|
50
|
+
|
51
|
+
The ranking integers stored in the `row_order` column will be big and spaced apart. When you
|
52
|
+
implement a sorting UI, just update the resource by appending the column name with `_position` and indicating the desired position:
|
53
|
+
|
54
|
+
``` ruby
|
55
|
+
@duck.update_attribute :row_order_position, 0 # or 1, 2, 37. :first, :last, :up and :down are also valid
|
56
|
+
```
|
57
|
+
|
58
|
+
Position numbers begin at zero. A position number greater than the number of records acts the
|
59
|
+
same as :last. :up and :down move the record up/down the ladder by one step.
|
60
|
+
|
61
|
+
So using a normal json controller where `@duck.attributes = params[:duck]; @duck.save`, JS can
|
62
|
+
look pretty elegant:
|
63
|
+
|
64
|
+
``` javascript
|
65
|
+
$.ajax({
|
66
|
+
type: 'PUT',
|
67
|
+
url: '/ducks',
|
68
|
+
dataType: 'json',
|
69
|
+
data: { duck: { row_order_position: 0 } }, // or whatever your new position is
|
70
|
+
});
|
71
|
+
```
|
72
|
+
|
73
|
+
Complex Use
|
74
|
+
-----------
|
75
|
+
|
76
|
+
The `ranks` method takes serveral arguments:
|
77
|
+
|
78
|
+
``` ruby
|
79
|
+
class Duck < ActiveRecord::Base
|
80
|
+
|
81
|
+
include RankedModel
|
82
|
+
|
83
|
+
ranks :row_order, # Name this ranker, used with rank()
|
84
|
+
:column => :sort_order # Override the default column, which defaults to the name
|
85
|
+
|
86
|
+
belongs_to :pond
|
87
|
+
ranks :swimming_order,
|
88
|
+
:with_same => :pond_id # Ducks belong_to Ponds, make the ranker scoped to one pond
|
89
|
+
|
90
|
+
scope :walking, where(:walking => true )
|
91
|
+
ranks :walking_order,
|
92
|
+
:scope => :walking # Narrow this ranker to a scope
|
93
|
+
|
94
|
+
end
|
95
|
+
```
|
96
|
+
|
97
|
+
When you make a query, add the rank:
|
98
|
+
|
99
|
+
``` ruby
|
100
|
+
Duck.rank(:row_order)
|
101
|
+
|
102
|
+
Pond.first.ducks.rank(:swimming_order)
|
103
|
+
|
104
|
+
Duck.walking.rank(:walking)
|
105
|
+
```
|
106
|
+
|
107
|
+
Single Table Inheritance (STI)
|
108
|
+
------------------------------
|
109
|
+
|
110
|
+
ranked-model scopes your records' positions based on the class name of the object. If you have
|
111
|
+
a STI `type` column set in your model, ranked-model will reference that class for positioning.
|
112
|
+
|
113
|
+
Consider this example:
|
114
|
+
|
115
|
+
``` ruby
|
116
|
+
class Vehicle < ActiveRecord::Base
|
117
|
+
ranks :row_order
|
118
|
+
end
|
119
|
+
|
120
|
+
class Car < Vehicle
|
121
|
+
end
|
122
|
+
|
123
|
+
class Truck < Vehicle
|
124
|
+
end
|
125
|
+
|
126
|
+
car = Car.create!
|
127
|
+
truck = Truck.create!
|
128
|
+
|
129
|
+
car.row_order
|
130
|
+
=> 0
|
131
|
+
truck.row_order
|
132
|
+
=> 0
|
133
|
+
```
|
134
|
+
|
135
|
+
In this example, the `row_order` for both `car` and `truck` will be set to `0` because they have
|
136
|
+
different class names (`Car` and `Truck`, respectively).
|
137
|
+
|
138
|
+
If you would like for both `car` and `truck` to be ranked together based on the base `Vehicle`
|
139
|
+
class instead, use the `class_name` option:
|
140
|
+
|
141
|
+
``` ruby
|
142
|
+
class Vehicle < ActiveRecord::Base
|
143
|
+
ranks :row_order, class_name: 'Vehicle'
|
144
|
+
end
|
145
|
+
|
146
|
+
class Car < Vehicle
|
147
|
+
end
|
148
|
+
|
149
|
+
class Truck < Vehicle
|
150
|
+
end
|
151
|
+
|
152
|
+
car = Car.create!
|
153
|
+
truck = Truck.create!
|
154
|
+
|
155
|
+
car.row_order
|
156
|
+
=> 0
|
157
|
+
truck.row_order
|
158
|
+
=> 4194304
|
159
|
+
```
|
160
|
+
|
161
|
+
Internals
|
162
|
+
---------
|
163
|
+
|
164
|
+
This library is written using ARel from the ground-up. This leaves the code much cleaner
|
165
|
+
than many implementations. ranked-model is also optimized to write to the database as little
|
166
|
+
as possible: ranks are stored as a number between -8388607 and 8388607 (the MEDIUMINT range in MySQL).
|
167
|
+
When an item is given a new position, it assigns itself a rank number between two neighbors.
|
168
|
+
This allows several movements of items before no digits are available between two neighbors. When
|
169
|
+
this occurs, ranked-model will try to shift other records out of the way. If items can't be easily
|
170
|
+
shifted anymore, it will rebalance the distribution of rank numbers across all members
|
171
|
+
of the ranked group.
|
172
|
+
|
173
|
+
Contributing
|
174
|
+
------------
|
175
|
+
|
176
|
+
Fork, clone, write a test, write some code, commit, push, send a pull request. Github FTW!
|
177
|
+
|
178
|
+
The specs can be run with sqlite, postgres, and mysql:
|
179
|
+
|
180
|
+
```
|
181
|
+
DB=postgres bundle exec rake
|
182
|
+
```
|
183
|
+
|
184
|
+
Is no DB is specified, the tests run against sqlite.
|
185
|
+
|
186
|
+
RankedModel is mostly the handiwork of Matthew Beale:
|
187
|
+
|
188
|
+
* [madhatted.com](http://madhatted.com) is where I blog. Also [@mixonic](http://twitter.com/mixonic).
|
189
|
+
|
190
|
+
A hearty thanks to these contributors:
|
191
|
+
|
192
|
+
* [Harvest](http://getharvest.com) where this Gem started. They are great, great folks.
|
193
|
+
* [yabawock](https://github.com/yabawock)
|
194
|
+
* [AndrewRadev](https://github.com/AndrewRadev)
|
195
|
+
* [adheerajkumar](https://github.com/adheerajkumar)
|
196
|
+
* [mikeycgto](https://github.com/mikeycgto)
|
197
|
+
* [robotex82](https://github.com/robotex82)
|
198
|
+
* [rociiu](https://github.com/rociiu)
|
199
|
+
* [codepodu](https://github.com/codepodu)
|
200
|
+
* [kakra](https://github.com/kakra)
|
201
|
+
* [metalon](https://github.com/metalon)
|
202
|
+
* [jamesalmond](https://github.com/jamesalmond)
|
203
|
+
* [jguyon](https://github.com/jguyon)
|
204
|
+
* [pehrlich](https://github.com/pehrlich)
|
205
|
+
* [petergoldstein](https://github.com/petergoldstein)
|
data/lib/ranked-model.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require File.dirname(__FILE__)+'/ranked-model/ranker'
|
2
|
+
require File.dirname(__FILE__)+'/ranked-model/railtie' if defined?(Rails::Railtie)
|
3
|
+
|
4
|
+
require "active_record" unless defined?(ActiveRecord::Base)
|
5
|
+
require "fake_arel"
|
6
|
+
|
7
|
+
module RankedModel
|
8
|
+
|
9
|
+
# Signed MEDIUMINT in MySQL
|
10
|
+
#
|
11
|
+
MAX_RANK_VALUE = 8388607
|
12
|
+
MIN_RANK_VALUE = -8388607
|
13
|
+
|
14
|
+
def self.included base
|
15
|
+
|
16
|
+
base.class_eval do
|
17
|
+
class_attribute :rankers
|
18
|
+
|
19
|
+
extend RankedModel::ClassMethods
|
20
|
+
|
21
|
+
before_save :handle_ranking
|
22
|
+
|
23
|
+
scope :rank, lambda { |name|
|
24
|
+
order ranker(name.to_sym).column
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def handle_ranking
|
33
|
+
self.class.rankers.each do |ranker|
|
34
|
+
ranker.with(self).handle_ranking
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
module ClassMethods
|
39
|
+
|
40
|
+
def q(column_name)
|
41
|
+
connection.quote_column_name(column_name)
|
42
|
+
end
|
43
|
+
|
44
|
+
def ranker name
|
45
|
+
rankers.find do |ranker|
|
46
|
+
ranker.name == name
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def ranks *args
|
53
|
+
self.rankers ||= []
|
54
|
+
ranker = RankedModel::Ranker.new(*args)
|
55
|
+
self.rankers << ranker
|
56
|
+
attr_reader "#{ranker.name}_position"
|
57
|
+
define_method "#{ranker.name}_position=" do |position|
|
58
|
+
if position.present?
|
59
|
+
send "#{ranker.column}_will_change!"
|
60
|
+
instance_variable_set "@#{ranker.name}_position", position
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
public "#{ranker.name}_position", "#{ranker.name}_position="
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|