acts_as_tree 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
data/README.rdoc ADDED
@@ -0,0 +1,31 @@
1
+ = ActsAsTree
2
+
3
+ Specify this +acts_as+ extension if you want to model a tree structure by providing a parent association and a children
4
+ association. This requires that you have a foreign key column, which by default is called +parent_id+.
5
+
6
+ == Install
7
+
8
+ gem install acts_as_tree --source http://gemcutter.org
9
+
10
+ == Example
11
+
12
+ class Category < ActiveRecord::Base
13
+ acts_as_tree :order => "name"
14
+ end
15
+
16
+ Example:
17
+ root
18
+ \_ child1
19
+ \_ subchild1
20
+ \_ subchild2
21
+
22
+ root = Category.create("name" => "root")
23
+ child1 = root.children.create("name" => "child1")
24
+ subchild1 = child1.children.create("name" => "subchild1")
25
+
26
+ root.parent # => nil
27
+ child1.parent # => root
28
+ root.children # => [child1]
29
+ root.children.first.children.first # => subchild1
30
+
31
+ Copyright (c) 2007 David Heinemeier Hansson, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,54 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "acts_as_tree"
8
+ gem.summary = %Q{Gem version of acts_as_tree Rails plugin.}
9
+ gem.description = %Q{Specify this acts_as extension if you want to model a tree structure by providing a parent association and a children association.}
10
+ gem.email = "erik.dahlstrand@gmail.com"
11
+ gem.homepage = "http://github.com/erdah/acts_as_tree"
12
+ gem.authors = ["Erik Dahlstrand", "Rails Core"]
13
+ end
14
+ rescue LoadError
15
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
16
+ end
17
+
18
+ require 'rake/testtask'
19
+ Rake::TestTask.new(:test) do |test|
20
+ test.libs << 'lib' << 'test'
21
+ test.pattern = 'test/**/*_test.rb'
22
+ test.verbose = true
23
+ end
24
+
25
+ begin
26
+ require 'rcov/rcovtask'
27
+ Rcov::RcovTask.new do |test|
28
+ test.libs << 'test'
29
+ test.pattern = 'test/**/*_test.rb'
30
+ test.verbose = true
31
+ end
32
+ rescue LoadError
33
+ task :rcov do
34
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
35
+ end
36
+ end
37
+
38
+ task :test => :check_dependencies
39
+
40
+ task :default => :test
41
+
42
+ require 'rake/rdoctask'
43
+ Rake::RDocTask.new do |rdoc|
44
+ if File.exist?('VERSION')
45
+ version = File.read('VERSION')
46
+ else
47
+ version = ""
48
+ end
49
+
50
+ rdoc.rdoc_dir = 'rdoc'
51
+ rdoc.title = "acts_as_tree #{version}"
52
+ rdoc.rdoc_files.include('README*')
53
+ rdoc.rdoc_files.include('lib/**/*.rb')
54
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,94 @@
1
+ module ActsAsTree
2
+ def self.included(base)
3
+ base.extend(ClassMethods)
4
+ end
5
+
6
+ # Specify this +acts_as+ extension if you want to model a tree structure by providing a parent association and a children
7
+ # association. This requires that you have a foreign key column, which by default is called +parent_id+.
8
+ #
9
+ # class Category < ActiveRecord::Base
10
+ # acts_as_tree :order => "name"
11
+ # end
12
+ #
13
+ # Example:
14
+ # root
15
+ # \_ child1
16
+ # \_ subchild1
17
+ # \_ subchild2
18
+ #
19
+ # root = Category.create("name" => "root")
20
+ # child1 = root.children.create("name" => "child1")
21
+ # subchild1 = child1.children.create("name" => "subchild1")
22
+ #
23
+ # root.parent # => nil
24
+ # child1.parent # => root
25
+ # root.children # => [child1]
26
+ # root.children.first.children.first # => subchild1
27
+ #
28
+ # In addition to the parent and children associations, the following instance methods are added to the class
29
+ # after calling <tt>acts_as_tree</tt>:
30
+ # * <tt>siblings</tt> - Returns all the children of the parent, excluding the current node (<tt>[subchild2]</tt> when called on <tt>subchild1</tt>)
31
+ # * <tt>self_and_siblings</tt> - Returns all the children of the parent, including the current node (<tt>[subchild1, subchild2]</tt> when called on <tt>subchild1</tt>)
32
+ # * <tt>ancestors</tt> - Returns all the ancestors of the current node (<tt>[child1, root]</tt> when called on <tt>subchild2</tt>)
33
+ # * <tt>root</tt> - Returns the root of the current node (<tt>root</tt> when called on <tt>subchild2</tt>)
34
+ module ClassMethods
35
+ # Configuration options are:
36
+ #
37
+ # * <tt>foreign_key</tt> - specifies the column name to use for tracking of the tree (default: +parent_id+)
38
+ # * <tt>order</tt> - makes it possible to sort the children according to this SQL snippet.
39
+ # * <tt>counter_cache</tt> - keeps a count in a +children_count+ column if set to +true+ (default: +false+).
40
+ def acts_as_tree(options = {})
41
+ configuration = { :foreign_key => "parent_id", :order => nil, :counter_cache => nil }
42
+ configuration.update(options) if options.is_a?(Hash)
43
+
44
+ belongs_to :parent, :class_name => name, :foreign_key => configuration[:foreign_key], :counter_cache => configuration[:counter_cache]
45
+ has_many :children, :class_name => name, :foreign_key => configuration[:foreign_key], :order => configuration[:order], :dependent => :destroy
46
+
47
+ class_eval <<-EOV
48
+ include ActsAsTree::InstanceMethods
49
+
50
+ def self.roots
51
+ find(:all, :conditions => "#{configuration[:foreign_key]} IS NULL", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}})
52
+ end
53
+
54
+ def self.root
55
+ find(:first, :conditions => "#{configuration[:foreign_key]} IS NULL", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}})
56
+ end
57
+ EOV
58
+ end
59
+ end
60
+
61
+ module InstanceMethods
62
+ # Returns list of ancestors, starting from parent until root.
63
+ #
64
+ # subchild1.ancestors # => [child1, root]
65
+ def ancestors
66
+ node, nodes = self, []
67
+ nodes << node = node.parent while node.parent
68
+ nodes
69
+ end
70
+
71
+ # Returns the root node of the tree.
72
+ def root
73
+ node = self
74
+ node = node.parent while node.parent
75
+ node
76
+ end
77
+
78
+ # Returns all siblings of the current node.
79
+ #
80
+ # subchild1.siblings # => [subchild2]
81
+ def siblings
82
+ self_and_siblings - [self]
83
+ end
84
+
85
+ # Returns all siblings and a reference to the current node.
86
+ #
87
+ # subchild1.self_and_siblings # => [subchild1, subchild2]
88
+ def self_and_siblings
89
+ parent ? parent.children : self.class.roots
90
+ end
91
+ end
92
+ end
93
+
94
+ ActiveRecord::Base.class_eval { include ActsAsTree }
@@ -0,0 +1,218 @@
1
+ require 'test/unit'
2
+
3
+ require 'rubygems'
4
+ require 'active_record'
5
+
6
+ require "#{File.dirname(__FILE__)}/../lib/acts_as_tree"
7
+
8
+ class Test::Unit::TestCase
9
+ def assert_queries(num = 1)
10
+ $query_count = 0
11
+ yield
12
+ ensure
13
+ assert_equal num, $query_count, "#{$query_count} instead of #{num} queries were executed."
14
+ end
15
+
16
+ def assert_no_queries(&block)
17
+ assert_queries(0, &block)
18
+ end
19
+ end
20
+
21
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :dbfile => ":memory:")
22
+
23
+ # AR keeps printing annoying schema statements
24
+ $stdout = StringIO.new
25
+
26
+ def setup_db
27
+ ActiveRecord::Base.logger
28
+ ActiveRecord::Schema.define(:version => 1) do
29
+ create_table :mixins do |t|
30
+ t.column :type, :string
31
+ t.column :parent_id, :integer
32
+ end
33
+ end
34
+ end
35
+
36
+ def teardown_db
37
+ ActiveRecord::Base.connection.tables.each do |table|
38
+ ActiveRecord::Base.connection.drop_table(table)
39
+ end
40
+ end
41
+
42
+ class Mixin < ActiveRecord::Base
43
+ end
44
+
45
+ class TreeMixin < Mixin
46
+ acts_as_tree :foreign_key => "parent_id", :order => "id"
47
+ end
48
+
49
+ class TreeMixinWithoutOrder < Mixin
50
+ acts_as_tree :foreign_key => "parent_id"
51
+ end
52
+
53
+ class RecursivelyCascadedTreeMixin < Mixin
54
+ acts_as_tree :foreign_key => "parent_id"
55
+ has_one :first_child, :class_name => 'RecursivelyCascadedTreeMixin', :foreign_key => :parent_id
56
+ end
57
+
58
+ class TreeTest < Test::Unit::TestCase
59
+
60
+ def setup
61
+ setup_db
62
+ @root1 = TreeMixin.create!
63
+ @root_child1 = TreeMixin.create! :parent_id => @root1.id
64
+ @child1_child = TreeMixin.create! :parent_id => @root_child1.id
65
+ @root_child2 = TreeMixin.create! :parent_id => @root1.id
66
+ @root2 = TreeMixin.create!
67
+ @root3 = TreeMixin.create!
68
+ end
69
+
70
+ def teardown
71
+ teardown_db
72
+ end
73
+
74
+ def test_children
75
+ assert_equal @root1.children, [@root_child1, @root_child2]
76
+ assert_equal @root_child1.children, [@child1_child]
77
+ assert_equal @child1_child.children, []
78
+ assert_equal @root_child2.children, []
79
+ end
80
+
81
+ def test_parent
82
+ assert_equal @root_child1.parent, @root1
83
+ assert_equal @root_child1.parent, @root_child2.parent
84
+ assert_nil @root1.parent
85
+ end
86
+
87
+ def test_delete
88
+ assert_equal 6, TreeMixin.count
89
+ @root1.destroy
90
+ assert_equal 2, TreeMixin.count
91
+ @root2.destroy
92
+ @root3.destroy
93
+ assert_equal 0, TreeMixin.count
94
+ end
95
+
96
+ def test_insert
97
+ @extra = @root1.children.create
98
+
99
+ assert @extra
100
+
101
+ assert_equal @extra.parent, @root1
102
+
103
+ assert_equal 3, @root1.children.size
104
+ assert @root1.children.include?(@extra)
105
+ assert @root1.children.include?(@root_child1)
106
+ assert @root1.children.include?(@root_child2)
107
+ end
108
+
109
+ def test_ancestors
110
+ assert_equal [], @root1.ancestors
111
+ assert_equal [@root1], @root_child1.ancestors
112
+ assert_equal [@root_child1, @root1], @child1_child.ancestors
113
+ assert_equal [@root1], @root_child2.ancestors
114
+ assert_equal [], @root2.ancestors
115
+ assert_equal [], @root3.ancestors
116
+ end
117
+
118
+ def test_root
119
+ assert_equal @root1, TreeMixin.root
120
+ assert_equal @root1, @root1.root
121
+ assert_equal @root1, @root_child1.root
122
+ assert_equal @root1, @child1_child.root
123
+ assert_equal @root1, @root_child2.root
124
+ assert_equal @root2, @root2.root
125
+ assert_equal @root3, @root3.root
126
+ end
127
+
128
+ def test_roots
129
+ assert_equal [@root1, @root2, @root3], TreeMixin.roots
130
+ end
131
+
132
+ def test_siblings
133
+ assert_equal [@root2, @root3], @root1.siblings
134
+ assert_equal [@root_child2], @root_child1.siblings
135
+ assert_equal [], @child1_child.siblings
136
+ assert_equal [@root_child1], @root_child2.siblings
137
+ assert_equal [@root1, @root3], @root2.siblings
138
+ assert_equal [@root1, @root2], @root3.siblings
139
+ end
140
+
141
+ def test_self_and_siblings
142
+ assert_equal [@root1, @root2, @root3], @root1.self_and_siblings
143
+ assert_equal [@root_child1, @root_child2], @root_child1.self_and_siblings
144
+ assert_equal [@child1_child], @child1_child.self_and_siblings
145
+ assert_equal [@root_child1, @root_child2], @root_child2.self_and_siblings
146
+ assert_equal [@root1, @root2, @root3], @root2.self_and_siblings
147
+ assert_equal [@root1, @root2, @root3], @root3.self_and_siblings
148
+ end
149
+ end
150
+
151
+ class TreeTestWithEagerLoading < Test::Unit::TestCase
152
+
153
+ def setup
154
+ teardown_db
155
+ setup_db
156
+ @root1 = TreeMixin.create!
157
+ @root_child1 = TreeMixin.create! :parent_id => @root1.id
158
+ @child1_child = TreeMixin.create! :parent_id => @root_child1.id
159
+ @root_child2 = TreeMixin.create! :parent_id => @root1.id
160
+ @root2 = TreeMixin.create!
161
+ @root3 = TreeMixin.create!
162
+
163
+ @rc1 = RecursivelyCascadedTreeMixin.create!
164
+ @rc2 = RecursivelyCascadedTreeMixin.create! :parent_id => @rc1.id
165
+ @rc3 = RecursivelyCascadedTreeMixin.create! :parent_id => @rc2.id
166
+ @rc4 = RecursivelyCascadedTreeMixin.create! :parent_id => @rc3.id
167
+ end
168
+
169
+ def teardown
170
+ teardown_db
171
+ end
172
+
173
+ def test_eager_association_loading
174
+ roots = TreeMixin.find(:all, :include => :children, :conditions => "mixins.parent_id IS NULL", :order => "mixins.id")
175
+ assert_equal [@root1, @root2, @root3], roots
176
+ assert_no_queries do
177
+ assert_equal 2, roots[0].children.size
178
+ assert_equal 0, roots[1].children.size
179
+ assert_equal 0, roots[2].children.size
180
+ end
181
+ end
182
+
183
+ def test_eager_association_loading_with_recursive_cascading_three_levels_has_many
184
+ root_node = RecursivelyCascadedTreeMixin.find(:first, :include => { :children => { :children => :children } }, :order => 'mixins.id')
185
+ assert_equal @rc4, assert_no_queries { root_node.children.first.children.first.children.first }
186
+ end
187
+
188
+ def test_eager_association_loading_with_recursive_cascading_three_levels_has_one
189
+ root_node = RecursivelyCascadedTreeMixin.find(:first, :include => { :first_child => { :first_child => :first_child } }, :order => 'mixins.id')
190
+ assert_equal @rc4, assert_no_queries { root_node.first_child.first_child.first_child }
191
+ end
192
+
193
+ def test_eager_association_loading_with_recursive_cascading_three_levels_belongs_to
194
+ leaf_node = RecursivelyCascadedTreeMixin.find(:first, :include => { :parent => { :parent => :parent } }, :order => 'mixins.id DESC')
195
+ assert_equal @rc1, assert_no_queries { leaf_node.parent.parent.parent }
196
+ end
197
+ end
198
+
199
+ class TreeTestWithoutOrder < Test::Unit::TestCase
200
+
201
+ def setup
202
+ setup_db
203
+ @root1 = TreeMixinWithoutOrder.create!
204
+ @root2 = TreeMixinWithoutOrder.create!
205
+ end
206
+
207
+ def teardown
208
+ teardown_db
209
+ end
210
+
211
+ def test_root
212
+ assert [@root1, @root2].include?(TreeMixinWithoutOrder.root)
213
+ end
214
+
215
+ def test_roots
216
+ assert_equal [], [@root1, @root2] - TreeMixinWithoutOrder.roots
217
+ end
218
+ end
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: acts_as_tree
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Erik Dahlstrand
8
+ - Rails Core
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2009-10-09 00:00:00 +02:00
14
+ default_executable:
15
+ dependencies: []
16
+
17
+ description: Specify this acts_as extension if you want to model a tree structure by providing a parent association and a children association.
18
+ email: erik.dahlstrand@gmail.com
19
+ executables: []
20
+
21
+ extensions: []
22
+
23
+ extra_rdoc_files:
24
+ - README.rdoc
25
+ files:
26
+ - .document
27
+ - .gitignore
28
+ - README.rdoc
29
+ - Rakefile
30
+ - VERSION
31
+ - lib/acts_as_tree.rb
32
+ - test/acts_as_tree_test.rb
33
+ has_rdoc: true
34
+ homepage: http://github.com/erdah/acts_as_tree
35
+ licenses: []
36
+
37
+ post_install_message:
38
+ rdoc_options:
39
+ - --charset=UTF-8
40
+ require_paths:
41
+ - lib
42
+ required_ruby_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: "0"
47
+ version:
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: "0"
53
+ version:
54
+ requirements: []
55
+
56
+ rubyforge_project:
57
+ rubygems_version: 1.3.5
58
+ signing_key:
59
+ specification_version: 3
60
+ summary: Gem version of acts_as_tree Rails plugin.
61
+ test_files:
62
+ - test/acts_as_tree_test.rb