mongo_mapper_acts_as_tree 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc ADDED
@@ -0,0 +1,49 @@
1
+ = mongo_mapper_acts_as_tree
2
+
3
+ This is a port of classic Rails {acts_as_tree}[http://github.com/rails/acts_as_tree] to Mongo Mapper. Specify this MongoMapper plugin if you want to model a tree structure by providing a parent association and a children association. This requires that you have a foreign key, which by default is called parent_id.
4
+
5
+ It has (almost) the same functionality and passes the original test-suite. Scope needs to be defined as symbol or array of symbols. It does not work for Embedded Documents. Please note, it is not yet optimized and therefore issues more queries than necessary.
6
+
7
+ == Installation
8
+
9
+ mongo_mapper_acts_as_list is available as RubyGem:
10
+
11
+ gem install mongo_mapper_acts_as_tree
12
+
13
+ == Example
14
+
15
+ class Category
16
+ include MongoMapper::Document
17
+
18
+ plugin MongoMapper::Plugins::ActsAsTree
19
+
20
+ key :parent_id, ObjectId
21
+ acts_as_tree :order => :name
22
+ end
23
+
24
+ Example:
25
+ root
26
+ \_ child1
27
+ \_ subchild1
28
+ \_ subchild2
29
+
30
+ root = Category.create(:name => "root")
31
+ child1 = root.children.create(:name => "child1")
32
+ subchild1 = child1.children.create(:name => "subchild1")
33
+
34
+ root.parent # => nil
35
+ child1.parent # => root
36
+ root.children # => [child1]
37
+ root.children.first.children.first # => subchild1
38
+
39
+ == Note on Patches/Pull Requests
40
+
41
+ * Fork the project.
42
+ * Make your feature addition or bug fix.
43
+ * Add tests for it. This is important so I don't break it in a future version unintentionally.
44
+ * Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
45
+ * Send me a pull request. Bonus points for topic branches.
46
+
47
+ == Copyright
48
+
49
+ Original Rails acts_as_tree Copyright (c) 2007 David Heinemeier Hansson, released under the MIT license
@@ -0,0 +1,63 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module ActsAsTree
4
+
5
+ require 'mongo_mapper'
6
+
7
+ module ClassMethods
8
+ def acts_as_tree(options = {})
9
+ configuration = { :foreign_key => :parent_id, :order => nil, :counter_cache => nil }
10
+ configuration.update(options) if options.is_a?(Hash)
11
+
12
+ belongs_to :parent, :class_name => name, :foreign_key => configuration[:foreign_key], :counter_cache => configuration[:counter_cache]
13
+ many :children, :class_name => name, :foreign_key => configuration[:foreign_key], :order => configuration[:order], :dependent => :destroy
14
+
15
+ class_eval <<-EOV
16
+ include MongoMapper::Plugins::ActsAsTree::InstanceMethods
17
+
18
+ def self.roots
19
+ where("#{configuration[:foreign_key]}".to_sym => nil).sort("#{configuration[:order]}").all
20
+ end
21
+
22
+ def self.root
23
+ where("#{configuration[:foreign_key]}".to_sym => nil).sort("#{configuration[:order]}").first
24
+ end
25
+ EOV
26
+ end
27
+ end
28
+
29
+ module InstanceMethods
30
+ # Returns list of ancestors, starting from parent until root.
31
+ #
32
+ # subchild1.ancestors # => [child1, root]
33
+ def ancestors
34
+ node, nodes = self, []
35
+ nodes << node = node.parent while node.parent?
36
+ nodes
37
+ end
38
+
39
+ # Returns the root node of the tree.
40
+ def root
41
+ node = self
42
+ node = node.parent while node.parent?
43
+ node
44
+ end
45
+
46
+ # Returns all siblings of the current node.
47
+ #
48
+ # subchild1.siblings # => [subchild2]
49
+ def siblings
50
+ self_and_siblings - [self]
51
+ end
52
+
53
+ # Returns all siblings and a reference to the current node.
54
+ #
55
+ # subchild1.self_and_siblings # => [subchild1, subchild2]
56
+ def self_and_siblings
57
+ parent? ? parent.children : self.class.roots
58
+ end
59
+ end
60
+
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,8 @@
1
+ # encoding: UTF-8
2
+ module MongoMapper
3
+ module Plugins
4
+ module ActsAsTree
5
+ Version = '0.1'
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,202 @@
1
+ require 'test_helper'
2
+
3
+
4
+
5
+ # SETUP TEST
6
+
7
+ # class ActiveSupport::TestCase
8
+ # def assert_queries(num = 1)
9
+ # $query_count = 0
10
+ # yield
11
+ # ensure
12
+ # assert_equal num, $query_count, "#{$query_count} instead of #{num} queries were executed."
13
+ # end
14
+ #
15
+ # def assert_no_queries(&block)
16
+ # assert_queries(0, &block)
17
+ # end
18
+ # end
19
+
20
+
21
+
22
+ # SETUP CLASSES
23
+
24
+ class Mixin
25
+ include MongoMapper::Document
26
+ plugin MongoMapper::Plugins::ActsAsTree
27
+ key :parent_id, ObjectId
28
+ end
29
+
30
+ class TreeMixin < Mixin
31
+ acts_as_tree :foreign_key => :parent_id, :order => :id
32
+ end
33
+
34
+ class TreeMixinWithoutOrder < Mixin
35
+ acts_as_tree :foreign_key => :parent_id
36
+ end
37
+
38
+ class RecursivelyCascadedTreeMixin < Mixin
39
+ acts_as_tree :foreign_key => :parent_id
40
+ has_one :first_child, :class_name => 'RecursivelyCascadedTreeMixin', :foreign_key => :parent_id
41
+ end
42
+
43
+
44
+
45
+ # TESTS
46
+
47
+ class TreeTest < ActiveSupport::TestCase
48
+
49
+ def setup
50
+ @root1 = TreeMixin.create!
51
+ @root_child1 = TreeMixin.create! :parent_id => @root1.id
52
+ @child1_child = TreeMixin.create! :parent_id => @root_child1.id
53
+ @root_child2 = TreeMixin.create! :parent_id => @root1.id
54
+ @root2 = TreeMixin.create!
55
+ @root3 = TreeMixin.create!
56
+ end
57
+
58
+ def test_children
59
+ assert_equal @root1.children, [@root_child1, @root_child2]
60
+ assert_equal @root_child1.children, [@child1_child]
61
+ assert_equal @child1_child.children, []
62
+ assert_equal @root_child2.children, []
63
+ end
64
+
65
+
66
+ def test_parent
67
+ assert_equal @root_child1.parent, @root1
68
+ assert_equal @root_child1.parent, @root_child2.parent
69
+ assert @root1.parent.nil?
70
+ end
71
+
72
+ def test_delete
73
+ assert_equal 6, TreeMixin.count
74
+ @root1.destroy
75
+ assert_equal 2, TreeMixin.count
76
+ @root2.destroy
77
+ @root3.destroy
78
+ assert_equal 0, TreeMixin.count
79
+ end
80
+
81
+ def test_insert
82
+ @extra = @root1.children.create
83
+
84
+ assert @extra
85
+
86
+ assert_equal @extra.parent, @root1
87
+
88
+ assert_equal 3, @root1.children.size
89
+ assert @root1.children.include?(@extra)
90
+ assert @root1.children.include?(@root_child1)
91
+ assert @root1.children.include?(@root_child2)
92
+ end
93
+
94
+ def test_ancestors
95
+ assert_equal [], @root1.ancestors
96
+ assert_equal [@root1], @root_child1.ancestors
97
+ assert_equal [@root_child1, @root1], @child1_child.ancestors
98
+ assert_equal [@root1], @root_child2.ancestors
99
+ assert_equal [], @root2.ancestors
100
+ assert_equal [], @root3.ancestors
101
+ end
102
+
103
+ def test_root
104
+ assert_equal @root1, TreeMixin.root
105
+ assert_equal @root1, @root1.root
106
+ assert_equal @root1, @root_child1.root
107
+ assert_equal @root1, @child1_child.root
108
+ assert_equal @root1, @root_child2.root
109
+ assert_equal @root2, @root2.root
110
+ assert_equal @root3, @root3.root
111
+ end
112
+
113
+ def test_roots
114
+ assert_equal [@root1, @root2, @root3], TreeMixin.roots
115
+ end
116
+
117
+ def test_siblings
118
+ assert_equal [@root2, @root3], @root1.siblings
119
+ assert_equal [@root_child2], @root_child1.siblings
120
+ assert_equal [], @child1_child.siblings
121
+ assert_equal [@root_child1], @root_child2.siblings
122
+ assert_equal [@root1, @root3], @root2.siblings
123
+ assert_equal [@root1, @root2], @root3.siblings
124
+ end
125
+
126
+ def test_self_and_siblings
127
+ assert_equal [@root1, @root2, @root3], @root1.self_and_siblings
128
+ assert_equal [@root_child1, @root_child2], @root_child1.self_and_siblings
129
+ assert_equal [@child1_child], @child1_child.self_and_siblings
130
+ assert_equal [@root_child1, @root_child2], @root_child2.self_and_siblings
131
+ assert_equal [@root1, @root2, @root3], @root2.self_and_siblings
132
+ assert_equal [@root1, @root2, @root3], @root3.self_and_siblings
133
+ end
134
+ end
135
+
136
+
137
+ # SKIP THIS AS THERE IS NO EAGER LOADING IN MONGOMAPPER
138
+
139
+ # class TreeTestWithEagerLoading < Test::Unit::TestCase
140
+ #
141
+ # def setup
142
+ # teardown_db
143
+ # setup_db
144
+ # @root1 = TreeMixin.create!
145
+ # @root_child1 = TreeMixin.create! :parent_id => @root1.id
146
+ # @child1_child = TreeMixin.create! :parent_id => @root_child1.id
147
+ # @root_child2 = TreeMixin.create! :parent_id => @root1.id
148
+ # @root2 = TreeMixin.create!
149
+ # @root3 = TreeMixin.create!
150
+ #
151
+ # @rc1 = RecursivelyCascadedTreeMixin.create!
152
+ # @rc2 = RecursivelyCascadedTreeMixin.create! :parent_id => @rc1.id
153
+ # @rc3 = RecursivelyCascadedTreeMixin.create! :parent_id => @rc2.id
154
+ # @rc4 = RecursivelyCascadedTreeMixin.create! :parent_id => @rc3.id
155
+ # end
156
+ #
157
+ # def test_eager_association_loading
158
+ # roots = TreeMixin.find(:all, :include => :children, :conditions => "mixins.parent_id IS NULL", :order => "mixins.id")
159
+ # assert_equal [@root1, @root2, @root3], roots
160
+ # assert_no_queries do
161
+ # assert_equal 2, roots[0].children.size
162
+ # assert_equal 0, roots[1].children.size
163
+ # assert_equal 0, roots[2].children.size
164
+ # end
165
+ # end
166
+ #
167
+ # def test_eager_association_loading_with_recursive_cascading_three_levels_has_many
168
+ # root_node = RecursivelyCascadedTreeMixin.find(:first, :include => { :children => { :children => :children } }, :order => 'mixins.id')
169
+ # assert_equal @rc4, assert_no_queries { root_node.children.first.children.first.children.first }
170
+ # end
171
+ #
172
+ # def test_eager_association_loading_with_recursive_cascading_three_levels_has_one
173
+ # root_node = RecursivelyCascadedTreeMixin.find(:first, :include => { :first_child => { :first_child => :first_child } }, :order => 'mixins.id')
174
+ # assert_equal @rc4, assert_no_queries { root_node.first_child.first_child.first_child }
175
+ # end
176
+ #
177
+ # def test_eager_association_loading_with_recursive_cascading_three_levels_belongs_to
178
+ # leaf_node = RecursivelyCascadedTreeMixin.find(:first, :include => { :parent => { :parent => :parent } }, :order => 'mixins.id DESC')
179
+ # assert_equal @rc1, assert_no_queries { leaf_node.parent.parent.parent }
180
+ # end
181
+ # end
182
+
183
+
184
+
185
+
186
+
187
+
188
+ class TreeTestWithoutOrder < ActiveSupport::TestCase
189
+
190
+ def setup
191
+ @root1 = TreeMixinWithoutOrder.create!
192
+ @root2 = TreeMixinWithoutOrder.create!
193
+ end
194
+
195
+ def test_root
196
+ assert [@root1, @root2].include?(TreeMixinWithoutOrder.root)
197
+ end
198
+
199
+ def test_roots
200
+ assert_equal [], [@root1, @root2] - TreeMixinWithoutOrder.roots
201
+ end
202
+ end
@@ -0,0 +1,31 @@
1
+ require 'rubygems'
2
+ $:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
3
+
4
+ require 'mongo_mapper'
5
+ require 'mongo_mapper/plugins/acts_as_tree'
6
+ require 'test/unit'
7
+
8
+
9
+
10
+ class ActiveSupport::TestCase
11
+
12
+ # Drop all collections after each test case.
13
+ def teardown
14
+ MongoMapper.database.collections.each { |coll| coll.remove }
15
+ end
16
+
17
+ # Make sure that each test case has a teardown
18
+ # method to clear the db after each test.
19
+ def inherited(base)
20
+ base.define_method teardown do
21
+ super
22
+ end
23
+ end
24
+
25
+ end
26
+
27
+
28
+
29
+ MongoMapper.connection = Mongo::Connection.new('127.0.0.1', 27017)
30
+ MongoMapper.database = "mongo_mapper_acts_as_tree_test"
31
+ MongoMapper.database.collections.each { |c| c.drop_indexes }
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mongo_mapper_acts_as_tree
3
+ version: !ruby/object:Gem::Version
4
+ hash: 9
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ version: "0.1"
10
+ platform: ruby
11
+ authors:
12
+ - Tomas Celizna
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-10-31 01:00:00 +02:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: mongo_mapper
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: rake
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ hash: 3
43
+ segments:
44
+ - 0
45
+ version: "0"
46
+ type: :development
47
+ version_requirements: *id002
48
+ description:
49
+ email:
50
+ - tomas.celizna@gmail.com
51
+ executables: []
52
+
53
+ extensions: []
54
+
55
+ extra_rdoc_files: []
56
+
57
+ files:
58
+ - lib/mongo_mapper/plugins/acts_as_tree.rb
59
+ - lib/mongo_mapper/plugins/version.rb
60
+ - test/acts_as_tree_test.rb
61
+ - test/test_helper.rb
62
+ - README.rdoc
63
+ has_rdoc: true
64
+ homepage: http://github.com/tomasc/mongo_mapper_acts_as_tree
65
+ licenses: []
66
+
67
+ post_install_message:
68
+ rdoc_options: []
69
+
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ hash: 3
78
+ segments:
79
+ - 0
80
+ version: "0"
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ hash: 3
87
+ segments:
88
+ - 0
89
+ version: "0"
90
+ requirements: []
91
+
92
+ rubyforge_project:
93
+ rubygems_version: 1.3.7
94
+ signing_key:
95
+ specification_version: 3
96
+ summary: Port of classic Rails ActsAsTree for MongoMapper
97
+ test_files: []
98
+