acts_as_ordered_tree 1.2.1 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/acts_as_ordered_tree.rb +5 -5
- data/lib/acts_as_ordered_tree/adapters/postgresql_adapter.rb +11 -4
- data/lib/acts_as_ordered_tree/arrangeable.rb +80 -0
- data/lib/acts_as_ordered_tree/instance_methods.rb +9 -5
- data/lib/acts_as_ordered_tree/version.rb +1 -1
- data/spec/acts_as_ordered_tree_spec.rb +91 -0
- data/spec/spec_helper.rb +1 -0
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8d1d945d226f1b6846fa1b58f24a6adab1d41425
|
4
|
+
data.tar.gz: a894af6acac2e1d1bbc044cc008effc846b5b42a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 458af4339a7cdb37ab604344970a7c83264827b62923b90f90e5e607355babd918df26b96897ff91ca2f1b6064f8d0cb6f2d95d1af14c3da6d67cb9cd3ecd060
|
7
|
+
data.tar.gz: 62cd0756ebb2c63518cd73ef53b6163eac7bf1716ebb3d3614217bd2bebe2eefa14109dd87a53ae755032f63ce0c75a24a4f43fd1069b8d5dbca0c7edd970f78
|
data/lib/acts_as_ordered_tree.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
1
|
+
require 'active_record'
|
2
|
+
require 'acts_as_ordered_tree/version'
|
3
|
+
require 'acts_as_ordered_tree/class_methods'
|
4
|
+
require 'acts_as_ordered_tree/instance_methods'
|
5
|
+
require 'acts_as_ordered_tree/validators'
|
6
6
|
|
7
7
|
module ActsAsOrderedTree
|
8
8
|
PROTECTED_ATTRIBUTES_SUPPORTED = ActiveRecord::VERSION::STRING < '4.0.0' ||
|
@@ -1,3 +1,6 @@
|
|
1
|
+
require 'acts_as_ordered_tree/arrangeable'
|
2
|
+
require 'acts_as_ordered_tree/relation/preloaded'
|
3
|
+
|
1
4
|
module ActsAsOrderedTree
|
2
5
|
module Adapters
|
3
6
|
module PostgreSQLAdapter
|
@@ -15,9 +18,10 @@ module ActsAsOrderedTree
|
|
15
18
|
QUERY
|
16
19
|
|
17
20
|
with_recursive_join(query, 'self_and_ancestors').
|
18
|
-
order('self_and_ancestors._depth DESC')
|
21
|
+
order('self_and_ancestors._depth DESC').
|
22
|
+
extending(Arrangeable)
|
19
23
|
else
|
20
|
-
ancestors + [self]
|
24
|
+
(ancestors + [self]).tap { |ary| ary.extend(Arrangeable) }
|
21
25
|
end
|
22
26
|
end
|
23
27
|
|
@@ -33,7 +37,9 @@ module ActsAsOrderedTree
|
|
33
37
|
INNER JOIN ancestors ON alias1.id = ancestors.#{parent_column}
|
34
38
|
QUERY
|
35
39
|
|
36
|
-
with_recursive_join(query, 'ancestors').
|
40
|
+
with_recursive_join(query, 'ancestors').
|
41
|
+
order('ancestors._depth DESC').
|
42
|
+
extending(Arrangeable)
|
37
43
|
end
|
38
44
|
|
39
45
|
def root
|
@@ -52,7 +58,8 @@ module ActsAsOrderedTree
|
|
52
58
|
QUERY
|
53
59
|
|
54
60
|
with_recursive_join(query, 'descendants').
|
55
|
-
order('descendants._positions ASC')
|
61
|
+
order('descendants._positions ASC').
|
62
|
+
extending(Arrangeable)
|
56
63
|
end
|
57
64
|
|
58
65
|
def descendants
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module ActsAsOrderedTree
|
2
|
+
module Arrangeable
|
3
|
+
# @api private
|
4
|
+
class Arranger
|
5
|
+
attr_reader :collection, :cache
|
6
|
+
|
7
|
+
def initialize(collection, options = {})
|
8
|
+
@collection = collection
|
9
|
+
@discard_orphans = options[:orphans] == :discard
|
10
|
+
@min_level = nil
|
11
|
+
|
12
|
+
if discard_orphans? && !collection.klass.depth_column && ActiveRecord::Base.logger
|
13
|
+
ActiveRecord::Base.logger.warn {
|
14
|
+
'%s model has no `depth` column, '\
|
15
|
+
'it can lead to N+1 queries during #arrange method invocation' % collection.klass
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
@cache = Hash.new
|
20
|
+
@prepared = false
|
21
|
+
end
|
22
|
+
|
23
|
+
def arrange
|
24
|
+
prepare unless prepared?
|
25
|
+
|
26
|
+
@arranged ||= collection.each_with_object(Hash.new) do |node, result|
|
27
|
+
ancestors = ancestors(node)
|
28
|
+
|
29
|
+
if discard_orphans?
|
30
|
+
root = ancestors.first || node
|
31
|
+
|
32
|
+
next if root.level > @min_level
|
33
|
+
end
|
34
|
+
|
35
|
+
insertion_point = result
|
36
|
+
|
37
|
+
ancestors.each { |a| insertion_point = (insertion_point[a] ||= {}) }
|
38
|
+
|
39
|
+
insertion_point[node] = {}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
def prepare
|
45
|
+
collection.each do |node|
|
46
|
+
cache[node.id] = node if node.id
|
47
|
+
@min_level = [@min_level, node.level].compact.min
|
48
|
+
end
|
49
|
+
|
50
|
+
@prepared = true
|
51
|
+
end
|
52
|
+
|
53
|
+
def discard_orphans?
|
54
|
+
@discard_orphans
|
55
|
+
end
|
56
|
+
|
57
|
+
def prepared?
|
58
|
+
@prepared
|
59
|
+
end
|
60
|
+
|
61
|
+
# get parent node of +node+
|
62
|
+
def parent(node)
|
63
|
+
cache[node[node.parent_column]]
|
64
|
+
end
|
65
|
+
|
66
|
+
def ancestors(node)
|
67
|
+
parent = parent(node)
|
68
|
+
parent ? ancestors(parent) + [parent] : []
|
69
|
+
end
|
70
|
+
end
|
71
|
+
private_constant :Arranger
|
72
|
+
|
73
|
+
# Arrange associated collection into a nested hash of the form
|
74
|
+
# {node => children}, where children = {} if the node has no children.
|
75
|
+
def arrange(options = {})
|
76
|
+
@arranger ||= Arranger.new(self, options)
|
77
|
+
@arranger.arrange
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# coding: utf-8
|
2
|
-
require
|
3
|
-
require
|
2
|
+
require 'acts_as_ordered_tree/tenacious_transaction'
|
3
|
+
require 'acts_as_ordered_tree/relation/preloaded'
|
4
|
+
require 'acts_as_ordered_tree/arrangeable'
|
4
5
|
|
5
6
|
module ActsAsOrderedTree
|
6
7
|
module InstanceMethods
|
@@ -16,7 +17,7 @@ module ActsAsOrderedTree
|
|
16
17
|
persisted? && if children_counter_cache_column
|
17
18
|
self[children_counter_cache_column] == 0
|
18
19
|
else
|
19
|
-
children.
|
20
|
+
!children.reorder(nil).exists?
|
20
21
|
end
|
21
22
|
end
|
22
23
|
|
@@ -50,7 +51,8 @@ module ActsAsOrderedTree
|
|
50
51
|
|
51
52
|
# 3. create fake scope
|
52
53
|
ActsAsOrderedTree::Relation::Preloaded.new(self.class).
|
53
|
-
where(:id => nodes.map(&:id)).
|
54
|
+
where(:id => nodes.map(&:id).compact).
|
55
|
+
extending(Arrangeable).
|
54
56
|
records(nodes)
|
55
57
|
end
|
56
58
|
|
@@ -93,7 +95,8 @@ module ActsAsOrderedTree
|
|
93
95
|
records = fetch_self_and_descendants - [self]
|
94
96
|
|
95
97
|
ActsAsOrderedTree::Relation::Preloaded.new(self.class).
|
96
|
-
where(:id => records.map(&:id)).
|
98
|
+
where(:id => records.map(&:id).compact).
|
99
|
+
extending(Arrangeable).
|
97
100
|
records(records)
|
98
101
|
end
|
99
102
|
|
@@ -103,6 +106,7 @@ module ActsAsOrderedTree
|
|
103
106
|
|
104
107
|
ActsAsOrderedTree::Relation::Preloaded.new(self.class).
|
105
108
|
where(:id => records.map(&:id)).
|
109
|
+
extending(Arrangeable).
|
106
110
|
records(records)
|
107
111
|
end
|
108
112
|
|
@@ -415,6 +415,97 @@ describe ActsAsOrderedTree, :transactional do
|
|
415
415
|
end
|
416
416
|
end
|
417
417
|
|
418
|
+
describe '#arrange' do
|
419
|
+
shared_examples 'arrangeable' do
|
420
|
+
let(:child_1) { root.children.first }
|
421
|
+
let(:child_2) { root.children.last }
|
422
|
+
let(:grandchild_11) { child_1.children.first }
|
423
|
+
let(:grandchild_12) { child_1.children.last }
|
424
|
+
let(:grandchild_21) { child_2.children.first }
|
425
|
+
let(:grandchild_22) { child_2.children.last }
|
426
|
+
|
427
|
+
specify '#descendants scope should be arrangeable' do
|
428
|
+
expect(root.descendants.arrange).to eq Hash[
|
429
|
+
child_1 => {
|
430
|
+
grandchild_11 => {},
|
431
|
+
grandchild_12 => {}
|
432
|
+
},
|
433
|
+
child_2 => {
|
434
|
+
grandchild_21 => {},
|
435
|
+
grandchild_22 => {}
|
436
|
+
}
|
437
|
+
]
|
438
|
+
end
|
439
|
+
|
440
|
+
specify '#self_and_descendants should be arrangeable' do
|
441
|
+
expect(root.self_and_descendants.arrange).to eq Hash[
|
442
|
+
root => {
|
443
|
+
child_1 => {
|
444
|
+
grandchild_11 => {},
|
445
|
+
grandchild_12 => {}
|
446
|
+
},
|
447
|
+
child_2 => {
|
448
|
+
grandchild_21 => {},
|
449
|
+
grandchild_22 => {}
|
450
|
+
}
|
451
|
+
}
|
452
|
+
]
|
453
|
+
end
|
454
|
+
|
455
|
+
specify '#ancestors should be arrangeable' do
|
456
|
+
expect(grandchild_11.ancestors.arrange).to eq Hash[
|
457
|
+
root => {
|
458
|
+
child_1 => {}
|
459
|
+
}
|
460
|
+
]
|
461
|
+
end
|
462
|
+
|
463
|
+
specify '#self_and_ancestors should be arrangeable' do
|
464
|
+
expect(grandchild_11.self_and_ancestors.arrange).to eq Hash[
|
465
|
+
root => {
|
466
|
+
child_1 => {
|
467
|
+
grandchild_11 => {}
|
468
|
+
}
|
469
|
+
}
|
470
|
+
]
|
471
|
+
end
|
472
|
+
|
473
|
+
it 'should not discard orphaned nodes by default' do
|
474
|
+
relation = root.descendants.where(root.class.arel_table[:id].not_eq(child_1.id))
|
475
|
+
|
476
|
+
expect(relation.arrange).to eq Hash[
|
477
|
+
grandchild_11 => {},
|
478
|
+
grandchild_12 => {},
|
479
|
+
child_2 => {
|
480
|
+
grandchild_21 => {},
|
481
|
+
grandchild_22 => {}
|
482
|
+
}
|
483
|
+
]
|
484
|
+
end
|
485
|
+
|
486
|
+
it 'should discard orphans if option :discard passed' do
|
487
|
+
relation = root.descendants.where(root.class.arel_table[:id].not_eq(child_1.id))
|
488
|
+
|
489
|
+
expect(relation.arrange(:orphans => :discard)).to eq Hash[
|
490
|
+
child_2 => {
|
491
|
+
grandchild_21 => {},
|
492
|
+
grandchild_22 => {}
|
493
|
+
}
|
494
|
+
]
|
495
|
+
end
|
496
|
+
end
|
497
|
+
|
498
|
+
context 'when persisted tree given' do
|
499
|
+
it_should_behave_like 'arrangeable' do
|
500
|
+
let(:root) { create :default }
|
501
|
+
|
502
|
+
before { create_list :default, 2, :parent => root }
|
503
|
+
before { create_list :default, 2, :parent => root.children.first }
|
504
|
+
before { create_list :default, 2, :parent => root.children.last }
|
505
|
+
end
|
506
|
+
end
|
507
|
+
end
|
508
|
+
|
418
509
|
describe "move actions" do
|
419
510
|
let!(:root) { create :default_with_counter_cache, :name => 'root' }
|
420
511
|
let!(:child_1) { create :default_with_counter_cache, :parent => root, :name => 'child_1' }
|
data/spec/spec_helper.rb
CHANGED
@@ -28,6 +28,7 @@ ActiveRecord::Base.configurations = YAML::load(ERB.new(IO.read(File.join(test_di
|
|
28
28
|
ActiveRecord::Base.establish_connection(ENV['DB'])
|
29
29
|
ActiveRecord::Base.logger = Logger.new(ENV['DEBUG'] ? $stderr : '/dev/null')
|
30
30
|
ActiveRecord::Migration.verbose = false
|
31
|
+
I18n.enforce_available_locales = false if I18n.respond_to?(:enforce_available_locales=)
|
31
32
|
load(File.join(test_dir, 'db', 'schema.rb'))
|
32
33
|
|
33
34
|
require 'shoulda-matchers'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: acts_as_ordered_tree
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexei Mikhailov
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2014-02-18 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
@@ -73,14 +73,14 @@ dependencies:
|
|
73
73
|
requirements:
|
74
74
|
- - '>='
|
75
75
|
- !ruby/object:Gem::Version
|
76
|
-
version:
|
76
|
+
version: 2.4.0
|
77
77
|
type: :development
|
78
78
|
prerelease: false
|
79
79
|
version_requirements: !ruby/object:Gem::Requirement
|
80
80
|
requirements:
|
81
81
|
- - '>='
|
82
82
|
- !ruby/object:Gem::Version
|
83
|
-
version:
|
83
|
+
version: 2.4.0
|
84
84
|
- !ruby/object:Gem::Dependency
|
85
85
|
name: factory_girl
|
86
86
|
requirement: !ruby/object:Gem::Requirement
|
@@ -119,6 +119,7 @@ extra_rdoc_files: []
|
|
119
119
|
files:
|
120
120
|
- lib/acts_as_ordered_tree.rb
|
121
121
|
- lib/acts_as_ordered_tree/adapters/postgresql_adapter.rb
|
122
|
+
- lib/acts_as_ordered_tree/arrangeable.rb
|
122
123
|
- lib/acts_as_ordered_tree/class_methods.rb
|
123
124
|
- lib/acts_as_ordered_tree/instance_methods.rb
|
124
125
|
- lib/acts_as_ordered_tree/relation/base.rb
|