mongoid_nested_set 0.1.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.
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +19 -0
- data/Gemfile.lock +59 -0
- data/LICENSE.txt +20 -0
- data/README.markdown +147 -0
- data/Rakefile +47 -0
- data/VERSION +1 -0
- data/lib/mongoid_nested_set.rb +40 -0
- data/lib/mongoid_nested_set/base.rb +90 -0
- data/lib/mongoid_nested_set/document.rb +207 -0
- data/lib/mongoid_nested_set/fields.rb +60 -0
- data/lib/mongoid_nested_set/outline_number.rb +122 -0
- data/lib/mongoid_nested_set/rebuild.rb +41 -0
- data/lib/mongoid_nested_set/relations.rb +100 -0
- data/lib/mongoid_nested_set/remove_order_by.rb +11 -0
- data/lib/mongoid_nested_set/update.rb +233 -0
- data/lib/mongoid_nested_set/validation.rb +59 -0
- data/mongoid_nested_set.gemspec +100 -0
- data/spec/matchers/nestedset_pos.rb +46 -0
- data/spec/models/circle_node.rb +4 -0
- data/spec/models/node.rb +10 -0
- data/spec/models/node_without_nested_set.rb +6 -0
- data/spec/models/numbering_node.rb +10 -0
- data/spec/models/renamed_fields.rb +7 -0
- data/spec/models/shape_node.rb +18 -0
- data/spec/models/square_node.rb +4 -0
- data/spec/models/test_document.rb +35 -0
- data/spec/models/unscoped_node.rb +9 -0
- data/spec/mongoid_nested_set_spec.rb +723 -0
- data/spec/spec_helper.rb +44 -0
- metadata +196 -0
@@ -0,0 +1,59 @@
|
|
1
|
+
module Mongoid::Acts::NestedSet
|
2
|
+
|
3
|
+
module Validation
|
4
|
+
|
5
|
+
# Warning: Very expensive! Do not use unless you know what you are doing.
|
6
|
+
# This method is only useful for determining if the entire tree is valid
|
7
|
+
def valid?
|
8
|
+
left_and_rights_valid? && no_duplicates_for_fields? && all_roots_valid?
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
# Warning: Very expensive! Do not use unless you know what you are doing.
|
13
|
+
def left_and_rights_valid?
|
14
|
+
all.detect { |node|
|
15
|
+
node.send(left_field_name).nil? ||
|
16
|
+
node.send(right_field_name).nil? ||
|
17
|
+
node.send(left_field_name) >= node.send(right_field_name) ||
|
18
|
+
!node.parent.nil? && (
|
19
|
+
node.send(left_field_name) <= node.parent.send(left_field_name) ||
|
20
|
+
node.send(right_field_name) >= node.parent.send(right_field_name)
|
21
|
+
)
|
22
|
+
}.nil?
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
# Warning: Very expensive! Do not use unless you know what you are doing.
|
27
|
+
def no_duplicates_for_fields?
|
28
|
+
roots.group_by{|record| scope_field_names.collect{|field| record.send(field.to_sym)}}.all? do |scope, grouped_roots|
|
29
|
+
[left_field_name, right_field_name].all? do |field|
|
30
|
+
grouped_roots.first.nested_set_scope.only(field).aggregate.all? {|c| c['count'] == 1}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
# Wrapper for each_root_valid? that can deal with scope
|
37
|
+
# Warning: Very expensive! Do not use unless you know what you are doing.
|
38
|
+
def all_roots_valid?
|
39
|
+
if acts_as_nested_set_options[:scope]
|
40
|
+
roots.group_by{|record| scope_field_names.collect{|field| record.send(field.to_sym)}}.all? do |scope, grouped_roots|
|
41
|
+
each_root_valid?(grouped_roots)
|
42
|
+
end
|
43
|
+
else
|
44
|
+
each_root_valid?(roots)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
def each_root_valid?(roots_to_validate)
|
50
|
+
right = 0
|
51
|
+
roots_to_validate.all? do |root|
|
52
|
+
(root.left > right && root.right > right).tap do
|
53
|
+
right = root.right
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{mongoid_nested_set}
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Brandon Turner"]
|
12
|
+
s.date = %q{2010-12-17}
|
13
|
+
s.description = %q{Fully featured tree implementation for Mongoid using the nested set model}
|
14
|
+
s.email = %q{bturner@bltweb.net}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.markdown"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".rspec",
|
22
|
+
"Gemfile",
|
23
|
+
"Gemfile.lock",
|
24
|
+
"LICENSE.txt",
|
25
|
+
"README.markdown",
|
26
|
+
"Rakefile",
|
27
|
+
"VERSION",
|
28
|
+
"lib/mongoid_nested_set.rb",
|
29
|
+
"lib/mongoid_nested_set/base.rb",
|
30
|
+
"lib/mongoid_nested_set/document.rb",
|
31
|
+
"lib/mongoid_nested_set/fields.rb",
|
32
|
+
"lib/mongoid_nested_set/outline_number.rb",
|
33
|
+
"lib/mongoid_nested_set/rebuild.rb",
|
34
|
+
"lib/mongoid_nested_set/relations.rb",
|
35
|
+
"lib/mongoid_nested_set/remove_order_by.rb",
|
36
|
+
"lib/mongoid_nested_set/update.rb",
|
37
|
+
"lib/mongoid_nested_set/validation.rb",
|
38
|
+
"mongoid_nested_set.gemspec",
|
39
|
+
"spec/matchers/nestedset_pos.rb",
|
40
|
+
"spec/models/circle_node.rb",
|
41
|
+
"spec/models/node.rb",
|
42
|
+
"spec/models/node_without_nested_set.rb",
|
43
|
+
"spec/models/numbering_node.rb",
|
44
|
+
"spec/models/renamed_fields.rb",
|
45
|
+
"spec/models/shape_node.rb",
|
46
|
+
"spec/models/square_node.rb",
|
47
|
+
"spec/models/test_document.rb",
|
48
|
+
"spec/models/unscoped_node.rb",
|
49
|
+
"spec/mongoid_nested_set_spec.rb",
|
50
|
+
"spec/spec_helper.rb"
|
51
|
+
]
|
52
|
+
s.homepage = %q{http://github.com/thinkwell/mongoid_nested_set}
|
53
|
+
s.licenses = ["MIT"]
|
54
|
+
s.require_paths = ["lib"]
|
55
|
+
s.rubygems_version = %q{1.3.7}
|
56
|
+
s.summary = %q{Nested set based tree implementation for Mongoid}
|
57
|
+
s.test_files = [
|
58
|
+
"spec/matchers/nestedset_pos.rb",
|
59
|
+
"spec/models/circle_node.rb",
|
60
|
+
"spec/models/node.rb",
|
61
|
+
"spec/models/node_without_nested_set.rb",
|
62
|
+
"spec/models/numbering_node.rb",
|
63
|
+
"spec/models/renamed_fields.rb",
|
64
|
+
"spec/models/shape_node.rb",
|
65
|
+
"spec/models/square_node.rb",
|
66
|
+
"spec/models/test_document.rb",
|
67
|
+
"spec/models/unscoped_node.rb",
|
68
|
+
"spec/mongoid_nested_set_spec.rb",
|
69
|
+
"spec/spec_helper.rb"
|
70
|
+
]
|
71
|
+
|
72
|
+
if s.respond_to? :specification_version then
|
73
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
74
|
+
s.specification_version = 3
|
75
|
+
|
76
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
77
|
+
s.add_runtime_dependency(%q<mongoid>, [">= 2.0.0.beta.20"])
|
78
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
79
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.5.1"])
|
80
|
+
s.add_development_dependency(%q<rcov>, [">= 0"])
|
81
|
+
s.add_development_dependency(%q<rspec-core>, [">= 0"])
|
82
|
+
s.add_runtime_dependency(%q<mongoid>, [">= 2.0.0.beta.20"])
|
83
|
+
else
|
84
|
+
s.add_dependency(%q<mongoid>, [">= 2.0.0.beta.20"])
|
85
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
86
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
|
87
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
88
|
+
s.add_dependency(%q<rspec-core>, [">= 0"])
|
89
|
+
s.add_dependency(%q<mongoid>, [">= 2.0.0.beta.20"])
|
90
|
+
end
|
91
|
+
else
|
92
|
+
s.add_dependency(%q<mongoid>, [">= 2.0.0.beta.20"])
|
93
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
94
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
|
95
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
96
|
+
s.add_dependency(%q<rspec-core>, [">= 0"])
|
97
|
+
s.add_dependency(%q<mongoid>, [">= 2.0.0.beta.20"])
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Mongoid::Acts::NestedSet
|
2
|
+
module Matchers
|
3
|
+
|
4
|
+
def have_nestedset_pos(lft, rgt, options = {})
|
5
|
+
NestedSetPosition.new(lft, rgt, options)
|
6
|
+
end
|
7
|
+
|
8
|
+
class NestedSetPosition
|
9
|
+
|
10
|
+
def initialize(lft, rgt, options)
|
11
|
+
@lft = lft
|
12
|
+
@rgt = rgt
|
13
|
+
@options = options
|
14
|
+
end
|
15
|
+
|
16
|
+
def matches?(node)
|
17
|
+
@node = node
|
18
|
+
!!(
|
19
|
+
node.respond_to?('left') && node.respond_to?('right') &&
|
20
|
+
node.left == @lft &&
|
21
|
+
node.right == @rgt
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
def description
|
26
|
+
"have position {left: #{@lft}, right: #{@rgt}}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def failure_message_for_should
|
30
|
+
sprintf("expected nested set position: {left: %2s, right: %2s}\n" +
|
31
|
+
" got: {left: %2s, right: %2s}",
|
32
|
+
@lft,
|
33
|
+
@rgt,
|
34
|
+
@node.respond_to?('left') ? @node.left : '?',
|
35
|
+
@node.respond_to?('right') ? @node.right : '?'
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
def failure_message_for_should_not
|
40
|
+
sprintf("expected nested set to not have position: {left: %2s, right: %2s}", @lft, @rgt)
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
data/spec/models/node.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/test_document"
|
2
|
+
|
3
|
+
class NumberingNode
|
4
|
+
include Mongoid::Document
|
5
|
+
include Mongoid::Acts::NestedSet::TestDocument
|
6
|
+
acts_as_nested_set :scope => :root_id, :outline_number_field => 'number'
|
7
|
+
|
8
|
+
field :name
|
9
|
+
field :root_id, :type => Integer
|
10
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/test_document"
|
2
|
+
|
3
|
+
class ShapeNode
|
4
|
+
include Mongoid::Document
|
5
|
+
include Mongoid::Acts::NestedSet::TestDocument
|
6
|
+
acts_as_nested_set
|
7
|
+
|
8
|
+
field :name
|
9
|
+
|
10
|
+
def test_set_attributes(attrs)
|
11
|
+
@attributes.update(attrs)
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.test_set_dependent_option(val)
|
16
|
+
self.acts_as_nested_set_options[:dependent] = val
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Mongoid::Acts::NestedSet
|
2
|
+
|
3
|
+
module TestDocument
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
base.send(:include, InstanceMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
|
13
|
+
def test_set_dependent_option(val)
|
14
|
+
self.acts_as_nested_set_options[:dependent] = val
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
module InstanceMethods
|
21
|
+
|
22
|
+
def test_set_attributes(attrs)
|
23
|
+
attrs.each do |key, val|
|
24
|
+
if Mongoid.allow_dynamic_fields ||
|
25
|
+
fields.keys.any? { |k| k.to_s == key.to_s } ||
|
26
|
+
associations.any? { |a| a[0].to_s == key.to_s || a[1].foreign_key.to_s == key.to_s }
|
27
|
+
@attributes[key] = val
|
28
|
+
end
|
29
|
+
end
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,723 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
|
4
|
+
describe Mongoid::Acts::NestedSet do
|
5
|
+
|
6
|
+
it "provides the acts_as_nested_set method" do
|
7
|
+
Node.should respond_to('acts_as_nested_set')
|
8
|
+
NodeWithoutNestedSet.should respond_to('acts_as_nested_set')
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
describe "A Mongoid::Document" do
|
15
|
+
|
16
|
+
def create_clothing_nodes(klass=Node)
|
17
|
+
nodes = {}
|
18
|
+
# See Wikipedia for an illustration of the first tree
|
19
|
+
# http://en.wikipedia.org/wiki/Nested_set_model#Example
|
20
|
+
nodes[:clothing] = klass.new.test_set_attributes('root_id' => 1, 'name' => 'Clothing', 'lft' => 1, 'rgt' => 22, 'depth' => 0, 'number' => nil, 'parent_id' => nil)
|
21
|
+
nodes[:mens] = klass.new.test_set_attributes('root_id' => 1, 'name' => 'Men\'s', 'lft' => 2, 'rgt' => 9, 'depth' => 1, 'number' => '1', 'parent_id' => nodes[:clothing].id)
|
22
|
+
nodes[:suits] = klass.new.test_set_attributes('root_id' => 1, 'name' => 'Suits', 'lft' => 3, 'rgt' => 8, 'depth' => 2, 'number' => '1.1', 'parent_id' => nodes[:mens].id)
|
23
|
+
nodes[:slacks] = klass.new.test_set_attributes('root_id' => 1, 'name' => 'Slacks', 'lft' => 4, 'rgt' => 5, 'depth' => 3, 'number' => '1.1.1', 'parent_id' => nodes[:suits].id)
|
24
|
+
nodes[:jackets] = klass.new.test_set_attributes('root_id' => 1, 'name' => 'Jackets', 'lft' => 6, 'rgt' => 7, 'depth' => 3, 'number' => '1.1.2', 'parent_id' => nodes[:suits].id)
|
25
|
+
nodes[:womens] = klass.new.test_set_attributes('root_id' => 1, 'name' => 'Women\'s', 'lft' => 10, 'rgt' => 21, 'depth' => 1, 'number' => '2', 'parent_id' => nodes[:clothing].id)
|
26
|
+
nodes[:dresses] = klass.new.test_set_attributes('root_id' => 1, 'name' => 'Dresses', 'lft' => 11, 'rgt' => 16, 'depth' => 2, 'number' => '2.1', 'parent_id' => nodes[:womens].id)
|
27
|
+
nodes[:skirts] = klass.new.test_set_attributes('root_id' => 1, 'name' => 'Skirts', 'lft' => 17, 'rgt' => 18, 'depth' => 2, 'number' => '2.2', 'parent_id' => nodes[:womens].id)
|
28
|
+
nodes[:blouses] = klass.new.test_set_attributes('root_id' => 1, 'name' => 'Blouses', 'lft' => 19, 'rgt' => 20, 'depth' => 2, 'number' => '2.3', 'parent_id' => nodes[:womens].id)
|
29
|
+
nodes[:gowns] = klass.new.test_set_attributes('root_id' => 1, 'name' => 'Gowns', 'lft' => 12, 'rgt' => 13, 'depth' => 3, 'number' => '2.1.1', 'parent_id' => nodes[:dresses].id)
|
30
|
+
nodes[:sundress] = klass.new.test_set_attributes('root_id' => 1, 'name' => 'Sun Dresses', 'lft' => 14, 'rgt' => 15, 'depth' => 3, 'number' => '2.1.2', 'parent_id' => nodes[:dresses].id)
|
31
|
+
nodes
|
32
|
+
end
|
33
|
+
|
34
|
+
def create_electronics_nodes(klass=Node)
|
35
|
+
nodes = {}
|
36
|
+
# See MySQL for an illustration of the second tree
|
37
|
+
# http://dev.mysql.com/tech-resources/articles/hierarchical-data.html
|
38
|
+
nodes[:electronics] = klass.new.test_set_attributes('root_id' => 2, 'name' => 'Electronics', 'lft' => 1, 'rgt' => 20, 'depth' => 0, 'number' => nil, 'parent_id' => nil)
|
39
|
+
nodes[:televisions] = klass.new.test_set_attributes('root_id' => 2, 'name' => 'Televisions', 'lft' => 2, 'rgt' => 9, 'depth' => 1, 'number' => '1', 'parent_id' => nodes[:electronics].id)
|
40
|
+
nodes[:tube] = klass.new.test_set_attributes('root_id' => 2, 'name' => 'Tube', 'lft' => 3, 'rgt' => 4, 'depth' => 2, 'number' => '1.1', 'parent_id' => nodes[:televisions].id)
|
41
|
+
nodes[:lcd] = klass.new.test_set_attributes('root_id' => 2, 'name' => 'LCD', 'lft' => 5, 'rgt' => 6, 'depth' => 2, 'number' => '1.2', 'parent_id' => nodes[:televisions].id)
|
42
|
+
nodes[:plasma] = klass.new.test_set_attributes('root_id' => 2, 'name' => 'Plasma', 'lft' => 7, 'rgt' => 8, 'depth' => 2, 'number' => '1.3', 'parent_id' => nodes[:televisions].id)
|
43
|
+
nodes[:portable] = klass.new.test_set_attributes('root_id' => 2, 'name' => 'Portable', 'lft' => 10, 'rgt' => 19, 'depth' => 1, 'number' => '2', 'parent_id' => nodes[:electronics].id)
|
44
|
+
nodes[:mp3] = klass.new.test_set_attributes('root_id' => 2, 'name' => 'MP3', 'lft' => 11, 'rgt' => 14, 'depth' => 2, 'number' => '2.1', 'parent_id' => nodes[:portable].id)
|
45
|
+
nodes[:cd] = klass.new.test_set_attributes('root_id' => 2, 'name' => 'CD', 'lft' => 15, 'rgt' => 16, 'depth' => 2, 'number' => '2.2', 'parent_id' => nodes[:portable].id)
|
46
|
+
nodes[:radio] = klass.new.test_set_attributes('root_id' => 2, 'name' => '2 Way Radio', 'lft' => 17, 'rgt' => 18, 'depth' => 2, 'number' => '2.3', 'parent_id' => nodes[:portable].id)
|
47
|
+
nodes[:flash] = klass.new.test_set_attributes('root_id' => 2, 'name' => 'Flash', 'lft' => 12, 'rgt' => 13, 'depth' => 3, 'number' => '2.1.1', 'parent_id' => nodes[:mp3].id)
|
48
|
+
nodes
|
49
|
+
end
|
50
|
+
|
51
|
+
def persist_nodes(nodes, collection_name=nil)
|
52
|
+
nodes = {:first => nodes} unless nodes.is_a? Hash
|
53
|
+
collection_name = nodes.values.first.class.collection_name if collection_name.nil?
|
54
|
+
coll = Mongoid.master[collection_name]
|
55
|
+
|
56
|
+
nodes.each_value do |node|
|
57
|
+
# Bypass the ORM (and the nested set callbacks) and save directly with the underlying driver
|
58
|
+
coll.update({:_id => node.id}, node.attributes, {:upsert => true})
|
59
|
+
node.new_record = false
|
60
|
+
end
|
61
|
+
nodes
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
|
66
|
+
|
67
|
+
context "that does not act as a nested set" do
|
68
|
+
it "does not have a left field" do
|
69
|
+
NodeWithoutNestedSet.should_not have_field('lft', :type => Integer)
|
70
|
+
end
|
71
|
+
|
72
|
+
it "does not have a right field" do
|
73
|
+
NodeWithoutNestedSet.should_not have_field('rgt', :type => Integer)
|
74
|
+
end
|
75
|
+
|
76
|
+
it "does not include NestedSet methods" do
|
77
|
+
NodeWithoutNestedSet.should_not respond_to('descendant_of')
|
78
|
+
NodeWithoutNestedSet.new.should_not respond_to('left')
|
79
|
+
NodeWithoutNestedSet.should_not respond_to('each_with_outline_number')
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
context "that acts as an un-scoped nested set" do
|
85
|
+
|
86
|
+
context "in a tree" do
|
87
|
+
before(:each) do
|
88
|
+
@nodes = persist_nodes(create_clothing_nodes(UnscopedNode))
|
89
|
+
end
|
90
|
+
|
91
|
+
it "can detect if roots are valid" do
|
92
|
+
UnscopedNode.should be_all_roots_valid
|
93
|
+
|
94
|
+
persist_nodes(UnscopedNode.new(:name => 'Test').test_set_attributes(:lft => 20, :rgt => 30, :parent_id=>nil))
|
95
|
+
UnscopedNode.should_not be_all_roots_valid
|
96
|
+
end
|
97
|
+
|
98
|
+
it "can detect if left and rights are valid" do
|
99
|
+
UnscopedNode.should be_left_and_rights_valid
|
100
|
+
|
101
|
+
# left > right
|
102
|
+
n = UnscopedNode.new(:name => 'Test').test_set_attributes(:lft => 6, :rgt => 5, :parent_id=>@nodes[:suits].id)
|
103
|
+
persist_nodes(n)
|
104
|
+
UnscopedNode.should_not be_left_and_rights_valid
|
105
|
+
|
106
|
+
# left == right
|
107
|
+
persist_nodes(n.test_set_attributes(:rgt => 6))
|
108
|
+
UnscopedNode.should_not be_left_and_rights_valid
|
109
|
+
|
110
|
+
# Overlaps parent
|
111
|
+
persist_nodes(n.test_set_attributes(:rgt => 8))
|
112
|
+
UnscopedNode.should_not be_left_and_rights_valid
|
113
|
+
end
|
114
|
+
|
115
|
+
it "can detect duplicate left and right values" do
|
116
|
+
UnscopedNode.should be_no_duplicates_for_fields
|
117
|
+
|
118
|
+
n = UnscopedNode.new(:name => 'Test').test_set_attributes(:lft => 6, :rgt => 25, :parent_id=>@nodes[:suits].id)
|
119
|
+
persist_nodes(n)
|
120
|
+
UnscopedNode.should_not be_no_duplicates_for_fields
|
121
|
+
|
122
|
+
persist_nodes(n.test_set_attributes(:lft => 5, :rgt => 7, :parent_id=>@nodes[:suits].id))
|
123
|
+
UnscopedNode.should_not be_no_duplicates_for_fields
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
|
129
|
+
context "that acts as a scoped nested set" do
|
130
|
+
|
131
|
+
it "does not include outline number methods" do
|
132
|
+
Node.should_not respond_to('each_with_outline_number')
|
133
|
+
end
|
134
|
+
|
135
|
+
# Adds fields
|
136
|
+
|
137
|
+
it "has a left field" do
|
138
|
+
Node.should have_field('lft', :type => Integer)
|
139
|
+
RenamedFields.should have_field('red', :type => Integer)
|
140
|
+
RenamedFields.should_not have_field('lft', :type => Integer)
|
141
|
+
end
|
142
|
+
|
143
|
+
it "has a right field" do
|
144
|
+
Node.should have_field('rgt', :type => Integer)
|
145
|
+
RenamedFields.should have_field('red', :type => Integer)
|
146
|
+
RenamedFields.should_not have_field('rgt', :type => Integer)
|
147
|
+
end
|
148
|
+
|
149
|
+
it "has a parent field" do
|
150
|
+
Node.should have_field('parent_id', :type => String)
|
151
|
+
RenamedFields.should have_field('mother_id', :type => String)
|
152
|
+
RenamedFields.should_not have_field('parent_id', :type => String)
|
153
|
+
end
|
154
|
+
|
155
|
+
it "does not have a number field" do
|
156
|
+
Node.should_not have_field('number', :type => String)
|
157
|
+
end
|
158
|
+
|
159
|
+
it "has a default left field name" do
|
160
|
+
Node.acts_as_nested_set_options[:left_field].should == 'lft'
|
161
|
+
end
|
162
|
+
|
163
|
+
it "has a default right field name" do
|
164
|
+
Node.acts_as_nested_set_options[:right_field].should == 'rgt'
|
165
|
+
end
|
166
|
+
|
167
|
+
it "has a default parent field name" do
|
168
|
+
Node.acts_as_nested_set_options[:parent_field].should == 'parent_id'
|
169
|
+
end
|
170
|
+
|
171
|
+
it "returns the left field name" do
|
172
|
+
Node.left_field_name.should == 'lft'
|
173
|
+
Node.new.left_field_name.should == 'lft'
|
174
|
+
RenamedFields.left_field_name.should == 'red'
|
175
|
+
RenamedFields.new.left_field_name.should == 'red'
|
176
|
+
end
|
177
|
+
|
178
|
+
it "returns the right field name" do
|
179
|
+
Node.right_field_name.should == 'rgt'
|
180
|
+
Node.new.right_field_name.should == 'rgt'
|
181
|
+
RenamedFields.right_field_name.should == 'black'
|
182
|
+
RenamedFields.new.right_field_name.should == 'black'
|
183
|
+
end
|
184
|
+
|
185
|
+
it "returns the parent field name" do
|
186
|
+
Node.parent_field_name.should == 'parent_id'
|
187
|
+
Node.new.parent_field_name.should == 'parent_id'
|
188
|
+
RenamedFields.parent_field_name.should == 'mother_id'
|
189
|
+
RenamedFields.new.parent_field_name.should == 'mother_id'
|
190
|
+
end
|
191
|
+
|
192
|
+
it "does not allow assigning the left field" do
|
193
|
+
expect { Node.new.lft = 1 }.to raise_error(NameError)
|
194
|
+
expect { RenamedFields.new.red = 1 }.to raise_error(NameError)
|
195
|
+
end
|
196
|
+
|
197
|
+
it "does not allow assigning the right field" do
|
198
|
+
expect { Node.new.rgt = 1 }.to raise_error(NameError)
|
199
|
+
expect { RenamedFields.new.black = 1 }.to raise_error(NameError)
|
200
|
+
end
|
201
|
+
|
202
|
+
|
203
|
+
|
204
|
+
|
205
|
+
# No-Database Calculations
|
206
|
+
|
207
|
+
context "with other nodes" do
|
208
|
+
before(:each) do
|
209
|
+
@nodes = create_clothing_nodes.merge(create_electronics_nodes)
|
210
|
+
end
|
211
|
+
|
212
|
+
it "determines if it is a root node" do
|
213
|
+
@nodes[:mens].should_not be_root
|
214
|
+
@nodes[:clothing].should be_root
|
215
|
+
end
|
216
|
+
|
217
|
+
it "determines if it is a leaf node" do
|
218
|
+
@nodes[:suits].should_not be_leaf
|
219
|
+
@nodes[:jackets].should be_leaf
|
220
|
+
end
|
221
|
+
|
222
|
+
it "determines if it is a child node" do
|
223
|
+
@nodes[:mens].should be_child
|
224
|
+
@nodes[:clothing].should_not be_child
|
225
|
+
end
|
226
|
+
|
227
|
+
it "determines if it is a descendant of another node" do
|
228
|
+
@nodes[:sundress].should be_descendant_of(@nodes[:dresses])
|
229
|
+
@nodes[:dresses].should_not be_descendant_of(@nodes[:sundress])
|
230
|
+
@nodes[:dresses].should_not be_descendant_of(@nodes[:dresses])
|
231
|
+
@nodes[:flash].should_not be_descendant_of(@nodes[:dresses])
|
232
|
+
end
|
233
|
+
|
234
|
+
it "determines if it is a descendant of or equal to another node" do
|
235
|
+
@nodes[:sundress].should be_is_or_is_descendant_of(@nodes[:dresses])
|
236
|
+
@nodes[:sundress].should be_is_or_is_descendant_of(@nodes[:sundress])
|
237
|
+
@nodes[:dresses].should_not be_is_or_is_descendant_of(@nodes[:sundress])
|
238
|
+
@nodes[:flash].should_not be_is_or_is_descendant_of(@nodes[:dresses])
|
239
|
+
@nodes[:skirts].should_not be_is_or_is_descendant_of(@nodes[:radio])
|
240
|
+
end
|
241
|
+
|
242
|
+
it "determines if it is an ancestor of another node" do
|
243
|
+
@nodes[:suits].should be_ancestor_of(@nodes[:jackets])
|
244
|
+
@nodes[:jackets].should_not be_ancestor_of(@nodes[:suits])
|
245
|
+
@nodes[:suits].should_not be_ancestor_of(@nodes[:suits])
|
246
|
+
@nodes[:dresses].should_not be_ancestor_of(@nodes[:flash])
|
247
|
+
end
|
248
|
+
|
249
|
+
it "determines if it is an ancestor of or equal to another node" do
|
250
|
+
@nodes[:suits].should be_is_or_is_ancestor_of(@nodes[:jackets])
|
251
|
+
@nodes[:suits].should be_is_or_is_ancestor_of(@nodes[:suits])
|
252
|
+
@nodes[:jackets].should_not be_is_or_is_ancestor_of(@nodes[:suits])
|
253
|
+
@nodes[:dresses].should_not be_is_or_is_ancestor_of(@nodes[:flash])
|
254
|
+
@nodes[:radio].should_not be_is_or_is_ancestor_of(@nodes[:skirts])
|
255
|
+
end
|
256
|
+
|
257
|
+
end
|
258
|
+
|
259
|
+
|
260
|
+
context "in an empty tree" do
|
261
|
+
|
262
|
+
it "can create a root node" do
|
263
|
+
root = Node.create(:name => 'Root Category')
|
264
|
+
root.should have_nestedset_pos(1, 2)
|
265
|
+
root.depth.should == 0
|
266
|
+
end
|
267
|
+
|
268
|
+
it "can create a child node via children.create" do
|
269
|
+
root = Node.create(:name => 'Root Category')
|
270
|
+
child = root.children.create(:name => 'Child Category')
|
271
|
+
child.should have_nestedset_pos(2, 3)
|
272
|
+
child.parent_id.should == root.id
|
273
|
+
child.depth.should == 1
|
274
|
+
root.reload.should have_nestedset_pos(1, 4)
|
275
|
+
end
|
276
|
+
|
277
|
+
it "can create a child node via children<<" do
|
278
|
+
root = Node.create(:name => 'Root Category')
|
279
|
+
child = Node.create(:name => 'Child Category')
|
280
|
+
root.children << child
|
281
|
+
child.parent_id.should == root.id
|
282
|
+
child.should have_nestedset_pos(2, 3)
|
283
|
+
child.depth.should == 1
|
284
|
+
root.reload.should have_nestedset_pos(1, 4)
|
285
|
+
end
|
286
|
+
|
287
|
+
it "can create a child node with parent pre-assigned" do
|
288
|
+
root = Node.create(:name => 'Root Category')
|
289
|
+
child = Node.create(:name => 'Child Category', :parent => root)
|
290
|
+
child.should have_nestedset_pos(2, 3)
|
291
|
+
child.parent_id.should == root.id
|
292
|
+
child.depth.should == 1
|
293
|
+
root.reload.should have_nestedset_pos(1, 4)
|
294
|
+
end
|
295
|
+
|
296
|
+
it "can create a child node with parent id pre-assigned" do
|
297
|
+
root = Node.create(:name => 'Root Category')
|
298
|
+
child = Node.create(:name => 'Child Category', :parent_id => root.id)
|
299
|
+
child.should have_nestedset_pos(2, 3)
|
300
|
+
child.parent_id.should == root.id
|
301
|
+
child.depth.should == 1
|
302
|
+
root.reload.should have_nestedset_pos(1, 4)
|
303
|
+
end
|
304
|
+
|
305
|
+
it "can change a new node's parent before saving" do
|
306
|
+
root = Node.create(:name => 'Root Category')
|
307
|
+
child = Node.new(:name => 'Child Category')
|
308
|
+
child.parent = root
|
309
|
+
child.save
|
310
|
+
child.should have_nestedset_pos(2, 3)
|
311
|
+
child.parent_id.should == root.id
|
312
|
+
child.depth.should == 1
|
313
|
+
root.reload.should have_nestedset_pos(1, 4)
|
314
|
+
end
|
315
|
+
|
316
|
+
it "can change a new node's parent id before saving" do
|
317
|
+
root = Node.create(:name => 'Root Category')
|
318
|
+
child = Node.new(:name => 'Child Category')
|
319
|
+
child.parent_id = root.id
|
320
|
+
child.save
|
321
|
+
child.should have_nestedset_pos(2, 3)
|
322
|
+
child.parent_id.should == root.id
|
323
|
+
child.depth.should == 1
|
324
|
+
root.reload.should have_nestedset_pos(1, 4)
|
325
|
+
end
|
326
|
+
|
327
|
+
end
|
328
|
+
|
329
|
+
|
330
|
+
context "in a tree" do
|
331
|
+
|
332
|
+
before(:each) do
|
333
|
+
@nodes = persist_nodes(create_clothing_nodes.merge(create_electronics_nodes))
|
334
|
+
end
|
335
|
+
|
336
|
+
|
337
|
+
# Scopes
|
338
|
+
|
339
|
+
it "fetches all root nodes" do
|
340
|
+
Node.roots.should have(2).entries
|
341
|
+
end
|
342
|
+
|
343
|
+
it "fetches all leaf nodes in order" do
|
344
|
+
Node.leaves.where(:root_id=>1).map {|e| e.name}.should == %w[Slacks Jackets Gowns Sun\ Dresses Skirts Blouses]
|
345
|
+
end
|
346
|
+
|
347
|
+
it "fetches all nodes with a given depth in order" do
|
348
|
+
Node.with_depth(1).where(:root_id=>1).map {|e| e.name}.should == %w[Men's Women's]
|
349
|
+
end
|
350
|
+
|
351
|
+
|
352
|
+
# Queries
|
353
|
+
|
354
|
+
it "fetches descendants of multiple parents" do
|
355
|
+
parents = Node.any_in(:name => %w[Men's Dresses])
|
356
|
+
Node.where(:root_id=>1).descendants_of(parents).should have(5).entries
|
357
|
+
end
|
358
|
+
|
359
|
+
it "fetches self and ancestors in order" do
|
360
|
+
@nodes[:dresses].self_and_ancestors.map {|e| e.name}.should == %w[Clothing Women's Dresses]
|
361
|
+
end
|
362
|
+
|
363
|
+
it "fetches ancestors in order" do
|
364
|
+
@nodes[:dresses].ancestors.map {|e| e.name}.should == %w[Clothing Women's]
|
365
|
+
end
|
366
|
+
|
367
|
+
it "fetches its root" do
|
368
|
+
@nodes[:dresses].root.name.should == 'Clothing'
|
369
|
+
end
|
370
|
+
|
371
|
+
it "fetches self and siblings in order" do
|
372
|
+
@nodes[:skirts].self_and_siblings.map {|e| e.name}.should == %w[Dresses Skirts Blouses]
|
373
|
+
end
|
374
|
+
|
375
|
+
it "fetches siblings in order" do
|
376
|
+
@nodes[:skirts].siblings.map {|e| e.name}.should == %w[Dresses Blouses]
|
377
|
+
end
|
378
|
+
|
379
|
+
it "fetches leaves in order" do
|
380
|
+
@nodes[:womens].leaves.map {|e| e.name}.should == %w[Gowns Sun\ Dresses Skirts Blouses]
|
381
|
+
end
|
382
|
+
|
383
|
+
it "fetches its current level" do
|
384
|
+
@nodes[:suits].level.should == 2
|
385
|
+
end
|
386
|
+
|
387
|
+
it "fetches self and descendants in order" do
|
388
|
+
@nodes[:womens].self_and_descendants.map {|e| e.name}.should == %w[Women's Dresses Gowns Sun\ Dresses Skirts Blouses]
|
389
|
+
end
|
390
|
+
|
391
|
+
it "fetches descendants in order" do
|
392
|
+
@nodes[:womens].descendants.map {|e| e.name}.should == %w[Dresses Gowns Sun\ Dresses Skirts Blouses]
|
393
|
+
end
|
394
|
+
|
395
|
+
it "fetches its first sibling to the left" do
|
396
|
+
@nodes[:skirts].left_sibling.name.should == 'Dresses'
|
397
|
+
@nodes[:slacks].left_sibling.should == nil
|
398
|
+
end
|
399
|
+
|
400
|
+
it "fetches its first sibling to the right" do
|
401
|
+
@nodes[:skirts].right_sibling.name.should == 'Blouses'
|
402
|
+
@nodes[:jackets].right_sibling.should == nil
|
403
|
+
end
|
404
|
+
|
405
|
+
it "can detect if roots are valid" do
|
406
|
+
Node.should be_all_roots_valid
|
407
|
+
|
408
|
+
persist_nodes(Node.new(:name => 'Test').test_set_attributes(:root_id => 1, :lft => 20, :rgt => 30, :parent_id=>nil))
|
409
|
+
Node.should_not be_all_roots_valid
|
410
|
+
end
|
411
|
+
|
412
|
+
it "can detect if left and rights are valid" do
|
413
|
+
Node.should be_left_and_rights_valid
|
414
|
+
|
415
|
+
# left > right
|
416
|
+
n = Node.new(:name => 'Test').test_set_attributes(:root_id => 1, :lft => 6, :rgt => 5, :parent_id=>@nodes[:suits].id)
|
417
|
+
persist_nodes(n)
|
418
|
+
Node.should_not be_left_and_rights_valid
|
419
|
+
|
420
|
+
# left == right
|
421
|
+
persist_nodes(n.test_set_attributes(:rgt => 6))
|
422
|
+
Node.should_not be_left_and_rights_valid
|
423
|
+
|
424
|
+
# Overlaps parent
|
425
|
+
persist_nodes(n.test_set_attributes(:rgt => 8))
|
426
|
+
Node.should_not be_left_and_rights_valid
|
427
|
+
end
|
428
|
+
|
429
|
+
it "can detect duplicate left and right values" do
|
430
|
+
Node.should be_no_duplicates_for_fields
|
431
|
+
|
432
|
+
n = Node.new(:name => 'Test').test_set_attributes(:root_id => 1, :lft => 6, :rgt => 25, :parent_id=>@nodes[:suits].id)
|
433
|
+
persist_nodes(n)
|
434
|
+
Node.should_not be_no_duplicates_for_fields
|
435
|
+
|
436
|
+
persist_nodes(n.test_set_attributes(:lft => 5, :rgt => 7, :parent_id=>@nodes[:suits].id))
|
437
|
+
Node.should_not be_no_duplicates_for_fields
|
438
|
+
end
|
439
|
+
|
440
|
+
|
441
|
+
# Moves
|
442
|
+
|
443
|
+
it "cannot move a new node" do
|
444
|
+
n = Node.new(:name => 'Test', :root_id => 1)
|
445
|
+
expect {
|
446
|
+
n.move_to_right_of(Node.where(:name => 'Jackets').first)
|
447
|
+
}.to raise_error(Mongoid::Errors::MongoidError, /move.*new node/)
|
448
|
+
end
|
449
|
+
|
450
|
+
it "cannot move a node inside its tree" do
|
451
|
+
n = Node.where(:name => 'Men\'s').first
|
452
|
+
expect {
|
453
|
+
n.move_to_right_of(Node.where(:name => 'Suits').first)
|
454
|
+
}.to raise_error(Mongoid::Errors::MongoidError, /possible/)
|
455
|
+
end
|
456
|
+
|
457
|
+
it "cannot move a node to a non-existent target" do
|
458
|
+
@nodes[:mens].parent_id = BSON::ObjectId.new
|
459
|
+
expect {
|
460
|
+
@nodes[:mens].save
|
461
|
+
}.to raise_error(Mongoid::Errors::MongoidError, /possible.*(exist|found)/)
|
462
|
+
end
|
463
|
+
|
464
|
+
it "adds newly created nodes to the end of the tree" do
|
465
|
+
Node.create(:name => 'Vests', :root_id => 1).should have_nestedset_pos(23, 24)
|
466
|
+
|
467
|
+
n = Node.new(:name => 'Test', :root_id => 1)
|
468
|
+
n.save
|
469
|
+
n.should have_nestedset_pos(25, 26)
|
470
|
+
end
|
471
|
+
|
472
|
+
it "can move left" do
|
473
|
+
@nodes[:jackets].move_left
|
474
|
+
@nodes[:jackets] .should have_nestedset_pos( 4, 5)
|
475
|
+
@nodes[:slacks].reload.should have_nestedset_pos( 6, 7)
|
476
|
+
@nodes[:suits] .reload.should have_nestedset_pos( 3, 8)
|
477
|
+
@nodes[:jackets].depth.should == 3
|
478
|
+
end
|
479
|
+
|
480
|
+
it "can move right" do
|
481
|
+
@nodes[:slacks].move_right
|
482
|
+
@nodes[:slacks] .should have_nestedset_pos( 6, 7)
|
483
|
+
@nodes[:jackets].reload.should have_nestedset_pos( 4, 5)
|
484
|
+
@nodes[:suits] .reload.should have_nestedset_pos( 3, 8)
|
485
|
+
@nodes[:slacks].depth.should == 3
|
486
|
+
end
|
487
|
+
|
488
|
+
it "can move left of another node" do
|
489
|
+
@nodes[:slacks].move_to_left_of(@nodes[:skirts])
|
490
|
+
@nodes[:slacks] .should have_nestedset_pos(15, 16)
|
491
|
+
@nodes[:skirts] .should have_nestedset_pos(17, 18)
|
492
|
+
@nodes[:skirts] .reload.should have_nestedset_pos(17, 18)
|
493
|
+
@nodes[:dresses].reload.should have_nestedset_pos( 9, 14)
|
494
|
+
@nodes[:womens] .reload.should have_nestedset_pos( 8, 21)
|
495
|
+
@nodes[:slacks].depth.should == 2
|
496
|
+
end
|
497
|
+
|
498
|
+
it "can move right of another node" do
|
499
|
+
@nodes[:slacks].move_to_right_of(@nodes[:skirts])
|
500
|
+
@nodes[:slacks] .should have_nestedset_pos(17, 18)
|
501
|
+
@nodes[:skirts] .should have_nestedset_pos(15, 16)
|
502
|
+
@nodes[:skirts] .reload.should have_nestedset_pos(15, 16)
|
503
|
+
@nodes[:blouses].reload.should have_nestedset_pos(19, 20)
|
504
|
+
@nodes[:womens] .reload.should have_nestedset_pos( 8, 21)
|
505
|
+
@nodes[:slacks].depth.should == 2
|
506
|
+
end
|
507
|
+
|
508
|
+
it "can move as a child of another node" do
|
509
|
+
@nodes[:slacks].move_to_child_of(@nodes[:dresses])
|
510
|
+
@nodes[:slacks] .should have_nestedset_pos(14, 15)
|
511
|
+
@nodes[:dresses] .should have_nestedset_pos( 9, 16)
|
512
|
+
@nodes[:dresses].reload.should have_nestedset_pos( 9, 16)
|
513
|
+
@nodes[:gowns] .reload.should have_nestedset_pos(10, 11)
|
514
|
+
@nodes[:mens] .reload.should have_nestedset_pos( 2, 7)
|
515
|
+
@nodes[:slacks].depth.should == 3
|
516
|
+
end
|
517
|
+
|
518
|
+
it "can change it's parent id" do
|
519
|
+
@nodes[:slacks].parent_id = @nodes[:dresses].id
|
520
|
+
@nodes[:slacks].save
|
521
|
+
@nodes[:slacks] .reload.should have_nestedset_pos(14, 15)
|
522
|
+
@nodes[:dresses].reload.should have_nestedset_pos( 9, 16)
|
523
|
+
@nodes[:gowns] .reload.should have_nestedset_pos(10, 11)
|
524
|
+
@nodes[:mens] .reload.should have_nestedset_pos( 2, 7)
|
525
|
+
@nodes[:slacks].depth.should == 3
|
526
|
+
end
|
527
|
+
|
528
|
+
it "can move to the root position" do
|
529
|
+
@nodes[:suits].move_to_root
|
530
|
+
@nodes[:suits] .should be_root
|
531
|
+
@nodes[:suits] .should have_nestedset_pos( 1, 6)
|
532
|
+
@nodes[:jackets] .reload.should have_nestedset_pos( 4, 5)
|
533
|
+
@nodes[:clothing].reload.should have_nestedset_pos( 7, 22)
|
534
|
+
@nodes[:mens] .reload.should have_nestedset_pos( 8, 9)
|
535
|
+
@nodes[:womens] .reload.should have_nestedset_pos(10, 21)
|
536
|
+
end
|
537
|
+
|
538
|
+
it "can move to the left of root" do
|
539
|
+
@nodes[:suits].move_to_left_of(@nodes[:clothing])
|
540
|
+
@nodes[:suits] .should be_root
|
541
|
+
@nodes[:suits] .should have_nestedset_pos( 1, 6)
|
542
|
+
@nodes[:jackets] .reload.should have_nestedset_pos( 4, 5)
|
543
|
+
@nodes[:clothing].reload.should have_nestedset_pos( 7, 22)
|
544
|
+
@nodes[:mens] .reload.should have_nestedset_pos( 8, 9)
|
545
|
+
@nodes[:womens] .reload.should have_nestedset_pos(10, 21)
|
546
|
+
end
|
547
|
+
|
548
|
+
it "can move to the right of root" do
|
549
|
+
@nodes[:suits].move_to_right_of(@nodes[:clothing])
|
550
|
+
@nodes[:suits] .should be_root
|
551
|
+
@nodes[:suits] .should have_nestedset_pos(17, 22)
|
552
|
+
@nodes[:jackets] .reload.should have_nestedset_pos(20, 21)
|
553
|
+
@nodes[:clothing].reload.should have_nestedset_pos( 1, 16)
|
554
|
+
@nodes[:mens] .reload.should have_nestedset_pos( 2, 3)
|
555
|
+
@nodes[:womens] .reload.should have_nestedset_pos( 4, 15)
|
556
|
+
end
|
557
|
+
|
558
|
+
it "can move node with children" do
|
559
|
+
@nodes[:suits].move_to_child_of(@nodes[:dresses])
|
560
|
+
@nodes[:suits] .should have_nestedset_pos(10, 15)
|
561
|
+
@nodes[:dresses] .should have_nestedset_pos( 5, 16)
|
562
|
+
@nodes[:mens] .reload.should have_nestedset_pos( 2, 3)
|
563
|
+
@nodes[:womens] .reload.should have_nestedset_pos( 4, 21)
|
564
|
+
@nodes[:sundress].reload.should have_nestedset_pos( 8, 9)
|
565
|
+
@nodes[:jackets] .reload.should have_nestedset_pos(13, 14)
|
566
|
+
@nodes[:suits].depth.should == 3
|
567
|
+
@nodes[:jackets].depth.should == 4
|
568
|
+
end
|
569
|
+
|
570
|
+
context "with dependent=delete_all" do
|
571
|
+
it "deletes descendants when destroyed" do
|
572
|
+
@nodes[:mens].destroy
|
573
|
+
@nodes[:clothing].reload.should have_nestedset_pos( 1, 14)
|
574
|
+
@nodes[:womens] .reload.should have_nestedset_pos( 2, 13)
|
575
|
+
Node.where(:name => 'Men\'s').count.should == 0
|
576
|
+
Node.where(:name => 'Suits').count.should == 0
|
577
|
+
Node.where(:name => 'Slacks').count.should == 0
|
578
|
+
end
|
579
|
+
end
|
580
|
+
|
581
|
+
context "with dependent=destroy" do
|
582
|
+
it "deletes descendants when destroyed" do
|
583
|
+
Node.test_set_dependent_option :destroy
|
584
|
+
@nodes[:mens].destroy
|
585
|
+
@nodes[:clothing].reload.should have_nestedset_pos( 1, 14)
|
586
|
+
@nodes[:womens] .reload.should have_nestedset_pos( 2, 13)
|
587
|
+
Node.where(:name => 'Men\'s').count.should == 0
|
588
|
+
Node.where(:name => 'Suits').count.should == 0
|
589
|
+
Node.where(:name => 'Slacks').count.should == 0
|
590
|
+
end
|
591
|
+
end
|
592
|
+
|
593
|
+
end
|
594
|
+
|
595
|
+
|
596
|
+
context "in an adjaceny list tree" do
|
597
|
+
before(:each) do
|
598
|
+
@nodes = create_clothing_nodes(Node)
|
599
|
+
@nodes.each_value { |node| node.test_set_attributes(:rgt => nil) }
|
600
|
+
persist_nodes(@nodes)
|
601
|
+
end
|
602
|
+
|
603
|
+
it "can rebuild nested set properties" do
|
604
|
+
Node.rebuild!
|
605
|
+
root = Node.root
|
606
|
+
root.should be_a(Node)
|
607
|
+
root.name.should == 'Clothing'
|
608
|
+
|
609
|
+
@nodes[:clothing].reload.should have_nestedset_pos( 1, 22)
|
610
|
+
@nodes[:mens] .reload.should have_nestedset_pos( 2, 9)
|
611
|
+
@nodes[:womens] .reload.should have_nestedset_pos(10, 21)
|
612
|
+
@nodes[:suits] .reload.should have_nestedset_pos( 3, 8)
|
613
|
+
@nodes[:skirts] .reload.should have_nestedset_pos(17, 18)
|
614
|
+
end
|
615
|
+
|
616
|
+
end
|
617
|
+
end
|
618
|
+
|
619
|
+
|
620
|
+
context "that acts as a nested set with inheritance" do
|
621
|
+
def create_shape_nodes
|
622
|
+
nodes = {}
|
623
|
+
nodes[:root] = SquareNode.new.test_set_attributes('name' => 'Root', 'lft' => 1, 'rgt' => 12, 'depth' => 0, 'parent_id' => nil)
|
624
|
+
nodes[:c1] = SquareNode.new.test_set_attributes('name' => '1', 'lft' => 2, 'rgt' => 7, 'depth' => 1, 'parent_id' => nodes[:root].id)
|
625
|
+
nodes[:c2] = SquareNode.new.test_set_attributes('name' => '2', 'lft' => 8, 'rgt' => 9, 'depth' => 1, 'parent_id' => nodes[:root].id)
|
626
|
+
nodes[:c3] = CircleNode.new.test_set_attributes('name' => '3', 'lft' => 10, 'rgt' => 11, 'depth' => 1, 'parent_id' => nodes[:root].id)
|
627
|
+
nodes[:c11] = CircleNode.new.test_set_attributes('name' => '1.1', 'lft' => 3, 'rgt' => 4, 'depth' => 2, 'parent_id' => nodes[:c1].id)
|
628
|
+
nodes[:c12] = SquareNode.new.test_set_attributes('name' => '1.2', 'lft' => 5, 'rgt' => 6, 'depth' => 2, 'parent_id' => nodes[:c1].id)
|
629
|
+
nodes
|
630
|
+
end
|
631
|
+
|
632
|
+
context "in a tree" do
|
633
|
+
before(:each) do
|
634
|
+
@nodes = create_shape_nodes
|
635
|
+
persist_nodes(@nodes)
|
636
|
+
end
|
637
|
+
|
638
|
+
it "fetches self and descendants in order" do
|
639
|
+
@nodes[:root].self_and_descendants.map {|e| e.name}.should == %w[Root 1 1.1 1.2 2 3]
|
640
|
+
end
|
641
|
+
end
|
642
|
+
end
|
643
|
+
|
644
|
+
|
645
|
+
context "that acts as a nested set with outline numbering" do
|
646
|
+
|
647
|
+
it "includes outline number methods" do
|
648
|
+
NumberingNode.should respond_to('each_with_outline_number')
|
649
|
+
end
|
650
|
+
|
651
|
+
it "does not have a number field" do
|
652
|
+
NumberingNode.should have_field('number', :type => String)
|
653
|
+
end
|
654
|
+
|
655
|
+
context "in a tree" do
|
656
|
+
before(:each) do
|
657
|
+
@nodes = persist_nodes(create_clothing_nodes(NumberingNode).merge(create_electronics_nodes(NumberingNode)))
|
658
|
+
end
|
659
|
+
|
660
|
+
it "sets the number for new child nodes" do
|
661
|
+
n = NumberingNode.create(:name => 'Vests', :root_id => 1, :parent_id => @nodes[:suits].id)
|
662
|
+
n.number.should == '1.1.3'
|
663
|
+
end
|
664
|
+
|
665
|
+
it "updates the number for nodes moved within the same parent" do
|
666
|
+
@nodes[:slacks].move_right
|
667
|
+
@nodes[:slacks] .number.should == '1.1.2'
|
668
|
+
@nodes[:jackets].reload.number.should == '1.1.1'
|
669
|
+
end
|
670
|
+
|
671
|
+
it "updates the number for nodes moved to a new parent" do
|
672
|
+
@nodes[:slacks].move_to_child_of(@nodes[:dresses])
|
673
|
+
@nodes[:slacks].number.should == '2.1.3'
|
674
|
+
end
|
675
|
+
|
676
|
+
it "updates the number for nodes moved to root" do
|
677
|
+
@nodes[:suits].move_to_root
|
678
|
+
@nodes[:suits] .number.should be_nil
|
679
|
+
@nodes[:suits] .reload.number.should be_nil
|
680
|
+
@nodes[:jackets].reload.number.should == '2'
|
681
|
+
@nodes[:skirts] .reload.number.should == '2.2'
|
682
|
+
end
|
683
|
+
|
684
|
+
it "updates the number for old siblings of moved nodes" do
|
685
|
+
@nodes[:slacks].move_to_child_of(@nodes[:dresses])
|
686
|
+
@nodes[:jackets].reload.number.should == '1.1.1'
|
687
|
+
end
|
688
|
+
|
689
|
+
it "updates the number for new siblings of moved nodes" do
|
690
|
+
@nodes[:slacks].move_to_left_of(@nodes[:gowns])
|
691
|
+
@nodes[:gowns].reload.number.should == '2.1.2'
|
692
|
+
end
|
693
|
+
|
694
|
+
it "updates the number for descendants of moved nodes" do
|
695
|
+
@nodes[:suits].move_to_child_of(@nodes[:dresses])
|
696
|
+
@nodes[:suits] .number.should == '2.1.3'
|
697
|
+
@nodes[:jackets].reload.number.should == '2.1.3.2'
|
698
|
+
end
|
699
|
+
|
700
|
+
it "updates the number for descendants of old siblings of moved nodes" do
|
701
|
+
@nodes[:mens].move_to_child_of(@nodes[:womens])
|
702
|
+
@nodes[:womens] .reload.number.should == '1'
|
703
|
+
@nodes[:dresses].reload.number.should == '1.1'
|
704
|
+
end
|
705
|
+
|
706
|
+
it "updates the number for descendants of new siblings of moved nodes" do
|
707
|
+
@nodes[:dresses].move_to_left_of(@nodes[:suits])
|
708
|
+
@nodes[:jackets].reload.number == '1.2.2'
|
709
|
+
end
|
710
|
+
|
711
|
+
it "updates the number for a single node" do
|
712
|
+
@nodes[:suits].update_attributes(NumberingNode.outline_number_field_name => '3.1')
|
713
|
+
@nodes[:suits].number.should == '3.1'
|
714
|
+
@nodes[:suits].update_outline_number
|
715
|
+
@nodes[:suits].number.should == '1.1'
|
716
|
+
end
|
717
|
+
|
718
|
+
|
719
|
+
end
|
720
|
+
|
721
|
+
end
|
722
|
+
|
723
|
+
end
|