mdeering-acts_as_tree 0.1.2

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,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