mongoid_tree 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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: