activeadmin-orderable-table 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|