has_hierarchy 0.3.1 → 0.4.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 +10 -0
- data/README.md +35 -14
- data/has_hierarchy.gemspec +6 -3
- data/lib/has_hierarchy/counter_cache.rb +26 -0
- data/lib/has_hierarchy/order.rb +5 -0
- data/lib/has_hierarchy/orm_adapter/active_record.rb +34 -0
- data/lib/has_hierarchy/orm_adapter/mongoid.rb +27 -0
- data/lib/has_hierarchy/orm_adapter.rb +29 -0
- data/lib/has_hierarchy/path.rb +9 -33
- data/lib/has_hierarchy/version.rb +1 -1
- data/lib/has_hierarchy.rb +10 -17
- data/spec/has_hierarchy_spec.rb +1 -306
- data/spec/spec_helper.rb +7 -17
- data/spec/support/models.rb +2 -8
- data/spec/{db/schema.rb → support/orm/active_record/item_model.rb} +4 -0
- data/spec/support/orm/active_record/setup.rb +18 -0
- data/spec/support/orm/mongoid/item_model.rb +12 -0
- data/spec/support/orm/mongoid/setup.rb +12 -0
- data/spec/tree.rb +323 -0
- metadata +39 -14
- data/CHANGELOG.md +0 -59
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 296f6af57a4b4997d4f63b1ad555c9723da8701b
|
4
|
+
data.tar.gz: 83c399372635843bbeac74f6aa632d2eaeea0b05
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 14a6ad138900fa59c5e174e26497ede7184c8e33137f8fc724045b1fb4d2c4befc89f82ed1071f82b5507bac917f517994e592f5cefd83928579d5f6a47e4549
|
7
|
+
data.tar.gz: 3df4da8267691965a285f537ab10e994596de77e3fda143223a9f00f6bb180eba0fc73368a8ef1dcac56d45092b464892dea7b5997daedcf575a8c1aff1d30f4
|
data/.travis.yml
CHANGED
@@ -5,6 +5,16 @@ rvm:
|
|
5
5
|
- 2.0.0
|
6
6
|
- 2.1.0
|
7
7
|
|
8
|
+
env:
|
9
|
+
- HAS_HIERARCHY_ORM=mongoid
|
10
|
+
- HAS_HIERARCHY_ORM=active_record
|
11
|
+
|
12
|
+
matrix:
|
13
|
+
fast_finish: true
|
14
|
+
|
15
|
+
services:
|
16
|
+
- mongodb
|
17
|
+
|
8
18
|
addons:
|
9
19
|
code_climate:
|
10
20
|
repo_token: cf2619e78bacdbeab2c8c1e2e1f37ff6c47b06374ee717f38cbbcdafaceeeb59
|
data/README.md
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
|
6
6
|
# has_hierarchy
|
7
7
|
|
8
|
-
|
8
|
+
Tree behavior for ActiveRecord models and Mongoid documents.
|
9
9
|
|
10
10
|
## Installation
|
11
11
|
|
@@ -19,15 +19,15 @@ And then execute:
|
|
19
19
|
|
20
20
|
## Usage
|
21
21
|
|
22
|
-
Example
|
22
|
+
Example model:
|
23
23
|
```sh
|
24
24
|
$ rails g model Item \
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
25
|
+
name:string \
|
26
|
+
path:string \
|
27
|
+
depth:integer \
|
28
|
+
position:integer \
|
29
|
+
parent:belongs_to \
|
30
|
+
children_count:integer
|
31
31
|
```
|
32
32
|
```ruby
|
33
33
|
class Item < ActiveRecord::Base
|
@@ -36,12 +36,24 @@ class Item < ActiveRecord::Base
|
|
36
36
|
counter_cache: true,
|
37
37
|
dependent: :destroy
|
38
38
|
end
|
39
|
+
```
|
39
40
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
41
|
+
or Mongoid document:
|
42
|
+
```ruby
|
43
|
+
class Item
|
44
|
+
include Mongoid::Document
|
45
|
+
include Mongoid::HasHierarchy
|
46
|
+
|
47
|
+
has_hierarchy path_part: :name,
|
48
|
+
depth_cache: true,
|
49
|
+
counter_cache: true,
|
50
|
+
dependent: :destroy
|
51
|
+
|
52
|
+
field :name, type: String
|
53
|
+
field :path, type: String
|
54
|
+
field :depth, type: Fixnum, default: 0
|
55
|
+
field :children_count, type: Fixnum, default: 0
|
56
|
+
end
|
45
57
|
```
|
46
58
|
|
47
59
|
Options:
|
@@ -58,6 +70,12 @@ dependent - optional, :dependent option for children association.
|
|
58
70
|
|
59
71
|
Operations on the tree:
|
60
72
|
```ruby
|
73
|
+
foo = Item.create!(name: 'foo')
|
74
|
+
bar = Item.create!(name: 'bar')
|
75
|
+
qux = bar.children.create!(name: 'qux')
|
76
|
+
baz = bar.children.create!(name: 'baz')
|
77
|
+
quux = qux.children.create!(name: 'quux')
|
78
|
+
|
61
79
|
Item.roots
|
62
80
|
# => [ foo, bar ]
|
63
81
|
|
@@ -88,7 +106,7 @@ bar.root? # => true
|
|
88
106
|
qux.leaf? # => false
|
89
107
|
```
|
90
108
|
|
91
|
-
|
109
|
+
Ancestors/descendants (requires path_cache):
|
92
110
|
```ruby
|
93
111
|
bar.root_of?(quux) # => true
|
94
112
|
bar.ancestor_of?(quux) # => true
|
@@ -100,6 +118,9 @@ bar.descendants # => [ qux, quux, baz ]
|
|
100
118
|
|
101
119
|
Ordering (see [has_order](https://github.com/kolesnikovde/has_order)):
|
102
120
|
```ruby
|
121
|
+
bar.previous_siblings # => [ foo, quux ]
|
122
|
+
foo.next_siblings # => [ quux, bar ]
|
123
|
+
|
103
124
|
foo.move_after(quux)
|
104
125
|
Item.ordered.tree
|
105
126
|
# => {
|
data/has_hierarchy.gemspec
CHANGED
@@ -21,10 +21,13 @@ Gem::Specification.new do |spec|
|
|
21
21
|
spec.add_development_dependency 'bundler', '~> 1'
|
22
22
|
spec.add_development_dependency 'rake', '~> 10'
|
23
23
|
spec.add_development_dependency 'rspec', '~> 3'
|
24
|
-
|
24
|
+
|
25
|
+
spec.add_development_dependency 'sqlite3', '~> 1'
|
26
|
+
spec.add_development_dependency 'activerecord', '~> 4'
|
27
|
+
spec.add_development_dependency 'mongoid', '~> 4'
|
28
|
+
|
25
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
|
-
spec.add_runtime_dependency 'has_order', '~> 0.
|
32
|
+
spec.add_runtime_dependency 'has_order', '~> 0.2'
|
30
33
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module HasHierarchy
|
2
|
+
module CounterCache
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
after_save :update_children_counter, if: :parent_id_changed?
|
7
|
+
after_destroy :decrement_children_counter, if: :parent_id?
|
8
|
+
end
|
9
|
+
|
10
|
+
protected
|
11
|
+
|
12
|
+
def update_children_counter
|
13
|
+
if parent_id
|
14
|
+
self.class.increment_counter(children_count_column, parent_id)
|
15
|
+
end
|
16
|
+
|
17
|
+
if parent_id_was
|
18
|
+
self.class.decrement_counter(children_count_column, parent_id_was)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def decrement_children_counter
|
23
|
+
self.class.decrement_counter(children_count_column, parent_id)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/has_hierarchy/order.rb
CHANGED
@@ -5,11 +5,16 @@ module HasHierarchy
|
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
7
|
included do
|
8
|
+
include Mongoid::HasOrder if defined?(Mongoid)
|
9
|
+
|
8
10
|
options = has_hierarchy_options
|
9
11
|
|
10
12
|
has_order scope: Array(options[:scope]).concat([ :parent_id ]),
|
11
13
|
position_column: options[:order]
|
12
14
|
|
15
|
+
alias_method :previous_siblings, :lower
|
16
|
+
alias_method :next_siblings, :higher
|
17
|
+
|
13
18
|
include HasOrderOverrides
|
14
19
|
end
|
15
20
|
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module HasHierarchy
|
2
|
+
module OrmAdapter
|
3
|
+
module ActiveRecord
|
4
|
+
def ancestors
|
5
|
+
tree_scope.where(path_part_column => path_parts)
|
6
|
+
end
|
7
|
+
|
8
|
+
def siblings
|
9
|
+
t = self.class.arel_table
|
10
|
+
|
11
|
+
tree_scope.where(t[:parent_id].eq(parent_id).and(t[:id].not_eq(id)))
|
12
|
+
end
|
13
|
+
|
14
|
+
def subtree
|
15
|
+
t = self.class.arel_table
|
16
|
+
|
17
|
+
tree_scope.where(t[:id].eq(id).or(descendants_conditions))
|
18
|
+
end
|
19
|
+
|
20
|
+
def descendants
|
21
|
+
tree_scope.where(descendants_conditions)
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
def descendants_conditions
|
27
|
+
t = self.class.arel_table
|
28
|
+
|
29
|
+
t[path_column].matches("#{path_for_children}%")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module HasHierarchy
|
2
|
+
module OrmAdapter
|
3
|
+
module Mongoid
|
4
|
+
def ancestors
|
5
|
+
tree_scope.where(path_part_column.in => path_parts)
|
6
|
+
end
|
7
|
+
|
8
|
+
def siblings
|
9
|
+
tree_scope.where(:parent_id => parent_id, :id.ne => id)
|
10
|
+
end
|
11
|
+
|
12
|
+
def subtree
|
13
|
+
tree_scope.or({ id: id }, descendants_conditions)
|
14
|
+
end
|
15
|
+
|
16
|
+
def descendants
|
17
|
+
tree_scope.where(descendants_conditions)
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
def descendants_conditions
|
23
|
+
{ path_column => /^#{path_for_children}/ }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module HasHierarchy
|
2
|
+
# :nocov:
|
3
|
+
module OrmAdapter
|
4
|
+
if defined?(::ActiveRecord)
|
5
|
+
::ActiveRecord::Base.extend(HasHierarchy)
|
6
|
+
end
|
7
|
+
|
8
|
+
if defined?(::Mongoid)
|
9
|
+
module ::Mongoid::HasHierarchy
|
10
|
+
def self.included(base)
|
11
|
+
base.extend(::HasHierarchy)
|
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_hierarchy/orm_adapter/active_record'
|
20
|
+
include ActiveRecord
|
21
|
+
elsif defined?(::Mongoid) and self < ::Mongoid::Document
|
22
|
+
require 'has_hierarchy/orm_adapter/mongoid'
|
23
|
+
include Mongoid
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
# :nocov:
|
29
|
+
end
|
data/lib/has_hierarchy/path.rb
CHANGED
@@ -23,33 +23,23 @@ module HasHierarchy
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def root
|
26
|
-
|
26
|
+
if root_part = path_parts.first
|
27
|
+
self.class.find_by(path_part_column => root_part)
|
28
|
+
end
|
27
29
|
end
|
28
30
|
|
29
31
|
def root_of?(node)
|
30
32
|
node.path_parts.first == path_part if path_part.present?
|
31
33
|
end
|
32
34
|
|
33
|
-
def ancestors
|
34
|
-
tree_scope.where(ancestors_conditions)
|
35
|
-
end
|
36
|
-
|
37
35
|
def ancestor_of?(node)
|
38
36
|
node.path_parts.include?(path_part)
|
39
37
|
end
|
40
38
|
|
41
|
-
def descendants
|
42
|
-
tree_scope.where(descendants_conditions)
|
43
|
-
end
|
44
|
-
|
45
39
|
def descendant_of?(node)
|
46
40
|
path_parts.include?(node.path_part)
|
47
41
|
end
|
48
42
|
|
49
|
-
def subtree
|
50
|
-
tree_scope.where(subtree_conditions)
|
51
|
-
end
|
52
|
-
|
53
43
|
def depth
|
54
44
|
path_parts.size
|
55
45
|
end
|
@@ -80,35 +70,21 @@ module HasHierarchy
|
|
80
70
|
[ path, path_part, path_separator ].join
|
81
71
|
end
|
82
72
|
|
83
|
-
def populate_path
|
84
|
-
self.path = root? ? '' : parent.path_for_children
|
85
|
-
end
|
86
|
-
|
87
|
-
def ancestors_conditions
|
88
|
-
{ path_part_column => path_parts }
|
89
|
-
end
|
90
|
-
|
91
|
-
def descendants_conditions
|
92
|
-
arel_path = self.class.arel_table[path_column]
|
93
|
-
arel_path.matches("#{path_for_children}%")
|
94
|
-
end
|
95
|
-
|
96
|
-
def subtree_conditions
|
97
|
-
arel_path_part = self.class.arel_table[path_part_column]
|
98
|
-
arel_path_part.eq(path_part).or(descendants_conditions)
|
73
|
+
def populate_path(path = nil)
|
74
|
+
self.path = root? ? '' : (path || parent.path_for_children)
|
99
75
|
end
|
100
76
|
|
101
|
-
def rebuild_subtree
|
102
|
-
populate_path
|
77
|
+
def rebuild_subtree(path = nil)
|
78
|
+
populate_path(path)
|
103
79
|
|
104
80
|
children.each do |child|
|
105
|
-
child.rebuild_subtree
|
81
|
+
child.rebuild_subtree(path_for_children)
|
106
82
|
child.save!
|
107
83
|
end
|
108
84
|
end
|
109
85
|
|
110
86
|
def need_to_rebuild_subtree?
|
111
|
-
parent_id_changed? or changed_attributes.include?(path_part_column)
|
87
|
+
parent_id_changed? or changed_attributes.include?(path_part_column.to_s)
|
112
88
|
end
|
113
89
|
end
|
114
90
|
end
|
data/lib/has_hierarchy.rb
CHANGED
@@ -3,6 +3,8 @@ require 'has_hierarchy/version'
|
|
3
3
|
require 'has_hierarchy/order'
|
4
4
|
require 'has_hierarchy/path'
|
5
5
|
require 'has_hierarchy/depth_cache'
|
6
|
+
require 'has_hierarchy/counter_cache'
|
7
|
+
require 'has_hierarchy/orm_adapter'
|
6
8
|
|
7
9
|
module HasHierarchy
|
8
10
|
DEFAULT_OPTIONS = {
|
@@ -22,13 +24,13 @@ module HasHierarchy
|
|
22
24
|
|
23
25
|
setup_has_hierarchy_options(options)
|
24
26
|
|
25
|
-
include Order
|
26
|
-
include Path
|
27
|
-
include DepthCache
|
27
|
+
include Order if options[:order]
|
28
|
+
include Path if options[:path_cache]
|
29
|
+
include DepthCache if options[:depth_cache]
|
30
|
+
include CounterCache if options[:counter_cache]
|
28
31
|
|
29
32
|
belongs_to :parent, class_name: self.name,
|
30
|
-
inverse_of: :children
|
31
|
-
counter_cache: options[:counter_cache]
|
33
|
+
inverse_of: :children
|
32
34
|
|
33
35
|
has_many :children, class_name: self.name,
|
34
36
|
foreign_key: :parent_id,
|
@@ -36,6 +38,8 @@ module HasHierarchy
|
|
36
38
|
dependent: options[:dependent]
|
37
39
|
|
38
40
|
define_tree_scope(options[:scope])
|
41
|
+
|
42
|
+
include HasHierarchy::OrmAdapter
|
39
43
|
end
|
40
44
|
|
41
45
|
module ClassMethods
|
@@ -72,6 +76,7 @@ module HasHierarchy
|
|
72
76
|
cattr_accessor(:path_part_column) { options[:path_part] }
|
73
77
|
cattr_accessor(:path_separator) { options[:path_separator] }
|
74
78
|
cattr_accessor(:depth_column) { options[:depth_cache] }
|
79
|
+
cattr_accessor(:children_count_column) { options[:counter_cache] }
|
75
80
|
cattr_accessor(:has_hierarchy_options) { options }
|
76
81
|
end
|
77
82
|
|
@@ -112,10 +117,6 @@ module HasHierarchy
|
|
112
117
|
parent_id == node.parent_id and id != node.id
|
113
118
|
end
|
114
119
|
|
115
|
-
def siblings
|
116
|
-
tree_scope.where(siblings_conditions)
|
117
|
-
end
|
118
|
-
|
119
120
|
def move_children_to_parent
|
120
121
|
children.each do |c|
|
121
122
|
c.parent = self.parent
|
@@ -128,13 +129,5 @@ module HasHierarchy
|
|
128
129
|
def tree_scope
|
129
130
|
self.class.tree_scope(self)
|
130
131
|
end
|
131
|
-
|
132
|
-
def siblings_conditions
|
133
|
-
t = self.class.arel_table
|
134
|
-
|
135
|
-
t[:parent_id].eq(parent_id).and(t[:id].not_eq(id))
|
136
|
-
end
|
137
132
|
end
|
138
133
|
end
|
139
|
-
|
140
|
-
ActiveRecord::Base.extend(HasHierarchy)
|
data/spec/has_hierarchy_spec.rb
CHANGED
@@ -1,310 +1,5 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
|
3
|
-
shared_context 'example tree' do
|
4
|
-
let!(:foo) { described_class.create!(name: 'foo') }
|
5
|
-
let!(:bar) { described_class.create!(name: 'bar') }
|
6
|
-
let!(:qux) { bar.children.create!(name: 'qux') }
|
7
|
-
let!(:baz) { bar.children.create!(name: 'baz') }
|
8
|
-
let!(:quux) { qux.children.create!(name: 'quux') }
|
9
|
-
|
10
|
-
def reload_items
|
11
|
-
[ foo, bar, baz, qux, quux ].each(&:reload)
|
12
|
-
end
|
13
|
-
|
14
|
-
before do
|
15
|
-
reload_items
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
shared_examples 'adjacency list' do
|
20
|
-
include_context 'example tree'
|
21
|
-
|
22
|
-
describe '.tree' do
|
23
|
-
it 'arranges tree' do
|
24
|
-
expect(described_class.tree).to be_arranged_like({
|
25
|
-
foo => {},
|
26
|
-
bar => {
|
27
|
-
qux => {
|
28
|
-
quux => {}
|
29
|
-
},
|
30
|
-
baz => {}
|
31
|
-
}
|
32
|
-
})
|
33
|
-
end
|
34
|
-
|
35
|
-
it 'allows custom order' do
|
36
|
-
expect(described_class.alphabetic.tree).to be_arranged_like({
|
37
|
-
bar => {
|
38
|
-
baz => {},
|
39
|
-
qux => {
|
40
|
-
quux => {}
|
41
|
-
}
|
42
|
-
},
|
43
|
-
foo => {}
|
44
|
-
})
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
describe '.roots' do
|
49
|
-
it 'returns roots' do
|
50
|
-
expect(described_class.roots).to match_array([ foo, bar ])
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
describe '#move_children_to_parent' do
|
55
|
-
it 'changes children parent' do
|
56
|
-
bar.move_children_to_parent
|
57
|
-
|
58
|
-
expect(described_class.tree).to be_arranged_like({
|
59
|
-
foo => {},
|
60
|
-
bar => {},
|
61
|
-
qux => {
|
62
|
-
quux => {}
|
63
|
-
},
|
64
|
-
baz => {}
|
65
|
-
})
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
describe '#root?' do
|
70
|
-
it 'returns true if node has parent' do
|
71
|
-
expect(bar).to be_root
|
72
|
-
expect(baz).not_to be_root
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
describe '#leaf?' do
|
77
|
-
it 'returns true if node does not have children' do
|
78
|
-
expect(quux).to be_leaf
|
79
|
-
expect(qux).not_to be_leaf
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
describe '#parent_of?' do
|
84
|
-
it 'returns true if node is a parent of given node' do
|
85
|
-
expect(bar).to be_parent_of(qux)
|
86
|
-
expect(bar).not_to be_parent_of(quux)
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
describe '#child_of?' do
|
91
|
-
it 'returns true if node is a child of given node' do
|
92
|
-
expect(qux).to be_child_of(bar)
|
93
|
-
expect(qux).not_to be_child_of(quux)
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
describe '#sibling_of?' do
|
98
|
-
it 'returns true if both nodes have same parent' do
|
99
|
-
expect(foo).to be_sibling_of(bar)
|
100
|
-
expect(baz).to be_sibling_of(qux)
|
101
|
-
expect(foo).not_to be_sibling_of(qux)
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
shared_examples 'materialized path' do
|
107
|
-
include_context 'example tree'
|
108
|
-
it_behaves_like 'adjacency list'
|
109
|
-
|
110
|
-
describe '.find_by_path' do
|
111
|
-
it 'returns node' do
|
112
|
-
expect(described_class.find_by_path('bar')).to eq(bar)
|
113
|
-
expect(described_class.find_by_path('bar/qux/')).to eq(qux)
|
114
|
-
expect(described_class.find_by_path('bar/qux/quux')).to eq(quux)
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
describe '.find_by_path!' do
|
119
|
-
it 'returns node or raises RecordNotFound' do
|
120
|
-
expect(described_class.find_by_path!('bar/qux/')).to eq(qux)
|
121
|
-
expect{ described_class.find_by_path!('wrong') }.to raise_error(ActiveRecord::RecordNotFound)
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
describe '#full_path' do
|
126
|
-
it 'returns full node path' do
|
127
|
-
expect(bar.full_path).to eq('bar')
|
128
|
-
expect(qux.full_path).to eq('bar/qux')
|
129
|
-
expect(quux.full_path).to eq('bar/qux/quux')
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
describe '#root' do
|
134
|
-
it 'returns first node ancestor' do
|
135
|
-
expect(baz.root).to eq(bar)
|
136
|
-
end
|
137
|
-
|
138
|
-
it 'returns nil if node is a root' do
|
139
|
-
expect(bar.root).to be nil
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
describe '#ancestors' do
|
144
|
-
it 'returns node ancestors' do
|
145
|
-
expect(quux.ancestors).to match_array([ qux, bar ])
|
146
|
-
expect(qux.ancestors).to match_array([ bar ])
|
147
|
-
expect(bar.ancestors).to be_empty
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
|
-
describe '#descendants' do
|
152
|
-
it 'returns node descendants' do
|
153
|
-
expect(bar.descendants).to match_array([ qux, quux, baz ])
|
154
|
-
expect(qux.descendants).to match_array([ quux ])
|
155
|
-
expect(quux.descendants).to be_empty
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
describe '#subtree' do
|
160
|
-
it 'returns node with descendants' do
|
161
|
-
expect(bar.subtree.tree).to be_arranged_like({
|
162
|
-
bar => {
|
163
|
-
qux => {
|
164
|
-
quux => {}
|
165
|
-
},
|
166
|
-
baz => {}
|
167
|
-
}
|
168
|
-
})
|
169
|
-
end
|
170
|
-
|
171
|
-
it 'returns node if node is a leaf' do
|
172
|
-
expect(baz.subtree).to eq([ baz ])
|
173
|
-
end
|
174
|
-
end
|
175
|
-
|
176
|
-
describe '#root_of?' do
|
177
|
-
it 'returns true of node is a root of given node' do
|
178
|
-
expect(bar).to be_root_of(qux)
|
179
|
-
expect(bar).to be_root_of(quux)
|
180
|
-
expect(bar).not_to be_root_of(bar)
|
181
|
-
expect(bar).not_to be_root_of(foo)
|
182
|
-
end
|
183
|
-
end
|
184
|
-
|
185
|
-
describe '#ancestor_of?' do
|
186
|
-
it 'returns true if node is an ancestors of given node' do
|
187
|
-
expect(bar).to be_ancestor_of(qux)
|
188
|
-
expect(bar).to be_ancestor_of(quux)
|
189
|
-
expect(bar).not_to be_ancestor_of(bar)
|
190
|
-
expect(bar).not_to be_ancestor_of(foo)
|
191
|
-
end
|
192
|
-
end
|
193
|
-
|
194
|
-
describe '#descendant_of?' do
|
195
|
-
it 'returns true if node is a descendant of given node' do
|
196
|
-
expect(quux).to be_descendant_of(qux)
|
197
|
-
expect(quux).to be_descendant_of(bar)
|
198
|
-
expect(quux).not_to be_descendant_of(quux)
|
199
|
-
expect(quux).not_to be_descendant_of(foo)
|
200
|
-
end
|
201
|
-
end
|
202
|
-
|
203
|
-
describe '#depth' do
|
204
|
-
it 'returns ancestors count' do
|
205
|
-
expect(bar.depth).to eq(0)
|
206
|
-
expect(qux.depth).to eq(1)
|
207
|
-
expect(quux.depth).to eq(2)
|
208
|
-
end
|
209
|
-
end
|
210
|
-
|
211
|
-
describe 'node id column change' do
|
212
|
-
before do
|
213
|
-
bar.name = 'bor'
|
214
|
-
bar.save!
|
215
|
-
end
|
216
|
-
|
217
|
-
it 'updates children pathes' do
|
218
|
-
expect(described_class.find_by_path('bor')).to eq(bar)
|
219
|
-
expect(described_class.find_by_path('bor/qux')).to eq(qux)
|
220
|
-
expect(described_class.find_by_path('bor/qux/quux')).to eq(quux)
|
221
|
-
end
|
222
|
-
end
|
223
|
-
|
224
|
-
describe 'parent change' do
|
225
|
-
let(:prev_parent) { baz.parent }
|
226
|
-
let(:new_parent) { foo }
|
227
|
-
let(:new_ancestors) { [ foo ] }
|
228
|
-
|
229
|
-
before do
|
230
|
-
baz.parent = new_parent
|
231
|
-
baz.save!
|
232
|
-
reload_items
|
233
|
-
end
|
234
|
-
|
235
|
-
it 'updates counter_cache' do
|
236
|
-
expect(prev_parent.children_count).to eq(prev_parent.children.count)
|
237
|
-
expect(new_parent.children_count).to eq(new_parent.children.count)
|
238
|
-
end
|
239
|
-
|
240
|
-
it 'changes ancestors' do
|
241
|
-
expect(baz.ancestors).to eq(new_ancestors)
|
242
|
-
end
|
243
|
-
|
244
|
-
it 'applies to all descendants' do
|
245
|
-
baz.children.each do |child|
|
246
|
-
expect(child).to be_descendant_of(new_parent)
|
247
|
-
|
248
|
-
child.children.each do |subchild|
|
249
|
-
expect(subchild).to be_descendant_of(new_parent)
|
250
|
-
end
|
251
|
-
end
|
252
|
-
end
|
253
|
-
end
|
254
|
-
end
|
255
|
-
|
256
|
-
shared_examples 'tree with cached depth' do
|
257
|
-
include_context 'example tree'
|
258
|
-
it_behaves_like 'adjacency list'
|
259
|
-
|
260
|
-
it 'stores node level' do
|
261
|
-
expect(described_class.where(depth: 0)).to match_array([ foo, bar ])
|
262
|
-
expect(described_class.where(depth: 1)).to match_array([ qux, baz ])
|
263
|
-
expect(described_class.where(depth: 2)).to match_array([ quux ])
|
264
|
-
end
|
265
|
-
end
|
266
|
-
|
267
|
-
shared_examples 'scoped tree' do
|
268
|
-
let!(:foo) { described_class.create!(name: 'foo', category: 'foo') }
|
269
|
-
let!(:bar) { described_class.create!(name: 'bar', category: 'bar') }
|
270
|
-
|
271
|
-
it 'restricts scope' do
|
272
|
-
expect(bar.siblings).to be_empty
|
273
|
-
end
|
274
|
-
end
|
275
|
-
|
276
|
-
shared_examples 'ordered tree' do
|
277
|
-
include_context 'example tree'
|
278
|
-
|
279
|
-
it '#move_after' do
|
280
|
-
quux.move_after(foo)
|
281
|
-
reload_items
|
282
|
-
|
283
|
-
expect(described_class.ordered.tree).to be_arranged_like({
|
284
|
-
foo => {},
|
285
|
-
quux => {},
|
286
|
-
bar => {
|
287
|
-
qux => {},
|
288
|
-
baz => {}
|
289
|
-
}
|
290
|
-
})
|
291
|
-
end
|
292
|
-
|
293
|
-
it '#move_before' do
|
294
|
-
baz.move_before(quux)
|
295
|
-
reload_items
|
296
|
-
|
297
|
-
expect(described_class.ordered.tree).to be_arranged_like({
|
298
|
-
foo => {},
|
299
|
-
bar => {
|
300
|
-
qux => {
|
301
|
-
baz => {},
|
302
|
-
quux => {},
|
303
|
-
}
|
304
|
-
}
|
305
|
-
})
|
306
|
-
end
|
307
|
-
end
|
2
|
+
require 'tree'
|
308
3
|
|
309
4
|
describe AdjacencyListTreeItem do
|
310
5
|
it_behaves_like 'adjacency list'
|
data/spec/spec_helper.rb
CHANGED
@@ -1,22 +1,12 @@
|
|
1
1
|
require 'codeclimate-test-reporter'
|
2
2
|
CodeClimate::TestReporter.start
|
3
3
|
|
4
|
-
|
5
|
-
require 'active_record'
|
4
|
+
orm_adapter = ENV['HAS_HIERARCHY_ORM']
|
6
5
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
require File.expand_path('../db/schema.rb', __FILE__)
|
11
|
-
require File.expand_path('../support/models.rb', __FILE__)
|
12
|
-
require File.expand_path('../support/matchers.rb', __FILE__)
|
13
|
-
|
14
|
-
RSpec.configure do |config|
|
15
|
-
config.around :each do |example|
|
16
|
-
ActiveRecord::Base.transaction do
|
17
|
-
example.run
|
18
|
-
|
19
|
-
raise ActiveRecord::Rollback
|
20
|
-
end
|
21
|
-
end
|
6
|
+
unless %w[active_record mongoid].include?(orm_adapter)
|
7
|
+
raise 'Unknown ORM.'
|
22
8
|
end
|
9
|
+
|
10
|
+
require "support/orm/#{orm_adapter}/setup"
|
11
|
+
require 'support/models'
|
12
|
+
require 'support/matchers'
|
data/spec/support/models.rb
CHANGED
@@ -1,17 +1,11 @@
|
|
1
|
-
require 'has_hierarchy'
|
2
|
-
|
3
|
-
class Item < ActiveRecord::Base
|
4
|
-
scope :alphabetic, ->{ order('name asc') }
|
5
|
-
end
|
6
|
-
|
7
1
|
class AdjacencyListTreeItem < Item
|
8
|
-
has_hierarchy counter_cache:
|
2
|
+
has_hierarchy counter_cache: true,
|
9
3
|
path_cache: false,
|
10
4
|
order: true
|
11
5
|
end
|
12
6
|
|
13
7
|
class MaterializedPathTreeItem < Item
|
14
|
-
has_hierarchy counter_cache:
|
8
|
+
has_hierarchy counter_cache: true,
|
15
9
|
path_part: :name
|
16
10
|
end
|
17
11
|
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'sqlite3'
|
3
|
+
require 'has_hierarchy'
|
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
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class Item
|
2
|
+
include Mongoid::Document
|
3
|
+
include Mongoid::HasHierarchy
|
4
|
+
|
5
|
+
field :name, type: String
|
6
|
+
field :path, type: String
|
7
|
+
field :depth, type: Fixnum, default: 0
|
8
|
+
field :category, type: String
|
9
|
+
field :children_count, type: Fixnum, default: 0
|
10
|
+
|
11
|
+
scope :alphabetic, ->{ asc(:name) }
|
12
|
+
end
|
data/spec/tree.rb
ADDED
@@ -0,0 +1,323 @@
|
|
1
|
+
shared_context 'example tree' do
|
2
|
+
let!(:foo) { described_class.create!(name: 'foo') }
|
3
|
+
let!(:bar) { described_class.create!(name: 'bar') }
|
4
|
+
let!(:qux) { bar.children.create!(name: 'qux') }
|
5
|
+
let!(:baz) { bar.children.create!(name: 'baz') }
|
6
|
+
let!(:quux) { qux.children.create!(name: 'quux') }
|
7
|
+
|
8
|
+
def reload_items
|
9
|
+
[ foo, bar, baz, qux, quux ].each(&:reload)
|
10
|
+
end
|
11
|
+
|
12
|
+
before do
|
13
|
+
reload_items
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
shared_examples 'adjacency list' do
|
18
|
+
include_context 'example tree'
|
19
|
+
|
20
|
+
describe '.tree' do
|
21
|
+
it 'arranges tree' do
|
22
|
+
expect(described_class.tree).to be_arranged_like({
|
23
|
+
foo => {},
|
24
|
+
bar => {
|
25
|
+
qux => {
|
26
|
+
quux => {}
|
27
|
+
},
|
28
|
+
baz => {}
|
29
|
+
}
|
30
|
+
})
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'allows custom order' do
|
34
|
+
expect(described_class.alphabetic.tree).to be_arranged_like({
|
35
|
+
bar => {
|
36
|
+
baz => {},
|
37
|
+
qux => {
|
38
|
+
quux => {}
|
39
|
+
}
|
40
|
+
},
|
41
|
+
foo => {}
|
42
|
+
})
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe '.roots' do
|
47
|
+
it 'returns roots' do
|
48
|
+
expect(described_class.roots).to match_array([ foo, bar ])
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe '#move_children_to_parent' do
|
53
|
+
it 'changes children parent' do
|
54
|
+
bar.move_children_to_parent
|
55
|
+
|
56
|
+
expect(described_class.tree).to be_arranged_like({
|
57
|
+
foo => {},
|
58
|
+
bar => {},
|
59
|
+
qux => {
|
60
|
+
quux => {}
|
61
|
+
},
|
62
|
+
baz => {}
|
63
|
+
})
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe '#root?' do
|
68
|
+
it 'returns true if node has parent' do
|
69
|
+
expect(bar).to be_root
|
70
|
+
expect(baz).not_to be_root
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe '#leaf?' do
|
75
|
+
it 'returns true if node does not have children' do
|
76
|
+
expect(quux).to be_leaf
|
77
|
+
expect(qux).not_to be_leaf
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe '#parent_of?' do
|
82
|
+
it 'returns true if node is a parent of given node' do
|
83
|
+
expect(bar).to be_parent_of(qux)
|
84
|
+
expect(bar).not_to be_parent_of(quux)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe '#child_of?' do
|
89
|
+
it 'returns true if node is a child of given node' do
|
90
|
+
expect(qux).to be_child_of(bar)
|
91
|
+
expect(qux).not_to be_child_of(quux)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe '#sibling_of?' do
|
96
|
+
it 'returns true if both nodes have same parent' do
|
97
|
+
expect(foo).to be_sibling_of(bar)
|
98
|
+
expect(baz).to be_sibling_of(qux)
|
99
|
+
expect(foo).not_to be_sibling_of(qux)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
shared_examples 'materialized path' do
|
105
|
+
include_context 'example tree'
|
106
|
+
it_behaves_like 'adjacency list'
|
107
|
+
|
108
|
+
describe '.find_by_path' do
|
109
|
+
it 'returns node' do
|
110
|
+
expect(described_class.find_by_path('bar')).to eq(bar)
|
111
|
+
expect(described_class.find_by_path('bar/qux/')).to eq(qux)
|
112
|
+
expect(described_class.find_by_path('bar/qux/quux')).to eq(quux)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe '.find_by_path!' do
|
117
|
+
it 'returns node or raises RecordNotFound' do
|
118
|
+
expect(described_class.find_by_path!('bar/qux/')).to eq(qux)
|
119
|
+
expect{ described_class.find_by_path!('wrong') }.to raise_error(ActiveRecord::RecordNotFound)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe '#full_path' do
|
124
|
+
it 'returns full node path' do
|
125
|
+
expect(bar.full_path).to eq('bar')
|
126
|
+
expect(qux.full_path).to eq('bar/qux')
|
127
|
+
expect(quux.full_path).to eq('bar/qux/quux')
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
describe '#root' do
|
132
|
+
it 'returns first node ancestor' do
|
133
|
+
expect(baz.root).to eq(bar)
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'returns nil if node is a root' do
|
137
|
+
expect(bar.root).to be nil
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe '#ancestors' do
|
142
|
+
it 'returns node ancestors' do
|
143
|
+
expect(quux.ancestors).to match_array([ qux, bar ])
|
144
|
+
expect(qux.ancestors).to match_array([ bar ])
|
145
|
+
expect(bar.ancestors).to be_empty
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
describe '#descendants' do
|
150
|
+
it 'returns node descendants' do
|
151
|
+
expect(bar.descendants).to match_array([ qux, quux, baz ])
|
152
|
+
expect(qux.descendants).to match_array([ quux ])
|
153
|
+
expect(quux.descendants).to be_empty
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
describe '#subtree' do
|
158
|
+
it 'returns node with descendants' do
|
159
|
+
expect(bar.subtree.tree).to be_arranged_like({
|
160
|
+
bar => {
|
161
|
+
qux => {
|
162
|
+
quux => {}
|
163
|
+
},
|
164
|
+
baz => {}
|
165
|
+
}
|
166
|
+
})
|
167
|
+
end
|
168
|
+
|
169
|
+
it 'returns node if node is a leaf' do
|
170
|
+
expect(baz.subtree).to eq([ baz ])
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
describe '#root_of?' do
|
175
|
+
it 'returns true of node is a root of given node' do
|
176
|
+
expect(bar).to be_root_of(qux)
|
177
|
+
expect(bar).to be_root_of(quux)
|
178
|
+
expect(bar).not_to be_root_of(bar)
|
179
|
+
expect(bar).not_to be_root_of(foo)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
describe '#ancestor_of?' do
|
184
|
+
it 'returns true if node is an ancestors of given node' do
|
185
|
+
expect(bar).to be_ancestor_of(qux)
|
186
|
+
expect(bar).to be_ancestor_of(quux)
|
187
|
+
expect(bar).not_to be_ancestor_of(bar)
|
188
|
+
expect(bar).not_to be_ancestor_of(foo)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
describe '#descendant_of?' do
|
193
|
+
it 'returns true if node is a descendant of given node' do
|
194
|
+
expect(quux).to be_descendant_of(qux)
|
195
|
+
expect(quux).to be_descendant_of(bar)
|
196
|
+
expect(quux).not_to be_descendant_of(quux)
|
197
|
+
expect(quux).not_to be_descendant_of(foo)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
describe '#depth' do
|
202
|
+
it 'returns ancestors count' do
|
203
|
+
expect(bar.depth).to eq(0)
|
204
|
+
expect(qux.depth).to eq(1)
|
205
|
+
expect(quux.depth).to eq(2)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
describe 'node id column change' do
|
210
|
+
before do
|
211
|
+
bar.name = 'bor'
|
212
|
+
bar.save!
|
213
|
+
end
|
214
|
+
|
215
|
+
it 'updates children pathes' do
|
216
|
+
expect(described_class.find_by_path('bor')).to eq(bar)
|
217
|
+
expect(described_class.find_by_path('bor/qux')).to eq(qux)
|
218
|
+
expect(described_class.find_by_path('bor/qux/quux')).to eq(quux)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
describe 'parent change' do
|
223
|
+
let(:prev_parent) { bar }
|
224
|
+
let(:new_parent) { foo }
|
225
|
+
let(:new_ancestors) { [ foo ] }
|
226
|
+
|
227
|
+
before do
|
228
|
+
baz.parent = new_parent
|
229
|
+
baz.save!
|
230
|
+
reload_items
|
231
|
+
end
|
232
|
+
|
233
|
+
it 'updates counter cache' do
|
234
|
+
expect(prev_parent.children_count).to eq(prev_parent.children.count)
|
235
|
+
expect(new_parent.children_count).to eq(new_parent.children.count)
|
236
|
+
|
237
|
+
new_parent.descendants.destroy_all
|
238
|
+
new_parent.reload
|
239
|
+
|
240
|
+
expect(new_parent.children_count).to eq(0)
|
241
|
+
end
|
242
|
+
|
243
|
+
it 'changes ancestors' do
|
244
|
+
expect(baz.ancestors).to match_array(new_ancestors)
|
245
|
+
end
|
246
|
+
|
247
|
+
it 'applies to all descendants' do
|
248
|
+
baz.children.each do |child|
|
249
|
+
expect(child).to be_descendant_of(new_parent)
|
250
|
+
|
251
|
+
child.children.each do |subchild|
|
252
|
+
expect(subchild).to be_descendant_of(new_parent)
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
shared_examples 'tree with cached depth' do
|
260
|
+
include_context 'example tree'
|
261
|
+
it_behaves_like 'adjacency list'
|
262
|
+
|
263
|
+
it 'stores node level' do
|
264
|
+
expect(described_class.where(depth: 0)).to match_array([ foo, bar ])
|
265
|
+
expect(described_class.where(depth: 1)).to match_array([ qux, baz ])
|
266
|
+
expect(described_class.where(depth: 2)).to match_array([ quux ])
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
shared_examples 'scoped tree' do
|
271
|
+
let!(:foo) { described_class.create!(name: 'foo', category: 'foo') }
|
272
|
+
let!(:bar) { described_class.create!(name: 'bar', category: 'bar') }
|
273
|
+
|
274
|
+
it 'restricts scope' do
|
275
|
+
expect(bar.siblings).to be_empty
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
shared_examples 'ordered tree' do
|
280
|
+
include_context 'example tree'
|
281
|
+
|
282
|
+
before(:each) do
|
283
|
+
quux.move_after(foo)
|
284
|
+
reload_items
|
285
|
+
end
|
286
|
+
|
287
|
+
describe '#previous_siblings' do
|
288
|
+
it 'returns siblings with smaller position' do
|
289
|
+
expect(bar.previous_siblings).to match_array([ foo, quux ])
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
describe '#next_siblings' do
|
294
|
+
it 'returns siblings with greater position' do
|
295
|
+
expect(foo.next_siblings).to match_array([ quux, bar ])
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
it '#move_after' do
|
300
|
+
expect(described_class.ordered.tree).to be_arranged_like({
|
301
|
+
foo => {},
|
302
|
+
quux => {},
|
303
|
+
bar => {
|
304
|
+
qux => {},
|
305
|
+
baz => {}
|
306
|
+
}
|
307
|
+
})
|
308
|
+
end
|
309
|
+
|
310
|
+
it '#move_before' do
|
311
|
+
baz.move_before(quux)
|
312
|
+
reload_items
|
313
|
+
|
314
|
+
expect(described_class.ordered.tree).to be_arranged_like({
|
315
|
+
foo => {},
|
316
|
+
baz => {},
|
317
|
+
quux => {},
|
318
|
+
bar => {
|
319
|
+
qux => {}
|
320
|
+
}
|
321
|
+
})
|
322
|
+
end
|
323
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: has_hierarchy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.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-10-
|
11
|
+
date: 2014-10-29 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
|
@@ -114,14 +128,14 @@ dependencies:
|
|
114
128
|
requirements:
|
115
129
|
- - "~>"
|
116
130
|
- !ruby/object:Gem::Version
|
117
|
-
version: '0.
|
131
|
+
version: '0.2'
|
118
132
|
type: :runtime
|
119
133
|
prerelease: false
|
120
134
|
version_requirements: !ruby/object:Gem::Requirement
|
121
135
|
requirements:
|
122
136
|
- - "~>"
|
123
137
|
- !ruby/object:Gem::Version
|
124
|
-
version: '0.
|
138
|
+
version: '0.2'
|
125
139
|
description: Provides tree behavior to active_record models.
|
126
140
|
email:
|
127
141
|
- kolesnikovde@gmail.com
|
@@ -133,21 +147,28 @@ files:
|
|
133
147
|
- ".gitignore"
|
134
148
|
- ".rspec"
|
135
149
|
- ".travis.yml"
|
136
|
-
- CHANGELOG.md
|
137
150
|
- Gemfile
|
138
151
|
- README.md
|
139
152
|
- Rakefile
|
140
153
|
- has_hierarchy.gemspec
|
141
154
|
- lib/has_hierarchy.rb
|
155
|
+
- lib/has_hierarchy/counter_cache.rb
|
142
156
|
- lib/has_hierarchy/depth_cache.rb
|
143
157
|
- lib/has_hierarchy/order.rb
|
158
|
+
- lib/has_hierarchy/orm_adapter.rb
|
159
|
+
- lib/has_hierarchy/orm_adapter/active_record.rb
|
160
|
+
- lib/has_hierarchy/orm_adapter/mongoid.rb
|
144
161
|
- lib/has_hierarchy/path.rb
|
145
162
|
- lib/has_hierarchy/version.rb
|
146
|
-
- spec/db/schema.rb
|
147
163
|
- spec/has_hierarchy_spec.rb
|
148
164
|
- spec/spec_helper.rb
|
149
165
|
- spec/support/matchers.rb
|
150
166
|
- spec/support/models.rb
|
167
|
+
- spec/support/orm/active_record/item_model.rb
|
168
|
+
- spec/support/orm/active_record/setup.rb
|
169
|
+
- spec/support/orm/mongoid/item_model.rb
|
170
|
+
- spec/support/orm/mongoid/setup.rb
|
171
|
+
- spec/tree.rb
|
151
172
|
homepage: https://github.com/kolesnikovde/has_hierarchy
|
152
173
|
licenses:
|
153
174
|
- MIT
|
@@ -173,8 +194,12 @@ signing_key:
|
|
173
194
|
specification_version: 4
|
174
195
|
summary: Provides tree behavior to active_record models.
|
175
196
|
test_files:
|
176
|
-
- spec/db/schema.rb
|
177
197
|
- spec/has_hierarchy_spec.rb
|
178
198
|
- spec/spec_helper.rb
|
179
199
|
- spec/support/matchers.rb
|
180
200
|
- spec/support/models.rb
|
201
|
+
- spec/support/orm/active_record/item_model.rb
|
202
|
+
- spec/support/orm/active_record/setup.rb
|
203
|
+
- spec/support/orm/mongoid/item_model.rb
|
204
|
+
- spec/support/orm/mongoid/setup.rb
|
205
|
+
- spec/tree.rb
|
data/CHANGELOG.md
DELETED
@@ -1,59 +0,0 @@
|
|
1
|
-
# Changelog
|
2
|
-
|
3
|
-
## 0.3.1
|
4
|
-
|
5
|
-
- Added path normalization to `.find_by_path`
|
6
|
-
- Added `.find_by_path!`
|
7
|
-
- Added `#full_path`
|
8
|
-
- Fixed boolean options.
|
9
|
-
- Added options validation.
|
10
|
-
|
11
|
-
## 0.3.0
|
12
|
-
|
13
|
-
- Added "path_separator" option.
|
14
|
-
- Added ordering support.
|
15
|
-
- Cleaned up options.
|
16
|
-
- Renamed to has_hierarchy.
|
17
|
-
|
18
|
-
## 0.2.1
|
19
|
-
|
20
|
-
- Fixed `.find_by_node_path`.
|
21
|
-
- Added tree rebuilding on node_id change.
|
22
|
-
- Added depth caching.
|
23
|
-
|
24
|
-
## 0.2.0
|
25
|
-
|
26
|
-
- Added custom node path values.
|
27
|
-
- Added `.find_by_node_path`.
|
28
|
-
- Added `#child_of?`.
|
29
|
-
- Updated "node_path_column" option (renamed to "node_path_cache").
|
30
|
-
- Rewrited specs.
|
31
|
-
|
32
|
-
## 0.1.3
|
33
|
-
|
34
|
-
- Added README.md.
|
35
|
-
- Added `#leaf?`.
|
36
|
-
- Added `has_children_options` accessor.
|
37
|
-
- Added counter cache and root scope specs.
|
38
|
-
- Updated "orphan_strategy" option (renamed to "dependent").
|
39
|
-
- Updated rake tasks.
|
40
|
-
- Updated .gitignore.
|
41
|
-
- Updated codestyle.
|
42
|
-
- Fixed rspec deprication warnings.
|
43
|
-
|
44
|
-
## 0.1.2
|
45
|
-
|
46
|
-
- Added root association.
|
47
|
-
- Added `#root_id`.
|
48
|
-
- Added `#root_of?`.
|
49
|
-
- Added `#parent_of?`.
|
50
|
-
|
51
|
-
## 0.1.1
|
52
|
-
|
53
|
-
- Fixed scopes (always using lambdas).
|
54
|
-
|
55
|
-
## 0.1.0
|
56
|
-
|
57
|
-
- Added `#move_children_to_parent`.
|
58
|
-
- `#ancestor_tokens` renamed to `#ancestor_ids`.
|
59
|
-
- Added lambda scopes support.
|