ranked-model 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +81 -0
- data/LICENSE +19 -0
- data/Rakefile +7 -0
- data/Readme.mkd +98 -0
- data/lib/ranked-model.rb +53 -0
- data/lib/ranked-model/railtie.rb +14 -0
- data/lib/ranked-model/ranker.rb +153 -0
- data/lib/ranked-model/version.rb +3 -0
- data/rails/init.rb +1 -0
- data/ranked-model.gemspec +28 -0
- data/spec/duck-model/duck_spec.rb +127 -0
- data/spec/ranked-model/ranker_spec.rb +18 -0
- data/spec/ranked-model/version_spec.rb +7 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/support/active_record.rb +32 -0
- data/spec/support/database.yml +5 -0
- metadata +177 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
ranked-model (0.0.1)
|
5
|
+
activerecord (>= 3.0.3)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: http://rubygems.org/
|
9
|
+
specs:
|
10
|
+
abstract (1.0.0)
|
11
|
+
actionpack (3.0.3)
|
12
|
+
activemodel (= 3.0.3)
|
13
|
+
activesupport (= 3.0.3)
|
14
|
+
builder (~> 2.1.2)
|
15
|
+
erubis (~> 2.6.6)
|
16
|
+
i18n (~> 0.4)
|
17
|
+
rack (~> 1.2.1)
|
18
|
+
rack-mount (~> 0.6.13)
|
19
|
+
rack-test (~> 0.5.6)
|
20
|
+
tzinfo (~> 0.3.23)
|
21
|
+
activemodel (3.0.3)
|
22
|
+
activesupport (= 3.0.3)
|
23
|
+
builder (~> 2.1.2)
|
24
|
+
i18n (~> 0.4)
|
25
|
+
activerecord (3.0.3)
|
26
|
+
activemodel (= 3.0.3)
|
27
|
+
activesupport (= 3.0.3)
|
28
|
+
arel (~> 2.0.2)
|
29
|
+
tzinfo (~> 0.3.23)
|
30
|
+
activesupport (3.0.3)
|
31
|
+
arel (2.0.7)
|
32
|
+
builder (2.1.2)
|
33
|
+
diff-lcs (1.1.2)
|
34
|
+
erubis (2.6.6)
|
35
|
+
abstract (>= 1.0.0)
|
36
|
+
genspec (0.1.1)
|
37
|
+
rspec
|
38
|
+
sc-core-ext (>= 1.2.0)
|
39
|
+
i18n (0.5.0)
|
40
|
+
mocha (0.9.10)
|
41
|
+
rake
|
42
|
+
rack (1.2.1)
|
43
|
+
rack-mount (0.6.13)
|
44
|
+
rack (>= 1.0.0)
|
45
|
+
rack-test (0.5.7)
|
46
|
+
rack (>= 1.0)
|
47
|
+
railties (3.0.3)
|
48
|
+
actionpack (= 3.0.3)
|
49
|
+
activesupport (= 3.0.3)
|
50
|
+
rake (>= 0.8.7)
|
51
|
+
thor (~> 0.14.4)
|
52
|
+
rake (0.8.7)
|
53
|
+
rspec (2.4.0)
|
54
|
+
rspec-core (~> 2.4.0)
|
55
|
+
rspec-expectations (~> 2.4.0)
|
56
|
+
rspec-mocks (~> 2.4.0)
|
57
|
+
rspec-core (2.4.0)
|
58
|
+
rspec-expectations (2.4.0)
|
59
|
+
diff-lcs (~> 1.1.2)
|
60
|
+
rspec-mocks (2.4.0)
|
61
|
+
rspec-rails (2.4.1)
|
62
|
+
actionpack (~> 3.0)
|
63
|
+
activesupport (~> 3.0)
|
64
|
+
railties (~> 3.0)
|
65
|
+
rspec (~> 2.4.0)
|
66
|
+
sc-core-ext (1.2.1)
|
67
|
+
activesupport (>= 2.3.5)
|
68
|
+
sqlite3 (1.3.3)
|
69
|
+
thor (0.14.6)
|
70
|
+
tzinfo (0.3.24)
|
71
|
+
|
72
|
+
PLATFORMS
|
73
|
+
ruby
|
74
|
+
|
75
|
+
DEPENDENCIES
|
76
|
+
genspec
|
77
|
+
mocha
|
78
|
+
ranked-model!
|
79
|
+
rspec
|
80
|
+
rspec-rails
|
81
|
+
sqlite3
|
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,98 @@
|
|
1
|
+
**ranked-model** is a modern row sorting library built for Rails 3. It uses ARel aggressivly and is better optimized than most other libraries.
|
2
|
+
|
3
|
+
Installation
|
4
|
+
------------
|
5
|
+
|
6
|
+
To install ranked-model, just add it to your `Gemfile`:
|
7
|
+
|
8
|
+
gem 'ranked-model'
|
9
|
+
|
10
|
+
# Or pin ranked-model to git
|
11
|
+
# gem 'ranked-model',
|
12
|
+
# :git => 'git@github.com:harvesthq/ranked-model.git'
|
13
|
+
|
14
|
+
Then use `bundle install` to update your `Gemfile.lock`.
|
15
|
+
|
16
|
+
Simple Use
|
17
|
+
----------
|
18
|
+
|
19
|
+
Use of ranked-model is straight ahead. Get some ducks:
|
20
|
+
|
21
|
+
class Duck < ActiveRecord::Base
|
22
|
+
end
|
23
|
+
|
24
|
+
Put your ducks in a row:
|
25
|
+
|
26
|
+
class Duck < ActiveRecord::Base
|
27
|
+
|
28
|
+
include RankedModel
|
29
|
+
ranks :row_order
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
This simple example assumes an integer column called `row_order`. To order Ducks by this order:
|
34
|
+
|
35
|
+
Duck.rank(:row_order).all
|
36
|
+
|
37
|
+
The ranking integers stored in the `raw_order` column will be big and spaced apart. When you
|
38
|
+
implement a sorting UI, just update the resource with the position instead:
|
39
|
+
|
40
|
+
@duck.update_attribute :row_order_position, 0 # or 1, 2, 37. :first and :last are also valid
|
41
|
+
|
42
|
+
So using a normal json controller where `@duck.attributes = params[:duck]; @duck.save`, JS can
|
43
|
+
look pretty elegant:
|
44
|
+
|
45
|
+
$.ajax({
|
46
|
+
type: 'PUT',
|
47
|
+
url: '/ducks',
|
48
|
+
dataType: 'json',
|
49
|
+
data: { duck: { row_order_position: 0 } }, // or whatever your new position is
|
50
|
+
});
|
51
|
+
|
52
|
+
Complex Use
|
53
|
+
-----------
|
54
|
+
|
55
|
+
The `ranks` method takes serveral arguments:
|
56
|
+
|
57
|
+
class Duck < ActiveRecord::Base
|
58
|
+
|
59
|
+
include RankedModel
|
60
|
+
|
61
|
+
ranks :row_order, # Name this ranker, used with rank()
|
62
|
+
:column => :sort_order # Override the default column, which defaults to the name
|
63
|
+
|
64
|
+
belongs_to :pond
|
65
|
+
ranks :swimming_order,
|
66
|
+
:with_same => :pond_id # Ducks belong_to Ponds, make the ranker scoped to one pond
|
67
|
+
|
68
|
+
scope :walking, where(:walking => true )
|
69
|
+
ranks :walking_order,
|
70
|
+
:scope => :walking # Narrow this ranker to a scope
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
When you make a query, add the rank:
|
75
|
+
|
76
|
+
Duck.rank(:row_order)
|
77
|
+
|
78
|
+
Pond.first.ducks.rank(:swimming_order)
|
79
|
+
|
80
|
+
Duck.walking.rank(:walking)
|
81
|
+
|
82
|
+
Internals
|
83
|
+
---------
|
84
|
+
|
85
|
+
This libarary is written using ARel from the ground-up. This leaves the code much cleaner
|
86
|
+
than many implementations. ranked-model is also optimized to write to the database as little
|
87
|
+
as possible: ranks are stored as a number between 0 and 65534 (just below MEDIUMINT in MySQL).
|
88
|
+
When an item is given a new position, it assigns itself a rank number between two neighbors.
|
89
|
+
This allows several movements of items before no digits are available between to neighbors. When
|
90
|
+
this occurs, ranked-model will rebalance the distribution of rank numbers across all memebers
|
91
|
+
of the ranked group.
|
92
|
+
|
93
|
+
Contributing
|
94
|
+
------------
|
95
|
+
|
96
|
+
Fork, clone, write a test, write some code, commit, push, send a pull request. Github FTW!
|
97
|
+
|
98
|
+
This project was open-sourced by [Harvest](http://getharvest.com/). [We're hiring!](http://www.getharvest.com/careers)
|
data/lib/ranked-model.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require File.dirname(__FILE__)+'/ranked-model/ranker'
|
2
|
+
require File.dirname(__FILE__)+'/ranked-model/railtie' if defined?(Rails::Railtie)
|
3
|
+
|
4
|
+
module RankedModel
|
5
|
+
|
6
|
+
# Signed MEDIUMINT in MySQL
|
7
|
+
#
|
8
|
+
MAX_RANK_VALUE = 65534
|
9
|
+
|
10
|
+
def self.included base
|
11
|
+
|
12
|
+
base.class_eval do
|
13
|
+
cattr_accessor :rankers
|
14
|
+
|
15
|
+
extend RankedModel::ClassMethods
|
16
|
+
|
17
|
+
before_save :handle_ranking
|
18
|
+
|
19
|
+
scope :rank, lambda { |name|
|
20
|
+
order arel_table[ ranker(name.to_sym).column ]
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def handle_ranking
|
29
|
+
self.class.rankers.each do |ranker|
|
30
|
+
ranker.with(self).handle_ranking
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
module ClassMethods
|
35
|
+
|
36
|
+
def ranker name
|
37
|
+
rankers.find do |ranker|
|
38
|
+
ranker.name == name
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def ranks *args
|
45
|
+
self.rankers ||= []
|
46
|
+
ranker = RankedModel::Ranker.new(*args)
|
47
|
+
self.rankers << ranker
|
48
|
+
attr_accessor "#{ranker.name}_position"
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
module RankedModel
|
2
|
+
|
3
|
+
class Ranker
|
4
|
+
attr_accessor :name, :column, :scope, :with_same
|
5
|
+
|
6
|
+
def initialize name, options={}
|
7
|
+
self.name = name.to_sym
|
8
|
+
self.column = options[:column] || name
|
9
|
+
|
10
|
+
[ :scope, :with_same ].each do |key|
|
11
|
+
self.send "#{key}=", options[key]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def with instance
|
16
|
+
Mapper.new self, instance
|
17
|
+
end
|
18
|
+
|
19
|
+
class Mapper
|
20
|
+
attr_accessor :ranker, :instance
|
21
|
+
|
22
|
+
def initialize ranker, instance
|
23
|
+
self.ranker = ranker
|
24
|
+
self.instance = instance
|
25
|
+
end
|
26
|
+
|
27
|
+
def handle_ranking
|
28
|
+
update_index_from_position
|
29
|
+
assure_unique_position
|
30
|
+
end
|
31
|
+
|
32
|
+
def update_rank! value
|
33
|
+
# Bypass callbacks
|
34
|
+
#
|
35
|
+
instance.class.where(:id => instance.id).update_all ["#{ranker.column} = ?", value]
|
36
|
+
end
|
37
|
+
|
38
|
+
def position
|
39
|
+
instance.send "#{ranker.name}_position"
|
40
|
+
end
|
41
|
+
|
42
|
+
def rank
|
43
|
+
instance.send "#{ranker.column}"
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def position_at value
|
49
|
+
instance.send "#{ranker.name}_position=", value
|
50
|
+
update_index_from_position
|
51
|
+
end
|
52
|
+
|
53
|
+
def rank_at value
|
54
|
+
instance.send "#{ranker.column}=", value
|
55
|
+
end
|
56
|
+
|
57
|
+
def rank_changed?
|
58
|
+
instance.send "#{ranker.column}_changed?"
|
59
|
+
end
|
60
|
+
|
61
|
+
def new_record?
|
62
|
+
instance.new_record?
|
63
|
+
end
|
64
|
+
|
65
|
+
def update_index_from_position
|
66
|
+
case position
|
67
|
+
when :first
|
68
|
+
if !current_order.empty? && current_order.first.rank
|
69
|
+
rank_at( current_order.first.rank / 2 )
|
70
|
+
else
|
71
|
+
rank_at 0
|
72
|
+
end
|
73
|
+
when :last
|
74
|
+
if !current_order.empty? && current_order.last.rank
|
75
|
+
rank_at( ( RankedModel::MAX_RANK_VALUE + current_order.last.rank ) / 2 )
|
76
|
+
else
|
77
|
+
rank_at RankedModel::MAX_RANK_VALUE
|
78
|
+
end
|
79
|
+
when String
|
80
|
+
position_at position.to_i
|
81
|
+
when 0
|
82
|
+
position_at :first
|
83
|
+
when Integer
|
84
|
+
if current_order[position]
|
85
|
+
rank_at( ( current_order[position-1].rank + current_order[position].rank ) / 2 )
|
86
|
+
else
|
87
|
+
position_at :last
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def assure_unique_position
|
93
|
+
if ( new_record? || rank_changed? )
|
94
|
+
unless rank
|
95
|
+
rank_at RankedModel::MAX_RANK_VALUE
|
96
|
+
end
|
97
|
+
|
98
|
+
if (rank > RankedModel::MAX_RANK_VALUE) || (current_order.find do |rankable|
|
99
|
+
rankable.rank.nil? ||
|
100
|
+
rankable.rank == rank
|
101
|
+
end)
|
102
|
+
rebalance_ranks
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def rebalance_ranks
|
108
|
+
total = current_order.size + 2
|
109
|
+
has_set_self = false
|
110
|
+
total.times do |index|
|
111
|
+
next if index == 0 || index == total
|
112
|
+
rank_value = RankedModel::MAX_RANK_VALUE / total * index
|
113
|
+
index = index - 1
|
114
|
+
if has_set_self
|
115
|
+
index = index - 1
|
116
|
+
else
|
117
|
+
if !current_order[index] ||
|
118
|
+
( !current_order[index].rank.nil? &&
|
119
|
+
current_order[index].rank >= rank )
|
120
|
+
rank_at rank_value
|
121
|
+
has_set_self = true
|
122
|
+
next
|
123
|
+
end
|
124
|
+
end
|
125
|
+
current_order[index].update_rank! rank_value
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def current_order
|
130
|
+
@current_order ||= begin
|
131
|
+
finder = instance.class
|
132
|
+
if ranker.scope
|
133
|
+
finder = finder.send ranker.scope
|
134
|
+
end
|
135
|
+
if ranker.with_same
|
136
|
+
finder = finder.where \
|
137
|
+
instance.class.arel_table[ranker.with_same].eq(instance.attributes["#{ranker.with_same}"])
|
138
|
+
end
|
139
|
+
if !new_record?
|
140
|
+
finder = finder.where \
|
141
|
+
instance.class.arel_table[:id].not_eq(instance.id)
|
142
|
+
end
|
143
|
+
finder.order(ranker.column).select([:id, ranker.column]).collect { |ordered_instance|
|
144
|
+
RankedModel::Ranker::Mapper.new ranker, ordered_instance
|
145
|
+
}
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'ranked-model/railtie'
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "ranked-model/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "ranked-model"
|
7
|
+
s.version = RankedModel::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Matthew Beale"]
|
10
|
+
s.email = ["matt.beale@madhatted.com"]
|
11
|
+
s.homepage = "https://github.com/harvesthq/ranked-model"
|
12
|
+
s.summary = %q{An acts_as_sortable replacement built for Rails 3}
|
13
|
+
s.description = %q{ranked-model is a modern row sorting library built for Rails 3. It uses ARel aggressivly and is better optimized than most other libraries.}
|
14
|
+
|
15
|
+
s.add_dependency "activerecord", ">= 3.0.3"
|
16
|
+
s.add_development_dependency "rspec"
|
17
|
+
s.add_development_dependency "rspec-rails"
|
18
|
+
s.add_development_dependency "sqlite3"
|
19
|
+
s.add_development_dependency "genspec"
|
20
|
+
s.add_development_dependency "mocha"
|
21
|
+
|
22
|
+
# s.rubyforge_project = "ranked-model"
|
23
|
+
|
24
|
+
s.files = `git ls-files`.split("\n")
|
25
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
26
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
27
|
+
s.require_paths = ["lib"]
|
28
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Duck do
|
4
|
+
|
5
|
+
before {
|
6
|
+
@ducks = {
|
7
|
+
:quacky => Duck.create(
|
8
|
+
:name => 'Quacky',
|
9
|
+
:pond => 'Shin' ),
|
10
|
+
:feathers => Duck.create(
|
11
|
+
:name => 'Feathers',
|
12
|
+
:pond => 'Shin' ),
|
13
|
+
:wingy => Duck.create(
|
14
|
+
:name => 'Wingy',
|
15
|
+
:pond => 'Shin' ),
|
16
|
+
:webby => Duck.create(
|
17
|
+
:name => 'Webby',
|
18
|
+
:pond => 'Boyden' ),
|
19
|
+
:waddly => Duck.create(
|
20
|
+
:name => 'Waddly',
|
21
|
+
:pond => 'Meddybemps' ),
|
22
|
+
:beaky => Duck.create(
|
23
|
+
:name => 'Beaky',
|
24
|
+
:pond => 'Great Moose' )
|
25
|
+
}
|
26
|
+
@ducks.each { |name, duck|
|
27
|
+
duck.reload
|
28
|
+
duck.row_position = 0
|
29
|
+
duck.size_position = 0
|
30
|
+
duck.age_position = 0
|
31
|
+
duck.save!
|
32
|
+
}
|
33
|
+
@ducks.each {|name, duck| duck.reload }
|
34
|
+
}
|
35
|
+
|
36
|
+
describe "sorting by size on in_shin_pond" do
|
37
|
+
|
38
|
+
before {
|
39
|
+
@ducks[:quacky].update_attribute :size_position, 0
|
40
|
+
@ducks[:wingy].update_attribute :size_position, 2
|
41
|
+
}
|
42
|
+
|
43
|
+
subject { Duck.in_shin_pond.rank(:size).all }
|
44
|
+
|
45
|
+
its(:size) { should == 3 }
|
46
|
+
|
47
|
+
its(:first) { should == @ducks[:quacky] }
|
48
|
+
|
49
|
+
its(:last) { should == @ducks[:wingy] }
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "sorting by age on Shin pond" do
|
54
|
+
|
55
|
+
before {
|
56
|
+
@ducks[:feathers].update_attribute :age_position, 0
|
57
|
+
@ducks[:wingy].update_attribute :age_position, 0
|
58
|
+
}
|
59
|
+
|
60
|
+
subject { Duck.where(:pond => 'Shin').rank(:age).all }
|
61
|
+
|
62
|
+
its(:size) { should == 3 }
|
63
|
+
|
64
|
+
its(:first) { should == @ducks[:wingy] }
|
65
|
+
|
66
|
+
its(:last) { should == @ducks[:quacky] }
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "sorting by row" do
|
71
|
+
|
72
|
+
before {
|
73
|
+
@ducks[:beaky].update_attribute :row_position, 0
|
74
|
+
@ducks[:webby].update_attribute :row_position, 2
|
75
|
+
@ducks[:waddly].update_attribute :row_position, 2
|
76
|
+
@ducks[:wingy].update_attribute :row_position, 6
|
77
|
+
}
|
78
|
+
|
79
|
+
subject { Duck.rank(:row).all }
|
80
|
+
|
81
|
+
its(:size) { should == 6 }
|
82
|
+
|
83
|
+
its(:first) { should == @ducks[:beaky] }
|
84
|
+
|
85
|
+
its(:last) { should == @ducks[:wingy] }
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
describe "mixed sorting by" do
|
90
|
+
|
91
|
+
before {
|
92
|
+
@ducks[:quacky].update_attribute :size_position, 0
|
93
|
+
@ducks[:beaky].update_attribute :row_position, 0
|
94
|
+
@ducks[:webby].update_attribute :row_position, 2
|
95
|
+
@ducks[:wingy].update_attribute :size_position, 1
|
96
|
+
@ducks[:waddly].update_attribute :row_position, 2
|
97
|
+
@ducks[:wingy].update_attribute :row_position, 6
|
98
|
+
@ducks[:webby].update_attribute :row_position, 6
|
99
|
+
}
|
100
|
+
|
101
|
+
describe "row" do
|
102
|
+
|
103
|
+
subject { Duck.rank(:row).all }
|
104
|
+
|
105
|
+
its(:size) { should == 6 }
|
106
|
+
|
107
|
+
its(:first) { should == @ducks[:beaky] }
|
108
|
+
|
109
|
+
its(:last) { should == @ducks[:webby] }
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
describe "row" do
|
114
|
+
|
115
|
+
subject { Duck.in_shin_pond.rank(:size).all }
|
116
|
+
|
117
|
+
its(:size) { should == 3 }
|
118
|
+
|
119
|
+
its(:first) { should == @ducks[:quacky] }
|
120
|
+
|
121
|
+
its(:last) { should == @ducks[:feathers] }
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RankedModel::Ranker, 'initialized' do
|
4
|
+
|
5
|
+
subject {
|
6
|
+
RankedModel::Ranker.new \
|
7
|
+
:overview,
|
8
|
+
:column => :a_sorting_column,
|
9
|
+
:scope => :a_scope,
|
10
|
+
:with_same => :a_column
|
11
|
+
}
|
12
|
+
|
13
|
+
its(:name) { should == :overview }
|
14
|
+
its(:column) { should == :a_sorting_column }
|
15
|
+
its(:scope) { should == :a_scope }
|
16
|
+
its(:with_same) { should == :a_column }
|
17
|
+
|
18
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
|
4
|
+
require 'ranked-model' # and any other gems you need
|
5
|
+
|
6
|
+
Dir[File.join(File.dirname(__FILE__), 'support/**/*.rb')].each {|f| require f}
|
7
|
+
|
8
|
+
RSpec.configure do |config|
|
9
|
+
config.mock_with :mocha
|
10
|
+
config.use_transactional_fixtures = true
|
11
|
+
end
|
12
|
+
|
13
|
+
RSpec::Matchers.define :define_constant do |expected|
|
14
|
+
match { |actual| actual.const_defined?(expected) }
|
15
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'sqlite3'
|
3
|
+
require 'logger'
|
4
|
+
require 'rspec/rails/adapters'
|
5
|
+
require 'rspec/rails/fixture_support'
|
6
|
+
|
7
|
+
ROOT = File.join(File.dirname(__FILE__), '..')
|
8
|
+
|
9
|
+
ActiveRecord::Base.logger = Logger.new('tmp/ar_debug.log')
|
10
|
+
ActiveRecord::Base.configurations = YAML::load(IO.read('spec/support/database.yml'))
|
11
|
+
ActiveRecord::Base.establish_connection('development')
|
12
|
+
|
13
|
+
ActiveRecord::Schema.define :version => 0 do
|
14
|
+
create_table :ducks, :force => true do |t|
|
15
|
+
t.string :name
|
16
|
+
t.integer :row
|
17
|
+
t.integer :size
|
18
|
+
t.integer :age
|
19
|
+
t.string :pond
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Duck < ActiveRecord::Base
|
24
|
+
|
25
|
+
include RankedModel
|
26
|
+
ranks :row
|
27
|
+
ranks :size, :scope => :in_shin_pond
|
28
|
+
ranks :age, :with_same => :pond
|
29
|
+
|
30
|
+
scope :in_shin_pond, where(:pond => 'Shin')
|
31
|
+
|
32
|
+
end
|
metadata
ADDED
@@ -0,0 +1,177 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ranked-model
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Matthew Beale
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-02-08 00:00:00 -05:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: activerecord
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 1
|
30
|
+
segments:
|
31
|
+
- 3
|
32
|
+
- 0
|
33
|
+
- 3
|
34
|
+
version: 3.0.3
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: rspec
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 3
|
46
|
+
segments:
|
47
|
+
- 0
|
48
|
+
version: "0"
|
49
|
+
type: :development
|
50
|
+
version_requirements: *id002
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
name: rspec-rails
|
53
|
+
prerelease: false
|
54
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 3
|
60
|
+
segments:
|
61
|
+
- 0
|
62
|
+
version: "0"
|
63
|
+
type: :development
|
64
|
+
version_requirements: *id003
|
65
|
+
- !ruby/object:Gem::Dependency
|
66
|
+
name: sqlite3
|
67
|
+
prerelease: false
|
68
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
hash: 3
|
74
|
+
segments:
|
75
|
+
- 0
|
76
|
+
version: "0"
|
77
|
+
type: :development
|
78
|
+
version_requirements: *id004
|
79
|
+
- !ruby/object:Gem::Dependency
|
80
|
+
name: genspec
|
81
|
+
prerelease: false
|
82
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
hash: 3
|
88
|
+
segments:
|
89
|
+
- 0
|
90
|
+
version: "0"
|
91
|
+
type: :development
|
92
|
+
version_requirements: *id005
|
93
|
+
- !ruby/object:Gem::Dependency
|
94
|
+
name: mocha
|
95
|
+
prerelease: false
|
96
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
hash: 3
|
102
|
+
segments:
|
103
|
+
- 0
|
104
|
+
version: "0"
|
105
|
+
type: :development
|
106
|
+
version_requirements: *id006
|
107
|
+
description: ranked-model is a modern row sorting library built for Rails 3. It uses ARel aggressivly and is better optimized than most other libraries.
|
108
|
+
email:
|
109
|
+
- matt.beale@madhatted.com
|
110
|
+
executables: []
|
111
|
+
|
112
|
+
extensions: []
|
113
|
+
|
114
|
+
extra_rdoc_files: []
|
115
|
+
|
116
|
+
files:
|
117
|
+
- .gitignore
|
118
|
+
- .rspec
|
119
|
+
- Gemfile
|
120
|
+
- Gemfile.lock
|
121
|
+
- LICENSE
|
122
|
+
- Rakefile
|
123
|
+
- Readme.mkd
|
124
|
+
- lib/ranked-model.rb
|
125
|
+
- lib/ranked-model/railtie.rb
|
126
|
+
- lib/ranked-model/ranker.rb
|
127
|
+
- lib/ranked-model/version.rb
|
128
|
+
- rails/init.rb
|
129
|
+
- ranked-model.gemspec
|
130
|
+
- spec/duck-model/duck_spec.rb
|
131
|
+
- spec/ranked-model/ranker_spec.rb
|
132
|
+
- spec/ranked-model/version_spec.rb
|
133
|
+
- spec/spec_helper.rb
|
134
|
+
- spec/support/active_record.rb
|
135
|
+
- spec/support/database.yml
|
136
|
+
- tmp/.gitignore
|
137
|
+
has_rdoc: true
|
138
|
+
homepage: https://github.com/harvesthq/ranked-model
|
139
|
+
licenses: []
|
140
|
+
|
141
|
+
post_install_message:
|
142
|
+
rdoc_options: []
|
143
|
+
|
144
|
+
require_paths:
|
145
|
+
- lib
|
146
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
147
|
+
none: false
|
148
|
+
requirements:
|
149
|
+
- - ">="
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
hash: 3
|
152
|
+
segments:
|
153
|
+
- 0
|
154
|
+
version: "0"
|
155
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
156
|
+
none: false
|
157
|
+
requirements:
|
158
|
+
- - ">="
|
159
|
+
- !ruby/object:Gem::Version
|
160
|
+
hash: 3
|
161
|
+
segments:
|
162
|
+
- 0
|
163
|
+
version: "0"
|
164
|
+
requirements: []
|
165
|
+
|
166
|
+
rubyforge_project:
|
167
|
+
rubygems_version: 1.5.0
|
168
|
+
signing_key:
|
169
|
+
specification_version: 3
|
170
|
+
summary: An acts_as_sortable replacement built for Rails 3
|
171
|
+
test_files:
|
172
|
+
- spec/duck-model/duck_spec.rb
|
173
|
+
- spec/ranked-model/ranker_spec.rb
|
174
|
+
- spec/ranked-model/version_spec.rb
|
175
|
+
- spec/spec_helper.rb
|
176
|
+
- spec/support/active_record.rb
|
177
|
+
- spec/support/database.yml
|