Skip to content

Commit

Permalink
Added basic tree implementation, including some documentation.
Browse files Browse the repository at this point in the history
  • Loading branch information
benedikt committed Jul 25, 2010
1 parent b6a3ae4 commit 606fbe5
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 4 deletions.
4 changes: 3 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
source :rubygems

gemspec

gem 'bson_ext', '1.0.4'
gem 'bson_ext', '>= 1.0.4'
2 changes: 1 addition & 1 deletion README.rdoc
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Finally, run

== Usage

Read the API documentation at http://rdoc.info/projects/benedikt/mongoid_tree
Read the API documentation at http://benedikt.github.com/mongoid_tree and take a look at the Mongoid::Tree module

class Node
include Mongoid::Document
Expand Down
11 changes: 9 additions & 2 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require 'rspec/core/rake_task'
require 'rake/rdoctask'
require 'hanna/rdoctask'
require 'grancher/task'

spec = Gem::Specification.load("mongoid_tree.gemspec")

Expand All @@ -17,10 +18,16 @@ end

desc "Build the .gem file"
task :build do
system "gem build #{spec.name}.spec"
system "gem build #{spec.name}.gemspec"
end

desc "Push the .gem file to rubygems.org"
task :release => :build do
system "gem push #{spec.name}-#{spec.version}.gem"
end

Grancher::Task.new(:publish => :rdoc) do |g|
g.branch = 'gh-pages'
g.push_to = 'origin'
g.directory 'doc'
end
87 changes: 87 additions & 0 deletions lib/mongoid/tree.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
module Mongoid # :nodoc:
##
# = Mongoid::Tree
#
# This module extends any Mongoid document with tree functionality.
#
# == Usage
#
# Simply include the module in any Mongoid document:
#
# class Node
# include Mongoid::Document
# include Mongoid::Tree
# end
#
# === Using the tree structure
#
# Each document references many children. You can access them using the <tt>#children</tt> method.
#
# node = Node.create
# node.children.create
# node.children.count # => 1
#
# Every document references one parent (unless it's a root document).
#
# node = Node.create
# node.parent # => nil
# node.children.create
# node.children.first.parent # => node
#
module Tree
extend ActiveSupport::Concern

included do
reference_many :children, :class_name => self.name, :foreign_key => :parent_id, :inverse_of => :parent
referenced_in :parent, :class_name => self.name, :inverse_of => :children

field :parent_ids, :type => Array, :default => []

set_callback :validation, :before, :rearrange
set_callback :save, :after, :rearrange_children, :if => :rearrange_children?
end

##
# :method: children
# Returns a list of the document's children. It's a <tt>reference_many</tt> association.
# (Generated by Mongoid)

##
# :method: parent
# Returns the document's parent (unless it's a root document). It's a <tt>referenced_in</tt> association.
# (Generated by Mongoid)

##
# :method: parent_ids
# Returns a list of the document's parent_ids, starting with the root node.
# (Generated by Mongoid)

##
# Forces rearranging of all children after next save
def rearrange_children!
@rearrange_children = true
end

##
# Will the children be rearranged after next save?
def rearrange_children?
!!@rearrange_children
end

private

def rearrange
if parent_id
self.parent_ids = self.class.find(self.parent_id).parent_ids + [self.parent_id]
end

rearrange_children! if self.parent_ids_changed?
return true
end

def rearrange_children
@rearrange_children = false
self.children.find(:all).each { |c| c.save }
end
end
end
2 changes: 2 additions & 0 deletions mongoid_tree.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@ Gem::Specification.new do |s|
s.add_runtime_dependency('mongoid', ['>= 2.0.0.beta9'])
s.add_development_dependency('rspec', ['>= 2.0.0.beta.18'])
s.add_development_dependency('autotest', ['>= 4.3.2'])
s.add_development_dependency('hanna', ['>= 0.1.12'])
s.add_development_dependency('grancher', ['>= 0.1.5'])
end
75 changes: 75 additions & 0 deletions spec/mongoid/tree_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
require 'spec_helper'

class Node
include Mongoid::Document
include Mongoid::Tree
end

describe Mongoid::Tree do

it "should reference many children as inverse of parent" do
a = Node.associations['children']
a.should_not be_nil
a.association.should == Mongoid::Associations::ReferencesMany
a.options.class_name.should == 'Node'
a.options.foreign_key.should == 'parent_id'
a.options.inverse_of.should == :parent
end

it "should be referenced in one parent as inverse of children" do
a = Node.associations['parent']
a.should_not be_nil
a.association.should == Mongoid::Associations::ReferencedIn
a.options.class_name.should == 'Node'
a.options.inverse_of.should == :children
end

it "should store parent_ids as Array with [] as default" do
f = Node.fields['parent_ids']
f.should_not be_nil
f.options[:type].should == Array
f.options[:default].should == []
end

it "should rebuild its parent_ids when saved" do
root = Node.create; child = Node.create; subchild = Node.create
root.children << child
child.children << subchild # Mongoid implicitly saves
subchild.parent_ids.should == [root.id, child.id]
end

it "should rebuild its children's parent_ids when its own parent_ids changed" do
root = Node.create; child = Node.create; subchild = Node.create; new_root = Node.create
root.children << child
child.children << subchild

new_root.children << child

subchild.reload
subchild.parent_ids.should == [new_root.id, child.id]
end

it "should correctly rebuild its descendants' parent_ids when moved into an other subtree" do
root = Node.create; child = Node.create; subchild = Node.create; subsubchild = Node.create
new_root = Node.create; new_child = Node.create

root.children << child
child.children << subchild
subchild.children << subsubchild

subsubchild.parent_ids.should == [root.id, child.id, subchild.id]

new_root.children << new_child
new_child.children << subchild
subsubchild.reload
subsubchild.parent_ids.should == [new_root.id, new_child.id, subchild.id]
end

it "should not rebuild its children's parent_ids when its not required" do
root = Node.create; child = Node.create;
root.children << child
root.should_not_receive(:rearrange_children)
root.save
end

end

0 comments on commit 606fbe5

Please sign in to comment.