sortifiable 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/.gemtest +0 -0
- data/CHANGELOG +3 -0
- data/LICENSE +19 -0
- data/README +27 -0
- data/Rakefile +25 -0
- data/lib/sortifiable/version.rb +3 -0
- data/lib/sortifiable.rb +267 -0
- data/sortifiable.gemspec +42 -0
- data/test/sortifiable_test.rb +566 -0
- metadata +147 -0
data/.gemtest
ADDED
File without changes
|
data/CHANGELOG
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2011 Andrew White
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the "Software"),
|
5
|
+
to deal in the Software without restriction, including without limitation
|
6
|
+
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
7
|
+
and/or sell copies of the Software, and to permit persons to whom the
|
8
|
+
Software is furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be
|
11
|
+
included in all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
14
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
15
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
16
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
17
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
18
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
19
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
== Sortifiable
|
2
|
+
|
3
|
+
This gem provides an acts_as_list compatible capability for sorting
|
4
|
+
and reordering a number of objects in a list. The class that has this
|
5
|
+
specified needs to have a +position+ column defined as an integer on
|
6
|
+
the mapped database table.
|
7
|
+
|
8
|
+
This gem requires ActiveRecord 3.0 as it has been refactored to use
|
9
|
+
the scope methods and query interface introduced with Ruby on Rails 3.0
|
10
|
+
|
11
|
+
|
12
|
+
=== Example
|
13
|
+
|
14
|
+
class TodoList < ActiveRecord::Base
|
15
|
+
has_many :todo_items, :order => "position"
|
16
|
+
end
|
17
|
+
|
18
|
+
class TodoItem < ActiveRecord::Base
|
19
|
+
belongs_to :todo_list
|
20
|
+
acts_as_list :scope => :todo_list
|
21
|
+
end
|
22
|
+
|
23
|
+
todo_list.first.move_to_bottom
|
24
|
+
todo_list.last.move_higher
|
25
|
+
|
26
|
+
|
27
|
+
Copyright (c) 2011 Andrew White, released under the MIT license
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/rdoctask'
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'bundler'
|
5
|
+
|
6
|
+
Bundler::GemHelper.install_tasks
|
7
|
+
|
8
|
+
desc 'Default: run sortifiable unit tests.'
|
9
|
+
task :default => :test
|
10
|
+
|
11
|
+
desc 'Test the sortifiable gem.'
|
12
|
+
Rake::TestTask.new(:test) do |t|
|
13
|
+
t.libs << 'lib'
|
14
|
+
t.pattern = 'test/**/*_test.rb'
|
15
|
+
t.verbose = true
|
16
|
+
end
|
17
|
+
|
18
|
+
desc 'Generate documentation for the sortifiable gem.'
|
19
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
20
|
+
rdoc.rdoc_dir = 'rdoc'
|
21
|
+
rdoc.title = 'Sortifiable'
|
22
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
23
|
+
rdoc.rdoc_files.include('README')
|
24
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
25
|
+
end
|
data/lib/sortifiable.rb
ADDED
@@ -0,0 +1,267 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'active_support/core_ext/array/wrap'
|
3
|
+
require 'active_support/core_ext/class/attribute'
|
4
|
+
require 'active_support/core_ext/hash/reverse_merge'
|
5
|
+
require 'active_record'
|
6
|
+
require 'sortifiable/version'
|
7
|
+
|
8
|
+
# This +acts_as+ extension provides the capabilities for sorting and
|
9
|
+
# reordering a number of objects in a list. The class that has this
|
10
|
+
# specified needs to have a +position+ column defined as an integer on
|
11
|
+
# the mapped database table.
|
12
|
+
#
|
13
|
+
# Todo list example:
|
14
|
+
#
|
15
|
+
# class TodoList < ActiveRecord::Base
|
16
|
+
# has_many :todo_items, :order => "position"
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# class TodoItem < ActiveRecord::Base
|
20
|
+
# belongs_to :todo_list
|
21
|
+
# acts_as_list :scope => :todo_list
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# todo_list.first.move_to_bottom
|
25
|
+
# todo_list.last.move_higher
|
26
|
+
module Sortifiable
|
27
|
+
extend ActiveSupport::Concern
|
28
|
+
|
29
|
+
included do
|
30
|
+
class_attribute :acts_as_list_options, :instance_writer => false
|
31
|
+
self.acts_as_list_options = {}
|
32
|
+
|
33
|
+
before_create :add_to_list_bottom
|
34
|
+
before_destroy :decrement_position_on_lower_items, :if => :in_list?
|
35
|
+
end
|
36
|
+
|
37
|
+
module ClassMethods
|
38
|
+
# Configuration options are:
|
39
|
+
#
|
40
|
+
# * +column+ - specifies the column name to use for keeping the
|
41
|
+
# position integer (default: +position+)
|
42
|
+
# * +scope+ - restricts what is to be considered a list. Given a symbol,
|
43
|
+
# it'll attach <tt>_id</tt> (if it hasn't already been added) and use
|
44
|
+
# that as the foreign key restriction. It's also possible to give it
|
45
|
+
# an entire string that is interpolated if you need a tighter scope
|
46
|
+
# than just a foreign key. Example:
|
47
|
+
#
|
48
|
+
# acts_as_list :scope => 'user_id = #{user_id} AND completed = 0'
|
49
|
+
#
|
50
|
+
# It can also be given an array of symbols or a belongs_to association.
|
51
|
+
def acts_as_list(options = {})
|
52
|
+
options.reverse_merge!(:scope => [], :column => :position)
|
53
|
+
|
54
|
+
if options[:scope].is_a?(Symbol) && reflections.key?(options[:scope])
|
55
|
+
reflection = reflections[options.delete(:scope)]
|
56
|
+
|
57
|
+
if reflection.belongs_to?
|
58
|
+
if reflection.options[:polymorphic]
|
59
|
+
options[:scope] = [
|
60
|
+
reflection.association_foreign_key.to_sym,
|
61
|
+
reflection.options[:foreign_type].to_sym
|
62
|
+
]
|
63
|
+
else
|
64
|
+
reflection.association_foreign_key.to_sym
|
65
|
+
end
|
66
|
+
else
|
67
|
+
raise ArgumentError, "Only belongs_to associations can be used as a scope"
|
68
|
+
end
|
69
|
+
elsif options[:scope].is_a?(Symbol) && options[:scope].to_s !~ /_id$/
|
70
|
+
options[:scope] = "#{options[:scope]}_id".to_sym
|
71
|
+
end
|
72
|
+
|
73
|
+
self.acts_as_list_options = options
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# All the methods available to a record that has had <tt>acts_as_list</tt>
|
78
|
+
# specified. Each method works by assuming the object to be the item in the
|
79
|
+
# list, so <tt>chapter.move_lower</tt> would move that chapter lower in the
|
80
|
+
# list of all chapters. Likewise, <tt>chapter.first?</tt> would return +true+
|
81
|
+
# if that chapter is the first in the list of all chapters.
|
82
|
+
|
83
|
+
# Add the item to the end of the list
|
84
|
+
def add_to_list
|
85
|
+
remove_from_list if in_list?
|
86
|
+
update_attribute(position_column, last_position + 1)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns the current position
|
90
|
+
def current_position
|
91
|
+
send(position_column).to_i
|
92
|
+
end
|
93
|
+
|
94
|
+
# Decrease the position of this item without adjusting the rest of the list.
|
95
|
+
def decrement_position
|
96
|
+
in_list? && update_attribute(position_column, current_position - 1)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Return +true+ if this object is the first in the list.
|
100
|
+
def first?
|
101
|
+
in_list? && current_position == 1
|
102
|
+
end
|
103
|
+
alias_method :top?, :first?
|
104
|
+
|
105
|
+
# Returns the first item in the list
|
106
|
+
def first_item
|
107
|
+
list_scope.first
|
108
|
+
end
|
109
|
+
alias_method :top_item, :first_item
|
110
|
+
|
111
|
+
# Return the next higher item in the list.
|
112
|
+
def higher_item
|
113
|
+
item_at_offset(-1)
|
114
|
+
end
|
115
|
+
alias_method :previous_item, :higher_item
|
116
|
+
|
117
|
+
# Return items lower than this item or an empty array if it is the last item
|
118
|
+
def higher_items
|
119
|
+
list_scope.where(["#{quoted_position_column} < ?", current_position]).all
|
120
|
+
end
|
121
|
+
|
122
|
+
# Test if this record is in a list
|
123
|
+
def in_list?
|
124
|
+
!new_record? && !send(position_column).nil?
|
125
|
+
end
|
126
|
+
|
127
|
+
# Increase the position of this item without adjusting the rest of the list.
|
128
|
+
def increment_position
|
129
|
+
in_list? && update_attribute(position_column, current_position + 1)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Insert the item at the given position (defaults to the top position of 1).
|
133
|
+
def insert_at(position = 1)
|
134
|
+
if position > 0
|
135
|
+
remove_from_list
|
136
|
+
if position > last_position
|
137
|
+
add_to_list
|
138
|
+
else
|
139
|
+
increment_position_on_lower_items(position - 1)
|
140
|
+
update_attribute(position_column, position)
|
141
|
+
end
|
142
|
+
else
|
143
|
+
false
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Return the item at the offset specified from the current position
|
148
|
+
def item_at_offset(offset)
|
149
|
+
in_list? ? offset_scope(offset).first : nil
|
150
|
+
end
|
151
|
+
|
152
|
+
# Return +true+ if this object is the last in the list.
|
153
|
+
def last?
|
154
|
+
in_list? && current_position == last_position
|
155
|
+
end
|
156
|
+
alias_method :bottom?, :last?
|
157
|
+
|
158
|
+
# Returns the bottom item
|
159
|
+
def last_item
|
160
|
+
list_scope.last
|
161
|
+
end
|
162
|
+
alias_method :bottom_item, :last_item
|
163
|
+
|
164
|
+
# Returns the bottom position in the list.
|
165
|
+
def last_position
|
166
|
+
item = last_item
|
167
|
+
item ? item.current_position : 0
|
168
|
+
end
|
169
|
+
alias_method :bottom_position, :last_position
|
170
|
+
|
171
|
+
# Return the next lower item in the list.
|
172
|
+
def lower_item
|
173
|
+
item_at_offset(1)
|
174
|
+
end
|
175
|
+
alias_method :next_item, :lower_item
|
176
|
+
|
177
|
+
# Return items lower than this item or an empty array if it is the last item
|
178
|
+
def lower_items
|
179
|
+
list_scope.where(["#{quoted_position_column} > ?", current_position]).all
|
180
|
+
end
|
181
|
+
|
182
|
+
# Swap positions with the next higher item, if one exists.
|
183
|
+
def move_higher
|
184
|
+
in_list? && (first? || insert_at(current_position - 1))
|
185
|
+
end
|
186
|
+
alias_method :move_up, :move_higher
|
187
|
+
|
188
|
+
# Swap positions with the next lower item, if one exists.
|
189
|
+
def move_lower
|
190
|
+
in_list? && (last? || insert_at(current_position + 1))
|
191
|
+
end
|
192
|
+
alias_method :move_down, :move_lower
|
193
|
+
|
194
|
+
# Move to the bottom of the list. If the item is already in the list,
|
195
|
+
# the items below it have their position adjusted accordingly.
|
196
|
+
def move_to_bottom
|
197
|
+
in_list? && (last? || add_to_list)
|
198
|
+
end
|
199
|
+
|
200
|
+
# Move to the top of the list. If the item is already in the list,
|
201
|
+
# the items above it have their position adjusted accordingly.
|
202
|
+
def move_to_top
|
203
|
+
in_list? && (first? || insert_at(1))
|
204
|
+
end
|
205
|
+
|
206
|
+
# Removes the item from the list.
|
207
|
+
def remove_from_list
|
208
|
+
if in_list?
|
209
|
+
decrement_position_on_lower_items
|
210
|
+
update_attribute(position_column, nil)
|
211
|
+
else
|
212
|
+
false
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
private
|
217
|
+
def add_to_list_bottom #:nodoc:
|
218
|
+
send("#{position_column}=".to_sym, last_position + 1)
|
219
|
+
end
|
220
|
+
|
221
|
+
def base_scope #:nodoc:
|
222
|
+
self.class.unscoped.where(scope_condition)
|
223
|
+
end
|
224
|
+
|
225
|
+
def decrement_position_on_lower_items #:nodoc:
|
226
|
+
lower_scope(current_position).update_all(position_update('- 1'))
|
227
|
+
end
|
228
|
+
|
229
|
+
def increment_position_on_lower_items(position) #:nodoc:
|
230
|
+
lower_scope(position).update_all(position_update('+ 1'))
|
231
|
+
end
|
232
|
+
|
233
|
+
def list_scope #:nodoc:
|
234
|
+
base_scope.order(position_column).where("#{quoted_position_column} IS NOT NULL")
|
235
|
+
end
|
236
|
+
|
237
|
+
def lower_scope(position) #:nodoc:
|
238
|
+
base_scope.where(["#{quoted_position_column} > ?", position])
|
239
|
+
end
|
240
|
+
|
241
|
+
def offset_scope(offset) #:nodoc:
|
242
|
+
base_scope.where(position_column => current_position + offset)
|
243
|
+
end
|
244
|
+
|
245
|
+
def position_column #:nodoc:
|
246
|
+
acts_as_list_options[:column]
|
247
|
+
end
|
248
|
+
|
249
|
+
def position_update(direction) #:nodoc:
|
250
|
+
"#{quoted_position_column} = (#{quoted_position_column} #{direction})"
|
251
|
+
end
|
252
|
+
|
253
|
+
def quoted_position_column #:nodoc:
|
254
|
+
connection.quote_column_name(position_column)
|
255
|
+
end
|
256
|
+
|
257
|
+
def scope_condition #:nodoc:
|
258
|
+
if acts_as_list_options[:scope].is_a?(String)
|
259
|
+
instance_eval("\"#{acts_as_list_options[:scope]}\"")
|
260
|
+
else
|
261
|
+
Array.wrap(acts_as_list_options[:scope]).inject({}){ |m,k| m[k] = send(k); m }
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
end
|
266
|
+
|
267
|
+
ActiveRecord::Base.send(:include, Sortifiable)
|
data/sortifiable.gemspec
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "sortifiable/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "sortifiable"
|
7
|
+
s.version = Sortifiable::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Andrew White"]
|
10
|
+
s.email = ["andyw@pixeltrix.co.uk"]
|
11
|
+
s.homepage = %q{http://github.com/pixeltrix/sortifiable/}
|
12
|
+
s.summary = %q{Sort your models}
|
13
|
+
s.description = <<-EOF
|
14
|
+
This gem provides an acts_as_list compatible capability for sorting
|
15
|
+
and reordering a number of objects in a list. The class that has this
|
16
|
+
specified needs to have a +position+ column defined as an integer on
|
17
|
+
the mapped database table.
|
18
|
+
|
19
|
+
This gem requires ActiveRecord 3.0 as it has been refactored to use
|
20
|
+
the scope methods and query interface introduced with Ruby on Rails 3.0
|
21
|
+
EOF
|
22
|
+
|
23
|
+
s.files = [
|
24
|
+
".gemtest",
|
25
|
+
"CHANGELOG",
|
26
|
+
"LICENSE",
|
27
|
+
"README",
|
28
|
+
"Rakefile",
|
29
|
+
"lib/sortifiable.rb",
|
30
|
+
"lib/sortifiable/version.rb",
|
31
|
+
"sortifiable.gemspec",
|
32
|
+
"test/sortifiable_test.rb"
|
33
|
+
]
|
34
|
+
|
35
|
+
s.test_files = ["test/sortifiable_test.rb"]
|
36
|
+
s.require_paths = ["lib"]
|
37
|
+
|
38
|
+
s.add_dependency "activesupport", "~> 3.0.3"
|
39
|
+
s.add_dependency "activerecord", "~> 3.0.3"
|
40
|
+
s.add_development_dependency "bundler", "~> 1.0.10"
|
41
|
+
s.add_development_dependency "sqlite3", "~> 1.3.3"
|
42
|
+
end
|
@@ -0,0 +1,566 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'active_record'
|
4
|
+
require 'active_support/core_ext/kernel/reporting'
|
5
|
+
require 'sortifiable'
|
6
|
+
|
7
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
8
|
+
|
9
|
+
def setup_db
|
10
|
+
silence_stream(STDOUT) do
|
11
|
+
ActiveRecord::Schema.define(:version => 1) do
|
12
|
+
create_table :mixins do |t|
|
13
|
+
t.column :pos, :integer
|
14
|
+
t.column :parent_id, :integer
|
15
|
+
t.column :parent_type, :string
|
16
|
+
t.column :created_at, :datetime
|
17
|
+
t.column :updated_at, :datetime
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def teardown_db
|
24
|
+
ActiveRecord::Base.connection.tables.each do |table|
|
25
|
+
ActiveRecord::Base.connection.drop_table(table)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
setup_db
|
30
|
+
|
31
|
+
class Mixin < ActiveRecord::Base
|
32
|
+
end
|
33
|
+
|
34
|
+
class ListMixin < Mixin
|
35
|
+
acts_as_list :column => "pos", :scope => :parent
|
36
|
+
set_table_name "mixins"
|
37
|
+
default_scope order(:pos)
|
38
|
+
end
|
39
|
+
|
40
|
+
class ListMixinSub1 < ListMixin
|
41
|
+
end
|
42
|
+
|
43
|
+
class ListMixinSub2 < ListMixin
|
44
|
+
end
|
45
|
+
|
46
|
+
class ListWithStringScopeMixin < ActiveRecord::Base
|
47
|
+
acts_as_list :column => "pos", :scope => 'parent_id = #{parent_id}'
|
48
|
+
set_table_name "mixins"
|
49
|
+
default_scope order(:pos)
|
50
|
+
end
|
51
|
+
|
52
|
+
class ArrayScopeListMixin < Mixin
|
53
|
+
acts_as_list :column => "pos", :scope => [:parent_id, :parent_type]
|
54
|
+
set_table_name "mixins"
|
55
|
+
default_scope order(:pos)
|
56
|
+
end
|
57
|
+
|
58
|
+
teardown_db
|
59
|
+
|
60
|
+
class ListTest < Test::Unit::TestCase
|
61
|
+
|
62
|
+
def setup
|
63
|
+
setup_db
|
64
|
+
(1..4).each { |counter| ListMixin.create! :pos => counter, :parent_id => 5 }
|
65
|
+
end
|
66
|
+
|
67
|
+
def teardown
|
68
|
+
teardown_db
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_reordering
|
72
|
+
assert_equal [1, 2, 3, 4], ListMixin.where('parent_id = 5').map(&:id)
|
73
|
+
|
74
|
+
ListMixin.find(2).move_lower
|
75
|
+
assert_equal [1, 3, 2, 4], ListMixin.where('parent_id = 5').map(&:id)
|
76
|
+
|
77
|
+
ListMixin.find(2).move_higher
|
78
|
+
assert_equal [1, 2, 3, 4], ListMixin.where('parent_id = 5').map(&:id)
|
79
|
+
|
80
|
+
ListMixin.find(1).move_to_bottom
|
81
|
+
assert_equal [2, 3, 4, 1], ListMixin.where('parent_id = 5').map(&:id)
|
82
|
+
|
83
|
+
ListMixin.find(1).move_to_top
|
84
|
+
assert_equal [1, 2, 3, 4], ListMixin.where('parent_id = 5').map(&:id)
|
85
|
+
|
86
|
+
ListMixin.find(2).move_to_bottom
|
87
|
+
assert_equal [1, 3, 4, 2], ListMixin.where('parent_id = 5').map(&:id)
|
88
|
+
|
89
|
+
ListMixin.find(4).move_to_top
|
90
|
+
assert_equal [4, 1, 3, 2], ListMixin.where('parent_id = 5').map(&:id)
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_move_to_bottom_with_next_to_last_item
|
94
|
+
assert_equal [1, 2, 3, 4], ListMixin.where('parent_id = 5').map(&:id)
|
95
|
+
ListMixin.find(3).move_to_bottom
|
96
|
+
assert_equal [1, 2, 4, 3], ListMixin.where('parent_id = 5').map(&:id)
|
97
|
+
end
|
98
|
+
|
99
|
+
def test_next_prev
|
100
|
+
assert_equal ListMixin.find(2), ListMixin.find(1).lower_item
|
101
|
+
assert_nil ListMixin.find(1).higher_item
|
102
|
+
assert_equal ListMixin.find(3), ListMixin.find(4).higher_item
|
103
|
+
assert_nil ListMixin.find(4).lower_item
|
104
|
+
end
|
105
|
+
|
106
|
+
def test_injection
|
107
|
+
item = ListMixin.new(:parent_id => 1)
|
108
|
+
assert_equal({ :parent_id => 1 }, item.send(:scope_condition))
|
109
|
+
assert_equal("pos", item.send(:position_column))
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_insert
|
113
|
+
new = ListMixin.create(:parent_id => 20)
|
114
|
+
assert_equal 1, new.pos
|
115
|
+
assert new.first?
|
116
|
+
assert new.last?
|
117
|
+
|
118
|
+
new = ListMixin.create(:parent_id => 20)
|
119
|
+
assert_equal 2, new.pos
|
120
|
+
assert !new.first?
|
121
|
+
assert new.last?
|
122
|
+
|
123
|
+
new = ListMixin.create(:parent_id => 20)
|
124
|
+
assert_equal 3, new.pos
|
125
|
+
assert !new.first?
|
126
|
+
assert new.last?
|
127
|
+
|
128
|
+
new = ListMixin.create(:parent_id => 0)
|
129
|
+
assert_equal 1, new.pos
|
130
|
+
assert new.first?
|
131
|
+
assert new.last?
|
132
|
+
end
|
133
|
+
|
134
|
+
def test_insert_at
|
135
|
+
new = ListMixin.create(:parent_id => 20)
|
136
|
+
assert_equal 1, new.pos
|
137
|
+
|
138
|
+
new = ListMixin.create(:parent_id => 20)
|
139
|
+
assert_equal 2, new.pos
|
140
|
+
|
141
|
+
new = ListMixin.create(:parent_id => 20)
|
142
|
+
assert_equal 3, new.pos
|
143
|
+
|
144
|
+
new4 = ListMixin.create(:parent_id => 20)
|
145
|
+
assert_equal 4, new4.pos
|
146
|
+
|
147
|
+
new4.insert_at(3)
|
148
|
+
assert_equal 3, new4.pos
|
149
|
+
|
150
|
+
new.reload
|
151
|
+
assert_equal 4, new.pos
|
152
|
+
|
153
|
+
new.insert_at(2)
|
154
|
+
assert_equal 2, new.pos
|
155
|
+
|
156
|
+
new4.reload
|
157
|
+
assert_equal 4, new4.pos
|
158
|
+
|
159
|
+
new5 = ListMixin.create(:parent_id => 20)
|
160
|
+
assert_equal 5, new5.pos
|
161
|
+
|
162
|
+
new5.insert_at(1)
|
163
|
+
assert_equal 1, new5.pos
|
164
|
+
|
165
|
+
new4.reload
|
166
|
+
assert_equal 5, new4.pos
|
167
|
+
end
|
168
|
+
|
169
|
+
def test_delete_middle
|
170
|
+
assert_equal [1, 2, 3, 4], ListMixin.where('parent_id = 5').map(&:id)
|
171
|
+
|
172
|
+
ListMixin.find(2).destroy
|
173
|
+
|
174
|
+
assert_equal [1, 3, 4], ListMixin.where('parent_id = 5').map(&:id)
|
175
|
+
|
176
|
+
assert_equal 1, ListMixin.find(1).pos
|
177
|
+
assert_equal 2, ListMixin.find(3).pos
|
178
|
+
assert_equal 3, ListMixin.find(4).pos
|
179
|
+
|
180
|
+
ListMixin.find(1).destroy
|
181
|
+
|
182
|
+
assert_equal [3, 4], ListMixin.where('parent_id = 5').map(&:id)
|
183
|
+
|
184
|
+
assert_equal 1, ListMixin.find(3).pos
|
185
|
+
assert_equal 2, ListMixin.find(4).pos
|
186
|
+
end
|
187
|
+
|
188
|
+
def test_with_string_based_scope
|
189
|
+
new = ListWithStringScopeMixin.create(:parent_id => 500)
|
190
|
+
assert_equal 1, new.pos
|
191
|
+
assert new.first?
|
192
|
+
assert new.last?
|
193
|
+
end
|
194
|
+
|
195
|
+
def test_nil_scope
|
196
|
+
new1, new2, new3 = ListMixin.create, ListMixin.create, ListMixin.create
|
197
|
+
new2.move_higher
|
198
|
+
assert_equal [new2, new1, new3], ListMixin.where('parent_id IS NULL')
|
199
|
+
end
|
200
|
+
|
201
|
+
def test_remove_from_list_should_then_fail_in_list?
|
202
|
+
assert_equal true, ListMixin.find(1).in_list?
|
203
|
+
ListMixin.find(1).remove_from_list
|
204
|
+
assert_equal false, ListMixin.find(1).in_list?
|
205
|
+
end
|
206
|
+
|
207
|
+
def test_remove_from_list_should_set_position_to_nil
|
208
|
+
assert_equal [1, 2, 3, 4], ListMixin.where('parent_id = 5').map(&:id)
|
209
|
+
|
210
|
+
ListMixin.find(2).remove_from_list
|
211
|
+
|
212
|
+
assert_equal [2, 1, 3, 4], ListMixin.where('parent_id = 5').map(&:id)
|
213
|
+
|
214
|
+
assert_equal 1, ListMixin.find(1).pos
|
215
|
+
assert_equal nil, ListMixin.find(2).pos
|
216
|
+
assert_equal 2, ListMixin.find(3).pos
|
217
|
+
assert_equal 3, ListMixin.find(4).pos
|
218
|
+
end
|
219
|
+
|
220
|
+
def test_remove_before_destroy_does_not_shift_lower_items_twice
|
221
|
+
assert_equal [1, 2, 3, 4], ListMixin.where('parent_id = 5').map(&:id)
|
222
|
+
|
223
|
+
ListMixin.find(2).remove_from_list
|
224
|
+
ListMixin.find(2).destroy
|
225
|
+
|
226
|
+
assert_equal [1, 3, 4], ListMixin.where('parent_id = 5').map(&:id)
|
227
|
+
|
228
|
+
assert_equal 1, ListMixin.find(1).pos
|
229
|
+
assert_equal 2, ListMixin.find(3).pos
|
230
|
+
assert_equal 3, ListMixin.find(4).pos
|
231
|
+
end
|
232
|
+
|
233
|
+
def test_before_destroy_callbacks_do_not_update_position_to_nil_before_deleting_the_record
|
234
|
+
assert_equal [1, 2, 3, 4], ListMixin.where('parent_id = 5').map(&:id)
|
235
|
+
|
236
|
+
# We need to trigger all the before_destroy callbacks without actually
|
237
|
+
# destroying the record so we can see the affect the callbacks have on
|
238
|
+
# the record.
|
239
|
+
list = ListMixin.find(2)
|
240
|
+
if list.respond_to?(:run_callbacks)
|
241
|
+
list.run_callbacks(:destroy)
|
242
|
+
else
|
243
|
+
list.send(:callback, :before_destroy)
|
244
|
+
end
|
245
|
+
|
246
|
+
assert_equal [1, 2, 3, 4], ListMixin.where('parent_id = 5').map(&:id)
|
247
|
+
|
248
|
+
assert_equal 1, ListMixin.find(1).pos
|
249
|
+
assert_equal 2, ListMixin.find(2).pos
|
250
|
+
assert_equal 2, ListMixin.find(3).pos
|
251
|
+
assert_equal 3, ListMixin.find(4).pos
|
252
|
+
end
|
253
|
+
|
254
|
+
def test_higher_items
|
255
|
+
assert_equal [1, 2], ListMixin.find(3).higher_items.map(&:id)
|
256
|
+
assert_equal [], ListMixin.find(1).higher_items.map(&:id)
|
257
|
+
end
|
258
|
+
|
259
|
+
def test_lower_items
|
260
|
+
assert_equal [3, 4], ListMixin.find(2).lower_items.map(&:id)
|
261
|
+
assert_equal [], ListMixin.find(4).lower_items.map(&:id)
|
262
|
+
end
|
263
|
+
|
264
|
+
end
|
265
|
+
|
266
|
+
class ListSubTest < Test::Unit::TestCase
|
267
|
+
|
268
|
+
def setup
|
269
|
+
setup_db
|
270
|
+
(1..4).each { |i| ((i % 2 == 1) ? ListMixinSub1 : ListMixinSub2).create! :pos => i, :parent_id => 5000 }
|
271
|
+
end
|
272
|
+
|
273
|
+
def teardown
|
274
|
+
teardown_db
|
275
|
+
end
|
276
|
+
|
277
|
+
def test_reordering
|
278
|
+
assert_equal [1, 2, 3, 4], ListMixin.where('parent_id = 5000').map(&:id)
|
279
|
+
|
280
|
+
ListMixin.find(2).move_lower
|
281
|
+
assert_equal [1, 3, 2, 4], ListMixin.where('parent_id = 5000').map(&:id)
|
282
|
+
|
283
|
+
ListMixin.find(2).move_higher
|
284
|
+
assert_equal [1, 2, 3, 4], ListMixin.where('parent_id = 5000').map(&:id)
|
285
|
+
|
286
|
+
ListMixin.find(1).move_to_bottom
|
287
|
+
assert_equal [2, 3, 4, 1], ListMixin.where('parent_id = 5000').map(&:id)
|
288
|
+
|
289
|
+
ListMixin.find(1).move_to_top
|
290
|
+
assert_equal [1, 2, 3, 4], ListMixin.where('parent_id = 5000').map(&:id)
|
291
|
+
|
292
|
+
ListMixin.find(2).move_to_bottom
|
293
|
+
assert_equal [1, 3, 4, 2], ListMixin.where('parent_id = 5000').map(&:id)
|
294
|
+
|
295
|
+
ListMixin.find(4).move_to_top
|
296
|
+
assert_equal [4, 1, 3, 2], ListMixin.where('parent_id = 5000').map(&:id)
|
297
|
+
end
|
298
|
+
|
299
|
+
def test_move_to_bottom_with_next_to_last_item
|
300
|
+
assert_equal [1, 2, 3, 4], ListMixin.where('parent_id = 5000').map(&:id)
|
301
|
+
ListMixin.find(3).move_to_bottom
|
302
|
+
assert_equal [1, 2, 4, 3], ListMixin.where('parent_id = 5000').map(&:id)
|
303
|
+
end
|
304
|
+
|
305
|
+
def test_next_prev
|
306
|
+
assert_equal ListMixin.find(2), ListMixin.find(1).lower_item
|
307
|
+
assert_nil ListMixin.find(1).higher_item
|
308
|
+
assert_equal ListMixin.find(3), ListMixin.find(4).higher_item
|
309
|
+
assert_nil ListMixin.find(4).lower_item
|
310
|
+
end
|
311
|
+
|
312
|
+
def test_injection
|
313
|
+
item = ListMixin.new("parent_id"=>1)
|
314
|
+
assert_equal({ :parent_id => 1 }, item.send(:scope_condition))
|
315
|
+
assert_equal("pos", item.send(:position_column))
|
316
|
+
end
|
317
|
+
|
318
|
+
def test_insert_at
|
319
|
+
new = ListMixin.create("parent_id" => 20)
|
320
|
+
assert_equal 1, new.pos
|
321
|
+
|
322
|
+
new = ListMixinSub1.create("parent_id" => 20)
|
323
|
+
assert_equal 2, new.pos
|
324
|
+
|
325
|
+
new = ListMixinSub2.create("parent_id" => 20)
|
326
|
+
assert_equal 3, new.pos
|
327
|
+
|
328
|
+
new4 = ListMixin.create("parent_id" => 20)
|
329
|
+
assert_equal 4, new4.pos
|
330
|
+
|
331
|
+
new4.insert_at(3)
|
332
|
+
assert_equal 3, new4.pos
|
333
|
+
|
334
|
+
new.reload
|
335
|
+
assert_equal 4, new.pos
|
336
|
+
|
337
|
+
new.insert_at(2)
|
338
|
+
assert_equal 2, new.pos
|
339
|
+
|
340
|
+
new4.reload
|
341
|
+
assert_equal 4, new4.pos
|
342
|
+
|
343
|
+
new5 = ListMixinSub1.create("parent_id" => 20)
|
344
|
+
assert_equal 5, new5.pos
|
345
|
+
|
346
|
+
new5.insert_at(1)
|
347
|
+
assert_equal 1, new5.pos
|
348
|
+
|
349
|
+
new4.reload
|
350
|
+
assert_equal 5, new4.pos
|
351
|
+
end
|
352
|
+
|
353
|
+
def test_delete_middle
|
354
|
+
assert_equal [1, 2, 3, 4], ListMixin.where('parent_id = 5000').map(&:id)
|
355
|
+
|
356
|
+
ListMixin.find(2).destroy
|
357
|
+
|
358
|
+
assert_equal [1, 3, 4], ListMixin.where('parent_id = 5000').map(&:id)
|
359
|
+
|
360
|
+
assert_equal 1, ListMixin.find(1).pos
|
361
|
+
assert_equal 2, ListMixin.find(3).pos
|
362
|
+
assert_equal 3, ListMixin.find(4).pos
|
363
|
+
|
364
|
+
ListMixin.find(1).destroy
|
365
|
+
|
366
|
+
assert_equal [3, 4], ListMixin.where('parent_id = 5000').map(&:id)
|
367
|
+
|
368
|
+
assert_equal 1, ListMixin.find(3).pos
|
369
|
+
assert_equal 2, ListMixin.find(4).pos
|
370
|
+
end
|
371
|
+
|
372
|
+
def test_higher_items
|
373
|
+
ListMixin.find(2).remove_from_list
|
374
|
+
assert_equal [1], ListMixin.find(3).higher_items.map(&:id)
|
375
|
+
assert_equal [], ListMixin.find(1).higher_items.map(&:id)
|
376
|
+
end
|
377
|
+
|
378
|
+
def test_lower_items
|
379
|
+
ListMixin.find(3).remove_from_list
|
380
|
+
assert_equal [4], ListMixin.find(2).lower_items.map(&:id)
|
381
|
+
assert_equal [], ListMixin.find(4).lower_items.map(&:id)
|
382
|
+
end
|
383
|
+
|
384
|
+
end
|
385
|
+
|
386
|
+
class ArrayScopeListTest < Test::Unit::TestCase
|
387
|
+
|
388
|
+
def setup
|
389
|
+
setup_db
|
390
|
+
(1..4).each do |counter|
|
391
|
+
ArrayScopeListMixin.create!(
|
392
|
+
:pos => counter,
|
393
|
+
:parent_id => 5,
|
394
|
+
:parent_type => 'ParentClass'
|
395
|
+
)
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
def teardown
|
400
|
+
teardown_db
|
401
|
+
end
|
402
|
+
|
403
|
+
def conditions(options = {})
|
404
|
+
{ :parent_id => 5, :parent_type => 'ParentClass' }.merge(options)
|
405
|
+
end
|
406
|
+
|
407
|
+
def test_reordering
|
408
|
+
assert_equal [1, 2, 3, 4], ArrayScopeListMixin.where(conditions).map(&:id)
|
409
|
+
|
410
|
+
ArrayScopeListMixin.find(2).move_lower
|
411
|
+
assert_equal [1, 3, 2, 4], ArrayScopeListMixin.where(conditions).map(&:id)
|
412
|
+
|
413
|
+
ArrayScopeListMixin.find(2).move_higher
|
414
|
+
assert_equal [1, 2, 3, 4], ArrayScopeListMixin.where(conditions).map(&:id)
|
415
|
+
|
416
|
+
ArrayScopeListMixin.find(1).move_to_bottom
|
417
|
+
assert_equal [2, 3, 4, 1], ArrayScopeListMixin.where(conditions).map(&:id)
|
418
|
+
|
419
|
+
ArrayScopeListMixin.find(1).move_to_top
|
420
|
+
assert_equal [1, 2, 3, 4], ArrayScopeListMixin.where(conditions).map(&:id)
|
421
|
+
|
422
|
+
ArrayScopeListMixin.find(2).move_to_bottom
|
423
|
+
assert_equal [1, 3, 4, 2], ArrayScopeListMixin.where(conditions).map(&:id)
|
424
|
+
|
425
|
+
ArrayScopeListMixin.find(4).move_to_top
|
426
|
+
assert_equal [4, 1, 3, 2], ArrayScopeListMixin.where(conditions).map(&:id)
|
427
|
+
end
|
428
|
+
|
429
|
+
def test_move_to_bottom_with_next_to_last_item
|
430
|
+
assert_equal [1, 2, 3, 4], ArrayScopeListMixin.where(conditions).map(&:id)
|
431
|
+
ArrayScopeListMixin.find(3).move_to_bottom
|
432
|
+
assert_equal [1, 2, 4, 3], ArrayScopeListMixin.where(conditions).map(&:id)
|
433
|
+
end
|
434
|
+
|
435
|
+
def test_next_prev
|
436
|
+
assert_equal ArrayScopeListMixin.find(2), ArrayScopeListMixin.find(1).lower_item
|
437
|
+
assert_nil ArrayScopeListMixin.find(1).higher_item
|
438
|
+
assert_equal ArrayScopeListMixin.find(3), ArrayScopeListMixin.find(4).higher_item
|
439
|
+
assert_nil ArrayScopeListMixin.find(4).lower_item
|
440
|
+
end
|
441
|
+
|
442
|
+
def test_injection
|
443
|
+
item = ArrayScopeListMixin.new(conditions)
|
444
|
+
assert_equal(conditions, item.send(:scope_condition))
|
445
|
+
assert_equal("pos", item.send(:position_column))
|
446
|
+
end
|
447
|
+
|
448
|
+
def test_insert
|
449
|
+
new = ArrayScopeListMixin.create(conditions(:parent_id => 20))
|
450
|
+
assert_equal 1, new.pos
|
451
|
+
assert new.first?
|
452
|
+
assert new.last?
|
453
|
+
|
454
|
+
new = ArrayScopeListMixin.create(conditions(:parent_id => 20))
|
455
|
+
assert_equal 2, new.pos
|
456
|
+
assert !new.first?
|
457
|
+
assert new.last?
|
458
|
+
|
459
|
+
new = ArrayScopeListMixin.create(conditions(:parent_id => 20))
|
460
|
+
assert_equal 3, new.pos
|
461
|
+
assert !new.first?
|
462
|
+
assert new.last?
|
463
|
+
|
464
|
+
new = ArrayScopeListMixin.create(conditions(:parent_id => 0))
|
465
|
+
assert_equal 1, new.pos
|
466
|
+
assert new.first?
|
467
|
+
assert new.last?
|
468
|
+
end
|
469
|
+
|
470
|
+
def test_insert_at
|
471
|
+
new = ArrayScopeListMixin.create(conditions(:parent_id => 20))
|
472
|
+
assert_equal 1, new.pos
|
473
|
+
|
474
|
+
new = ArrayScopeListMixin.create(conditions(:parent_id => 20))
|
475
|
+
assert_equal 2, new.pos
|
476
|
+
|
477
|
+
new = ArrayScopeListMixin.create(conditions(:parent_id => 20))
|
478
|
+
assert_equal 3, new.pos
|
479
|
+
|
480
|
+
new4 = ArrayScopeListMixin.create(conditions(:parent_id => 20))
|
481
|
+
assert_equal 4, new4.pos
|
482
|
+
|
483
|
+
new4.insert_at(3)
|
484
|
+
assert_equal 3, new4.pos
|
485
|
+
|
486
|
+
new.reload
|
487
|
+
assert_equal 4, new.pos
|
488
|
+
|
489
|
+
new.insert_at(2)
|
490
|
+
assert_equal 2, new.pos
|
491
|
+
|
492
|
+
new4.reload
|
493
|
+
assert_equal 4, new4.pos
|
494
|
+
|
495
|
+
new5 = ArrayScopeListMixin.create(conditions(:parent_id => 20))
|
496
|
+
assert_equal 5, new5.pos
|
497
|
+
|
498
|
+
new5.insert_at(1)
|
499
|
+
assert_equal 1, new5.pos
|
500
|
+
|
501
|
+
new4.reload
|
502
|
+
assert_equal 5, new4.pos
|
503
|
+
end
|
504
|
+
|
505
|
+
def test_delete_middle
|
506
|
+
assert_equal [1, 2, 3, 4], ArrayScopeListMixin.where(conditions).map(&:id)
|
507
|
+
|
508
|
+
ArrayScopeListMixin.find(2).destroy
|
509
|
+
|
510
|
+
assert_equal [1, 3, 4], ArrayScopeListMixin.where(conditions).map(&:id)
|
511
|
+
|
512
|
+
assert_equal 1, ArrayScopeListMixin.find(1).pos
|
513
|
+
assert_equal 2, ArrayScopeListMixin.find(3).pos
|
514
|
+
assert_equal 3, ArrayScopeListMixin.find(4).pos
|
515
|
+
|
516
|
+
ArrayScopeListMixin.find(1).destroy
|
517
|
+
|
518
|
+
assert_equal [3, 4], ArrayScopeListMixin.where(conditions).map(&:id)
|
519
|
+
|
520
|
+
assert_equal 1, ArrayScopeListMixin.find(3).pos
|
521
|
+
assert_equal 2, ArrayScopeListMixin.find(4).pos
|
522
|
+
end
|
523
|
+
|
524
|
+
def test_remove_from_list_should_then_fail_in_list?
|
525
|
+
assert_equal true, ArrayScopeListMixin.find(1).in_list?
|
526
|
+
ArrayScopeListMixin.find(1).remove_from_list
|
527
|
+
assert_equal false, ArrayScopeListMixin.find(1).in_list?
|
528
|
+
end
|
529
|
+
|
530
|
+
def test_remove_from_list_should_set_position_to_nil
|
531
|
+
assert_equal [1, 2, 3, 4], ArrayScopeListMixin.where(conditions).map(&:id)
|
532
|
+
|
533
|
+
ArrayScopeListMixin.find(2).remove_from_list
|
534
|
+
|
535
|
+
assert_equal [2, 1, 3, 4], ArrayScopeListMixin.where(conditions).map(&:id)
|
536
|
+
|
537
|
+
assert_equal 1, ArrayScopeListMixin.find(1).pos
|
538
|
+
assert_equal nil, ArrayScopeListMixin.find(2).pos
|
539
|
+
assert_equal 2, ArrayScopeListMixin.find(3).pos
|
540
|
+
assert_equal 3, ArrayScopeListMixin.find(4).pos
|
541
|
+
end
|
542
|
+
|
543
|
+
def test_remove_before_destroy_does_not_shift_lower_items_twice
|
544
|
+
assert_equal [1, 2, 3, 4], ArrayScopeListMixin.where(conditions).map(&:id)
|
545
|
+
|
546
|
+
ArrayScopeListMixin.find(2).remove_from_list
|
547
|
+
ArrayScopeListMixin.find(2).destroy
|
548
|
+
|
549
|
+
assert_equal [1, 3, 4], ArrayScopeListMixin.where(conditions).map(&:id)
|
550
|
+
|
551
|
+
assert_equal 1, ArrayScopeListMixin.find(1).pos
|
552
|
+
assert_equal 2, ArrayScopeListMixin.find(3).pos
|
553
|
+
assert_equal 3, ArrayScopeListMixin.find(4).pos
|
554
|
+
end
|
555
|
+
|
556
|
+
def test_higher_items
|
557
|
+
assert_equal [1, 2], ArrayScopeListMixin.find(3).higher_items.map(&:id)
|
558
|
+
assert_equal [], ArrayScopeListMixin.find(1).higher_items.map(&:id)
|
559
|
+
end
|
560
|
+
|
561
|
+
def test_lower_items
|
562
|
+
assert_equal [3, 4], ArrayScopeListMixin.find(2).lower_items.map(&:id)
|
563
|
+
assert_equal [], ArrayScopeListMixin.find(4).lower_items.map(&:id)
|
564
|
+
end
|
565
|
+
|
566
|
+
end
|
metadata
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sortifiable
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Andrew White
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-02-07 00:00:00 +00:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: activesupport
|
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: activerecord
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 1
|
46
|
+
segments:
|
47
|
+
- 3
|
48
|
+
- 0
|
49
|
+
- 3
|
50
|
+
version: 3.0.3
|
51
|
+
type: :runtime
|
52
|
+
version_requirements: *id002
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: bundler
|
55
|
+
prerelease: false
|
56
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
hash: 3
|
62
|
+
segments:
|
63
|
+
- 1
|
64
|
+
- 0
|
65
|
+
- 10
|
66
|
+
version: 1.0.10
|
67
|
+
type: :development
|
68
|
+
version_requirements: *id003
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: sqlite3
|
71
|
+
prerelease: false
|
72
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
hash: 29
|
78
|
+
segments:
|
79
|
+
- 1
|
80
|
+
- 3
|
81
|
+
- 3
|
82
|
+
version: 1.3.3
|
83
|
+
type: :development
|
84
|
+
version_requirements: *id004
|
85
|
+
description: |
|
86
|
+
This gem provides an acts_as_list compatible capability for sorting
|
87
|
+
and reordering a number of objects in a list. The class that has this
|
88
|
+
specified needs to have a +position+ column defined as an integer on
|
89
|
+
the mapped database table.
|
90
|
+
|
91
|
+
This gem requires ActiveRecord 3.0 as it has been refactored to use
|
92
|
+
the scope methods and query interface introduced with Ruby on Rails 3.0
|
93
|
+
|
94
|
+
email:
|
95
|
+
- andyw@pixeltrix.co.uk
|
96
|
+
executables: []
|
97
|
+
|
98
|
+
extensions: []
|
99
|
+
|
100
|
+
extra_rdoc_files: []
|
101
|
+
|
102
|
+
files:
|
103
|
+
- .gemtest
|
104
|
+
- CHANGELOG
|
105
|
+
- LICENSE
|
106
|
+
- README
|
107
|
+
- Rakefile
|
108
|
+
- lib/sortifiable.rb
|
109
|
+
- lib/sortifiable/version.rb
|
110
|
+
- sortifiable.gemspec
|
111
|
+
- test/sortifiable_test.rb
|
112
|
+
has_rdoc: true
|
113
|
+
homepage: http://github.com/pixeltrix/sortifiable/
|
114
|
+
licenses: []
|
115
|
+
|
116
|
+
post_install_message:
|
117
|
+
rdoc_options: []
|
118
|
+
|
119
|
+
require_paths:
|
120
|
+
- lib
|
121
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
122
|
+
none: false
|
123
|
+
requirements:
|
124
|
+
- - ">="
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
hash: 3
|
127
|
+
segments:
|
128
|
+
- 0
|
129
|
+
version: "0"
|
130
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
131
|
+
none: false
|
132
|
+
requirements:
|
133
|
+
- - ">="
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
hash: 3
|
136
|
+
segments:
|
137
|
+
- 0
|
138
|
+
version: "0"
|
139
|
+
requirements: []
|
140
|
+
|
141
|
+
rubyforge_project:
|
142
|
+
rubygems_version: 1.4.2
|
143
|
+
signing_key:
|
144
|
+
specification_version: 3
|
145
|
+
summary: Sort your models
|
146
|
+
test_files:
|
147
|
+
- test/sortifiable_test.rb
|