activeadmin-orderable-table 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.
- checksums.yaml +7 -0
- data/.gitignore +7 -0
- data/.rspec +2 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +61 -0
- data/activeadmin-orderable-table.gemspec +24 -0
- data/app/assets/javascripts/activeadmin-orderable-table.js +97 -0
- data/app/assets/stylesheets/activeadmin-orderable-table.css +1 -0
- data/lib/active_admin/orderable_table.rb +42 -0
- data/lib/active_record/acts_as.rb +7 -0
- data/lib/active_record/acts_as/orderable_table.rb +93 -0
- data/lib/activeadmin-orderable-table.rb +7 -0
- data/lib/activeadmin-orderable-table/version.rb +5 -0
- data/spec/db_helper.rb +14 -0
- data/spec/lib/active_record/acts_as/orderable_table_spec.rb +181 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/support/utilities.rb +4 -0
- metadata +158 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1d219b5422a68bd3eb67ef48836e3b9792d00515
|
4
|
+
data.tar.gz: 1442df0bc372616af4422f4210c82542b9203082
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 94f61daf00fbf7d6753012f2bb783ac5e59b42128bd26e5ecfc9f76f2cb26d74c2650922d2c0eabed71ca90d6b65abe5cf94e9d68cc5e19691fb1c97b05d2d5e
|
7
|
+
data.tar.gz: fb6d9c701d956a26ef8807b6131a9c6f814810d93de07d4cd405874b4eefdd355a56b40624af89b41519f70b87e7b306b677c28a52973669b76d4881e7cc0dff
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2016 Max Hordiichuk
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# Active Admin Orderable Table
|
2
|
+
|
3
|
+
This gem extends ActiveAdmin so that your index page's table rows can be
|
4
|
+
orderable without page reloading via a drag-and-drop interface.
|
5
|
+
|
6
|
+
## Usage
|
7
|
+
|
8
|
+
### Add gem to your Gemfile
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem 'sortable-rails'
|
12
|
+
gem 'activeadmin-orderable-table'
|
13
|
+
```
|
14
|
+
|
15
|
+
### Include the JavaScript in active_admin.js
|
16
|
+
|
17
|
+
```javascript
|
18
|
+
//= require sortable-rails
|
19
|
+
//= require activeadmin-orderable-table
|
20
|
+
```
|
21
|
+
|
22
|
+
### Include the Stylesheet in active_admin.css
|
23
|
+
```css
|
24
|
+
//= require activeadmin-orderable-table
|
25
|
+
```
|
26
|
+
|
27
|
+
### Configure your ActiveRecord model
|
28
|
+
You need to add an ordinal column to desired table:
|
29
|
+
```bash
|
30
|
+
rails g migration AddOrdinalToPage ordinal:integer
|
31
|
+
rake db:migrate
|
32
|
+
```
|
33
|
+
|
34
|
+
Then add following line to model that suppose to be orderable:
|
35
|
+
```ruby
|
36
|
+
acts_as_orderable_table
|
37
|
+
```
|
38
|
+
|
39
|
+
### Configure your ActiveAdmin Resource
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
ActiveAdmin.register Page do
|
43
|
+
config.sort_order = 'ordinal_asc'
|
44
|
+
config.paginate = false # optional; drag-and-drop across pages is not supported
|
45
|
+
|
46
|
+
orderable # creates the controller action which handles the ordering
|
47
|
+
|
48
|
+
index do
|
49
|
+
orderable_handle_column # inserts a drag handle
|
50
|
+
# other columns...
|
51
|
+
end
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
## Contributing
|
56
|
+
|
57
|
+
1. Fork it
|
58
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
59
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
60
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
61
|
+
5. Create new Pull Request
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/activeadmin-orderable-table/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = 'activeadmin-orderable-table'
|
6
|
+
s.version = Activeadmin::OrderableTable::VERSION
|
7
|
+
s.date = Time.new.strftime('%Y-%m-%d')
|
8
|
+
s.summary = 'Order ActiveAdmin tables'
|
9
|
+
s.description = 'Drag and drop order interface for ActiveAdmin tables'
|
10
|
+
s.authors = ['Max Hordiichuk']
|
11
|
+
s.email = 'hordijchuk.m.i@gmail.com'
|
12
|
+
s.files = `git ls-files`.split("\n")
|
13
|
+
s.homepage = 'https://github.com/MaximHordijchuk/activeadmin-orderable-table'
|
14
|
+
s.license = 'MIT'
|
15
|
+
|
16
|
+
s.add_development_dependency 'bundler', '~> 1.10'
|
17
|
+
s.add_development_dependency 'rspec-rails', '~> 3.5'
|
18
|
+
s.add_development_dependency 'test-unit', '~> 3.0'
|
19
|
+
s.add_development_dependency 'sqlite3', '~> 1.3'
|
20
|
+
s.add_development_dependency 'sass-rails'
|
21
|
+
s.add_development_dependency 'activerecord'
|
22
|
+
|
23
|
+
s.add_runtime_dependency 'sortable-rails', '~> 1.4'
|
24
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
(function($) {
|
2
|
+
$(document).ready(function() {
|
3
|
+
var $handle = $('.activeadmin-orderable-handle');
|
4
|
+
if ($handle.length > 0) {
|
5
|
+
$handle.closest('tbody').activeAdminOrderableTable();
|
6
|
+
}
|
7
|
+
});
|
8
|
+
|
9
|
+
$.fn.activeAdminOrderableTable = function() {
|
10
|
+
var $container = this;
|
11
|
+
|
12
|
+
// Returns all sibling elements between $firstElement and $lastElement
|
13
|
+
// including these elements
|
14
|
+
var findElements = function ($container, $firstElement, $lastElement) {
|
15
|
+
var $elements = $firstElement.nextUntil($lastElement);
|
16
|
+
var elements = [$firstElement[0]];
|
17
|
+
for (var i = 0; i < $elements.length; i++) {
|
18
|
+
elements.push($elements[i]);
|
19
|
+
}
|
20
|
+
elements.push($lastElement[0]);
|
21
|
+
return elements;
|
22
|
+
};
|
23
|
+
|
24
|
+
// [1, 2, 3, 4, 5] -> [2, 3, 4, 5, 1]
|
25
|
+
// Reverse: [1, 2, 3, 4, 5] -> [5, 1, 2, 3, 4]
|
26
|
+
var rotateArray = function (elements, reverse) {
|
27
|
+
var result = [], i;
|
28
|
+
if (reverse) {
|
29
|
+
result.push(elements[elements.length - 1]);
|
30
|
+
for (i = 0; i < elements.length - 1; i++) {
|
31
|
+
result.push(elements[i]);
|
32
|
+
}
|
33
|
+
} else {
|
34
|
+
for (i = 1; i < elements.length; i++) {
|
35
|
+
result.push(elements[i]);
|
36
|
+
}
|
37
|
+
result.push(elements[0]);
|
38
|
+
}
|
39
|
+
return result;
|
40
|
+
};
|
41
|
+
|
42
|
+
// Returns array of elements' ordinal values
|
43
|
+
var getOrdinals = function (elements) {
|
44
|
+
var ordinals = [];
|
45
|
+
for (var i = 0; i < elements.length; i++) {
|
46
|
+
ordinals.push($(elements[i]).find('[data-ordinal]').data('ordinal'));
|
47
|
+
}
|
48
|
+
return ordinals;
|
49
|
+
};
|
50
|
+
|
51
|
+
var setOrdinals = function (elements, ordinals) {
|
52
|
+
for (var i = 0; i < elements.length; i++) {
|
53
|
+
$(elements[i]).find('[data-ordinal]').data('ordinal', ordinals[i]);
|
54
|
+
}
|
55
|
+
};
|
56
|
+
|
57
|
+
var sendReorderRequest = function ($element, ordinals) {
|
58
|
+
var url = $element.find('[data-reorder-url]').data('reorder-url');
|
59
|
+
$.ajax({
|
60
|
+
url: url,
|
61
|
+
type: 'post',
|
62
|
+
data: { position: $element.find('[data-ordinal]').data('ordinal'),
|
63
|
+
ordinals: ordinals },
|
64
|
+
error: function(error) { console.log('Reordering failed:', error) }
|
65
|
+
});
|
66
|
+
};
|
67
|
+
|
68
|
+
var logOrdinals = function (elements) {
|
69
|
+
for (var i = 0; i < elements.length; i++) {
|
70
|
+
console.log($(elements[i]).find('[data-ordinal]').data('ordinal'));
|
71
|
+
}
|
72
|
+
};
|
73
|
+
|
74
|
+
Sortable.create($container[0], {
|
75
|
+
animation: 150,
|
76
|
+
draggable: 'tr',
|
77
|
+
onEnd: function (event) {
|
78
|
+
if (event.oldIndex != event.newIndex) {
|
79
|
+
var $firstElement, $lastElement, $movedElement, elements, ordinals;
|
80
|
+
if (event.oldIndex < event.newIndex) { // from left to right
|
81
|
+
$firstElement = $container.find('tr').eq(event.oldIndex);
|
82
|
+
$lastElement = $container.find('tr').eq(event.newIndex);
|
83
|
+
$movedElement = $lastElement
|
84
|
+
} else { // from right to left
|
85
|
+
$firstElement = $container.find('tr').eq(event.newIndex);
|
86
|
+
$lastElement = $container.find('tr').eq(event.oldIndex);
|
87
|
+
$movedElement = $firstElement
|
88
|
+
}
|
89
|
+
elements = findElements($container, $firstElement, $lastElement);
|
90
|
+
ordinals = rotateArray(getOrdinals(elements), event.oldIndex < event.newIndex);
|
91
|
+
setOrdinals(elements, ordinals);
|
92
|
+
sendReorderRequest($movedElement, ordinals);
|
93
|
+
}
|
94
|
+
}
|
95
|
+
});
|
96
|
+
}
|
97
|
+
})(jQuery);
|
@@ -0,0 +1 @@
|
|
1
|
+
.activeadmin-orderable-column .activeadmin-orderable-handle { cursor: ns-resize; }
|
@@ -0,0 +1,42 @@
|
|
1
|
+
if defined?(ActiveAdmin)
|
2
|
+
module ActiveAdmin
|
3
|
+
module OrderableTable
|
4
|
+
module ControllerActions
|
5
|
+
def orderable
|
6
|
+
member_action :reorder, method: :post do
|
7
|
+
position = params[:position].to_i
|
8
|
+
if params[:ordinals]
|
9
|
+
ordinals_scope = params[:ordinals].map { |ordinal| ordinal.to_i }
|
10
|
+
resource.insert_at position, ordinals_scope
|
11
|
+
else
|
12
|
+
resource.insert_at position
|
13
|
+
end
|
14
|
+
head 200
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module TableMethods
|
20
|
+
HANDLE = '↕'.html_safe
|
21
|
+
|
22
|
+
def orderable_handle_column
|
23
|
+
column_name = resource_class.ordinal_field
|
24
|
+
return if params[:order] != "#{column_name}_asc" && params[:order] != "#{column_name}_desc"
|
25
|
+
|
26
|
+
column '', class: 'activeadmin-orderable-column' do |resource|
|
27
|
+
reorder_path = resource_path(resource) + '/reorder'
|
28
|
+
content_tag :span, HANDLE, class: 'activeadmin-orderable-handle', 'data-reorder-url': reorder_path,
|
29
|
+
'data-ordinal': resource.acts_ordinal_value
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
::ActiveAdmin::ResourceDSL.send(:include, ControllerActions)
|
35
|
+
::ActiveAdmin::Views::TableFor.send(:include, TableMethods)
|
36
|
+
|
37
|
+
class Engine < ::Rails::Engine
|
38
|
+
# Including an Engine tells Rails that this gem contains assets
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ActsAs
|
3
|
+
module OrderableTable
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(ClassMethods)
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
attr_reader :ordinal_field, :starts_from
|
10
|
+
|
11
|
+
def acts_as_orderable_table(options = {})
|
12
|
+
@ordinal_field = options[:ordinal_field] || :ordinal
|
13
|
+
@starts_from = options[:starts_from] || 0
|
14
|
+
|
15
|
+
validates @ordinal_field, presence: true
|
16
|
+
validate :check_ordinal_uniqueness
|
17
|
+
before_validation :set_defaults
|
18
|
+
|
19
|
+
class_eval do
|
20
|
+
include ActiveRecord::ActsAs::OrderableTable::InstanceMethods
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module InstanceMethods
|
26
|
+
def insert_at(position, ordinals_scope = nil)
|
27
|
+
return if position == acts_ordinal_value # if ordinal haven't changed
|
28
|
+
|
29
|
+
# if new position is not occupied just take this ordinal
|
30
|
+
unless self.class.where("#{acts_ordinal_field}": position).first
|
31
|
+
update_attributes("#{acts_ordinal_field}": position)
|
32
|
+
return
|
33
|
+
end
|
34
|
+
|
35
|
+
items = items_scoped(position, ordinals_scope)
|
36
|
+
current_positions = items.map { |item| item.send(acts_ordinal_field) }
|
37
|
+
reordered_positions = reorder_positions(position, current_positions)
|
38
|
+
update_ordinals(items, reordered_positions)
|
39
|
+
end
|
40
|
+
|
41
|
+
def acts_ordinal_field
|
42
|
+
self.class.ordinal_field
|
43
|
+
end
|
44
|
+
|
45
|
+
def acts_ordinal_value
|
46
|
+
self.send(acts_ordinal_field)
|
47
|
+
end
|
48
|
+
|
49
|
+
def starts_from
|
50
|
+
self.class.starts_from
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def ordinal_range(position)
|
56
|
+
position > acts_ordinal_value ? (acts_ordinal_value + 1)..position : position...acts_ordinal_value
|
57
|
+
end
|
58
|
+
|
59
|
+
def items_scoped(position, ordinals_scope)
|
60
|
+
actual_ordinals_scope = *ordinal_range(position)
|
61
|
+
actual_ordinals_scope &= ordinals_scope if ordinals_scope
|
62
|
+
self.class.where("#{acts_ordinal_field}": actual_ordinals_scope).order(acts_ordinal_field).to_a.push(self)
|
63
|
+
end
|
64
|
+
|
65
|
+
def reorder_positions(position, positions)
|
66
|
+
position > acts_ordinal_value ? positions.rotate(-1) : positions.rotate
|
67
|
+
end
|
68
|
+
|
69
|
+
def update_ordinals(items, positions)
|
70
|
+
items.each_with_index do |item, index|
|
71
|
+
item.assign_attributes("#{acts_ordinal_field}": positions[index])
|
72
|
+
item.save!(validate: false)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def check_ordinal_uniqueness
|
77
|
+
if acts_ordinal_value.present? &&
|
78
|
+
self.class.where("#{acts_ordinal_field}": acts_ordinal_value).reject { |record| record == self }.first
|
79
|
+
self.errors[acts_ordinal_field] << 'must be unique'
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def set_defaults
|
84
|
+
if acts_ordinal_value.blank?
|
85
|
+
ordinal_value_prev = self.class.maximum(acts_ordinal_field)
|
86
|
+
ordinal_value_next = (ordinal_value_prev ? ordinal_value_prev + 1 : starts_from)
|
87
|
+
assign_attributes("#{acts_ordinal_field}": ordinal_value_next)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
data/spec/db_helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'sqlite3'
|
2
|
+
|
3
|
+
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
|
4
|
+
|
5
|
+
ActiveRecord::Schema.define(:version => 1) do
|
6
|
+
create_table "entities", :force => true do |t|
|
7
|
+
t.integer "ordinal", null: false
|
8
|
+
t.integer "field"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class Entity < ActiveRecord::Base
|
13
|
+
acts_as_orderable_table
|
14
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RANDOM_ORDINAL_MAX = 100
|
4
|
+
# should be > 2 and <= RANDOM_ORDINAL_MAX
|
5
|
+
TEST_ENTITIES_COUNT = 10
|
6
|
+
# should be > 1 and <= TEST_ENTITIES_COUNT
|
7
|
+
TEST_ENTITIES_SCOPE_COUNT = 6
|
8
|
+
|
9
|
+
RSpec.describe ActiveRecord::ActsAs::OrderableTable do
|
10
|
+
describe 'without options' do
|
11
|
+
let(:random_ordinal) { Random.new.rand(RANDOM_ORDINAL_MAX) }
|
12
|
+
|
13
|
+
after { Entity.destroy_all }
|
14
|
+
|
15
|
+
describe 'set default values' do
|
16
|
+
it 'should create entity with custom ordinal value' do
|
17
|
+
entity = Entity.create!(ordinal: random_ordinal)
|
18
|
+
expect(entity.ordinal).to eq random_ordinal
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should set default ordinal value to zero with empty entities table' do
|
22
|
+
entity = Entity.create!
|
23
|
+
expect(entity.ordinal).to eq 0
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should set default ordinal value to incremented max existing ordinal' do
|
27
|
+
entities_with_random_ordinal(TEST_ENTITIES_COUNT, RANDOM_ORDINAL_MAX).each(&:save!)
|
28
|
+
|
29
|
+
entities = [Entity.new(ordinal: RANDOM_ORDINAL_MAX + random_ordinal)]
|
30
|
+
(TEST_ENTITIES_COUNT).times { entities << Entity.new }
|
31
|
+
entities.each(&:save!)
|
32
|
+
|
33
|
+
1.upto(entities.size - 1) { |i| expect(entities[i].ordinal).to eq entities[i - 1].ordinal + 1 }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should validate ordinal uniqueness' do
|
38
|
+
Entity.create!(ordinal: random_ordinal)
|
39
|
+
|
40
|
+
entity_with_different_ordinal = Entity.new(ordinal: random_ordinal + 1)
|
41
|
+
expect(entity_with_different_ordinal).to be_truthy
|
42
|
+
|
43
|
+
entity_with_same_ordinal = Entity.new(ordinal: random_ordinal)
|
44
|
+
expect(entity_with_same_ordinal.valid?).to be_falsey
|
45
|
+
end
|
46
|
+
|
47
|
+
describe 'reordering' do
|
48
|
+
context 'without scope' do
|
49
|
+
let(:entities) do
|
50
|
+
entities = entities_with_random_ordinal(TEST_ENTITIES_COUNT, RANDOM_ORDINAL_MAX)
|
51
|
+
entities.each(&:save!)
|
52
|
+
end
|
53
|
+
|
54
|
+
let(:entity) { entities.first }
|
55
|
+
|
56
|
+
it "shouldn't change position if ordinal haven't changed" do
|
57
|
+
expect { entity.insert_at(entity.ordinal) }.not_to change { entities.map(&:ordinal) }
|
58
|
+
end
|
59
|
+
|
60
|
+
context 'new position is now occupied' do
|
61
|
+
let(:position_new) { RANDOM_ORDINAL_MAX + random_ordinal }
|
62
|
+
|
63
|
+
it 'should not change other entities if new position is now occupied' do
|
64
|
+
expect { entity.insert_at(position_new) }.not_to change { entities.drop(1).map(&:ordinal) }
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'should set new position if new position is now occupied' do
|
68
|
+
entity.insert_at(position_new)
|
69
|
+
expect(entity.ordinal).to eq position_new
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'when new position is occupied' do
|
74
|
+
let(:sorted_scope) { entities.sort_by { |entity| entity.ordinal } }
|
75
|
+
|
76
|
+
it 'should correctly reorder close entities when entity moved down' do
|
77
|
+
scope = sorted_scope.take(2)
|
78
|
+
reordered_ids = scope.map(&:id).reverse! + sorted_scope.drop(2).map(&:id)
|
79
|
+
scope.first.insert_at(scope.second.ordinal)
|
80
|
+
expect(Entity.order(:ordinal).map(&:id)).to eq reordered_ids
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'should correctly reorder close entities when entity moved up' do
|
84
|
+
scope = sorted_scope.take(2)
|
85
|
+
reordered_ids = scope.map(&:id).reverse! + sorted_scope.drop(2).map(&:id)
|
86
|
+
scope.second.insert_at(scope.first.ordinal)
|
87
|
+
expect(Entity.order(:ordinal).map(&:id)).to eq reordered_ids
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'should correctly reorder entities when entity moved down' do
|
91
|
+
scope = sorted_scope.take(TEST_ENTITIES_SCOPE_COUNT)
|
92
|
+
# [1, 2, 3, 4, 5].rotate => [2, 3, 4, 5, 1]
|
93
|
+
reordered_ids = scope.map(&:id).rotate + sorted_scope.drop(TEST_ENTITIES_SCOPE_COUNT).map(&:id)
|
94
|
+
scope.first.insert_at(scope.last.ordinal)
|
95
|
+
expect(Entity.order(:ordinal).map(&:id)).to eq reordered_ids
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'should correctly reorder entities when entity moved up' do
|
99
|
+
scope = sorted_scope.take(TEST_ENTITIES_SCOPE_COUNT)
|
100
|
+
# [1, 2, 3, 4, 5].rotate(-1) => [5, 1, 2, 3, 4]
|
101
|
+
reordered_ids = scope.map(&:id).rotate(-1) + sorted_scope.drop(TEST_ENTITIES_SCOPE_COUNT).map(&:id)
|
102
|
+
scope.last.insert_at(scope.first.ordinal)
|
103
|
+
expect(Entity.order(:ordinal).map(&:id)).to eq reordered_ids
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context 'with scope' do
|
109
|
+
let(:entities) do
|
110
|
+
entities = entities_with_random_ordinal(TEST_ENTITIES_COUNT, RANDOM_ORDINAL_MAX)
|
111
|
+
entities.each(&:save!)
|
112
|
+
end
|
113
|
+
|
114
|
+
let(:entity) { entities.first }
|
115
|
+
|
116
|
+
it "shouldn't change position if ordinal haven't changed" do
|
117
|
+
expect { entity.insert_at(entity.ordinal, [entity.ordinal]) }
|
118
|
+
.not_to change { entities.map(&:ordinal) }
|
119
|
+
end
|
120
|
+
|
121
|
+
context 'new position is now occupied' do
|
122
|
+
let(:position_new) { RANDOM_ORDINAL_MAX + random_ordinal }
|
123
|
+
|
124
|
+
it 'should not change other entities if new position is now occupied' do
|
125
|
+
expect { entity.insert_at(position_new, entity.ordinal) }
|
126
|
+
.not_to change { entities.drop(1).map(&:ordinal) }
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'should set new position if new position is now occupied' do
|
130
|
+
entity.insert_at(position_new, entity.ordinal)
|
131
|
+
expect(entity.ordinal).to eq position_new
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
context 'when new position is occupied' do
|
136
|
+
let(:sorted_scope) { entities.sort_by { |entity| entity.ordinal } }
|
137
|
+
|
138
|
+
it 'should correctly reorder close entities when entity moved down' do
|
139
|
+
scope = sorted_scope.take(2)
|
140
|
+
reordered_ids = scope.map(&:id).reverse! + sorted_scope.drop(2).map(&:id)
|
141
|
+
scope.first.insert_at(scope.second.ordinal, scope.map(&:ordinal))
|
142
|
+
expect(Entity.order(:ordinal).map(&:id)).to eq reordered_ids
|
143
|
+
end
|
144
|
+
|
145
|
+
it 'should correctly reorder close entities when entity moved up' do
|
146
|
+
scope = sorted_scope.take(2)
|
147
|
+
reordered_ids = scope.map(&:id).reverse! + sorted_scope.drop(2).map(&:id)
|
148
|
+
scope.second.insert_at(scope.first.ordinal, scope.map(&:ordinal))
|
149
|
+
expect(Entity.order(:ordinal).map(&:id)).to eq reordered_ids
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'should correctly reorder entities when entity moved down' do
|
153
|
+
scope = sorted_scope.take(TEST_ENTITIES_SCOPE_COUNT)
|
154
|
+
# [1, 2, 3, 4, 5].rotate => [2, 3, 4, 5, 1]
|
155
|
+
reordered_ids = scope.map(&:id).rotate + sorted_scope.drop(TEST_ENTITIES_SCOPE_COUNT).map(&:id)
|
156
|
+
scope.first.insert_at(scope.last.ordinal, scope.map(&:ordinal))
|
157
|
+
expect(Entity.order(:ordinal).map(&:id)).to eq reordered_ids
|
158
|
+
end
|
159
|
+
|
160
|
+
it 'should correctly reorder entities when entity moved up' do
|
161
|
+
scope = sorted_scope.take(TEST_ENTITIES_SCOPE_COUNT)
|
162
|
+
# [1, 2, 3, 4, 5].rotate(-1) => [5, 1, 2, 3, 4]
|
163
|
+
reordered_ids = scope.map(&:id).rotate(-1) + sorted_scope.drop(TEST_ENTITIES_SCOPE_COUNT).map(&:id)
|
164
|
+
scope.last.insert_at(scope.first.ordinal, scope.map(&:ordinal))
|
165
|
+
expect(Entity.order(:ordinal).map(&:id)).to eq reordered_ids
|
166
|
+
end
|
167
|
+
|
168
|
+
it 'should reorder entities only from scope when entity moved down' do
|
169
|
+
entities.shuffle!
|
170
|
+
scope = entities.take(TEST_ENTITIES_SCOPE_COUNT).sort_by { |entity| entity.ordinal }
|
171
|
+
# [1, 2, 3, 4, 5].rotate => [2, 3, 4, 5, 1]
|
172
|
+
reordered_ids = scope.map(&:id).rotate
|
173
|
+
expect { scope.first.insert_at(scope.last.ordinal, scope.map(&:ordinal)) }
|
174
|
+
.not_to change { entities.drop(TEST_ENTITIES_SCOPE_COUNT).map(&:ordinal) }
|
175
|
+
expect(Entity.where(id: reordered_ids).order(:ordinal).map(&:id)).to eq reordered_ids
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: activeadmin-orderable-table
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Max Hordiichuk
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-03-15 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.10'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.10'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec-rails
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.5'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.5'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: test-unit
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: sqlite3
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.3'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.3'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: sass-rails
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: activerecord
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: sortable-rails
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '1.4'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '1.4'
|
111
|
+
description: Drag and drop order interface for ActiveAdmin tables
|
112
|
+
email: hordijchuk.m.i@gmail.com
|
113
|
+
executables: []
|
114
|
+
extensions: []
|
115
|
+
extra_rdoc_files: []
|
116
|
+
files:
|
117
|
+
- ".gitignore"
|
118
|
+
- ".rspec"
|
119
|
+
- Gemfile
|
120
|
+
- LICENSE.txt
|
121
|
+
- README.md
|
122
|
+
- activeadmin-orderable-table.gemspec
|
123
|
+
- app/assets/javascripts/activeadmin-orderable-table.js
|
124
|
+
- app/assets/stylesheets/activeadmin-orderable-table.css
|
125
|
+
- lib/active_admin/orderable_table.rb
|
126
|
+
- lib/active_record/acts_as.rb
|
127
|
+
- lib/active_record/acts_as/orderable_table.rb
|
128
|
+
- lib/activeadmin-orderable-table.rb
|
129
|
+
- lib/activeadmin-orderable-table/version.rb
|
130
|
+
- spec/db_helper.rb
|
131
|
+
- spec/lib/active_record/acts_as/orderable_table_spec.rb
|
132
|
+
- spec/spec_helper.rb
|
133
|
+
- spec/support/utilities.rb
|
134
|
+
homepage: https://github.com/MaximHordijchuk/activeadmin-orderable-table
|
135
|
+
licenses:
|
136
|
+
- MIT
|
137
|
+
metadata: {}
|
138
|
+
post_install_message:
|
139
|
+
rdoc_options: []
|
140
|
+
require_paths:
|
141
|
+
- lib
|
142
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
143
|
+
requirements:
|
144
|
+
- - ">="
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
version: '0'
|
147
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - ">="
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: '0'
|
152
|
+
requirements: []
|
153
|
+
rubyforge_project:
|
154
|
+
rubygems_version: 2.6.10
|
155
|
+
signing_key:
|
156
|
+
specification_version: 4
|
157
|
+
summary: Order ActiveAdmin tables
|
158
|
+
test_files: []
|