resort 0.0.1
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/.gitignore +7 -0
- data/.rspec +2 -0
- data/.rvmrc +1 -0
- data/Gemfile +3 -0
- data/Rakefile +47 -0
- data/Readme.md +92 -0
- data/lib/resort/version.rb +4 -0
- data/lib/resort.rb +204 -0
- data/resort.gemspec +27 -0
- data/spec/resort_spec.rb +283 -0
- data/spec/spec_helper.rb +19 -0
- metadata +124 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm --create use ruby-1.9.2@resort
|
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
desc "Run resort specs"
|
6
|
+
RSpec::Core::RakeTask.new
|
7
|
+
|
8
|
+
require 'yard'
|
9
|
+
YARD::Rake::YardocTask.new(:docs) do |t|
|
10
|
+
t.files = ['lib/**/*.rb']
|
11
|
+
t.options = ['-m', 'markdown', '--no-private', '-r', 'Readme.md', '--title', 'Resort documentation']
|
12
|
+
end
|
13
|
+
|
14
|
+
site = 'doc'
|
15
|
+
source_branch = 'master'
|
16
|
+
deploy_branch = 'gh-pages'
|
17
|
+
|
18
|
+
desc "generate and deploy documentation website to github pages"
|
19
|
+
multitask :pages do
|
20
|
+
puts ">>> Deploying #{deploy_branch} branch to Github Pages <<<"
|
21
|
+
require 'git'
|
22
|
+
repo = Git.open('.')
|
23
|
+
puts "\n>>> Checking out #{deploy_branch} branch <<<\n"
|
24
|
+
repo.branch("#{deploy_branch}").checkout
|
25
|
+
(Dir["*"] - [site]).each { |f| rm_rf(f) }
|
26
|
+
Dir["#{site}/*"].each {|f| mv(f, "./")}
|
27
|
+
rm_rf(site)
|
28
|
+
puts "\n>>> Moving generated site files <<<\n"
|
29
|
+
Dir["**/*"].each {|f| repo.add(f) }
|
30
|
+
repo.status.deleted.each {|f, s| repo.remove(f)}
|
31
|
+
puts "\n>>> Commiting: Site updated at #{Time.now.utc} <<<\n"
|
32
|
+
message = ENV["MESSAGE"] || "Site updated at #{Time.now.utc}"
|
33
|
+
repo.commit(message)
|
34
|
+
puts "\n>>> Pushing generated site to #{deploy_branch} branch <<<\n"
|
35
|
+
repo.push
|
36
|
+
puts "\n>>> Github Pages deploy complete <<<\n"
|
37
|
+
repo.branch("#{source_branch}").checkout
|
38
|
+
end
|
39
|
+
|
40
|
+
task :doc => [:docs]
|
41
|
+
|
42
|
+
desc "Generate and open class diagram (needs Graphviz installed)"
|
43
|
+
task :graph do |t|
|
44
|
+
`bundle exec yard graph -d --full --no-private | dot -Tpng -o graph.png && open graph.png`
|
45
|
+
end
|
46
|
+
|
47
|
+
task :default => [:spec]
|
data/Readme.md
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
#resort
|
2
|
+
|
3
|
+
Resort provides sorting capabilities to your Rails 3 models.
|
4
|
+
|
5
|
+
##Install
|
6
|
+
|
7
|
+
$ gem install resort
|
8
|
+
|
9
|
+
Or in your Gemfile:
|
10
|
+
|
11
|
+
gem 'resort'
|
12
|
+
|
13
|
+
##Rationale
|
14
|
+
|
15
|
+
Most other sorting plugins work with an absolute `position` attribute that sets
|
16
|
+
the _weight_ of a given element within a tree. This field has no semantic sense,
|
17
|
+
since "84" by itself gives you absolutely no information about an element's
|
18
|
+
position or its relations with other elements of the tree.
|
19
|
+
|
20
|
+
Resort is implemented quite like a [binary tree](http://en.wikipedia.org/wiki/Binary_tree),
|
21
|
+
rather than relying on absolute position values. This way, every model
|
22
|
+
references a `next` and a `previous`, which seems a bit more sensible :)
|
23
|
+
|
24
|
+
##Usage
|
25
|
+
|
26
|
+
You must add two fields (`next_id` and `first`) to your model's table:
|
27
|
+
|
28
|
+
class AddResortFieldsToProducts < ActiveRecord::Migration
|
29
|
+
def self.up
|
30
|
+
add_column :products, :next_id, :integer
|
31
|
+
add_column :products, :first, :boolean
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.down
|
35
|
+
remove_column :products, :next_id
|
36
|
+
remove_column :products, :first
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
Then in your Product model:
|
41
|
+
|
42
|
+
class Product < ActiveRecord::Base
|
43
|
+
resort!
|
44
|
+
end
|
45
|
+
|
46
|
+
**NOTE**: By default, Resort will treat _all products_ as a single big tree.
|
47
|
+
If you wanted to limit the tree scope, i.e. treating every ProductLine as a
|
48
|
+
separate tree of sortable products, you must override the `siblings` method:
|
49
|
+
|
50
|
+
class Product < ActiveRecord::Base
|
51
|
+
resort!
|
52
|
+
|
53
|
+
def siblings
|
54
|
+
# Tree contains only products from my own product line
|
55
|
+
self.product_line.products
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
###API
|
60
|
+
|
61
|
+
Every time a product is created, it will be appended after the last element.
|
62
|
+
|
63
|
+
Moreover, now a `product` responds to the following methods:
|
64
|
+
|
65
|
+
* `first?` — Returns true if the element is the first of the tree.
|
66
|
+
* `append_to(other_element)` — Appends the element _after_ another element.
|
67
|
+
|
68
|
+
And the class Product has a new scope named `ordered` that returns the
|
69
|
+
products in order.
|
70
|
+
|
71
|
+
##Under the hood
|
72
|
+
|
73
|
+
Run the test suite by typing:
|
74
|
+
|
75
|
+
rake spec
|
76
|
+
|
77
|
+
You can also build the documentation with the following command:
|
78
|
+
|
79
|
+
rake docs
|
80
|
+
|
81
|
+
## Note on Patches/Pull Requests
|
82
|
+
|
83
|
+
* Fork the project.
|
84
|
+
* Make your feature addition or bug fix.
|
85
|
+
* Add tests for it. This is important so I don't break it in a
|
86
|
+
future version unintentionally.
|
87
|
+
* Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
88
|
+
* Send us a pull request. Bonus points for topic branches.
|
89
|
+
|
90
|
+
## Copyright
|
91
|
+
|
92
|
+
Copyright (c) 2011 Codegram. See LICENSE for details.
|
data/lib/resort.rb
ADDED
@@ -0,0 +1,204 @@
|
|
1
|
+
# # Resort
|
2
|
+
#
|
3
|
+
# A tool that allows any ActiveRecord model to be sorted.
|
4
|
+
#
|
5
|
+
# Unlike most Rails sorting plugins (acts_as_list, etc), Resort is based
|
6
|
+
# on linked lists rather than absolute position fields.
|
7
|
+
#
|
8
|
+
# @example Using Resort in an ActiveRecord model
|
9
|
+
# # In the migration
|
10
|
+
# create_table :products do |t|
|
11
|
+
# t.text :name
|
12
|
+
# t.references :next
|
13
|
+
# t.boolean :first
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# # Model
|
17
|
+
# class Product < ActiveRecord::Base
|
18
|
+
# resort!
|
19
|
+
#
|
20
|
+
# # A sortable model must implement #siblings method, which should
|
21
|
+
# # return and ActiveRecord::Relation with all the models to be
|
22
|
+
# # considered as `peers` in the list representing the sorted
|
23
|
+
# # products, i.e. its siblings.
|
24
|
+
# def siblings
|
25
|
+
# self.class.scoped
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# product = Product.create(:name => 'Bread')
|
30
|
+
# product.first? # => true
|
31
|
+
#
|
32
|
+
# another_product = Product.create(:name => 'Milk')
|
33
|
+
# yet_another_product = Product.create(:name => 'Salami')
|
34
|
+
#
|
35
|
+
# yet_another_product.append_to(product)
|
36
|
+
#
|
37
|
+
# Product.ordered.map(&:name)
|
38
|
+
# # => ['Bread', 'Salami', 'Milk']
|
39
|
+
module Resort
|
40
|
+
# The module encapsulating all the Resort functionality.
|
41
|
+
#
|
42
|
+
# @todo Refactor into a more OO solution, maybe implementing a LinkedList
|
43
|
+
# object.
|
44
|
+
module Sortable
|
45
|
+
class << self
|
46
|
+
# When included, extends the includer with {ClassMethods}, and includes
|
47
|
+
# {InstanceMethods} in it.
|
48
|
+
#
|
49
|
+
# It also establishes the required relationships. It is necessary that
|
50
|
+
# the includer table has the following database columns:
|
51
|
+
#
|
52
|
+
# t.references :next
|
53
|
+
# t.boolean :first
|
54
|
+
#
|
55
|
+
# @param [ActiveRecord::Base] base the includer `ActiveRecord` model.
|
56
|
+
def included(base)
|
57
|
+
base.extend ClassMethods
|
58
|
+
base.send :include, InstanceMethods
|
59
|
+
|
60
|
+
base.has_one :previous, :class_name => base.name, :foreign_key => 'next_id', :inverse_of => :next
|
61
|
+
base.belongs_to :next, :class_name => base.name, :inverse_of => :previous
|
62
|
+
|
63
|
+
base.after_create :include_in_list!
|
64
|
+
base.after_destroy :delete_from_list
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Class methods to be used from the model class.
|
69
|
+
module ClassMethods
|
70
|
+
# Returns the first element of the list.
|
71
|
+
#
|
72
|
+
# @return [ActiveRecord::Base] the first element of the list.
|
73
|
+
def first_in_order
|
74
|
+
where(:first => true).first
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns eager-loaded Components in order.
|
78
|
+
#
|
79
|
+
# OPTIMIZE: Avoid creating as many hashes.
|
80
|
+
# @return [Array<ActiveRecord::Base>] the ordered elements
|
81
|
+
def ordered
|
82
|
+
ordered_elements = []
|
83
|
+
elements = {}
|
84
|
+
|
85
|
+
scoped.each do |element|
|
86
|
+
if element.first?
|
87
|
+
ordered_elements << element
|
88
|
+
else
|
89
|
+
elements[element.id] = element
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
elements.length.times do
|
94
|
+
ordered_elements << elements[ordered_elements.last.next_id]
|
95
|
+
end
|
96
|
+
ordered_elements
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Instance methods to use.
|
101
|
+
module InstanceMethods
|
102
|
+
|
103
|
+
# Default definition of siblings, i.e. every instance of the model.
|
104
|
+
#
|
105
|
+
# Can be overriden to specify a different scope for the siblings.
|
106
|
+
# For example, if we wanted to limit a products tree inside a ProductLine
|
107
|
+
# scope, we would do the following:
|
108
|
+
#
|
109
|
+
# class Product < ActiveRecord::Base
|
110
|
+
# belongs_to :product_line
|
111
|
+
#
|
112
|
+
# resort!
|
113
|
+
#
|
114
|
+
# def siblings
|
115
|
+
# self.product_line.products
|
116
|
+
# end
|
117
|
+
#
|
118
|
+
# This way, every product line is an independent tree of sortable
|
119
|
+
# products.
|
120
|
+
#
|
121
|
+
# @return [ActiveRecord::Relation] the element's siblings relation.
|
122
|
+
def siblings
|
123
|
+
self.class.scoped
|
124
|
+
end
|
125
|
+
# Includes the object in the linked list.
|
126
|
+
#
|
127
|
+
# If there are no other objects, it prepends the object so that it is
|
128
|
+
# in the first position. Otherwise, it appends it to the end of the
|
129
|
+
# empty list.
|
130
|
+
def include_in_list!
|
131
|
+
_siblings.count > 0 ? push\
|
132
|
+
: prepend
|
133
|
+
end
|
134
|
+
|
135
|
+
# Puts the object in the first position of the list.
|
136
|
+
def prepend
|
137
|
+
return if first?
|
138
|
+
|
139
|
+
if _siblings.count > 0
|
140
|
+
delete_from_list
|
141
|
+
_siblings.where(:first => true).first.append_to(self)
|
142
|
+
end
|
143
|
+
|
144
|
+
self.update_attribute(:first, true)
|
145
|
+
end
|
146
|
+
|
147
|
+
# Puts the object in the last position of the list.
|
148
|
+
def push
|
149
|
+
return if last?
|
150
|
+
last_element = _siblings.where(:next_id => nil).first
|
151
|
+
self.append_to(last_element)
|
152
|
+
end
|
153
|
+
|
154
|
+
# Puts the object right after another object in the list.
|
155
|
+
def append_to(another)
|
156
|
+
if self.next
|
157
|
+
delete_from_list
|
158
|
+
elsif last?
|
159
|
+
self.previous.update_attribute(:next_id, nil)
|
160
|
+
self.previous = nil
|
161
|
+
end
|
162
|
+
|
163
|
+
self.update_attribute(:next_id, another.next_id)
|
164
|
+
another.update_attribute(:next_id, self.id)
|
165
|
+
end
|
166
|
+
|
167
|
+
private
|
168
|
+
|
169
|
+
def delete_from_list
|
170
|
+
if first? && self.next
|
171
|
+
self.update_attribute(:first, nil) unless frozen?
|
172
|
+
self.next.first = true
|
173
|
+
self.next.previous = nil
|
174
|
+
self.next.save!
|
175
|
+
elsif self.previous
|
176
|
+
previous.next = self.next
|
177
|
+
previous.save!
|
178
|
+
self.update_attribute(:next_id, nil) unless frozen?
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def last?
|
183
|
+
self.previous && !self.next
|
184
|
+
end
|
185
|
+
|
186
|
+
def _siblings
|
187
|
+
table = self.class.arel_table
|
188
|
+
siblings.where(table[:id].not_eq(self.id))
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
# Helper class methods to be injected into ActiveRecord::Base class.
|
193
|
+
# They will be available to every model.
|
194
|
+
module ClassMethods
|
195
|
+
# Helper class method to include Resort::Sortable in an ActiveRecord
|
196
|
+
# model.
|
197
|
+
def resort!
|
198
|
+
include Sortable
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
require 'active_record' unless defined?(ActiveRecord)
|
204
|
+
ActiveRecord::Base.extend Resort::ClassMethods
|
data/resort.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "resort/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "resort"
|
7
|
+
s.version = Resort::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Oriol Gual", "Josep M. Bach", "Josep Jaume Rey"]
|
10
|
+
s.email = ["info@codegram.com"]
|
11
|
+
s.homepage = "http://codegram.github.com/resort"
|
12
|
+
s.summary = %q{Positionless model sorting for Rails 3.}
|
13
|
+
s.description = %q{Positionless model sorting for Rails 3.}
|
14
|
+
|
15
|
+
s.rubyforge_project = "resort"
|
16
|
+
|
17
|
+
s.add_runtime_dependency 'activerecord', '~> 3.0.5'
|
18
|
+
s.add_development_dependency 'sqlite3'
|
19
|
+
s.add_development_dependency 'rspec', '~> 2.5.0'
|
20
|
+
s.add_development_dependency 'yard'
|
21
|
+
s.add_development_dependency 'bluecloth'
|
22
|
+
|
23
|
+
s.files = `git ls-files`.split("\n")
|
24
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
25
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
26
|
+
s.require_paths = ["lib"]
|
27
|
+
end
|
data/spec/resort_spec.rb
ADDED
@@ -0,0 +1,283 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class Article < ActiveRecord::Base
|
4
|
+
resort!
|
5
|
+
end
|
6
|
+
|
7
|
+
module Resort
|
8
|
+
describe Sortable do
|
9
|
+
|
10
|
+
subject { Article.new }
|
11
|
+
|
12
|
+
context 'when included' do
|
13
|
+
it 'creates previous and next relationships' do
|
14
|
+
subject.should respond_to(:previous, :next)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'includes base with InstanceMethods' do
|
18
|
+
subject.class.ancestors.should include(Sortable::InstanceMethods)
|
19
|
+
end
|
20
|
+
it 'extend base with ClassMethods' do
|
21
|
+
(class << subject.class; self; end).ancestors.should include(Sortable::ClassMethods)
|
22
|
+
end
|
23
|
+
it 'defines a siblings method' do
|
24
|
+
subject.class.instance_methods.should include(:siblings)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe 'ClassMethods' do
|
29
|
+
describe "#first_in_order" do
|
30
|
+
it 'returns the first element of the list' do
|
31
|
+
first = double :article
|
32
|
+
Article.should_receive(:where).with(:first => true).and_return [first]
|
33
|
+
|
34
|
+
Article.first_in_order
|
35
|
+
end
|
36
|
+
end
|
37
|
+
describe "#ordered" do
|
38
|
+
before do
|
39
|
+
Article.destroy_all
|
40
|
+
|
41
|
+
4.times do |i|
|
42
|
+
Article.create(:name => i.to_s)
|
43
|
+
end
|
44
|
+
|
45
|
+
@article1 = Article.find_by_name('0')
|
46
|
+
@article2 = Article.find_by_name('1')
|
47
|
+
@article3 = Article.find_by_name('2')
|
48
|
+
@article4 = Article.find_by_name('3')
|
49
|
+
end
|
50
|
+
it 'returns the first element of the list' do
|
51
|
+
Article.ordered.should == [@article1, @article2, @article3, @article4]
|
52
|
+
end
|
53
|
+
after do
|
54
|
+
Article.destroy_all
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "after create" do
|
60
|
+
context 'when there are no siblings' do
|
61
|
+
it 'prepends the element' do
|
62
|
+
article = Article.create(:name => 'first!')
|
63
|
+
|
64
|
+
article.should be_first
|
65
|
+
article.next.should be_nil
|
66
|
+
article.previous.should be_nil
|
67
|
+
end
|
68
|
+
end
|
69
|
+
context 'otherwise' do
|
70
|
+
it 'appends the element' do
|
71
|
+
Article.create(:name => "1")
|
72
|
+
Article.create(:name => 'last!')
|
73
|
+
|
74
|
+
article = Article.find_by_name('last!')
|
75
|
+
|
76
|
+
article.should be_last
|
77
|
+
article.previous.name.should == '1'
|
78
|
+
end
|
79
|
+
end
|
80
|
+
after do
|
81
|
+
Article.destroy_all
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "after destroy" do
|
86
|
+
context 'when the element is the first' do
|
87
|
+
it 'removes the element' do
|
88
|
+
article = Article.create(:name => 'first!')
|
89
|
+
article2 = Article.create(:name => 'second!')
|
90
|
+
article3 = Article.create(:name => 'last!')
|
91
|
+
|
92
|
+
article = Article.find_by_name('first!')
|
93
|
+
article.destroy
|
94
|
+
|
95
|
+
article2 = Article.find_by_name('second!')
|
96
|
+
|
97
|
+
article2.should be_first
|
98
|
+
article2.previous.should be_nil
|
99
|
+
end
|
100
|
+
end
|
101
|
+
context 'when the element is in the middle' do
|
102
|
+
it 'removes the element' do
|
103
|
+
article = Article.create(:name => 'first!')
|
104
|
+
article2 = Article.create(:name => 'second!')
|
105
|
+
article3 = Article.create(:name => 'last!')
|
106
|
+
|
107
|
+
article = Article.find_by_name('first!')
|
108
|
+
|
109
|
+
article2 = Article.find_by_name('second!')
|
110
|
+
article2.destroy
|
111
|
+
|
112
|
+
article = Article.find_by_name('first!')
|
113
|
+
article3 = Article.find_by_name('last!')
|
114
|
+
|
115
|
+
article.should be_first
|
116
|
+
article.next.name.should == 'last!'
|
117
|
+
article3.previous.name.should == 'first!'
|
118
|
+
end
|
119
|
+
end
|
120
|
+
context 'when the element is last' do
|
121
|
+
it 'removes the element' do
|
122
|
+
article = Article.create(:name => 'first!')
|
123
|
+
article2 = Article.create(:name => 'second!')
|
124
|
+
article3 = Article.create(:name => 'last!')
|
125
|
+
|
126
|
+
article3.destroy
|
127
|
+
|
128
|
+
article2.next.should be_nil
|
129
|
+
end
|
130
|
+
end
|
131
|
+
after do
|
132
|
+
Article.destroy_all
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
describe 'InstanceMethods' do
|
137
|
+
before do
|
138
|
+
Article.destroy_all
|
139
|
+
Article.create(:name => "1")
|
140
|
+
Article.create(:name => "2")
|
141
|
+
Article.create(:name => "3")
|
142
|
+
Article.create(:name => "4")
|
143
|
+
|
144
|
+
@article1 = Article.find_by_name('1')
|
145
|
+
@article2 = Article.find_by_name('2')
|
146
|
+
@article3 = Article.find_by_name('3')
|
147
|
+
@article4 = Article.find_by_name('4')
|
148
|
+
end
|
149
|
+
|
150
|
+
describe "#push" do
|
151
|
+
it "appends the element to the list" do
|
152
|
+
@article1.push
|
153
|
+
|
154
|
+
article1 = Article.find_by_name('1')
|
155
|
+
article1.previous.should == @article4
|
156
|
+
article1.next.should be_nil
|
157
|
+
end
|
158
|
+
context 'when the article is already last' do
|
159
|
+
it 'does nothing' do
|
160
|
+
@article4.push
|
161
|
+
|
162
|
+
@article4.previous.name.should == '3'
|
163
|
+
@article4.next.should be_nil
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
describe "#prepend" do
|
169
|
+
it "prepends the element" do
|
170
|
+
@article3.prepend
|
171
|
+
|
172
|
+
article3 = Article.find_by_name('3')
|
173
|
+
|
174
|
+
article3.should be_first
|
175
|
+
article3.previous.should be_nil
|
176
|
+
article3.next.name.should == '1'
|
177
|
+
end
|
178
|
+
context 'when the article is already first' do
|
179
|
+
it 'does nothing' do
|
180
|
+
@article1.prepend
|
181
|
+
|
182
|
+
@article1.previous.should be_nil
|
183
|
+
@article1.next.name.should == '2'
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
describe "#append_to" do
|
189
|
+
context 'appending 1 after 2' do
|
190
|
+
it "appends the element after another element" do
|
191
|
+
@article1.append_to(@article2)
|
192
|
+
|
193
|
+
article2 = Article.find_by_name('2')
|
194
|
+
article2.next.name.should == '1'
|
195
|
+
|
196
|
+
article1 = Article.find_by_name('1')
|
197
|
+
article1.next.name.should == '3'
|
198
|
+
article1.previous.name.should == '2'
|
199
|
+
@article3.previous.name.should == '1'
|
200
|
+
|
201
|
+
article2.should be_first
|
202
|
+
end
|
203
|
+
end
|
204
|
+
context 'appending 1 after 3' do
|
205
|
+
it "appends the element after another element" do
|
206
|
+
@article1.append_to(@article3)
|
207
|
+
|
208
|
+
article2 = Article.find_by_name('2')
|
209
|
+
article2.should be_first
|
210
|
+
article2.previous.should be_nil
|
211
|
+
|
212
|
+
article1 = Article.find_by_name('1')
|
213
|
+
article1.should_not be_first
|
214
|
+
article1.previous.name.should == '3'
|
215
|
+
article1.next.name.should == '4'
|
216
|
+
|
217
|
+
@article3.next.name.should == '1'
|
218
|
+
|
219
|
+
@article4.previous.name.should == '1'
|
220
|
+
end
|
221
|
+
end
|
222
|
+
context 'appending 2 after 3' do
|
223
|
+
it "appends the element after another element" do
|
224
|
+
@article2.append_to(@article3)
|
225
|
+
|
226
|
+
article1 = Article.find_by_name('1')
|
227
|
+
article1.next.name.should == '3'
|
228
|
+
|
229
|
+
article2 = Article.find_by_name('2')
|
230
|
+
article2.previous.name.should == '3'
|
231
|
+
article2.next.name.should == '4'
|
232
|
+
|
233
|
+
@article3.previous.name.should == '1'
|
234
|
+
@article3.next.name.should == '2'
|
235
|
+
|
236
|
+
@article4.previous.name.should == '2'
|
237
|
+
end
|
238
|
+
end
|
239
|
+
context 'appending 2 after 4' do
|
240
|
+
it "appends the element after another element" do
|
241
|
+
@article2.append_to(@article4)
|
242
|
+
|
243
|
+
article1 = Article.find_by_name('1')
|
244
|
+
article3 = Article.find_by_name('3')
|
245
|
+
|
246
|
+
article1.next.name.should == '3'
|
247
|
+
article3.previous.name.should == '1'
|
248
|
+
|
249
|
+
article2 = Article.find_by_name('2')
|
250
|
+
article2.previous.name.should == '4'
|
251
|
+
article2.should be_last
|
252
|
+
|
253
|
+
@article4.next.name.should == '2'
|
254
|
+
end
|
255
|
+
end
|
256
|
+
context 'appending 4 after 2' do
|
257
|
+
it "appends the element after another element" do
|
258
|
+
@article4.append_to(@article2)
|
259
|
+
|
260
|
+
article3 = Article.find_by_name('3')
|
261
|
+
article3.next.should be_nil
|
262
|
+
article3.previous.name.should == '4'
|
263
|
+
|
264
|
+
article4 = Article.find_by_name('4')
|
265
|
+
@article2.next.name.should == '4'
|
266
|
+
article4.previous.name.should == '2'
|
267
|
+
article4.next.name.should == '3'
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
context 'when the article is already after the other element' do
|
272
|
+
it 'does nothing' do
|
273
|
+
@article2.append_to(@article1)
|
274
|
+
|
275
|
+
@article1.next.name.should == '2'
|
276
|
+
@article2.previous.name.should == '1'
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
end
|
283
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'resort'
|
3
|
+
|
4
|
+
ActiveRecord::Base.establish_connection(
|
5
|
+
:adapter => 'sqlite3',
|
6
|
+
:database => ':memory:'
|
7
|
+
)
|
8
|
+
|
9
|
+
ActiveRecord::Schema.define do
|
10
|
+
create_table :articles do |t|
|
11
|
+
t.string :name
|
12
|
+
t.integer :price
|
13
|
+
|
14
|
+
t.boolean :first
|
15
|
+
t.references :next
|
16
|
+
|
17
|
+
t.timestamps
|
18
|
+
end
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: resort
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Oriol Gual
|
9
|
+
- Josep M. Bach
|
10
|
+
- Josep Jaume Rey
|
11
|
+
autorequire:
|
12
|
+
bindir: bin
|
13
|
+
cert_chain: []
|
14
|
+
|
15
|
+
date: 2011-03-18 00:00:00 +01:00
|
16
|
+
default_executable:
|
17
|
+
dependencies:
|
18
|
+
- !ruby/object:Gem::Dependency
|
19
|
+
name: activerecord
|
20
|
+
prerelease: false
|
21
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
22
|
+
none: false
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 3.0.5
|
27
|
+
type: :runtime
|
28
|
+
version_requirements: *id001
|
29
|
+
- !ruby/object:Gem::Dependency
|
30
|
+
name: sqlite3
|
31
|
+
prerelease: false
|
32
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ">="
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: "0"
|
38
|
+
type: :development
|
39
|
+
version_requirements: *id002
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: rspec
|
42
|
+
prerelease: false
|
43
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
44
|
+
none: false
|
45
|
+
requirements:
|
46
|
+
- - ~>
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: 2.5.0
|
49
|
+
type: :development
|
50
|
+
version_requirements: *id003
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
name: yard
|
53
|
+
prerelease: false
|
54
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: "0"
|
60
|
+
type: :development
|
61
|
+
version_requirements: *id004
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: bluecloth
|
64
|
+
prerelease: false
|
65
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: "0"
|
71
|
+
type: :development
|
72
|
+
version_requirements: *id005
|
73
|
+
description: Positionless model sorting for Rails 3.
|
74
|
+
email:
|
75
|
+
- info@codegram.com
|
76
|
+
executables: []
|
77
|
+
|
78
|
+
extensions: []
|
79
|
+
|
80
|
+
extra_rdoc_files: []
|
81
|
+
|
82
|
+
files:
|
83
|
+
- .gitignore
|
84
|
+
- .rspec
|
85
|
+
- .rvmrc
|
86
|
+
- Gemfile
|
87
|
+
- Rakefile
|
88
|
+
- Readme.md
|
89
|
+
- lib/resort.rb
|
90
|
+
- lib/resort/version.rb
|
91
|
+
- resort.gemspec
|
92
|
+
- spec/resort_spec.rb
|
93
|
+
- spec/spec_helper.rb
|
94
|
+
has_rdoc: true
|
95
|
+
homepage: http://codegram.github.com/resort
|
96
|
+
licenses: []
|
97
|
+
|
98
|
+
post_install_message:
|
99
|
+
rdoc_options: []
|
100
|
+
|
101
|
+
require_paths:
|
102
|
+
- lib
|
103
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
104
|
+
none: false
|
105
|
+
requirements:
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: "0"
|
109
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
110
|
+
none: false
|
111
|
+
requirements:
|
112
|
+
- - ">="
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: "0"
|
115
|
+
requirements: []
|
116
|
+
|
117
|
+
rubyforge_project: resort
|
118
|
+
rubygems_version: 1.5.2
|
119
|
+
signing_key:
|
120
|
+
specification_version: 3
|
121
|
+
summary: Positionless model sorting for Rails 3.
|
122
|
+
test_files:
|
123
|
+
- spec/resort_spec.rb
|
124
|
+
- spec/spec_helper.rb
|