mongoid_tree 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.bundle/config ADDED
@@ -0,0 +1,2 @@
1
+ ---
2
+ BUNDLE_DISABLE_SHARED_GEMS: "1"
data/.gitignore CHANGED
@@ -19,3 +19,5 @@ rdoc
19
19
  pkg
20
20
 
21
21
  ## PROJECT::SPECIFIC
22
+
23
+ .rvmrc
data/.rspec CHANGED
@@ -1 +1,2 @@
1
+ --format nested
1
2
  --color
data/Gemfile CHANGED
@@ -1,6 +1,9 @@
1
- gem "mongoid", ">= 2.0.0.beta7"
1
+ source :rubygems
2
+ gem "jeweler"
3
+ gem "mongoid", "= 2.0.0.beta.16"
2
4
  # Currently used MongoDB version : 1.4.3
3
5
 
4
- gem "rspec", ">= 2.0.0.beta.15"
5
- gem "cucumber", ">= 0.8.2"
6
- gem "yard"
6
+ gem "rspec", ">= 2.0.0.beta.19"
7
+ gem "cucumber", ">= 0.8.5"
8
+ gem "yard"
9
+ gem "factory_girl"
data/Gemfile.lock ADDED
@@ -0,0 +1,62 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ activemodel (3.0.0.rc)
5
+ activesupport (= 3.0.0.rc)
6
+ builder (~> 2.1.2)
7
+ i18n (~> 0.4.1)
8
+ activesupport (3.0.0.rc)
9
+ bson (1.0.4)
10
+ builder (2.1.2)
11
+ cucumber (0.8.5)
12
+ builder (~> 2.1.2)
13
+ diff-lcs (~> 1.1.2)
14
+ gherkin (~> 2.1.4)
15
+ json_pure (~> 1.4.3)
16
+ term-ansicolor (~> 1.0.4)
17
+ diff-lcs (1.1.2)
18
+ factory_girl (1.3.1)
19
+ gemcutter (0.6.1)
20
+ gherkin (2.1.4)
21
+ trollop (~> 1.16.2)
22
+ git (1.2.5)
23
+ i18n (0.4.1)
24
+ jeweler (1.4.0)
25
+ gemcutter (>= 0.1.0)
26
+ git (>= 1.2.5)
27
+ rubyforge (>= 2.0.0)
28
+ json_pure (1.4.3)
29
+ mongo (1.0.7)
30
+ bson (>= 1.0.4)
31
+ mongoid (2.0.0.beta.16)
32
+ activemodel (= 3.0.0.rc)
33
+ bson (= 1.0.4)
34
+ mongo (= 1.0.7)
35
+ tzinfo (= 0.3.22)
36
+ will_paginate (~> 3.0.pre)
37
+ rspec (2.0.0.beta.19)
38
+ rspec-core (= 2.0.0.beta.19)
39
+ rspec-expectations (= 2.0.0.beta.19)
40
+ rspec-mocks (= 2.0.0.beta.19)
41
+ rspec-core (2.0.0.beta.19)
42
+ rspec-expectations (2.0.0.beta.19)
43
+ diff-lcs (>= 1.1.2)
44
+ rspec-mocks (2.0.0.beta.19)
45
+ rubyforge (2.0.4)
46
+ json_pure (>= 1.1.7)
47
+ term-ansicolor (1.0.5)
48
+ trollop (1.16.2)
49
+ tzinfo (0.3.22)
50
+ will_paginate (3.0.pre2)
51
+ yard (0.5.8)
52
+
53
+ PLATFORMS
54
+ ruby
55
+
56
+ DEPENDENCIES
57
+ cucumber (>= 0.8.5)
58
+ factory_girl
59
+ jeweler
60
+ mongoid (= 2.0.0.beta.16)
61
+ rspec (>= 2.0.0.beta.19)
62
+ yard
data/README.rdoc CHANGED
@@ -1,6 +1,14 @@
1
1
  = mongoid_tree
2
2
 
3
- Description goes here.
3
+ For our commercial application we need a proper tree structure with Depth-First and Breadth-First searches, parent and child information. Also subtrees need to be exported to JSON.
4
+
5
+ Initially I thought of an embedded solution, but this will only be possible once MongoDB supports embedded collections, and even deep-embedded collections.
6
+
7
+ However this tree is right now on the top of our priority list, means we will put effort into this and release everything in this public gem, as soon as we implement and test it. It will be fully tested with RSpec.
8
+ The lack of a proper tree structure is quite noticeable in the mongoid forums.
9
+
10
+ This gem will receive long-term support since we use it in a commercial long-term application.
11
+
4
12
 
5
13
  == Note on Patches/Pull Requests
6
14
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.0
1
+ 0.1.0
data/cucumber.yml ADDED
@@ -0,0 +1,3 @@
1
+ default: --format pretty features --color
2
+ autotest-all: --color --require features --require lib --format pretty features
3
+ autotest: --color --require features --require lib features --format pretty
@@ -0,0 +1,50 @@
1
+ Given /^I have no nodes$/ do
2
+ Node.delete_all
3
+ end
4
+
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])
16
+ end
17
+ end
18
+ end
19
+
20
+ Given /^I create a tree$/ do
21
+ 12.times{ Factory.create(:node) }
22
+
23
+ Node.where(:name => "Node_1").first.children << Node.where(:name => "Node_7").first
24
+ Node.where(:name => "Node_7").first.insert_before(Node.where(:name => "Node_2").first)
25
+ Node.where(:name => "Node_1").first.children << Node.where(:name => "Node_8").first
26
+ Node.where(:name => "Node_2").first.children << Node.where(:name => "Node_3").first
27
+ Node.where(:name => "Node_3").first.insert_after(Node.where(:name => "Node_6").first)
28
+ Node.where(:name => "Node_8").first.children << Node.where(:name => "Node_9").first
29
+ Node.where(:name => "Node_9").first.children << Node.where(:name => "Node_11").first
30
+ Node.where(:name => "Node_3").first.children << Node.where(:name => "Node_5").first
31
+ Node.where(:name => "Node_9").first.insert_after(Node.where(:name => "Node_12").first)
32
+ Node.where(:name => "Node_5").first.insert_before(Node.where(:name => "Node_4").first)
33
+ Node.where(:name => "Node_11").first.insert_before(Node.where(:name => "Node_10").first)
34
+ end
35
+
36
+
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]}
41
+ end
42
+
43
+ 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)
46
+ end
47
+
48
+ Then /^I should have (\d+) Nodes$/ do |count|
49
+ Node.count.should be(count.to_i)
50
+ end
@@ -1,4 +1,4 @@
1
1
  $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
2
- require 'mongoid_tree'
2
+ require File.expand_path(File.dirname(__FILE__)+'/../../spec/spec_helper')
3
3
 
4
4
  require 'rspec/expectations'
@@ -0,0 +1,93 @@
1
+ Feature: Tree Searches / Traversals
2
+ In order to traverse
3
+ As a user
4
+ I want a depth-first representation of the tree
5
+
6
+ Scenario: Depth First Search
7
+ # For a visual representation see http://en.wikipedia.org/wiki/File:Depth-first-tree.svg
8
+ Given I have no nodes
9
+ And the following nodes exist:
10
+ | name | parent |
11
+ | Node_1 | |
12
+ | Node_2 | Node_1 |
13
+ | Node_7 | Node_1 |
14
+ | Node_8 | Node_1 |
15
+ | Node_3 | Node_2 |
16
+ | Node_6 | Node_2 |
17
+ | Node_9 | Node_8 |
18
+ | Node_12 | Node_8 |
19
+ | Node_4 | Node_3 |
20
+ | Node_5 | Node_3 |
21
+ | Node_10 | Node_9 |
22
+ | Node_11 | Node_9 |
23
+ And I should have 12 Nodes
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
+ 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|
data/lib/mongoid_tree.rb CHANGED
@@ -0,0 +1,73 @@
1
+ module Mongoid
2
+ module Acts
3
+ module Tree
4
+ extend ActiveSupport::Concern
5
+ include Comparable
6
+
7
+ included do
8
+ references_many :children, :class_name => self.name, :stored_as => :array, :inverse_of => :parent do
9
+ def <<(*objects)
10
+ objects.flatten.each_with_index do |object, index|
11
+ if object.position == nil
12
+ object.position = @parent.send(@foreign_key).count + index + 1
13
+ end
14
+ end
15
+ super(objects)
16
+ end
17
+ end
18
+
19
+ referenced_in :parent, :class_name => self.name, :inverse_of => :children
20
+
21
+ # This stores the position in the children array of the parent object.
22
+ # Makes it easier to flatten / export / import a tree
23
+ field :position, :type => Integer
24
+ end
25
+
26
+ module InstanceMethods
27
+
28
+ #Comparable
29
+ def <=> (another_node)
30
+ self.position <=> another_node.position
31
+ end
32
+
33
+ def depth_first
34
+ result = [self]
35
+ if children.empty?
36
+ return result
37
+ else
38
+ self.children.each do |child|
39
+ result += child.depth_first
40
+ end
41
+ end
42
+ return result
43
+ end
44
+
45
+
46
+ def insert_before( new_child )
47
+ new_child.position = self.position
48
+ self.parent.children.each do |child|
49
+ if child.position >= new_child.position
50
+ child.position += 1
51
+ end
52
+ end
53
+ self.parent.children << new_child
54
+ end
55
+
56
+ def insert_after ( new_child )
57
+ self.save
58
+ self.parent.save
59
+ new_child.position = self.position
60
+ self.parent.children.each do |child|
61
+ if child.position >= new_child.position
62
+ child.position += 1
63
+ end
64
+ end
65
+ self.parent.children << new_child
66
+ end
67
+ end
68
+
69
+
70
+
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,80 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{mongoid_tree}
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 = ["Rainer Kuhn"]
12
+ s.date = %q{2010-08-10}
13
+ s.description = %q{Fully featured tree implementation for Mongoid using materialized paths and relative associations. Featuring Depth and Breadth first search.}
14
+ s.email = %q{rkuhn@littleweblab.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".bundle/config",
21
+ ".gitignore",
22
+ ".rspec",
23
+ ".yardoc/checksums",
24
+ ".yardoc/objects/root.dat",
25
+ ".yardoc/proxy_types",
26
+ "Gemfile",
27
+ "Gemfile.lock",
28
+ "LICENSE",
29
+ "README.rdoc",
30
+ "Rakefile",
31
+ "VERSION",
32
+ "autotest/discover.rb",
33
+ "cucumber.yml",
34
+ "doc/_index.html",
35
+ "doc/class_list.html",
36
+ "doc/css/common.css",
37
+ "doc/css/full_list.css",
38
+ "doc/css/style.css",
39
+ "doc/file.README.html",
40
+ "doc/file_list.html",
41
+ "doc/frames.html",
42
+ "doc/index.html",
43
+ "doc/js/app.js",
44
+ "doc/js/full_list.js",
45
+ "doc/js/jquery.js",
46
+ "doc/method_list.html",
47
+ "doc/top-level-namespace.html",
48
+ "features/step_definitions/mongoid_tree_steps.rb",
49
+ "features/support/env.rb",
50
+ "features/traversal.feature",
51
+ "lib/mongoid_tree.rb",
52
+ "mongoid_tree.gemspec",
53
+ "spec/factories/node.rb",
54
+ "spec/models/node.rb",
55
+ "spec/mongoid_tree_spec.rb",
56
+ "spec/spec_helper.rb"
57
+ ]
58
+ s.homepage = %q{http://github.com/rayls/mongoid_tree}
59
+ s.rdoc_options = ["--charset=UTF-8"]
60
+ s.require_paths = ["lib"]
61
+ s.rubygems_version = %q{1.3.7}
62
+ s.summary = %q{Materialized paths based tree implementation for Mongoid}
63
+ s.test_files = [
64
+ "spec/factories/node.rb",
65
+ "spec/models/node.rb",
66
+ "spec/mongoid_tree_spec.rb",
67
+ "spec/spec_helper.rb"
68
+ ]
69
+
70
+ if s.respond_to? :specification_version then
71
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
72
+ s.specification_version = 3
73
+
74
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
75
+ else
76
+ end
77
+ else
78
+ end
79
+ end
80
+
@@ -0,0 +1,3 @@
1
+ Factory.define :node do |f|
2
+ f.sequence(:name) {|n| "Node_#{n}"}
3
+ end
@@ -0,0 +1,10 @@
1
+ class Node
2
+ include Mongoid::Document
3
+ include Mongoid::Acts::Tree
4
+
5
+
6
+ field :name
7
+
8
+ validates_presence_of :name
9
+
10
+ end
@@ -1,7 +1,93 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
2
 
3
3
  describe "MongoidTree" do
4
- it "fails" do
5
- fail "hey buddy, you should probably rename this file and start specing for real"
6
- end
4
+
5
+ before do
6
+ [ Node ].each { |klass| klass.collection.remove }
7
+ end
8
+
9
+ describe "A tree of nodes" do
10
+
11
+ context "A node with a child" do
12
+
13
+ before do
14
+ @parent = Node.new(:name => "Parent")
15
+ @child = Node.new(:name => "Child")
16
+ @parent.children << @child
17
+ @parent.save
18
+ end
19
+
20
+ it "should have 1 child" do
21
+ @parent.children.count.should be(1)
22
+ @parent.children.first.should eq(@child)
23
+ end
24
+
25
+ it "a child should know it's parent" do
26
+ @child.parent.should eq(@parent)
27
+ end
28
+
29
+ it "the child should be at position 1" do
30
+ @parent.children.first.position.should eq(1)
31
+ end
32
+
33
+ end
34
+
35
+ context "a node with 2 children" do
36
+
37
+ def reload
38
+ @parent.reload
39
+ @child_1.reload
40
+ @child_2.reload
41
+ @new_child.reload
42
+ end
43
+
44
+ before do
45
+ @parent = Node.new(:name => "Parent")
46
+ @child_1 = Node.new(:name => "Child_1")
47
+ @child_2 = Node.new(:name => "Child_2")
48
+ @new_child = Node.new(:name => "new_child")
49
+ @parent.children << @child_1
50
+ @parent.children << @child_2
51
+ @child_1.position.should eq(1)
52
+ @child_2.position.should eq(2)
53
+ @parent.save
54
+ end
55
+
56
+ it "should add a child before child 1" do
57
+ @child_1.insert_before(@new_child)
58
+ reload
59
+ @parent.children.sort.should eq([@new_child, @child_1, @child_2])
60
+ end
61
+
62
+ it "should add a child after child 1" do
63
+ @child_1.insert_after(@new_child)
64
+ reload
65
+ @parent.children.sort.should eq([@child_1, @new_child, @child_2])
66
+ end
67
+
68
+ it "should add a child before child 2" do
69
+ @child_2.insert_before(@new_child)
70
+ reload
71
+ @parent.children.sort.should eq([@child_1, @new_child, @child_2])
72
+ end
73
+
74
+ it "should add a child after child 2" do
75
+ @child_2.insert_after(@new_child)
76
+ reload
77
+ @parent.children.sort.should eq([@child_1, @child_2, @new_child])
78
+ end
79
+
80
+ it "should append a child to children" do
81
+ @parent.reload
82
+ @parent.children << @new_child
83
+ @parent.children.count.should eq(3)
84
+ @parent.save
85
+ @new_child.save
86
+ reload
87
+ @parent.children.sort.should eq([@child_1, @child_2, @new_child])
88
+ end
89
+
90
+ end
91
+
92
+ end
7
93
  end
data/spec/spec_helper.rb CHANGED
@@ -1,9 +1,27 @@
1
1
  $LOAD_PATH.unshift(File.dirname(__FILE__))
2
2
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+
4
+ require "mongoid"
5
+
3
6
  require 'mongoid_tree'
4
7
  require 'rspec'
5
8
  require 'rspec/autorun'
9
+ require "factory_girl"
10
+
11
+ Mongoid.configure do |config|
12
+ name = "mongoid_tree_test"
13
+ host = "localhost"
14
+ config.master = Mongo::Connection.new.db(name)
15
+ # config.slaves = [
16
+ # Mongo::Connection.new(host, 27018, :slave_ok => true).db(name)
17
+ # ]
18
+ end
19
+
20
+ # I have never got the hang of including files, probably because the Syntax is so horrible.
21
+ # This is the easiest I could come up with.
22
+ Dir[File.dirname(__FILE__) + '/models/*.rb'].each {|file| require file }
23
+ Dir[File.dirname(__FILE__) + "/factories/*.rb"].each {|file| require file }
6
24
 
7
25
  RSpec.configure do |config|
8
-
26
+
9
27
  end
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
+ - 1
7
8
  - 0
8
- - 0
9
- version: 0.0.0
9
+ version: 0.1.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-07-07 00:00:00 +02:00
17
+ date: 2010-08-10 00:00:00 +02:00
18
18
  default_executable:
19
19
  dependencies: []
20
20
 
@@ -28,18 +28,20 @@ extra_rdoc_files:
28
28
  - LICENSE
29
29
  - README.rdoc
30
30
  files:
31
- - .document
31
+ - .bundle/config
32
32
  - .gitignore
33
33
  - .rspec
34
34
  - .yardoc/checksums
35
35
  - .yardoc/objects/root.dat
36
36
  - .yardoc/proxy_types
37
37
  - Gemfile
38
+ - Gemfile.lock
38
39
  - LICENSE
39
40
  - README.rdoc
40
41
  - Rakefile
41
42
  - VERSION
42
43
  - autotest/discover.rb
44
+ - cucumber.yml
43
45
  - doc/_index.html
44
46
  - doc/class_list.html
45
47
  - doc/css/common.css
@@ -54,10 +56,13 @@ files:
54
56
  - doc/js/jquery.js
55
57
  - doc/method_list.html
56
58
  - doc/top-level-namespace.html
57
- - features/mongoid_tree.feature
58
59
  - features/step_definitions/mongoid_tree_steps.rb
59
60
  - features/support/env.rb
61
+ - features/traversal.feature
60
62
  - lib/mongoid_tree.rb
63
+ - mongoid_tree.gemspec
64
+ - spec/factories/node.rb
65
+ - spec/models/node.rb
61
66
  - spec/mongoid_tree_spec.rb
62
67
  - spec/spec_helper.rb
63
68
  has_rdoc: true
@@ -93,5 +98,7 @@ signing_key:
93
98
  specification_version: 3
94
99
  summary: Materialized paths based tree implementation for Mongoid
95
100
  test_files:
101
+ - spec/factories/node.rb
102
+ - spec/models/node.rb
96
103
  - spec/mongoid_tree_spec.rb
97
104
  - spec/spec_helper.rb
data/.document DELETED
@@ -1,5 +0,0 @@
1
- README.rdoc
2
- lib/**/*.rb
3
- bin/*
4
- features/**/*.feature
5
- LICENSE
@@ -1,9 +0,0 @@
1
- Feature: something something
2
- In order to something something
3
- A user something something
4
- something something something
5
-
6
- Scenario: something something
7
- Given inspiration
8
- When I create a sweet new gem
9
- Then everyone should see how awesome I am