mongoid_tree 0.2.0 → 0.3.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/Rakefile CHANGED
@@ -8,7 +8,7 @@ begin
8
8
  gem.summary = %Q{Materialized paths based tree implementation for Mongoid}
9
9
  gem.description = %Q{Fully featured tree implementation for Mongoid using materialized paths and relative associations. Featuring Depth and Breadth first search.}
10
10
  gem.email = "rkuhn@littleweblab.com"
11
- gem.homepage = "http://github.com/rayls/mongoid_tree"
11
+ gem.homepage = "http://github.com/ticktricktrack/mongoid_tree"
12
12
  gem.authors = ["Rainer Kuhn"]
13
13
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
14
14
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.0
1
+ 0.3.0
@@ -0,0 +1,46 @@
1
+ Feature: Modify tree
2
+ In order make changes to a tree
3
+ As a tree hugger
4
+ I want move and delete branches
5
+
6
+ Scenario: Move Subtree
7
+ Given I have no nodes
8
+ And I create a tree
9
+ #
10
+ # http://en.wikipedia.org/wiki/File:Depth-first-tree.svg
11
+ # Node 9 and subtree are moved to Node 6
12
+ When I move a subtree
13
+ And I request the children depth first
14
+ Then I should get the children in the following order
15
+ | Node_1 |
16
+ | Node_2 |
17
+ | Node_3 |
18
+ | Node_4 |
19
+ | Node_5 |
20
+ | Node_6 |
21
+ | Node_9 |
22
+ | Node_10 |
23
+ | Node_11 |
24
+ | Node_7 |
25
+ | Node_8 |
26
+ | Node_12 |
27
+
28
+ Scenario: Delete Subtree
29
+ Given I have no nodes
30
+ And I create a tree
31
+ #
32
+ # http://en.wikipedia.org/wiki/File:Depth-first-tree.svg
33
+ # Node 9 and subtree are deleted
34
+ When I delete a subtree
35
+ And I request the children depth first
36
+ Then I should get the children in the following order
37
+ | Node_1 |
38
+ | Node_2 |
39
+ | Node_3 |
40
+ | Node_4 |
41
+ | Node_5 |
42
+ | Node_6 |
43
+ | Node_7 |
44
+ | Node_8 |
45
+ | Node_12 |
46
+ And I should have 9 Nodes
@@ -1,25 +1,25 @@
1
1
  Given /^I have no nodes$/ do
2
- Node.delete_all
2
+ Node.delete_all
3
3
  end
4
4
 
5
5
  Given /^the following nodes exist:$/ do |nodes_table|
6
- # table is a Cucumber::Ast::Table
7
- nodes_table.hashes.each do |hash|
8
- node = Node.new(:name => hash[:name])
9
- if !(hash[:parent] == "")
10
- parent = Node.find(:first, :conditions => { :name => hash[:parent]})
11
- parent.children << node
12
- parent.save!
13
- node.save!
14
- else
15
- Node.create( :name => hash[:name])
6
+ # table is a Cucumber::Ast::Table
7
+ nodes_table.hashes.each do |hash|
8
+ node = Node.new(:name => hash[:name])
9
+ if !(hash[:parent] == "")
10
+ parent = Node.find(:first, :conditions => { :name => hash[:parent]})
11
+ parent.children << node
12
+ parent.save!
13
+ node.save!
14
+ else
15
+ Node.create( :name => hash[:name])
16
+ end
16
17
  end
17
- end
18
18
  end
19
19
 
20
20
  Given /^I create a tree$/ do
21
- 12.times{ Factory.create(:node) }
22
-
21
+ 12.times{ |i| Factory.create(:node, :name => "Node_#{i+1}") }
22
+
23
23
  Node.where(:name => "Node_1").first.children << Node.where(:name => "Node_7").first
24
24
  Node.where(:name => "Node_7").first.insert_before(Node.where(:name => "Node_2").first)
25
25
  Node.where(:name => "Node_1").first.children << Node.where(:name => "Node_8").first
@@ -35,16 +35,32 @@ end
35
35
 
36
36
 
37
37
  When /^I request the children depth first$/ do
38
- @root = Node.find(:first, :conditions => { :name => "Node_1" })
39
- @root.should_not be(nil)
40
- @children = @root.depth_first.map{|node| [node.name]}
38
+ @root = Node.find(:first, :conditions => { :name => "Node_1" })
39
+ @root.should_not be(nil)
40
+ @children = @root.depth_first.map{|node| [node.name]}
41
+ puts @children.inspect
42
+ end
43
+
44
+ When /^I request the children breadth first$/ do
45
+ @root = Node.find(:first, :conditions => { :name => "Node_1" })
46
+ @root.should_not be(nil)
47
+ @children = @root.breadth_first.map{|node| [node.name]}
48
+ end
49
+
50
+ When /^I move a subtree$/ do
51
+ Node.first(:conditions => {:name => "Node_9"}).move_to(Node.first(:conditions => {:name => "Node_6"}))
52
+ end
53
+
54
+ When /^I delete a subtree$/ do
55
+ Node.first(:conditions => {:name => "Node_9"}).destroy
41
56
  end
42
57
 
58
+
43
59
  Then /^I should get the children in the following order$/ do |expected_children_order|
44
- # table is a Cucumber::Ast::Table
45
- expected_children_order.diff!(@children)
60
+ # table is a Cucumber::Ast::Table
61
+ expected_children_order.diff!(@children)
46
62
  end
47
63
 
48
64
  Then /^I should have (\d+) Nodes$/ do |count|
49
- Node.count.should be(count.to_i)
65
+ Node.count.should be(count.to_i)
50
66
  end
@@ -1,6 +1,6 @@
1
1
  Feature: Tree Searches / Traversals
2
2
  In order to traverse
3
- As a user
3
+ As a developer
4
4
  I want a depth-first representation of the tree
5
5
 
6
6
  Scenario: Depth First Search
@@ -22,6 +22,41 @@ Feature: Tree Searches / Traversals
22
22
  | Node_11 | Node_9 |
23
23
  And I should have 12 Nodes
24
24
  When I request the children depth first
25
+ Then I should get the children in the following order
26
+ | Node_1 |
27
+ | Node_2 |
28
+ | Node_3 |
29
+ | Node_4 |
30
+ | Node_5 |
31
+ | Node_6 |
32
+ | Node_7 |
33
+ | Node_8 |
34
+ | Node_9 |
35
+ | Node_10 |
36
+ | Node_11 |
37
+ | Node_12 |
38
+
39
+
40
+
41
+ Scenario: Depth First Search with other create Order
42
+ # For a visual representation see http://en.wikipedia.org/wiki/File:Depth-first-tree.svg
43
+ Given I have no nodes
44
+ And the following nodes exist:
45
+ | name | parent |
46
+ | Node_1 | |
47
+ | Node_2 | Node_1 |
48
+ | Node_7 | Node_1 |
49
+ | Node_8 | Node_1 |
50
+ | Node_9 | Node_8 |
51
+ | Node_10 | Node_9 |
52
+ | Node_11 | Node_9 |
53
+ | Node_3 | Node_2 |
54
+ | Node_6 | Node_2 |
55
+ | Node_12 | Node_8 |
56
+ | Node_4 | Node_3 |
57
+ | Node_5 | Node_3 |
58
+ And I should have 12 Nodes
59
+ When I request the children depth first
25
60
  Then I should get the children in the following order
26
61
  |Node_1|
27
62
  |Node_2|
@@ -35,59 +70,48 @@ Feature: Tree Searches / Traversals
35
70
  |Node_10|
36
71
  |Node_11|
37
72
  |Node_12|
38
-
39
-
40
- Scenario: Depth First Search with other create Order
41
- # For a visual representation see http://en.wikipedia.org/wiki/File:Depth-first-tree.svg
42
- Given I have no nodes
43
- And the following nodes exist:
44
- | name | parent |
45
- | Node_1 | |
46
- | Node_2 | Node_1 |
47
- | Node_7 | Node_1 |
48
- | Node_8 | Node_1 |
49
- | Node_9 | Node_8 |
50
- | Node_10 | Node_9 |
51
- | Node_11 | Node_9 |
52
- | Node_3 | Node_2 |
53
- | Node_6 | Node_2 |
54
- | Node_12 | Node_8 |
55
- | Node_4 | Node_3 |
56
- | Node_5 | Node_3 |
57
- And I should have 12 Nodes
58
- When I request the children depth first
59
- Then I should get the children in the following order
60
- |Node_1|
61
- |Node_2|
62
- |Node_3|
63
- |Node_4|
64
- |Node_5|
65
- |Node_6|
66
- |Node_7|
67
- |Node_8|
68
- |Node_9|
69
- |Node_10|
70
- |Node_11|
71
- |Node_12|
72
-
73
- # This scenario creates a tree using ".children << node", insert_before(node), insert_after(node)
74
- # Details in the "And i have a tree" step
75
- Scenario: Depth First Search with insert
76
- # For a visual representation see http://en.wikipedia.org/wiki/File:Depth-first-tree.svg
77
- Given I have no nodes
78
- And I create a tree
79
- And I should have 12 Nodes
80
- When I request the children depth first
81
- Then I should get the children in the following order
82
- |Node_1|
83
- |Node_2|
84
- |Node_3|
85
- |Node_4|
86
- |Node_5|
87
- |Node_6|
88
- |Node_7|
89
- |Node_8|
90
- |Node_9|
91
- |Node_10|
92
- |Node_11|
93
- |Node_12|
73
+
74
+ # This scenario creates a tree using ".children << node", insert_before(node), insert_after(node)
75
+ # Details in the "And i have a tree" step
76
+ Scenario: Depth First Search with insert
77
+ # For a visual representation see http://en.wikipedia.org/wiki/File:Depth-first-tree.svg
78
+ Given I have no nodes
79
+ And I create a tree
80
+ And I should have 12 Nodes
81
+ When I request the children depth first
82
+ Then I should get the children in the following order
83
+ | Node_1 |
84
+ | Node_2 |
85
+ | Node_3 |
86
+ | Node_4 |
87
+ | Node_5 |
88
+ | Node_6 |
89
+ | Node_7 |
90
+ | Node_8 |
91
+ | Node_9 |
92
+ | Node_10 |
93
+ | Node_11 |
94
+ | Node_12 |
95
+
96
+ # Same tree as in the Depth-First-Examples
97
+ # For a visual representation see http://en.wikipedia.org/wiki/File:Depth-first-tree.svg
98
+ # More info on BFS
99
+ # http://en.wikipedia.org/wiki/Breadth-first_search
100
+ # http://www.cs.bu.edu/teaching/cs112/spring-2000/breadth-first/
101
+ Scenario: Bread First Search
102
+ Given I have no nodes
103
+ And I create a tree
104
+ When I request the children breadth first
105
+ Then I should get the children in the following order
106
+ | Node_1 |
107
+ | Node_2 |
108
+ | Node_7 |
109
+ | Node_8 |
110
+ | Node_3 |
111
+ | Node_6 |
112
+ | Node_9 |
113
+ | Node_12 |
114
+ | Node_4 |
115
+ | Node_5 |
116
+ | Node_10 |
117
+ | Node_11 |
data/lib/mongoid_tree.rb CHANGED
@@ -3,9 +3,13 @@ module Mongoid
3
3
  module Tree
4
4
  extend ActiveSupport::Concern
5
5
  include Comparable
6
-
6
+
7
7
  included do
8
- references_many :children, :class_name => self.name, :stored_as => :array, :inverse_of => :parents do
8
+ references_many :children,
9
+ :class_name => self.name,
10
+ :stored_as => :array,
11
+ :inverse_of => :parents,
12
+ :dependent => :destroy do
9
13
  def <<(*objects)
10
14
  objects.flatten.each_with_index do |object, index|
11
15
  reverse_key = reverse_key(object)
@@ -18,13 +22,15 @@ module Mongoid
18
22
  object.send(reverse_key).concat(@parent.send(reverse_key))
19
23
  end
20
24
  super(objects)
21
-
25
+
22
26
  end
23
27
  end
24
-
25
- #referenced_in :parent, :class_name => self.name, :inverse_of => :children
26
- references_many :parents, :class_name => self.name, :stored_as => :array, :inverse_of => :children
27
-
28
+
29
+ references_many :parents,
30
+ :class_name => self.name,
31
+ :stored_as => :array,
32
+ :inverse_of => :children
33
+
28
34
  # This stores the position in the children array of the parent object.
29
35
  # Makes it easier to flatten / export / import a tree
30
36
  field :position, :type => Integer
@@ -32,34 +38,49 @@ module Mongoid
32
38
  end
33
39
 
34
40
  module InstanceMethods
35
-
41
+
36
42
  def parent
37
43
  self.parents.last
38
44
  end
39
-
45
+
40
46
  def depth
41
47
  self.parents.count
42
48
  end
43
-
49
+
44
50
  #Comparable
45
51
  def <=> (another_node)
46
52
  self.position <=> another_node.position
47
53
  end
48
-
49
-
50
- # TODO change this into a Mongoid Query
54
+
55
+ # Returns the whole subtree including itself as array
51
56
  def depth_first
52
57
  result = [self]
53
- if children.empty?
58
+ if self.child_ids.empty?
54
59
  return result
55
60
  else
56
- self.children.each do |child|
61
+ self.children.sort.each do |child|
57
62
  result += child.depth_first
58
63
  end
59
64
  end
60
65
  return result
61
66
  end
67
+ alias :dfs :depth_first
62
68
 
69
+ # Returns the whole subtree including itself as array
70
+ def breadth_first
71
+ result = []
72
+ queue = [self]
73
+ while !queue.empty?
74
+ node = queue.shift
75
+ result << node
76
+ node.children.sort.each do |child|
77
+ queue << child
78
+ end
79
+ end
80
+ return result
81
+ end
82
+ alias :bfs :breadth_first
83
+
63
84
  def insert_before( new_child )
64
85
  new_child.position = self.position
65
86
  self.parent.children.each do |child|
@@ -79,10 +100,31 @@ module Mongoid
79
100
  end
80
101
  self.parent.children << new_child
81
102
  end
103
+
104
+ def move_to(target_node)
105
+ # unhinge - I was getting a nil on another implementation, so this is a bit longer but works
106
+ child_ids_array = self.parent.child_ids.clone
107
+ child_ids_array.delete(self.id)
108
+ parent.update_attributes(:child_ids => child_ids_array )
109
+ self.update_attributes(:parent_ids => [])
110
+ # and append
111
+ target_node.children << self
112
+ # recurse through subtree
113
+ self.rebuild_paths
114
+ end
115
+
116
+ def rebuild_paths
117
+ self.update_path
118
+ self.children.each do |child|
119
+ child.rebuild_paths
120
+ end
121
+ end
122
+
123
+ def update_path
124
+ self.update_attributes(:parent_ids => self.parent.parent_ids + [self.parent.id])
125
+ end
126
+
82
127
  end
83
-
84
-
85
-
86
128
  end
87
129
  end
88
- end
130
+ end
data/mongoid_tree.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{mongoid_tree}
8
- s.version = "0.2.0"
8
+ s.version = "0.3.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Rainer Kuhn"]
12
- s.date = %q{2010-08-12}
12
+ s.date = %q{2010-08-13}
13
13
  s.description = %q{Fully featured tree implementation for Mongoid using materialized paths and relative associations. Featuring Depth and Breadth first search.}
14
14
  s.email = %q{rkuhn@littleweblab.com}
15
15
  s.extra_rdoc_files = [
@@ -45,6 +45,7 @@ Gem::Specification.new do |s|
45
45
  "doc/js/jquery.js",
46
46
  "doc/method_list.html",
47
47
  "doc/top-level-namespace.html",
48
+ "features/modify_tree.feature",
48
49
  "features/step_definitions/mongoid_tree_steps.rb",
49
50
  "features/support/env.rb",
50
51
  "features/traversal.feature",
@@ -55,7 +56,7 @@ Gem::Specification.new do |s|
55
56
  "spec/mongoid_tree_spec.rb",
56
57
  "spec/spec_helper.rb"
57
58
  ]
58
- s.homepage = %q{http://github.com/rayls/mongoid_tree}
59
+ s.homepage = %q{http://github.com/ticktricktrack/mongoid_tree}
59
60
  s.rdoc_options = ["--charset=UTF-8"]
60
61
  s.require_paths = ["lib"]
61
62
  s.rubygems_version = %q{1.3.7}
@@ -122,6 +122,19 @@ describe "MongoidTree" do
122
122
  getNode(5).parent_ids.should eql([getNode(1).id, getNode(2).id, getNode(3).id])
123
123
  getNode(10).parent_ids.should eql([getNode(1).id, getNode(8).id, getNode(9).id])
124
124
  end
125
+
126
+ context "Moving a subtree" do
127
+ it "should rebuild the paths" do
128
+ getNode(9).move_to(getNode(6))
129
+ getNode(2).child_ids.should eql([getNode(3).id, getNode(6).id])
130
+ getNode(8).child_ids.should eql([getNode(12).id])
131
+
132
+
133
+ getNode(9).parent_ids.should eql([getNode(1).id, getNode(2).id, getNode(6).id])
134
+ getNode(10).parent_ids.should eql([getNode(1).id, getNode(2).id, getNode(6).id, getNode(9).id])
135
+ getNode(11).parent_ids.should eql([getNode(1).id, getNode(2).id, getNode(6).id, getNode(9).id])
136
+ end
137
+ end
125
138
 
126
139
  end
127
140
 
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 2
7
+ - 3
8
8
  - 0
9
- version: 0.2.0
9
+ version: 0.3.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Rainer Kuhn
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-08-12 00:00:00 +02:00
17
+ date: 2010-08-13 00:00:00 +02:00
18
18
  default_executable:
19
19
  dependencies: []
20
20
 
@@ -56,6 +56,7 @@ files:
56
56
  - doc/js/jquery.js
57
57
  - doc/method_list.html
58
58
  - doc/top-level-namespace.html
59
+ - features/modify_tree.feature
59
60
  - features/step_definitions/mongoid_tree_steps.rb
60
61
  - features/support/env.rb
61
62
  - features/traversal.feature
@@ -66,7 +67,7 @@ files:
66
67
  - spec/mongoid_tree_spec.rb
67
68
  - spec/spec_helper.rb
68
69
  has_rdoc: true
69
- homepage: http://github.com/rayls/mongoid_tree
70
+ homepage: http://github.com/ticktricktrack/mongoid_tree
70
71
  licenses: []
71
72
 
72
73
  post_install_message: