ranked-model-rails2 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.org/mixonic/ranked-model.png)](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
|