has_order 0.1.2 → 0.2.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.
- checksums.yaml +4 -4
- data/.travis.yml +20 -0
- data/README.md +46 -12
- data/has_order.gemspec +11 -8
- data/lib/has_order.rb +37 -48
- data/lib/has_order/orm_adapter.rb +29 -0
- data/lib/has_order/orm_adapter/active_record.rb +34 -0
- data/lib/has_order/orm_adapter/mongoid.rb +39 -0
- data/lib/has_order/version.rb +1 -1
- data/spec/has_order_spec.rb +9 -189
- data/spec/list.rb +184 -0
- data/spec/spec_helper.rb +8 -25
- data/spec/support/models.rb +9 -3
- data/spec/{db/schema.rb → support/orm/activerecord/item_model.rb} +4 -0
- data/spec/support/orm/activerecord/setup.rb +18 -0
- data/spec/support/orm/mongoid/item_model.rb +7 -0
- data/spec/support/orm/mongoid/setup.rb +12 -0
- metadata +39 -14
- data/CHANGELOG.md +0 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7548cd9cfc471c16deef5e247548fbd06f4e0308
|
4
|
+
data.tar.gz: 32acddb9839f96d19282e3fe9e9d70f32a5debbb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 70b29b7e60b8d11ba435f16c6aed6f7f0369ca0807c66c52d261fe93932e98886e68df15e806f04e9660d72105e153ea46b54f1445375bc4896f7cfd607382b7
|
7
|
+
data.tar.gz: 2441f0889017894d863c0d966d8a1f1bb7c82cad31e3ec6890a4dddf6a717d0b2c1c8fca45cc80542564e231e59546e6a5145559eadfc1a2e28a59d7d5653c1a
|
data/.travis.yml
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
language: ruby
|
2
|
+
|
3
|
+
rvm:
|
4
|
+
- 1.9.3
|
5
|
+
- 2.0.0
|
6
|
+
- 2.1.0
|
7
|
+
|
8
|
+
env:
|
9
|
+
- HAS_ORDER_ORM=mongoid
|
10
|
+
- HAS_ORDER_ORM=activerecord
|
11
|
+
|
12
|
+
matrix:
|
13
|
+
fast_finish: true
|
14
|
+
|
15
|
+
services:
|
16
|
+
- mongodb
|
17
|
+
|
18
|
+
addons:
|
19
|
+
code_climate:
|
20
|
+
repo_token: 0ecd8512477ddbf9e06702762f756620b21ff1b7441a8c1dd275265acbefaf66
|
data/README.md
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
[](http://badge.fury.io/rb/has_order)
|
2
|
+
[](https://travis-ci.org/kolesnikovde/has_order)
|
3
|
+
[](https://codeclimate.com/github/kolesnikovde/has_order)
|
4
|
+
[](https://codeclimate.com/github/kolesnikovde/has_order)
|
2
5
|
|
3
6
|
# has_order
|
4
7
|
|
5
|
-
|
8
|
+
Ordering behavior for ActiveRecord models and Mongoid documents.
|
6
9
|
|
7
10
|
## Installation
|
8
11
|
|
@@ -16,14 +19,37 @@ And then execute:
|
|
16
19
|
|
17
20
|
## Usage
|
18
21
|
|
22
|
+
Example model:
|
23
|
+
```sh
|
24
|
+
$ rails g model Item \
|
25
|
+
name:string \
|
26
|
+
position:integer # Do not specify default value.
|
27
|
+
```
|
19
28
|
```ruby
|
20
29
|
class Item < ActiveRecord::Base
|
21
|
-
# :scope - optional, proc, symbol or an array of symbols.
|
22
|
-
# :position_column - optional, default 'position'.
|
23
|
-
# :shift_interval - optional, default 1000.
|
24
30
|
has_order
|
25
31
|
end
|
32
|
+
```
|
33
|
+
|
34
|
+
or Mongoid document:
|
35
|
+
```ruby
|
36
|
+
class Item
|
37
|
+
include Mongoid::Document
|
38
|
+
include Mongoid::HasOrder
|
26
39
|
|
40
|
+
has_order
|
41
|
+
end
|
42
|
+
```
|
43
|
+
|
44
|
+
Options:
|
45
|
+
```
|
46
|
+
scope - optional, proc, symbol or an array of symbols.
|
47
|
+
position_column - optional, default 'position'.
|
48
|
+
shift_interval - optional, default 1000.
|
49
|
+
```
|
50
|
+
|
51
|
+
Methods:
|
52
|
+
```ruby
|
27
53
|
foo, bar, baz, qux = Item.create([
|
28
54
|
{ name: 'foo' },
|
29
55
|
{ name: 'bar' },
|
@@ -32,19 +58,27 @@ foo, bar, baz, qux = Item.create([
|
|
32
58
|
])
|
33
59
|
|
34
60
|
Item.at(foo.position) # => foo
|
35
|
-
|
36
|
-
|
37
|
-
baz.
|
38
|
-
baz.
|
61
|
+
Item.ordered # => [ foo, bar, baz, qux ]
|
62
|
+
|
63
|
+
baz.higher # => [ qux ]
|
64
|
+
baz.and_higher # => [ baz, qux ]
|
65
|
+
baz.lower # => [ foo, bar ]
|
66
|
+
baz.and_lower # => [ foo, bar, baz ]
|
67
|
+
baz.previous # => bar
|
68
|
+
baz.prev # => bar
|
69
|
+
baz.next # => qux
|
39
70
|
|
40
|
-
baz.move_before(bar)
|
41
|
-
Item.ordered
|
71
|
+
baz.move_before(bar)
|
72
|
+
Item.ordered
|
73
|
+
# => [ foo, baz, bar, qux ]
|
42
74
|
|
43
75
|
foo.move_after(qux)
|
44
|
-
Item.ordered
|
76
|
+
Item.ordered
|
77
|
+
# => [ baz, bar, qux, foo ]
|
45
78
|
|
46
79
|
baz.move_to(qux)
|
47
|
-
Item.ordered
|
80
|
+
Item.ordered
|
81
|
+
# => [ bar, baz, qux, foo ]
|
48
82
|
```
|
49
83
|
|
50
84
|
## License
|
data/has_order.gemspec
CHANGED
@@ -9,8 +9,8 @@ Gem::Specification.new do |spec|
|
|
9
9
|
|
10
10
|
spec.authors = ['Kolesnikov Danil']
|
11
11
|
spec.email = ['kolesnikovde@gmail.com']
|
12
|
-
spec.description = '
|
13
|
-
spec.summary = '
|
12
|
+
spec.description = 'Ordering behavior for ActiveRecord models and Mongoid documents.'
|
13
|
+
spec.summary = 'Ordering behavior for ActiveRecord models and Mongoid documents.'
|
14
14
|
spec.homepage = 'https://github.com/kolesnikovde/has_order'
|
15
15
|
spec.license = 'MIT'
|
16
16
|
|
@@ -18,12 +18,15 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^spec/})
|
19
19
|
spec.require_paths = ['lib']
|
20
20
|
|
21
|
-
spec.add_development_dependency 'bundler',
|
22
|
-
spec.add_development_dependency 'rake',
|
23
|
-
spec.add_development_dependency 'rspec',
|
24
|
-
|
25
|
-
spec.add_development_dependency '
|
21
|
+
spec.add_development_dependency 'bundler', '~> 1'
|
22
|
+
spec.add_development_dependency 'rake', '~> 10'
|
23
|
+
spec.add_development_dependency 'rspec', '~> 3'
|
24
|
+
|
25
|
+
spec.add_development_dependency 'sqlite3', '~> 1'
|
26
|
+
spec.add_development_dependency 'activerecord', '~> 4'
|
27
|
+
spec.add_development_dependency 'mongoid', '~> 4'
|
28
|
+
|
29
|
+
spec.add_development_dependency 'codeclimate-test-reporter'
|
26
30
|
|
27
|
-
spec.add_runtime_dependency 'activerecord', '~> 4'
|
28
31
|
spec.add_runtime_dependency 'activesupport', '~> 4'
|
29
32
|
end
|
data/lib/has_order.rb
CHANGED
@@ -1,25 +1,23 @@
|
|
1
|
-
require 'active_record'
|
2
1
|
require 'has_order/version'
|
2
|
+
require 'has_order/orm_adapter'
|
3
3
|
|
4
4
|
module HasOrder
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
DEFAULT_OPTIONS = {
|
6
|
+
position_column: :position,
|
7
|
+
shift_interval: 1000
|
8
|
+
}
|
9
|
+
|
10
|
+
def has_order(options = {})
|
10
11
|
include InstanceMethods
|
12
|
+
extend ClassMethods
|
11
13
|
|
12
|
-
|
13
|
-
options[:position_column] || :position
|
14
|
-
end
|
15
|
-
|
16
|
-
cattr_accessor :position_shift_interval do
|
17
|
-
options[:shift_interval] || 1000
|
18
|
-
end
|
14
|
+
setup_has_order_options(options)
|
19
15
|
|
20
16
|
before_save :set_default_position, if: :set_default_position?
|
21
17
|
|
22
18
|
define_list_scope(options[:scope])
|
19
|
+
|
20
|
+
include HasOrder::OrmAdapter
|
23
21
|
end
|
24
22
|
|
25
23
|
module ClassMethods
|
@@ -27,17 +25,26 @@ module HasOrder
|
|
27
25
|
where(position_column => pos)
|
28
26
|
end
|
29
27
|
|
30
|
-
|
31
|
-
|
32
|
-
|
28
|
+
protected
|
29
|
+
|
30
|
+
def setup_has_order_options(options)
|
31
|
+
options.assert_valid_keys(:scope, :position_column, :shift_interval)
|
32
|
+
|
33
|
+
options.reverse_merge!(DEFAULT_OPTIONS)
|
33
34
|
|
34
|
-
|
35
|
-
|
36
|
-
update_all("#{col} = #{col} + #{position_shift_interval}")
|
35
|
+
cattr_accessor(:position_column) { options[:position_column] }
|
36
|
+
cattr_accessor(:position_shift_interval) { options[:shift_interval] }
|
37
37
|
end
|
38
38
|
|
39
|
-
def
|
40
|
-
|
39
|
+
def define_list_scope(list_scope)
|
40
|
+
scope :list_scope, case list_scope
|
41
|
+
when Proc
|
42
|
+
list_scope
|
43
|
+
when nil
|
44
|
+
->(model) { where(nil) }
|
45
|
+
else
|
46
|
+
->(model) { where(Hash[Array(list_scope).map{ |s| [ s, model[s] ] }]) }
|
47
|
+
end
|
41
48
|
end
|
42
49
|
end
|
43
50
|
|
@@ -70,12 +77,14 @@ module HasOrder
|
|
70
77
|
lower.ordered.last
|
71
78
|
end
|
72
79
|
|
80
|
+
alias_method :previous, :prev
|
81
|
+
|
73
82
|
def next
|
74
83
|
higher.ordered.first
|
75
84
|
end
|
76
85
|
|
77
86
|
def move_to(pos)
|
78
|
-
|
87
|
+
self.class.transaction do
|
79
88
|
if node = list_scope.at(pos).first
|
80
89
|
node.and_higher.shift!
|
81
90
|
end
|
@@ -86,10 +95,10 @@ module HasOrder
|
|
86
95
|
end
|
87
96
|
|
88
97
|
def move_before(node)
|
89
|
-
|
90
|
-
|
98
|
+
self.class.transaction do
|
99
|
+
node_pos = node.position
|
100
|
+
pos = node_pos > 0 ? node_pos - 1 : node_pos
|
91
101
|
|
92
|
-
ActiveRecord::Base.transaction do
|
93
102
|
if list_scope.at(pos).exists?
|
94
103
|
pos = node_pos
|
95
104
|
node.and_higher.shift!
|
@@ -101,10 +110,10 @@ module HasOrder
|
|
101
110
|
end
|
102
111
|
|
103
112
|
def move_after(node)
|
104
|
-
|
105
|
-
|
113
|
+
self.class.transaction do
|
114
|
+
node_pos = node.position
|
115
|
+
pos = node_pos + 1
|
106
116
|
|
107
|
-
ActiveRecord::Base.transaction do
|
108
117
|
if list_scope.at(pos).exists?
|
109
118
|
node.higher.shift!
|
110
119
|
end
|
@@ -120,11 +129,6 @@ module HasOrder
|
|
120
129
|
self.class.list_scope(self)
|
121
130
|
end
|
122
131
|
|
123
|
-
def where_position(cmp)
|
124
|
-
col = self.class.arel_table[position_column]
|
125
|
-
list_scope.where(col.send(cmp, position))
|
126
|
-
end
|
127
|
-
|
128
132
|
def set_default_position
|
129
133
|
self.position = list_scope.next_position
|
130
134
|
end
|
@@ -133,19 +137,4 @@ module HasOrder
|
|
133
137
|
position.nil?
|
134
138
|
end
|
135
139
|
end
|
136
|
-
|
137
|
-
protected
|
138
|
-
|
139
|
-
def define_list_scope(list_scope)
|
140
|
-
scope :list_scope, case list_scope
|
141
|
-
when Proc
|
142
|
-
list_scope
|
143
|
-
when nil
|
144
|
-
->(model) { self }
|
145
|
-
else
|
146
|
-
->(model) { where(Hash[Array(list_scope).map{ |s| [ s, model[s] ] }]) }
|
147
|
-
end
|
148
|
-
end
|
149
140
|
end
|
150
|
-
|
151
|
-
ActiveRecord::Base.extend(HasOrder)
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module HasOrder
|
2
|
+
# :nocov:
|
3
|
+
module OrmAdapter
|
4
|
+
if defined?(::ActiveRecord)
|
5
|
+
::ActiveRecord::Base.extend(HasOrder)
|
6
|
+
end
|
7
|
+
|
8
|
+
if defined?(::Mongoid)
|
9
|
+
module ::Mongoid::HasOrder
|
10
|
+
def self.included(base)
|
11
|
+
base.extend(::HasOrder)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.included(base)
|
17
|
+
base.class_eval do
|
18
|
+
if defined?(::ActiveRecord) and self < ::ActiveRecord::Base
|
19
|
+
require 'has_order/orm_adapter/active_record'
|
20
|
+
include ActiveRecord
|
21
|
+
elsif defined?(::Mongoid) and self < ::Mongoid::Document
|
22
|
+
require 'has_order/orm_adapter/mongoid'
|
23
|
+
include Mongoid
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
# :nocov:
|
29
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module HasOrder
|
2
|
+
module OrmAdapter
|
3
|
+
module ActiveRecord
|
4
|
+
def self.included(base)
|
5
|
+
base.class_eval do
|
6
|
+
extend ClassMethods
|
7
|
+
include InstanceMethods
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def ordered
|
13
|
+
order(arel_table[position_column].asc)
|
14
|
+
end
|
15
|
+
|
16
|
+
def shift!
|
17
|
+
col = position_column
|
18
|
+
update_all("#{col} = #{col} + #{position_shift_interval}")
|
19
|
+
end
|
20
|
+
|
21
|
+
def next_position
|
22
|
+
maximum(position_column).to_i + position_shift_interval
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module InstanceMethods
|
27
|
+
def where_position(cmp)
|
28
|
+
col = self.class.arel_table[position_column]
|
29
|
+
list_scope.where(col.send(cmp, position))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module HasOrder
|
2
|
+
module OrmAdapter
|
3
|
+
module Mongoid
|
4
|
+
def self.included(base)
|
5
|
+
base.class_eval do
|
6
|
+
extend ClassMethods
|
7
|
+
include InstanceMethods
|
8
|
+
|
9
|
+
field position_column, type: Integer
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
def transaction(&blk)
|
15
|
+
yield
|
16
|
+
end
|
17
|
+
|
18
|
+
def ordered
|
19
|
+
asc(position_column)
|
20
|
+
end
|
21
|
+
|
22
|
+
def shift!
|
23
|
+
scoped.inc(position_column => position_shift_interval)
|
24
|
+
end
|
25
|
+
|
26
|
+
def next_position
|
27
|
+
max(position_column).to_i + position_shift_interval
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module InstanceMethods
|
32
|
+
def where_position(cmp)
|
33
|
+
cmp = { gteq: :gte, lteq: :lte }[cmp] || cmp
|
34
|
+
list_scope.where(position_column.send(cmp) => position)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/has_order/version.rb
CHANGED
data/spec/has_order_spec.rb
CHANGED
@@ -1,194 +1,14 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'list'
|
2
3
|
|
3
|
-
describe
|
4
|
-
|
5
|
-
|
6
|
-
{ name: 'foo', category: 'A' },
|
7
|
-
{ name: 'bar', category: 'A' },
|
8
|
-
{ name: 'baz', category: 'B' },
|
9
|
-
{ name: 'qux', category: 'B' }
|
10
|
-
])
|
11
|
-
end
|
12
|
-
|
13
|
-
def reload_items
|
14
|
-
[ @foo, @bar, @baz, @qux ].each(&:reload)
|
15
|
-
end
|
16
|
-
|
17
|
-
describe 'position column' do
|
18
|
-
it 'defaults to "position"' do
|
19
|
-
expect(Item.position_column).to eq(:position)
|
20
|
-
end
|
21
|
-
|
22
|
-
it 'provides accessor' do
|
23
|
-
item = Item.new
|
24
|
-
item.position = 5
|
25
|
-
expect(item.position).to eq(5)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
describe '.at' do
|
30
|
-
it 'scopes position' do
|
31
|
-
@foo.position = 5
|
32
|
-
@foo.save
|
33
|
-
|
34
|
-
expect(Item.at(5).first).to eq(@foo)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
describe '.ordered' do
|
39
|
-
it 'ranks items according to position' do
|
40
|
-
@foo.update_attribute(:position, 2)
|
41
|
-
@bar.update_attribute(:position, 1)
|
42
|
-
@baz.update_attribute(:position, 4)
|
43
|
-
@qux.update_attribute(:position, 3)
|
44
|
-
|
45
|
-
expect(Item.ordered).to eq([ @bar, @foo, @qux, @baz ])
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
describe '.next_position' do
|
50
|
-
it 'returns next available position' do
|
51
|
-
@quux = Item.create!(name: 'quux')
|
52
|
-
|
53
|
-
expect(@quux.position).to be < Item.next_position
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
describe '#lower' do
|
58
|
-
it 'returns items with position less or equal to the item position' do
|
59
|
-
expect(@qux.lower).to match_array([ @foo, @bar, @baz ])
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
describe '#and_lower' do
|
64
|
-
it 'returns items with position less than or equal to the item position' do
|
65
|
-
expect(@baz.and_lower).to match_array([ @foo, @bar, @baz ])
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
describe '#higher' do
|
70
|
-
it 'returns items with position greater than the item position' do
|
71
|
-
expect(@foo.higher).to match_array([ @bar, @baz, @qux ])
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
describe '#and_higher' do
|
76
|
-
it 'returns items with position greater than or equal to the item' do
|
77
|
-
expect(@bar.and_higher).to match_array([ @bar, @baz, @qux ])
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
describe '#prev' do
|
82
|
-
it 'returns previous item' do
|
83
|
-
expect(@baz.prev).to eq(@bar)
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
describe '#next' do
|
88
|
-
it 'returns next item' do
|
89
|
-
expect(@bar.next).to eq(@baz)
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
context 'first item' do
|
94
|
-
subject { @foo }
|
95
|
-
|
96
|
-
it { expect(subject.lower).to be_empty }
|
97
|
-
it { expect(subject.and_lower).to eq([ subject ]) }
|
98
|
-
it { expect(subject.prev).to be_nil }
|
99
|
-
end
|
100
|
-
|
101
|
-
context 'last item' do
|
102
|
-
subject { @qux }
|
103
|
-
|
104
|
-
it { expect(subject.higher).to be_empty }
|
105
|
-
it { expect(subject.and_higher).to eq([ subject ]) }
|
106
|
-
it { expect(subject.next).to be_nil }
|
107
|
-
end
|
108
|
-
|
109
|
-
describe '#move_to' do
|
110
|
-
it 'shifts item and higher items when the position is occupied' do
|
111
|
-
@foo.update_attribute(:position, 1)
|
112
|
-
@bar.update_attribute(:position, 2)
|
113
|
-
@baz.update_attribute(:position, 3)
|
114
|
-
@qux.update_attribute(:position, 4)
|
115
|
-
|
116
|
-
@qux.move_to(@bar.position)
|
117
|
-
|
118
|
-
reload_items
|
119
|
-
|
120
|
-
expect(Item.ordered).to eq([ @foo, @qux, @bar, @baz ])
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
describe '#move_before' do
|
125
|
-
it 'decreases item position' do
|
126
|
-
prev_qux_position = @qux.position
|
127
|
-
@qux.move_before(@foo)
|
128
|
-
|
129
|
-
expect(@qux.position).to be < prev_qux_position
|
130
|
-
end
|
131
|
-
|
132
|
-
it 'shifts item and higher items when the position is occupied' do
|
133
|
-
@foo.update_attribute(:position, 1)
|
134
|
-
@bar.update_attribute(:position, 2)
|
135
|
-
@baz.update_attribute(:position, 3)
|
136
|
-
@qux.update_attribute(:position, 4)
|
137
|
-
|
138
|
-
@baz.move_before(@bar)
|
139
|
-
|
140
|
-
reload_items
|
141
|
-
|
142
|
-
expect(Item.ordered).to eq([ @foo, @baz, @bar, @qux ])
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
|
-
describe '#move_after' do
|
147
|
-
it 'increases item position' do
|
148
|
-
prev_foo_position = @foo.position
|
149
|
-
@foo.move_after(@qux)
|
150
|
-
|
151
|
-
expect(@foo.position).to be > prev_foo_position
|
152
|
-
end
|
153
|
-
|
154
|
-
it 'shifts higher items when the position is occupied' do
|
155
|
-
@foo.update_attribute(:position, 1)
|
156
|
-
@bar.update_attribute(:position, 2)
|
157
|
-
@baz.update_attribute(:position, 3)
|
158
|
-
@qux.update_attribute(:position, 4)
|
159
|
-
|
160
|
-
@foo.move_after(@bar)
|
161
|
-
|
162
|
-
reload_items
|
163
|
-
|
164
|
-
expect(Item.ordered).to eq([ @bar, @foo, @baz, @qux ])
|
165
|
-
end
|
166
|
-
end
|
167
|
-
|
168
|
-
describe 'scoping' do
|
169
|
-
shared_examples 'scoped' do
|
170
|
-
it { expect(subject.higher).to be_empty }
|
171
|
-
it { expect(subject.and_lower).to match_array([ @bar, @foo ]) }
|
172
|
-
end
|
173
|
-
|
174
|
-
describe 'via attributes' do
|
175
|
-
before(:all) do
|
176
|
-
Item.has_order scope: :category
|
177
|
-
end
|
178
|
-
|
179
|
-
subject { @bar }
|
180
|
-
|
181
|
-
it_behaves_like 'scoped'
|
182
|
-
end
|
183
|
-
|
184
|
-
describe 'via proc' do
|
185
|
-
before(:all) do
|
186
|
-
Item.has_order scope: ->(i){ Item.where(category: i.category) }
|
187
|
-
end
|
4
|
+
describe ListItem do
|
5
|
+
it_behaves_like 'list'
|
6
|
+
end
|
188
7
|
|
189
|
-
|
8
|
+
describe ScopedWithColumnListItem do
|
9
|
+
it_behaves_like 'scoped list'
|
10
|
+
end
|
190
11
|
|
191
|
-
|
192
|
-
|
193
|
-
end
|
12
|
+
describe ScopedWithLambdaListItem do
|
13
|
+
it_behaves_like 'scoped list'
|
194
14
|
end
|
data/spec/list.rb
ADDED
@@ -0,0 +1,184 @@
|
|
1
|
+
shared_examples 'list' do
|
2
|
+
before(:each) do
|
3
|
+
@foo, @bar, @baz, @qux = described_class.create([
|
4
|
+
{ name: 'foo' },
|
5
|
+
{ name: 'bar' },
|
6
|
+
{ name: 'baz' },
|
7
|
+
{ name: 'qux' }
|
8
|
+
])
|
9
|
+
end
|
10
|
+
|
11
|
+
def reload_items
|
12
|
+
[ @foo, @bar, @baz, @qux ].each(&:reload)
|
13
|
+
end
|
14
|
+
|
15
|
+
describe 'position column' do
|
16
|
+
it 'defaults to "position"' do
|
17
|
+
expect(described_class.position_column).to eq(:position)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'provides accessor' do
|
21
|
+
item = described_class.new
|
22
|
+
item.position = 5
|
23
|
+
expect(item.position).to eq(5)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '.at' do
|
28
|
+
it 'scopes position' do
|
29
|
+
@foo.position = 5
|
30
|
+
@foo.save
|
31
|
+
|
32
|
+
expect(described_class.at(5).first).to eq(@foo)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe '.ordered' do
|
37
|
+
it 'ranks items according to position' do
|
38
|
+
@foo.update_attribute(:position, 2)
|
39
|
+
@bar.update_attribute(:position, 1)
|
40
|
+
@baz.update_attribute(:position, 4)
|
41
|
+
@qux.update_attribute(:position, 3)
|
42
|
+
|
43
|
+
expect(described_class.ordered).to eq([ @bar, @foo, @qux, @baz ])
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe '.next_position' do
|
48
|
+
it 'returns next available position' do
|
49
|
+
@quux = described_class.create!(name: 'quux')
|
50
|
+
|
51
|
+
expect(@quux.position).to be < described_class.next_position
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe '#lower' do
|
56
|
+
it 'returns items with position less or equal to the item position' do
|
57
|
+
expect(@qux.lower).to match_array([ @foo, @bar, @baz ])
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe '#and_lower' do
|
62
|
+
it 'returns items with position less than or equal to the item position' do
|
63
|
+
expect(@baz.and_lower).to match_array([ @foo, @bar, @baz ])
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe '#higher' do
|
68
|
+
it 'returns items with position greater than the item position' do
|
69
|
+
expect(@foo.higher).to match_array([ @bar, @baz, @qux ])
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe '#and_higher' do
|
74
|
+
it 'returns items with position greater than or equal to the item' do
|
75
|
+
expect(@bar.and_higher).to match_array([ @bar, @baz, @qux ])
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe '#prev' do
|
80
|
+
it 'returns previous item' do
|
81
|
+
expect(@baz.prev).to eq(@bar)
|
82
|
+
expect(@baz.previous).to eq(@bar)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe '#next' do
|
87
|
+
it 'returns next item' do
|
88
|
+
expect(@bar.next).to eq(@baz)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context 'first item' do
|
93
|
+
subject { @foo }
|
94
|
+
|
95
|
+
it { expect(subject.lower).to be_empty }
|
96
|
+
it { expect(subject.and_lower).to eq([ subject ]) }
|
97
|
+
it { expect(subject.prev).to be_nil }
|
98
|
+
end
|
99
|
+
|
100
|
+
context 'last item' do
|
101
|
+
subject { @qux }
|
102
|
+
|
103
|
+
it { expect(subject.higher).to be_empty }
|
104
|
+
it { expect(subject.and_higher).to eq([ subject ]) }
|
105
|
+
it { expect(subject.next).to be_nil }
|
106
|
+
end
|
107
|
+
|
108
|
+
describe '#move_to' do
|
109
|
+
it 'shifts item and higher items when the position is occupied' do
|
110
|
+
@foo.update_attribute(:position, 1)
|
111
|
+
@bar.update_attribute(:position, 2)
|
112
|
+
@baz.update_attribute(:position, 3)
|
113
|
+
@qux.update_attribute(:position, 4)
|
114
|
+
|
115
|
+
@qux.move_to(@bar.position)
|
116
|
+
|
117
|
+
reload_items
|
118
|
+
|
119
|
+
expect(described_class.ordered).to eq([ @foo, @qux, @bar, @baz ])
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe '#move_before' do
|
124
|
+
it 'decreases item position' do
|
125
|
+
prev_qux_position = @qux.position
|
126
|
+
@qux.move_before(@foo)
|
127
|
+
|
128
|
+
expect(@qux.position).to be < prev_qux_position
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'shifts item and higher items when the position is occupied' do
|
132
|
+
@foo.update_attribute(:position, 1)
|
133
|
+
@bar.update_attribute(:position, 2)
|
134
|
+
@baz.update_attribute(:position, 3)
|
135
|
+
@qux.update_attribute(:position, 4)
|
136
|
+
|
137
|
+
@baz.move_before(@bar)
|
138
|
+
|
139
|
+
reload_items
|
140
|
+
|
141
|
+
expect(described_class.ordered).to eq([ @foo, @baz, @bar, @qux ])
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
describe '#move_after' do
|
146
|
+
it 'increases item position' do
|
147
|
+
prev_foo_position = @foo.position
|
148
|
+
@foo.move_after(@qux)
|
149
|
+
|
150
|
+
expect(@foo.position).to be > prev_foo_position
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'shifts higher items when the position is occupied' do
|
154
|
+
@foo.update_attribute(:position, 1)
|
155
|
+
@bar.update_attribute(:position, 2)
|
156
|
+
@baz.update_attribute(:position, 3)
|
157
|
+
@qux.update_attribute(:position, 4)
|
158
|
+
|
159
|
+
@foo.move_after(@bar)
|
160
|
+
|
161
|
+
reload_items
|
162
|
+
|
163
|
+
expect(described_class.ordered).to eq([ @bar, @foo, @baz, @qux ])
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
shared_examples 'scoped list' do
|
169
|
+
before(:each) do
|
170
|
+
@foo, @bar, @baz, @qux = described_class.create([
|
171
|
+
{ name: 'foo', category: 'A' },
|
172
|
+
{ name: 'bar', category: 'A' },
|
173
|
+
{ name: 'baz', category: 'B' },
|
174
|
+
{ name: 'qux', category: 'B' }
|
175
|
+
])
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'restricts scope' do
|
179
|
+
expect(@bar.higher).to be_empty
|
180
|
+
expect(@bar.and_lower).to match_array([ @bar, @foo ])
|
181
|
+
expect(@baz.higher).to match_array([ @qux ])
|
182
|
+
expect(@baz.lower).to be_empty
|
183
|
+
end
|
184
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,28 +1,11 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
SimpleCov.start 'rails'
|
4
|
-
end
|
5
|
-
|
6
|
-
require 'fileutils'
|
7
|
-
require 'logger'
|
8
|
-
require 'sqlite3'
|
9
|
-
require 'active_record'
|
10
|
-
|
11
|
-
FileUtils.mkdir_p('tmp/logs/')
|
12
|
-
ActiveRecord::Base.logger = Logger.new('tmp/logs/test.log')
|
13
|
-
ActiveRecord::Base.logger.level = Logger::DEBUG
|
14
|
-
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
|
15
|
-
ActiveRecord::Schema.verbose = false
|
1
|
+
require 'codeclimate-test-reporter'
|
2
|
+
CodeClimate::TestReporter.start
|
16
3
|
|
17
|
-
|
18
|
-
require File.expand_path('../support/models.rb', __FILE__)
|
4
|
+
orm_adapter = ENV['HAS_ORDER_ORM']
|
19
5
|
|
20
|
-
|
21
|
-
|
22
|
-
ActiveRecord::Base.transaction do
|
23
|
-
example.run
|
24
|
-
|
25
|
-
raise ActiveRecord::Rollback
|
26
|
-
end
|
27
|
-
end
|
6
|
+
unless %w[activerecord mongoid mongo_mapper].include?(orm_adapter)
|
7
|
+
raise 'Unknown ORM.'
|
28
8
|
end
|
9
|
+
|
10
|
+
require "support/orm/#{orm_adapter}/setup"
|
11
|
+
require 'support/models'
|
data/spec/support/models.rb
CHANGED
@@ -1,5 +1,11 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
class Item < ActiveRecord::Base
|
1
|
+
class ListItem < Item
|
4
2
|
has_order
|
5
3
|
end
|
4
|
+
|
5
|
+
class ScopedWithColumnListItem < Item
|
6
|
+
has_order scope: :category
|
7
|
+
end
|
8
|
+
|
9
|
+
class ScopedWithLambdaListItem < Item
|
10
|
+
has_order scope: ->(item){ where(category: item.category) }
|
11
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'sqlite3'
|
3
|
+
require 'has_order'
|
4
|
+
|
5
|
+
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
|
6
|
+
ActiveRecord::Schema.verbose = false
|
7
|
+
|
8
|
+
require_relative 'item_model'
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
config.around :each do |example|
|
12
|
+
ActiveRecord::Base.transaction do
|
13
|
+
example.run
|
14
|
+
|
15
|
+
raise ActiveRecord::Rollback
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: has_order
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kolesnikov Danil
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-10-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -67,33 +67,47 @@ dependencies:
|
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '1'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: activerecord
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- - "
|
73
|
+
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
75
|
+
version: '4'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- - "
|
80
|
+
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: '
|
82
|
+
version: '4'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
84
|
+
name: mongoid
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
87
|
- - "~>"
|
88
88
|
- !ruby/object:Gem::Version
|
89
89
|
version: '4'
|
90
|
-
type: :
|
90
|
+
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '4'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: codeclimate-test-reporter
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
97
111
|
- !ruby/object:Gem::Dependency
|
98
112
|
name: activesupport
|
99
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,7 +122,7 @@ dependencies:
|
|
108
122
|
- - "~>"
|
109
123
|
- !ruby/object:Gem::Version
|
110
124
|
version: '4'
|
111
|
-
description:
|
125
|
+
description: Ordering behavior for ActiveRecord models and Mongoid documents.
|
112
126
|
email:
|
113
127
|
- kolesnikovde@gmail.com
|
114
128
|
executables: []
|
@@ -118,17 +132,24 @@ files:
|
|
118
132
|
- ".editorconfig"
|
119
133
|
- ".gitignore"
|
120
134
|
- ".rspec"
|
121
|
-
-
|
135
|
+
- ".travis.yml"
|
122
136
|
- Gemfile
|
123
137
|
- README.md
|
124
138
|
- Rakefile
|
125
139
|
- has_order.gemspec
|
126
140
|
- lib/has_order.rb
|
141
|
+
- lib/has_order/orm_adapter.rb
|
142
|
+
- lib/has_order/orm_adapter/active_record.rb
|
143
|
+
- lib/has_order/orm_adapter/mongoid.rb
|
127
144
|
- lib/has_order/version.rb
|
128
|
-
- spec/db/schema.rb
|
129
145
|
- spec/has_order_spec.rb
|
146
|
+
- spec/list.rb
|
130
147
|
- spec/spec_helper.rb
|
131
148
|
- spec/support/models.rb
|
149
|
+
- spec/support/orm/activerecord/item_model.rb
|
150
|
+
- spec/support/orm/activerecord/setup.rb
|
151
|
+
- spec/support/orm/mongoid/item_model.rb
|
152
|
+
- spec/support/orm/mongoid/setup.rb
|
132
153
|
homepage: https://github.com/kolesnikovde/has_order
|
133
154
|
licenses:
|
134
155
|
- MIT
|
@@ -152,9 +173,13 @@ rubyforge_project:
|
|
152
173
|
rubygems_version: 2.2.2
|
153
174
|
signing_key:
|
154
175
|
specification_version: 4
|
155
|
-
summary:
|
176
|
+
summary: Ordering behavior for ActiveRecord models and Mongoid documents.
|
156
177
|
test_files:
|
157
|
-
- spec/db/schema.rb
|
158
178
|
- spec/has_order_spec.rb
|
179
|
+
- spec/list.rb
|
159
180
|
- spec/spec_helper.rb
|
160
181
|
- spec/support/models.rb
|
182
|
+
- spec/support/orm/activerecord/item_model.rb
|
183
|
+
- spec/support/orm/activerecord/setup.rb
|
184
|
+
- spec/support/orm/mongoid/item_model.rb
|
185
|
+
- spec/support/orm/mongoid/setup.rb
|
data/CHANGELOG.md
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
# Changelog
|
2
|
-
|
3
|
-
## 0.1.2
|
4
|
-
|
5
|
-
- Added README.md.
|
6
|
-
- Updated codestyle.
|
7
|
-
- Fixed rspec deprication warnings.
|
8
|
-
- Updated development dependencies.
|
9
|
-
|
10
|
-
## 0.1.1
|
11
|
-
|
12
|
-
- Fixed scopes (always using lambdas).
|
13
|
-
|
14
|
-
## 0.1.0
|
15
|
-
|
16
|
-
- Added `#move_to`.
|
17
|
-
- Added `#shift_interval` option.
|
18
|
-
- Added `#set_default_position?`.
|
19
|
-
- Added lambda scopes support.
|