acts_as_tree 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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