Permalink
Browse files

Tests

  • Loading branch information...
ramdiv committed Dec 20, 2009
1 parent b00dc45 commit a3c88fda930c8807c0e8078fa9d66c297e3d2406
View
@@ -1,7 +1,35 @@
-= mongo_mapper_acts_as_tree
+= mongo_mapper-acts_as_tree
-Description goes here.
+This is an implementation of a tree structure for MongoMapper.
+== Installation
+
+Install as gem
+
+ gem install ramdiv-mongo_mapper_acts_as_tree
+
+== Usage
+
+Enable the tree functionality by declaring acts_as_tree on your model
+
+ class Category
+ include MongoMapper::Document
+ include MongoMapper::Acts::Tree
+
+ key :name, String
+
+ acts_as_tree
+ end
+
+The method accepts :parent_id_field, :path_field, :depth_field, :order as a hash,
+:parent_id_field, :path_field, :depth_field => override the default field names
+:order => control the order (format "_field-name_ _[asc|desc]_")
+
+Check the test_tree.rb for examples.
+
+== !WARNING!
+
+This is not production-ready code. Bugs are to be expected.
== Note on Patches/Pull Requests
* Fork the project.
View
@@ -4,14 +4,15 @@ require 'rake'
begin
require 'jeweler'
Jeweler::Tasks.new do |gem|
- gem.name = "mongo_mapper_acts_as_tree"
- gem.summary = %Q{TODO: one-line summary of your gem}
- gem.description = %Q{TODO: longer description of your gem}
+ gem.name = "ramdiv-mongo_mapper_acts_as_tree"
+ gem.summary = %Q{ActsAsTree plugin for MongoMapper}
+ gem.description = %Q{Port of the old, venerable ActsAsTree with a bit of a twist}
gem.email = "jakob.vidmar@gmail.com"
gem.homepage = "http://github.com/ramdiv/mongo_mapper_acts_as_tree"
gem.authors = ["Jakob Vidmar"]
- gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
- # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
+ gem.add_dependency("mongo_mapper", ">= 0.6.8")
+
+ gem.add_development_dependency "shoulda", ">=2.10.2"
end
Jeweler::GemcutterTasks.new
rescue LoadError
@@ -0,0 +1,177 @@
+require "mongo_mapper"
+
+module MongoMapper
+ module Acts
+ module Tree
+ def self.included(model)
+ model.class_eval do
+ extend InitializerMethods
+ end
+ end
+
+ module InitializerMethods
+ def acts_as_tree(options = {})
+ options = {
+ :parent_id_field => "parent_id",
+ :path_field => "path",
+ :depth_field => "depth"
+ }.merge(options)
+
+ write_inheritable_attribute :acts_as_tree_options, options
+ class_inheritable_reader :acts_as_tree_options
+
+ include InstanceMethods
+ include Fields
+ extend Fields
+ extend ClassMethods
+
+ key parent_id_field, String
+ key path_field, String, :default => ""
+ key depth_field, Integer, :default => 0
+
+ after_save :move_children
+ before_save :will_save_tree
+ # before_destroy :destroy_descendants
+ end
+ end
+
+ module ClassMethods
+ def roots
+ self.find(:all, :conditions => {parent_id_field => nil}, :order => tree_order)
+ end
+ end
+
+ module InstanceMethods
+ def ==(other)
+ return true if other.equal?(self)
+ return true if other.instance_of?(self.class) and other._id == self._id
+ false
+ end
+
+ def parent=(var)
+ var = self.find(var) if var.is_a? String
+
+ if self.descendents.include? var
+ @_cyclic = true
+ else
+ @_parent = var
+ fix_position
+ @_will_move = true
+ end
+ end
+
+ def will_save_tree
+ !@_cyclic
+ end
+
+ def fix_position
+ if parent.nil?
+ self[parent_id_field] = nil
+ self[path_field] = ""
+ self[depth_field] = 0
+ else
+ self[parent_id_field] = parent.id
+ self[path_field] = parent[path_field] + ":" + parent.id.to_s
+ self[depth_field] = parent[depth_field] + 1
+ end
+ end
+
+ def parent
+ @_parent or (self[parent_id_field] ? self.class.find(self[parent_id_field]) : nil)
+ end
+
+ def root?
+ self[parent_id_field].nil?
+ end
+
+ def root
+ self.class.find(self[path_field].split(":")[1]) or self
+ end
+
+ def ancestors
+ return [] if root?
+ self.class.find(self[path_field].split(":")[1..-1].collect{|i| Mongo::ObjectID.from_string(i)})
+ end
+
+ def self_and_ancestors
+ ancestors << self
+ end
+
+ def siblings
+ self.class.find(:all, :conditions => {:_id => {"$ne" => self._id}, parent_id_field => self[parent_id_field]}, :order => tree_order)
+ end
+
+ def self_and_siblings
+ self.class.find(:all, :conditions => {parent_id_field => self[parent_id_field]}, :order => tree_order)
+ end
+
+ def children
+ self.class.find(:all, :conditions => {parent_id_field => self._id.to_s}, :order => tree_order)
+ end
+
+ def descendents
+ return [] if new_record?
+ sorting_options = tree_order.split(",").map(&:strip).map(&:split).map{|item| [item[0], ((item[1].nil? or item[1].downcase == "asc") ? "asc" : "desc")]}.flatten
+ self.class.collection.find({path_field => /#{self._id}/}, {:sort => sorting_options}).map{|i| self.class.new(i)} or []
+ end
+
+ def self_and_descendents
+ [self] + self.descendents
+ end
+
+ def is_ancestor_of?(other)
+ !(other[path_field] =~ /#{self._id}/).nil?
+ end
+
+ def is_or_is_ancestor_of?(other)
+ (other == self) or is_ancestor_of?(other)
+ end
+
+ def is_descendant_of?(other)
+ !(self[path_field] =~ /#{other._id}/).nil?
+ end
+
+ def is_or_is_descendant_of?(other)
+ (other == self) or is_descendant_of?(other)
+ end
+
+ def is_sibling_of?(other)
+ (other != self) and (other[parent_id_field] == self[parent_id_field])
+ end
+
+ def is_or_is_sibling_of?(other)
+ (other == self) or is_sibling_of?(other)
+ end
+
+ def move_children
+ if @_will_move
+ @_will_move = false
+ for child in self.children
+ child.fix_position
+ child.save
+ end
+ @_will_move = true
+ end
+ end
+ end
+
+ module Fields
+ def parent_id_field
+ acts_as_tree_options[:parent_id_field]
+ end
+
+ def path_field
+ acts_as_tree_options[:path_field]
+ end
+
+ def depth_field
+ acts_as_tree_options[:depth_field]
+ end
+
+ def tree_order
+ acts_as_tree_options[:order] or ""
+ end
+ end
+ end
+ end
+end
@@ -0,0 +1,58 @@
+# Generated by jeweler
+# DO NOT EDIT THIS FILE DIRECTLY
+# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
+# -*- encoding: utf-8 -*-
+
+Gem::Specification.new do |s|
+ s.name = %q{ramdiv-mongo_mapper_acts_as_tree}
+ s.version = "0.0.0"
+
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
+ s.authors = ["Jakob Vidmar"]
+ s.date = %q{2009-12-20}
+ s.description = %q{Port of the old, venerable ActsAsTree with a bit of a twist}
+ s.email = %q{jakob.vidmar@gmail.com}
+ s.extra_rdoc_files = [
+ "LICENSE",
+ "README.rdoc"
+ ]
+ s.files = [
+ ".document",
+ ".gitignore",
+ "LICENSE",
+ "README.rdoc",
+ "Rakefile",
+ "VERSION",
+ "lib/mongo_mapper_acts_as_tree.rb",
+ "test/helper.rb"
+ ]
+ s.homepage = %q{http://github.com/ramdiv/mongo_mapper_acts_as_tree}
+ s.rdoc_options = ["--charset=UTF-8"]
+ s.require_paths = ["lib"]
+ s.rubygems_version = %q{1.3.5}
+ s.summary = %q{ActsAsTree plugin for MongoMapper}
+ s.test_files = [
+ "test/helper.rb",
+ "test/models/category.rb",
+ "test/models/ordered_category.rb",
+ "test/test_order.rb",
+ "test/test_tree.rb"
+ ]
+
+ if s.respond_to? :specification_version then
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
+ s.specification_version = 3
+
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
+ s.add_runtime_dependency(%q<mongo_mapper>, [">= 0.6.8"])
+ s.add_development_dependency(%q<shoulda>, [">= 2.10.2"])
+ else
+ s.add_dependency(%q<mongo_mapper>, [">= 0.6.8"])
+ s.add_dependency(%q<shoulda>, [">= 2.10.2"])
+ end
+ else
+ s.add_dependency(%q<mongo_mapper>, [">= 0.6.8"])
+ s.add_dependency(%q<shoulda>, [">= 2.10.2"])
+ end
+end
+
View
@@ -4,7 +4,29 @@
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
$LOAD_PATH.unshift(File.dirname(__FILE__))
-require 'mongo_mapper_acts_as_tree'
+require 'mongo_mapper'
+
+MongoMapper.database = "acts_as_tree-test"
+
+Dir["#{File.dirname(__FILE__)}/models/*.rb"].each {|file| require file}
class Test::Unit::TestCase
+ # Drop all columns after each test case.
+ def teardown
+ MongoMapper.database.collections.each do |coll|
+ coll.remove
+ end
+ end
+
+ # Make sure that each test case has a teardown
+ # method to clear the db after each test.
+ def inherited(base)
+ base.define_method teardown do
+ super
+ end
+ end
+
+ def eql_arrays?(first, second)
+ first.collect(&:_id).to_set == second.collect(&:_id).to_set
+ end
end
View
@@ -0,0 +1,11 @@
+require "mongo_mapper"
+require "mongo_mapper_acts_as_tree"
+
+class Category
+ include MongoMapper::Document
+ include MongoMapper::Acts::Tree
+
+ key :name, String
+
+ acts_as_tree
+end
@@ -0,0 +1,12 @@
+require "mongo_mapper"
+require "mongo_mapper_acts_as_tree"
+
+class OrderedCategory
+ include MongoMapper::Document
+ include MongoMapper::Acts::Tree
+
+ key :name, String
+ key :value, Integer
+
+ acts_as_tree :order => "value asc"
+end
View
@@ -0,0 +1,28 @@
+require 'helper'
+require 'set'
+
+class TestMongomapperActsAsTree < Test::Unit::TestCase
+ context "Ordered tree" do
+ setup do
+ @root_1 = OrderedCategory.create(:name => "Root 1", :value => 2)
+ @child_1 = OrderedCategory.create(:name => "Child 1", :parent => @root_1, :value => 1)
+ @child_2 = OrderedCategory.create(:name => "Child 2", :parent => @root_1, :value => 9)
+ @child_2_1 = OrderedCategory.create(:name => "Child 2.1", :parent => @child_2, :value => 2)
+ @child_3 = OrderedCategory.create(:name => "Child 3", :parent => @root_1, :value => 5)
+ @root_2 = OrderedCategory.create(:name => "Root 2", :value => 1)
+ end
+
+ should "be in order" do
+ assert_equal OrderedCategory.roots, [@root_2, @root_1]
+
+ assert_equal @root_1.children, [@child_1, @child_3, @child_2]
+
+ assert_equal @root_1.descendents, [@child_1, @child_2_1, @child_3, @child_2]
+ assert_equal @root_1.self_and_descendents, [@root_1, @child_1, @child_2_1, @child_3, @child_2]
+
+ assert_equal @child_2.siblings, [@child_1, @child_3]
+ assert_equal @child_2.self_and_siblings, [@child_1, @child_3, @child_2]
+ assert_equal @root_1.self_and_siblings, [@root_2, @root_1]
+ end
+ end
+end
Oops, something went wrong.

0 comments on commit a3c88fd

Please sign in to comment.