mongo_mapper_tree 0.0.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.
- data/LICENSE +20 -0
- data/README.rdoc +44 -0
- data/lib/locale/en.yml +5 -0
- data/lib/mongo_mapper/plugins/tree.rb +160 -0
- data/lib/mongo_mapper_tree.rb +4 -0
- data/lib/version.rb +4 -0
- data/test/helper.rb +41 -0
- data/test/models/category.rb +6 -0
- data/test/models/ordered_category.rb +9 -0
- data/test/models/shapes.rb +10 -0
- data/test/test_order.rb +26 -0
- data/test/test_search_class.rb +90 -0
- data/test/test_tree.rb +157 -0
- metadata +102 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Joel Junström, Oktavilla
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
= mongo_mapper_tree
|
2
|
+
|
3
|
+
This is an modernized version of mongo_mapper_acts_as_tree (https://github.com/ramdiv/mongo_mapper_acts_as_tree)
|
4
|
+
In it's essence it's an implementation of a tree structure for MongoMapper, think acts as tree but for mongodb.
|
5
|
+
|
6
|
+
|
7
|
+
== Installation
|
8
|
+
|
9
|
+
Install as gem
|
10
|
+
|
11
|
+
gem install mongo_mapper_tree
|
12
|
+
|
13
|
+
== Usage
|
14
|
+
|
15
|
+
Enable the tree functionality by declaring acts_as_tree on your model
|
16
|
+
|
17
|
+
class Category
|
18
|
+
include MongoMapper::Document
|
19
|
+
plugin MongoMapper::Plugins::Tree
|
20
|
+
|
21
|
+
key :name, String
|
22
|
+
end
|
23
|
+
|
24
|
+
This adds class_attributes called parent_id_field, path_field, depth_field, tree_order and tree_search_class.
|
25
|
+
|
26
|
+
parent_id_field, path_field and depth_field overrides the default field names
|
27
|
+
tree_order controls the order (format :field_name.[asc|desc])
|
28
|
+
tree_search_class expects a Class that is a MongoMapper::Document to be used for search
|
29
|
+
|
30
|
+
Check test_tree.rb and test_search_class.rb for examples.
|
31
|
+
|
32
|
+
== Note on Patches/Pull Requests
|
33
|
+
|
34
|
+
* Fork the project.
|
35
|
+
* Make your feature addition or bug fix.
|
36
|
+
* Add tests for it. This is important so I don't break it in a
|
37
|
+
future version unintentionally.
|
38
|
+
* Commit, do not mess with rakefile, version, or history.
|
39
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
40
|
+
* Send me a pull request. Bonus points for topic branches.
|
41
|
+
|
42
|
+
== Copyright
|
43
|
+
|
44
|
+
Copyright (c) 2011 Joel Junström. See LICENSE for details.
|
data/lib/locale/en.yml
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
module MongoMapper
|
3
|
+
module Plugins
|
4
|
+
module Tree
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def roots
|
9
|
+
self.where(parent_id_field => nil).sort(tree_order).all
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module InstanceMethods
|
14
|
+
def tree_search_class
|
15
|
+
self.class.tree_search_class
|
16
|
+
end
|
17
|
+
|
18
|
+
def will_save_tree
|
19
|
+
if parent && self.descendants.include?(parent)
|
20
|
+
errors.add(:base, :cyclic)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def fix_position(opts = {})
|
25
|
+
if parent.nil?
|
26
|
+
self[parent_id_field] = nil
|
27
|
+
self[path_field] = []
|
28
|
+
self[depth_field] = 0
|
29
|
+
elsif !!opts[:force] || self.changes.include?(parent_id_field)
|
30
|
+
@_will_move = true
|
31
|
+
self[path_field] = parent[path_field] + [parent._id]
|
32
|
+
self[depth_field] = parent[depth_field] + 1
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def fix_position!
|
37
|
+
fix_position(:force => true)
|
38
|
+
save
|
39
|
+
end
|
40
|
+
|
41
|
+
def root?
|
42
|
+
self[parent_id_field].nil?
|
43
|
+
end
|
44
|
+
|
45
|
+
def root
|
46
|
+
self[path_field].first.nil? ? self : tree_search_class.find(self[path_field].first)
|
47
|
+
end
|
48
|
+
|
49
|
+
def ancestors
|
50
|
+
return [] if root?
|
51
|
+
tree_search_class.find(self[path_field])
|
52
|
+
end
|
53
|
+
|
54
|
+
def self_and_ancestors
|
55
|
+
ancestors << self
|
56
|
+
end
|
57
|
+
|
58
|
+
def siblings
|
59
|
+
tree_search_class.where({
|
60
|
+
:_id => { "$ne" => self._id },
|
61
|
+
parent_id_field => self[parent_id_field]
|
62
|
+
}).sort(tree_order).all
|
63
|
+
end
|
64
|
+
|
65
|
+
def self_and_siblings
|
66
|
+
tree_search_class.where({
|
67
|
+
parent_id_field => self[parent_id_field]
|
68
|
+
}).sort(tree_order).all
|
69
|
+
end
|
70
|
+
|
71
|
+
def children
|
72
|
+
tree_search_class.where(parent_id_field => self._id).sort(tree_order).all
|
73
|
+
end
|
74
|
+
|
75
|
+
def descendants
|
76
|
+
return [] if new_record?
|
77
|
+
tree_search_class.where(path_field => self._id).sort(tree_order).all
|
78
|
+
end
|
79
|
+
|
80
|
+
def self_and_descendants
|
81
|
+
[self] + self.descendants
|
82
|
+
end
|
83
|
+
|
84
|
+
def is_ancestor_of?(other)
|
85
|
+
other[path_field].include?(self._id)
|
86
|
+
end
|
87
|
+
|
88
|
+
def is_or_is_ancestor_of?(other)
|
89
|
+
(other == self) or is_ancestor_of?(other)
|
90
|
+
end
|
91
|
+
|
92
|
+
def is_descendant_of?(other)
|
93
|
+
self[path_field].include?(other._id)
|
94
|
+
end
|
95
|
+
|
96
|
+
def is_or_is_descendant_of?(other)
|
97
|
+
(other == self) or is_descendant_of?(other)
|
98
|
+
end
|
99
|
+
|
100
|
+
def is_sibling_of?(other)
|
101
|
+
(other != self) and (other[parent_id_field] == self[parent_id_field])
|
102
|
+
end
|
103
|
+
|
104
|
+
def is_or_is_sibling_of?(other)
|
105
|
+
(other == self) or is_sibling_of?(other)
|
106
|
+
end
|
107
|
+
|
108
|
+
def move_children
|
109
|
+
if @_will_move
|
110
|
+
@_will_move = false
|
111
|
+
self.children.each do |child|
|
112
|
+
child.fix_position!
|
113
|
+
end
|
114
|
+
@_will_move = true
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def destroy_descendants
|
119
|
+
tree_search_class.destroy(self.descendants.map(&:_id))
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
included do
|
124
|
+
# Tree search class will be used as the base from which to
|
125
|
+
# find tree objects. This is handy should you have a tree of objects that are of different types, but
|
126
|
+
# might be related through single table inheritance.
|
127
|
+
#
|
128
|
+
# self.tree_search_class = Shape
|
129
|
+
#
|
130
|
+
# In the above example, you could have a working tree ofShape, Circle and Square types (assuming
|
131
|
+
# Circle and Square were subclasses of Shape). If you want to do the same thing and you don't provide
|
132
|
+
# tree_search_class, nesting mixed types will not work.
|
133
|
+
class_attribute :tree_search_class
|
134
|
+
self.tree_search_class ||= self
|
135
|
+
|
136
|
+
class_attribute :parent_id_field
|
137
|
+
self.parent_id_field ||= "parent_id"
|
138
|
+
|
139
|
+
class_attribute :path_field
|
140
|
+
self.path_field ||= "path"
|
141
|
+
|
142
|
+
class_attribute :depth_field
|
143
|
+
self.depth_field ||= "depth"
|
144
|
+
|
145
|
+
class_attribute :tree_order
|
146
|
+
|
147
|
+
key parent_id_field, ObjectId
|
148
|
+
key path_field, Array, :default => [], :index => true
|
149
|
+
key depth_field, Integer, :default => 0
|
150
|
+
|
151
|
+
belongs_to :parent, :class => tree_search_class
|
152
|
+
|
153
|
+
validate :will_save_tree
|
154
|
+
after_validation :fix_position
|
155
|
+
after_save :move_children
|
156
|
+
before_destroy :destroy_descendants
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
data/lib/version.rb
ADDED
data/test/helper.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'shoulda'
|
4
|
+
require 'database_cleaner'
|
5
|
+
|
6
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
7
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
8
|
+
require 'mongo_mapper_tree'
|
9
|
+
|
10
|
+
MongoMapper.database = "mongo_mapper_tree-test"
|
11
|
+
|
12
|
+
Dir["#{File.dirname(__FILE__)}/models/*.rb"].each {|file| require file}
|
13
|
+
|
14
|
+
DatabaseCleaner.strategy = :truncation
|
15
|
+
|
16
|
+
class Test::Unit::TestCase
|
17
|
+
# Drop all collections after each test case.
|
18
|
+
def setup
|
19
|
+
DatabaseCleaner.start
|
20
|
+
end
|
21
|
+
|
22
|
+
def teardown
|
23
|
+
DatabaseCleaner.clean
|
24
|
+
end
|
25
|
+
|
26
|
+
# Make sure that each test case has a teardown
|
27
|
+
# method to clear the db after each test.
|
28
|
+
def inherited(base)
|
29
|
+
base.define_method setup do
|
30
|
+
super
|
31
|
+
end
|
32
|
+
|
33
|
+
base.define_method teardown do
|
34
|
+
super
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def eql_arrays?(first, second)
|
39
|
+
first.collect(&:_id).to_set == second.collect(&:_id).to_set
|
40
|
+
end
|
41
|
+
end
|
data/test/test_order.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'helper'
|
2
|
+
class TestMongomapperActsAsTree < Test::Unit::TestCase
|
3
|
+
context "Ordered tree" do
|
4
|
+
setup do
|
5
|
+
@root_1 = OrderedCategory.create(:name => "Root 1", :value => 2)
|
6
|
+
@child_1 = OrderedCategory.create(:name => "Child 1", :parent => @root_1, :value => 1)
|
7
|
+
@child_2 = OrderedCategory.create(:name => "Child 2", :parent => @root_1, :value => 9)
|
8
|
+
@child_2_1 = OrderedCategory.create(:name => "Child 2.1", :parent => @child_2, :value => 2)
|
9
|
+
@child_3 = OrderedCategory.create(:name => "Child 3", :parent => @root_1, :value => 5)
|
10
|
+
@root_2 = OrderedCategory.create(:name => "Root 2", :value => 1)
|
11
|
+
end
|
12
|
+
|
13
|
+
should "be in order" do
|
14
|
+
assert_equal OrderedCategory.roots, [@root_2, @root_1]
|
15
|
+
|
16
|
+
assert_equal @root_1.children, [@child_1, @child_3, @child_2]
|
17
|
+
|
18
|
+
assert_equal @root_1.descendants, [@child_1, @child_2_1, @child_3, @child_2]
|
19
|
+
assert_equal @root_1.self_and_descendants, [@root_1, @child_1, @child_2_1, @child_3, @child_2]
|
20
|
+
|
21
|
+
assert_equal @child_2.siblings, [@child_1, @child_3]
|
22
|
+
assert_equal @child_2.self_and_siblings, [@child_1, @child_3, @child_2]
|
23
|
+
assert_equal @root_1.self_and_siblings, [@root_2, @root_1]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestSearchScope < Test::Unit::TestCase
|
4
|
+
context "Simple, mixed type tree" do
|
5
|
+
setup do
|
6
|
+
shape = Shape.create(:name => "Root")
|
7
|
+
Circle.create(:name => "Circle", :parent => shape)
|
8
|
+
Square.create(:name => "Square", :parent => shape)
|
9
|
+
end
|
10
|
+
|
11
|
+
setup do
|
12
|
+
# We are loading from the database here because this process proves the point. If we never did it this
|
13
|
+
# way, there would be no reason to change the code.
|
14
|
+
@shape, @circle, @square = Shape.first, Circle.first, Square.first
|
15
|
+
end
|
16
|
+
|
17
|
+
should "return circle and square as children of shape" do
|
18
|
+
assert_equal [@circle, @square], @shape.children
|
19
|
+
end
|
20
|
+
|
21
|
+
should("return shape as parent of circle") { assert_equal @shape, @circle.parent }
|
22
|
+
should("return shape as parent of square") { assert_equal @shape, @square.parent }
|
23
|
+
|
24
|
+
should("return square as exclusive sibling of circle") { assert_equal [@square], @circle.siblings }
|
25
|
+
should "return self and square as inclusive siblings of circle" do
|
26
|
+
assert_equal [@circle, @square], @circle.self_and_siblings
|
27
|
+
end
|
28
|
+
|
29
|
+
should("return circle as exclusive sibling of square") { assert_equal [@circle], @square.siblings }
|
30
|
+
should "return self and circle as inclusive siblings of square" do
|
31
|
+
assert_equal [@circle, @square], @square.self_and_siblings
|
32
|
+
end
|
33
|
+
|
34
|
+
should "return circle and square as exclusive descendants of shape" do
|
35
|
+
assert_equal [@circle, @square], @shape.descendants
|
36
|
+
end
|
37
|
+
should "return shape, circle and square as inclusive descendants of shape" do
|
38
|
+
assert_equal [@shape, @circle, @square], @shape.self_and_descendants
|
39
|
+
end
|
40
|
+
|
41
|
+
should("return shape as exclusive ancestor of circle") { assert_equal [@shape], @circle.ancestors }
|
42
|
+
should "return self and shape as inclusive ancestors of circle" do
|
43
|
+
assert_equal [@shape, @circle], @circle.self_and_ancestors
|
44
|
+
end
|
45
|
+
|
46
|
+
should("return shape as exclusive ancestor of square") { assert_equal [@shape], @square.ancestors }
|
47
|
+
should "return self and shape as inclusive ancestors of square" do
|
48
|
+
assert_equal [@shape, @square], @square.self_and_ancestors
|
49
|
+
end
|
50
|
+
|
51
|
+
should("return shape as root of circle") { assert_equal @shape, @square.root }
|
52
|
+
should("return shape as root of square") { assert_equal @shape, @circle.root }
|
53
|
+
end
|
54
|
+
|
55
|
+
context "A tree with mixed types on either side of a branch" do
|
56
|
+
setup do
|
57
|
+
shape = Shape.create(:name => "Root")
|
58
|
+
circle = Circle.create(:name => "Circle", :parent => shape)
|
59
|
+
Square.create(:name => "Square", :parent => circle)
|
60
|
+
end
|
61
|
+
|
62
|
+
setup do
|
63
|
+
@shape, @circle, @square = Shape.first, Circle.first, Square.first
|
64
|
+
end
|
65
|
+
|
66
|
+
should("return circle as child of shape") { assert_equal [@circle], @shape.children }
|
67
|
+
should("return square as child of circle") { assert_equal [@square], @circle.children }
|
68
|
+
should("return circle as parent of square") { assert_equal @circle, @square.parent }
|
69
|
+
should("return shape as parent of circle") { assert_equal @shape, @circle.parent }
|
70
|
+
|
71
|
+
should "return circle and square as descendants of shape" do
|
72
|
+
assert_equal [@circle, @square], @shape.descendants
|
73
|
+
end
|
74
|
+
|
75
|
+
should("return square as descendant of circle") { assert_equal [@square], @circle.descendants }
|
76
|
+
|
77
|
+
should "return shape and circle as ancestors of square" do
|
78
|
+
assert_equal [@shape, @circle], @square.ancestors
|
79
|
+
end
|
80
|
+
|
81
|
+
should("return shape as ancestor of circle") { assert_equal [@shape], @circle.ancestors }
|
82
|
+
|
83
|
+
should "destroy descendants of shape" do
|
84
|
+
@shape.destroy_descendants
|
85
|
+
assert_nil Shape.find(@circle._id)
|
86
|
+
assert_nil Shape.find(@square._id)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end # TestSearchScope
|
data/test/test_tree.rb
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'helper'
|
2
|
+
class TestMongomapperActsAsTree < Test::Unit::TestCase
|
3
|
+
context "Tree" do
|
4
|
+
setup do
|
5
|
+
@root_1 = Category.create(:name => "Root 1")
|
6
|
+
@child_1 = Category.create(:name => "Child 1", :parent => @root_1)
|
7
|
+
@child_2 = Category.create(:name => "Child 2", :parent => @root_1)
|
8
|
+
@child_2_1 = Category.create(:name => "Child 2.1", :parent => @child_2)
|
9
|
+
@child_3 = Category.create(:name => "Child 3", :parent => @root_1)
|
10
|
+
@root_2 = Category.create(:name => "Root 2")
|
11
|
+
end
|
12
|
+
|
13
|
+
should "create node from id " do
|
14
|
+
assert Category.create(:name => "Child 2.2", :parent_id => @root_1.id).parent == @root_1
|
15
|
+
end
|
16
|
+
|
17
|
+
should "have roots" do
|
18
|
+
assert eql_arrays?(Category.roots, [@root_1, @root_2])
|
19
|
+
end
|
20
|
+
|
21
|
+
context "node" do
|
22
|
+
should "have a root" do
|
23
|
+
assert_equal @root_1.root, @root_1
|
24
|
+
assert_not_equal @root_1.root, @root_2.root
|
25
|
+
assert_equal @root_1, @child_2_1.root
|
26
|
+
end
|
27
|
+
|
28
|
+
should "have ancestors" do
|
29
|
+
assert_equal @root_1.ancestors, []
|
30
|
+
assert_equal @child_2_1.ancestors, [@root_1, @child_2]
|
31
|
+
assert_equal @root_1.self_and_ancestors, [@root_1]
|
32
|
+
assert_equal @child_2_1.self_and_ancestors, [@root_1, @child_2, @child_2_1]
|
33
|
+
end
|
34
|
+
|
35
|
+
should "have siblings" do
|
36
|
+
assert eql_arrays?(@root_1.siblings, [@root_2])
|
37
|
+
assert eql_arrays?(@child_2.siblings, [@child_1, @child_3])
|
38
|
+
assert eql_arrays?(@child_2_1.siblings, [])
|
39
|
+
assert eql_arrays?(@root_1.self_and_siblings, [@root_1, @root_2])
|
40
|
+
assert eql_arrays?(@child_2.self_and_siblings, [@child_1, @child_2, @child_3])
|
41
|
+
assert eql_arrays?(@child_2_1.self_and_siblings, [@child_2_1])
|
42
|
+
end
|
43
|
+
|
44
|
+
should "set depth" do
|
45
|
+
assert_equal 0, @root_1.depth
|
46
|
+
assert_equal 1, @child_1.depth
|
47
|
+
assert_equal 2, @child_2_1.depth
|
48
|
+
end
|
49
|
+
|
50
|
+
should "have children" do
|
51
|
+
assert @child_2_1.children.empty?
|
52
|
+
assert eql_arrays?(@root_1.children, [@child_1, @child_2, @child_3])
|
53
|
+
end
|
54
|
+
|
55
|
+
should "have descendants" do
|
56
|
+
assert eql_arrays?(@root_1.descendants, [@child_1, @child_2, @child_3, @child_2_1])
|
57
|
+
assert eql_arrays?(@child_2.descendants, [@child_2_1])
|
58
|
+
assert @child_2_1.descendants.empty?
|
59
|
+
assert eql_arrays?(@root_1.self_and_descendants, [@root_1, @child_1, @child_2, @child_3, @child_2_1])
|
60
|
+
assert eql_arrays?(@child_2.self_and_descendants, [@child_2, @child_2_1])
|
61
|
+
assert eql_arrays?(@child_2_1.self_and_descendants, [@child_2_1])
|
62
|
+
end
|
63
|
+
|
64
|
+
should "be able to tell if ancestor" do
|
65
|
+
assert @root_1.is_ancestor_of?(@child_1)
|
66
|
+
assert ! @root_2.is_ancestor_of?(@child_2_1)
|
67
|
+
assert ! @child_2.is_ancestor_of?(@child_2)
|
68
|
+
|
69
|
+
assert @root_1.is_or_is_ancestor_of?(@child_1)
|
70
|
+
assert ! @root_2.is_or_is_ancestor_of?(@child_2_1)
|
71
|
+
assert @child_2.is_or_is_ancestor_of?(@child_2)
|
72
|
+
end
|
73
|
+
|
74
|
+
should "be able to tell if descendant" do
|
75
|
+
assert ! @root_1.is_descendant_of?(@child_1)
|
76
|
+
assert @child_1.is_descendant_of?(@root_1)
|
77
|
+
assert ! @child_2.is_descendant_of?(@child_2)
|
78
|
+
|
79
|
+
assert ! @root_1.is_or_is_descendant_of?(@child_1)
|
80
|
+
assert @child_1.is_or_is_descendant_of?(@root_1)
|
81
|
+
assert @child_2.is_or_is_descendant_of?(@child_2)
|
82
|
+
end
|
83
|
+
|
84
|
+
should "be able to tell if sibling" do
|
85
|
+
assert ! @root_1.is_sibling_of?(@child_1)
|
86
|
+
assert ! @child_1.is_sibling_of?(@child_1)
|
87
|
+
assert ! @child_2.is_sibling_of?(@child_2)
|
88
|
+
|
89
|
+
assert ! @root_1.is_or_is_sibling_of?(@child_1)
|
90
|
+
assert @child_1.is_or_is_sibling_of?(@child_2)
|
91
|
+
assert @child_2.is_or_is_sibling_of?(@child_2)
|
92
|
+
end
|
93
|
+
|
94
|
+
context "when moving" do
|
95
|
+
should "recalculate path and depth" do
|
96
|
+
@child_3.parent = @child_2
|
97
|
+
@child_3.save
|
98
|
+
|
99
|
+
assert @child_2.is_or_is_ancestor_of?(@child_3)
|
100
|
+
assert @child_3.is_or_is_descendant_of?(@child_2)
|
101
|
+
assert @child_2.children.include?(@child_3)
|
102
|
+
assert @child_2.descendants.include?(@child_3)
|
103
|
+
assert @child_2_1.is_or_is_sibling_of?(@child_3)
|
104
|
+
assert_equal 2, @child_3.depth
|
105
|
+
end
|
106
|
+
|
107
|
+
should "move children on save" do
|
108
|
+
@child_2.parent = @root_2
|
109
|
+
|
110
|
+
assert ! @root_2.is_or_is_ancestor_of?(@child_2_1)
|
111
|
+
assert ! @child_2_1.is_or_is_descendant_of?(@root_2)
|
112
|
+
assert ! @root_2.descendants.include?(@child_2_1)
|
113
|
+
|
114
|
+
@child_2.save
|
115
|
+
@child_2_1.reload
|
116
|
+
|
117
|
+
assert @root_2.is_or_is_ancestor_of?(@child_2_1)
|
118
|
+
assert @child_2_1.is_or_is_descendant_of?(@root_2)
|
119
|
+
assert @root_2.descendants.include?(@child_2_1)
|
120
|
+
end
|
121
|
+
|
122
|
+
should "check against cyclic graph" do
|
123
|
+
@root_1.parent = @child_2_1
|
124
|
+
assert ! @root_1.valid?
|
125
|
+
assert_equal I18n.t(:'mongo_mapper.errors.messages.cyclic'), @root_1.errors[:base].first
|
126
|
+
end
|
127
|
+
|
128
|
+
should "be able to become root" do
|
129
|
+
@child_2.parent = nil
|
130
|
+
@child_2.save
|
131
|
+
@child_2.reload
|
132
|
+
assert_nil @child_2.parent
|
133
|
+
@child_2_1.reload
|
134
|
+
assert (@child_2_1.path == [@child_2.id])
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
|
139
|
+
should "destroy descendants when destroyed" do
|
140
|
+
@child_2.destroy
|
141
|
+
assert_nil Category.find(@child_2_1._id)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
context "root node" do
|
146
|
+
should "not have a parent" do
|
147
|
+
assert_nil @root_1.parent
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
context "child_node" do
|
152
|
+
should "have a parent" do
|
153
|
+
assert_equal @child_2, @child_2_1.parent
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
metadata
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mongo_mapper_tree
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- "Joel Junstr\xC3\xB6m"
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-08-05 00:00:00 +02:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: mongo_mapper
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ~>
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.9.1
|
24
|
+
type: :runtime
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: shoulda
|
29
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ~>
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: "2.10"
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: *id002
|
38
|
+
description: An Acts As Tree like implementation for MongoMapper based on mongo_mapper_acts_as_tree
|
39
|
+
email:
|
40
|
+
- joel.junstrom@oktavilla.se
|
41
|
+
executables: []
|
42
|
+
|
43
|
+
extensions: []
|
44
|
+
|
45
|
+
extra_rdoc_files: []
|
46
|
+
|
47
|
+
files:
|
48
|
+
- lib/locale/en.yml
|
49
|
+
- lib/mongo_mapper/plugins/tree.rb
|
50
|
+
- lib/mongo_mapper_tree.rb
|
51
|
+
- lib/version.rb
|
52
|
+
- test/helper.rb
|
53
|
+
- test/models/category.rb
|
54
|
+
- test/models/ordered_category.rb
|
55
|
+
- test/models/shapes.rb
|
56
|
+
- test/test_order.rb
|
57
|
+
- test/test_search_class.rb
|
58
|
+
- test/test_tree.rb
|
59
|
+
- LICENSE
|
60
|
+
- README.rdoc
|
61
|
+
has_rdoc: true
|
62
|
+
homepage: http://github.com/Oktavilla/mongo_mapper_tree
|
63
|
+
licenses: []
|
64
|
+
|
65
|
+
post_install_message:
|
66
|
+
rdoc_options: []
|
67
|
+
|
68
|
+
require_paths:
|
69
|
+
- lib
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
hash: -4220793794743588163
|
76
|
+
segments:
|
77
|
+
- 0
|
78
|
+
version: "0"
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
hash: -4220793794743588163
|
85
|
+
segments:
|
86
|
+
- 0
|
87
|
+
version: "0"
|
88
|
+
requirements: []
|
89
|
+
|
90
|
+
rubyforge_project:
|
91
|
+
rubygems_version: 1.6.2
|
92
|
+
signing_key:
|
93
|
+
specification_version: 3
|
94
|
+
summary: An Acts As Tree like implementation for MongoMapper
|
95
|
+
test_files:
|
96
|
+
- test/helper.rb
|
97
|
+
- test/models/category.rb
|
98
|
+
- test/models/ordered_category.rb
|
99
|
+
- test/models/shapes.rb
|
100
|
+
- test/test_order.rb
|
101
|
+
- test/test_search_class.rb
|
102
|
+
- test/test_tree.rb
|