mdeering-acts_as_tree 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,2 @@
1
+ README.rdoc
2
+ lib/**/*.rb
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ doc
5
+ rdoc
6
+ pkg
data/README.rdoc ADDED
@@ -0,0 +1,31 @@
1
+ = acts_as_tree
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 = "mdeering-acts_as_tree"
8
+ gem.summary = %Q{Gem version of acts_as_tree Rails plugin. With association callbacks added in.}
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 = "mdeering@mdeering.com"
11
+ gem.homepage = "http://github.com/mdeering/acts_as_tree"
12
+ gem.authors = ["Michael Deering", "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.2
@@ -0,0 +1,96 @@
1
+ require 'active_record/base'
2
+
3
+ module ActsAsTree
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ # Specify this +acts_as+ extension if you want to model a tree structure by providing a parent association and a children
9
+ # association. This requires that you have a foreign key column, which by default is called +parent_id+.
10
+ #
11
+ # class Category < ActiveRecord::Base
12
+ # acts_as_tree :order => "name"
13
+ # end
14
+ #
15
+ # Example:
16
+ # root
17
+ # \_ child1
18
+ # \_ subchild1
19
+ # \_ subchild2
20
+ #
21
+ # root = Category.create("name" => "root")
22
+ # child1 = root.children.create("name" => "child1")
23
+ # subchild1 = child1.children.create("name" => "subchild1")
24
+ #
25
+ # root.parent # => nil
26
+ # child1.parent # => root
27
+ # root.children # => [child1]
28
+ # root.children.first.children.first # => subchild1
29
+ #
30
+ # In addition to the parent and children associations, the following instance methods are added to the class
31
+ # after calling <tt>acts_as_tree</tt>:
32
+ # * <tt>siblings</tt> - Returns all the children of the parent, excluding the current node (<tt>[subchild2]</tt> when called on <tt>subchild1</tt>)
33
+ # * <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>)
34
+ # * <tt>ancestors</tt> - Returns all the ancestors of the current node (<tt>[child1, root]</tt> when called on <tt>subchild2</tt>)
35
+ # * <tt>root</tt> - Returns the root of the current node (<tt>root</tt> when called on <tt>subchild2</tt>)
36
+ module ClassMethods
37
+ # Configuration options are:
38
+ #
39
+ # * <tt>foreign_key</tt> - specifies the column name to use for tracking of the tree (default: +parent_id+)
40
+ # * <tt>order</tt> - makes it possible to sort the children according to this SQL snippet.
41
+ # * <tt>counter_cache</tt> - keeps a count in a +children_count+ column if set to +true+ (default: +false+).
42
+ def acts_as_tree(options = {})
43
+ configuration = { :foreign_key => "parent_id", :order => nil, :counter_cache => nil, :after_add => [], :before_add => [], :after_remove => [], :before_remove => [] }
44
+ configuration.update(options) if options.is_a?(Hash)
45
+
46
+ belongs_to :parent, :class_name => name, :foreign_key => configuration[:foreign_key], :counter_cache => configuration[:counter_cache]
47
+ has_many :children, :class_name => name, :foreign_key => configuration[:foreign_key], :order => configuration[:order], :dependent => :destroy, :before_add => configuration[:before_add], :after_add => configuration[:after_add], :before_remove => configuration[:before_remove], :after_remove => configuration[:after_remove]
48
+
49
+ class_eval <<-EOV
50
+ include ActsAsTree::InstanceMethods
51
+
52
+ def self.roots
53
+ find(:all, :conditions => "#{configuration[:foreign_key]} IS NULL", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}})
54
+ end
55
+
56
+ def self.root
57
+ find(:first, :conditions => "#{configuration[:foreign_key]} IS NULL", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}})
58
+ end
59
+ EOV
60
+ end
61
+ end
62
+
63
+ module InstanceMethods
64
+ # Returns list of ancestors, starting from parent until root.
65
+ #
66
+ # subchild1.ancestors # => [child1, root]
67
+ def ancestors
68
+ node, nodes = self, []
69
+ nodes << node = node.parent while node.parent
70
+ nodes
71
+ end
72
+
73
+ # Returns the root node of the tree.
74
+ def root
75
+ node = self
76
+ node = node.parent while node.parent
77
+ node
78
+ end
79
+
80
+ # Returns all siblings of the current node.
81
+ #
82
+ # subchild1.siblings # => [subchild2]
83
+ def siblings
84
+ self_and_siblings - [self]
85
+ end
86
+
87
+ # Returns all siblings and a reference to the current node.
88
+ #
89
+ # subchild1.self_and_siblings # => [subchild1, subchild2]
90
+ def self_and_siblings
91
+ parent ? parent.children : self.class.roots
92
+ end
93
+ end
94
+ end
95
+
96
+ ActiveRecord::Base.class_eval { include ActsAsTree }
@@ -0,0 +1,47 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{mdeering-acts_as_tree}
8
+ s.version = "0.1.2"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Michael Deering", "Erik Dahlstrand", "Rails Core"]
12
+ s.date = %q{2010-04-12}
13
+ s.description = %q{Specify this acts_as extension if you want to model a tree structure by providing a parent association and a children association.}
14
+ s.email = %q{mdeering@mdeering.com}
15
+ s.extra_rdoc_files = [
16
+ "README.rdoc"
17
+ ]
18
+ s.files = [
19
+ ".document",
20
+ ".gitignore",
21
+ "README.rdoc",
22
+ "Rakefile",
23
+ "VERSION",
24
+ "lib/acts_as_tree.rb",
25
+ "mdeering-acts_as_tree.gemspec",
26
+ "test/acts_as_tree_test.rb"
27
+ ]
28
+ s.homepage = %q{http://github.com/mdeering/acts_as_tree}
29
+ s.rdoc_options = ["--charset=UTF-8"]
30
+ s.require_paths = ["lib"]
31
+ s.rubygems_version = %q{1.3.6}
32
+ s.summary = %q{Gem version of acts_as_tree Rails plugin. With association callbacks added in.}
33
+ s.test_files = [
34
+ "test/acts_as_tree_test.rb"
35
+ ]
36
+
37
+ if s.respond_to? :specification_version then
38
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
39
+ s.specification_version = 3
40
+
41
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
42
+ else
43
+ end
44
+ else
45
+ end
46
+ end
47
+
@@ -0,0 +1,247 @@
1
+ require 'test/unit'
2
+
3
+ require 'rubygems'
4
+ require 'active_record'
5
+
6
+ require 'flexmock/test_unit'
7
+
8
+ require "#{File.dirname(__FILE__)}/../lib/acts_as_tree"
9
+
10
+ class Test::Unit::TestCase
11
+ def assert_queries(num = 1)
12
+ $query_count = 0
13
+ yield
14
+ ensure
15
+ assert_equal num, $query_count, "#{$query_count} instead of #{num} queries were executed."
16
+ end
17
+
18
+ def assert_no_queries(&block)
19
+ assert_queries(0, &block)
20
+ end
21
+ end
22
+
23
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
24
+
25
+ # AR keeps printing annoying schema statements
26
+ $stdout = StringIO.new
27
+
28
+ def setup_db
29
+ ActiveRecord::Base.logger
30
+ ActiveRecord::Schema.define(:version => 1) do
31
+ create_table :mixins do |t|
32
+ t.column :type, :string
33
+ t.column :parent_id, :integer
34
+ end
35
+ end
36
+ end
37
+
38
+ def teardown_db
39
+ ActiveRecord::Base.connection.tables.each do |table|
40
+ ActiveRecord::Base.connection.drop_table(table)
41
+ end
42
+ end
43
+
44
+ class Mixin < ActiveRecord::Base
45
+ end
46
+
47
+ class TreeMixin < Mixin
48
+ acts_as_tree :foreign_key => "parent_id", :order => "id"
49
+ end
50
+
51
+ class TreeMixinWithCallbacks < Mixin
52
+ acts_as_tree :foreign_key => "parent_id", :order => "id", :after_add => :after_add_callback, :after_remove => :after_remove_callback, :before_add => :before_add_callback, :before_remove => :before_remove_callback
53
+ end
54
+
55
+ class TreeMixinWithoutOrder < Mixin
56
+ acts_as_tree :foreign_key => "parent_id"
57
+ end
58
+
59
+ class RecursivelyCascadedTreeMixin < Mixin
60
+ acts_as_tree :foreign_key => "parent_id"
61
+ has_one :first_child, :class_name => 'RecursivelyCascadedTreeMixin', :foreign_key => :parent_id
62
+ end
63
+
64
+ class TreeTest < Test::Unit::TestCase
65
+
66
+ def setup
67
+ setup_db
68
+ @root1 = TreeMixin.create!
69
+ @root_child1 = TreeMixin.create! :parent_id => @root1.id
70
+ @child1_child = TreeMixin.create! :parent_id => @root_child1.id
71
+ @root_child2 = TreeMixin.create! :parent_id => @root1.id
72
+ @root2 = TreeMixin.create!
73
+ @root3 = TreeMixin.create!
74
+ end
75
+
76
+ def teardown
77
+ teardown_db
78
+ end
79
+
80
+ def test_children
81
+ assert_equal @root1.children, [@root_child1, @root_child2]
82
+ assert_equal @root_child1.children, [@child1_child]
83
+ assert_equal @child1_child.children, []
84
+ assert_equal @root_child2.children, []
85
+ end
86
+
87
+ def test_parent
88
+ assert_equal @root_child1.parent, @root1
89
+ assert_equal @root_child1.parent, @root_child2.parent
90
+ assert_nil @root1.parent
91
+ end
92
+
93
+ def test_delete
94
+ assert_equal 6, TreeMixin.count
95
+ @root1.destroy
96
+ assert_equal 2, TreeMixin.count
97
+ @root2.destroy
98
+ @root3.destroy
99
+ assert_equal 0, TreeMixin.count
100
+ end
101
+
102
+ def test_insert
103
+ @extra = @root1.children.create
104
+
105
+ assert @extra
106
+
107
+ assert_equal @extra.parent, @root1
108
+
109
+ assert_equal 3, @root1.children.size
110
+ assert @root1.children.include?(@extra)
111
+ assert @root1.children.include?(@root_child1)
112
+ assert @root1.children.include?(@root_child2)
113
+ end
114
+
115
+ def test_ancestors
116
+ assert_equal [], @root1.ancestors
117
+ assert_equal [@root1], @root_child1.ancestors
118
+ assert_equal [@root_child1, @root1], @child1_child.ancestors
119
+ assert_equal [@root1], @root_child2.ancestors
120
+ assert_equal [], @root2.ancestors
121
+ assert_equal [], @root3.ancestors
122
+ end
123
+
124
+ def test_root
125
+ assert_equal @root1, TreeMixin.root
126
+ assert_equal @root1, @root1.root
127
+ assert_equal @root1, @root_child1.root
128
+ assert_equal @root1, @child1_child.root
129
+ assert_equal @root1, @root_child2.root
130
+ assert_equal @root2, @root2.root
131
+ assert_equal @root3, @root3.root
132
+ end
133
+
134
+ def test_roots
135
+ assert_equal [@root1, @root2, @root3], TreeMixin.roots
136
+ end
137
+
138
+ def test_siblings
139
+ assert_equal [@root2, @root3], @root1.siblings
140
+ assert_equal [@root_child2], @root_child1.siblings
141
+ assert_equal [], @child1_child.siblings
142
+ assert_equal [@root_child1], @root_child2.siblings
143
+ assert_equal [@root1, @root3], @root2.siblings
144
+ assert_equal [@root1, @root2], @root3.siblings
145
+ end
146
+
147
+ def test_self_and_siblings
148
+ assert_equal [@root1, @root2, @root3], @root1.self_and_siblings
149
+ assert_equal [@root_child1, @root_child2], @root_child1.self_and_siblings
150
+ assert_equal [@child1_child], @child1_child.self_and_siblings
151
+ assert_equal [@root_child1, @root_child2], @root_child2.self_and_siblings
152
+ assert_equal [@root1, @root2, @root3], @root2.self_and_siblings
153
+ assert_equal [@root1, @root2, @root3], @root3.self_and_siblings
154
+ end
155
+ end
156
+
157
+ class TreeTestWithEagerLoading < Test::Unit::TestCase
158
+
159
+ def setup
160
+ teardown_db
161
+ setup_db
162
+ @root1 = TreeMixin.create!
163
+ @root_child1 = TreeMixin.create! :parent_id => @root1.id
164
+ @child1_child = TreeMixin.create! :parent_id => @root_child1.id
165
+ @root_child2 = TreeMixin.create! :parent_id => @root1.id
166
+ @root2 = TreeMixin.create!
167
+ @root3 = TreeMixin.create!
168
+
169
+ @rc1 = RecursivelyCascadedTreeMixin.create!
170
+ @rc2 = RecursivelyCascadedTreeMixin.create! :parent_id => @rc1.id
171
+ @rc3 = RecursivelyCascadedTreeMixin.create! :parent_id => @rc2.id
172
+ @rc4 = RecursivelyCascadedTreeMixin.create! :parent_id => @rc3.id
173
+ end
174
+
175
+ def teardown
176
+ teardown_db
177
+ end
178
+
179
+ def test_eager_association_loading
180
+ roots = TreeMixin.find(:all, :include => :children, :conditions => "mixins.parent_id IS NULL", :order => "mixins.id")
181
+ assert_equal [@root1, @root2, @root3], roots
182
+ assert_no_queries do
183
+ assert_equal 2, roots[0].children.size
184
+ assert_equal 0, roots[1].children.size
185
+ assert_equal 0, roots[2].children.size
186
+ end
187
+ end
188
+
189
+ def test_eager_association_loading_with_recursive_cascading_three_levels_has_many
190
+ root_node = RecursivelyCascadedTreeMixin.find(:first, :include => { :children => { :children => :children } }, :order => 'mixins.id')
191
+ assert_equal @rc4, assert_no_queries { root_node.children.first.children.first.children.first }
192
+ end
193
+
194
+ def test_eager_association_loading_with_recursive_cascading_three_levels_has_one
195
+ root_node = RecursivelyCascadedTreeMixin.find(:first, :include => { :first_child => { :first_child => :first_child } }, :order => 'mixins.id')
196
+ assert_equal @rc4, assert_no_queries { root_node.first_child.first_child.first_child }
197
+ end
198
+
199
+ def test_eager_association_loading_with_recursive_cascading_three_levels_belongs_to
200
+ leaf_node = RecursivelyCascadedTreeMixin.find(:first, :include => { :parent => { :parent => :parent } }, :order => 'mixins.id DESC')
201
+ assert_equal @rc1, assert_no_queries { leaf_node.parent.parent.parent }
202
+ end
203
+ end
204
+
205
+ class TreeTestWithoutOrder < Test::Unit::TestCase
206
+
207
+ def setup
208
+ setup_db
209
+ @root1 = TreeMixinWithoutOrder.create!
210
+ @root2 = TreeMixinWithoutOrder.create!
211
+ end
212
+
213
+ def teardown
214
+ teardown_db
215
+ end
216
+
217
+ def test_root
218
+ assert [@root1, @root2].include?(TreeMixinWithoutOrder.root)
219
+ end
220
+
221
+ def test_roots
222
+ assert_equal [], [@root1, @root2] - TreeMixinWithoutOrder.roots
223
+ end
224
+ end
225
+
226
+ class TreeTestWithCallbacks < Test::Unit::TestCase
227
+
228
+ def setup
229
+ setup_db
230
+ @root = TreeMixinWithCallbacks.create!
231
+ end
232
+
233
+ def teardown
234
+ teardown_db
235
+ end
236
+
237
+ def test_all_callbacks
238
+ @root = flexmock(@root)
239
+ @root.should_receive(:before_add_callback)
240
+ @root.should_receive(:after_add_callback)
241
+ @child = @root.children.create!
242
+ @root.should_receive(:before_remove_callback)
243
+ @root.should_receive(:after_remove_callback)
244
+ @root.children.delete(@child)
245
+ end
246
+
247
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mdeering-acts_as_tree
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 2
9
+ version: 0.1.2
10
+ platform: ruby
11
+ authors:
12
+ - Michael Deering
13
+ - Erik Dahlstrand
14
+ - Rails Core
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2010-04-12 00:00:00 -06:00
20
+ default_executable:
21
+ dependencies: []
22
+
23
+ description: Specify this acts_as extension if you want to model a tree structure by providing a parent association and a children association.
24
+ email: mdeering@mdeering.com
25
+ executables: []
26
+
27
+ extensions: []
28
+
29
+ extra_rdoc_files:
30
+ - README.rdoc
31
+ files:
32
+ - .document
33
+ - .gitignore
34
+ - README.rdoc
35
+ - Rakefile
36
+ - VERSION
37
+ - lib/acts_as_tree.rb
38
+ - mdeering-acts_as_tree.gemspec
39
+ - test/acts_as_tree_test.rb
40
+ has_rdoc: true
41
+ homepage: http://github.com/mdeering/acts_as_tree
42
+ licenses: []
43
+
44
+ post_install_message:
45
+ rdoc_options:
46
+ - --charset=UTF-8
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ segments:
54
+ - 0
55
+ version: "0"
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ segments:
61
+ - 0
62
+ version: "0"
63
+ requirements: []
64
+
65
+ rubyforge_project:
66
+ rubygems_version: 1.3.6
67
+ signing_key:
68
+ specification_version: 3
69
+ summary: Gem version of acts_as_tree Rails plugin. With association callbacks added in.
70
+ test_files:
71
+ - test/acts_as_tree_test.rb