aunderwo-acts_as_tree 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,28 @@
1
+ acts_as_tree
2
+ ============
3
+
4
+ Specify this +acts_as+ extension if you want to model a tree structure by providing a parent association and a children
5
+ association. This requires that you have a foreign key column, which by default is called +parent_id+.
6
+
7
+ class Category < ActiveRecord::Base
8
+ acts_as_tree :order => "name"
9
+ end
10
+
11
+ Example:
12
+ root
13
+ \_ child1
14
+ \_ subchild1
15
+ \_ subchild2
16
+
17
+ root = Category.create("name" => "root")
18
+ child1 = root.children.create("name" => "child1")
19
+ subchild1 = child1.children.create("name" => "subchild1")
20
+
21
+ root.parent # => nil
22
+ child1.parent # => root
23
+ root.children # => [child1]
24
+ root.children.first.children.first # => subchild1
25
+
26
+ Copyright (c) 2007 David Heinemeier Hansson, released under the MIT license
27
+
28
+ Includes patch from http://dev.rubyonrails.org/ticket/1924
data/Rakefile ADDED
@@ -0,0 +1,22 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Test acts_as_tree plugin.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.pattern = 'test/**/*_test.rb'
12
+ t.verbose = true
13
+ end
14
+
15
+ desc 'Generate documentation for acts_as_tree plugin.'
16
+ Rake::RDocTask.new(:rdoc) do |rdoc|
17
+ rdoc.rdoc_dir = 'rdoc'
18
+ rdoc.title = 'acts_as_tree'
19
+ rdoc.options << '--line-numbers' << '--inline-source'
20
+ rdoc.rdoc_files.include('README')
21
+ rdoc.rdoc_files.include('lib/**/*.rb')
22
+ end
@@ -0,0 +1,34 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'acts_as_tree'
3
+ s.version = '1.0.2'
4
+ s.date = '2008-10-02'
5
+
6
+ s.summary = "Allows ActiveRecord Models to be easily structured as a tree"
7
+ s.description = ""
8
+
9
+ s.authors = ["RailsJedi", 'David Heinemeier Hansson']
10
+ s.email = 'railsjedi@gmail.com'
11
+ s.homepage = 'http://github.com/aunderwo/acts_as_tree'
12
+
13
+ s.has_rdoc = true
14
+ s.rdoc_options = ["--main", "README"]
15
+ s.extra_rdoc_files = ["README"]
16
+
17
+ s.add_dependency 'rails', ['>= 2.1']
18
+
19
+
20
+ s.files = ["README",
21
+ "Rakefile",
22
+ "acts_as_tree.gemspec",
23
+ "init.rb",
24
+ "lib/active_record/acts/tree.rb",
25
+ "lib/acts_as_tree.rb",
26
+ "rails/init.rb"]
27
+
28
+ s.test_files = ["test/abstract_unit.rb",
29
+ "test/acts_as_tree_test.rb",
30
+ "test/database.yml",
31
+ "test/fixtures/mixin.rb",
32
+ "test/fixtures/mixins.yml",
33
+ "test/schema.rb"]
34
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__) + "/rails/init"
@@ -0,0 +1,133 @@
1
+ module ActiveRecord
2
+ module Acts
3
+ module Tree
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", :dependent => false
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, :dependent => :destroy }
44
+ configuration.update(options) if options.is_a?(Hash)
45
+
46
+ # Added for model Page
47
+ belongs_to :parent, :class_name => name, :foreign_key => configuration[:foreign_key], :counter_cache => configuration[:counter_cache]
48
+ if configuration[:dependent].nil?
49
+ has_many :children, :class_name => name, :foreign_key => configuration[:foreign_key], :order => configuration[:order]
50
+ else
51
+ has_many :children, :class_name => name, :foreign_key => configuration[:foreign_key], :order => configuration[:order], :dependent => configuration[:dependent]
52
+ end
53
+
54
+ class_eval <<-EOV
55
+ include ActiveRecord::Acts::Tree::InstanceMethods
56
+
57
+ named_scope :roots, :conditions => "#{configuration[:foreign_key]} IS NULL", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}}
58
+ named_scope :root, :conditions => "#{configuration[:foreign_key]} IS NULL", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}}
59
+
60
+
61
+ validate :ensure_foreign_key_does_not_reference_self_or_all_children, :on => :update
62
+
63
+ protected
64
+
65
+ def ensure_foreign_key_does_not_reference_self_or_all_children
66
+ unless new_record? # old AR versions don't seem to support the :on option for the validate method (e.g. :on => :update)
67
+ if self_and_all_children.collect(&:id).include?(self.#{configuration[:foreign_key]})
68
+ self.errors.add('#{configuration[:foreign_key]}', "can't be a reference to the current node or any of its children")
69
+ false
70
+ end
71
+ end
72
+ end
73
+ >>>>>>> shuber/master:lib/active_record/acts/tree.rb
74
+ EOV
75
+ end
76
+ end
77
+
78
+ module InstanceMethods
79
+ # Returns all children (recursively) of the current node.
80
+ #
81
+ # parent.all_children # => [child1, child1_child1, child1_child2, child2, child2_child1, child3]
82
+ def all_children
83
+ self_and_all_children - [self]
84
+ end
85
+
86
+ # Returns list of ancestors, starting from parent until root.
87
+ #
88
+ # subchild1.ancestors # => [child1, root]
89
+ def ancestors
90
+ node, nodes = self, []
91
+ nodes << node = node.parent while node.parent
92
+ nodes
93
+ end
94
+
95
+ # Checks if the current node is a root
96
+ #
97
+ # parent.is_root? # => true
98
+ # child.is_root? # => false
99
+ def is_root?
100
+ !new_record? && self.parent.nil?
101
+ end
102
+
103
+ # Returns the root node of the tree.
104
+ def root
105
+ node = self
106
+ node = node.parent while node.parent
107
+ node
108
+ end
109
+
110
+ # Returns all siblings of the current node.
111
+ #
112
+ # subchild1.siblings # => [subchild2]
113
+ def siblings
114
+ self_and_siblings - [self]
115
+ end
116
+
117
+ # Returns all children (recursively) and a reference to the current node.
118
+ #
119
+ # parent.self_and_all_children # => [parent, child1, child1_child1, child1_child2, child2, child2_child1, child3]
120
+ def self_and_all_children
121
+ self.children.inject([self]) { |array, child| array += child.self_and_all_children }.flatten
122
+ end
123
+
124
+ # Returns all siblings and a reference to the current node.
125
+ #
126
+ # subchild1.self_and_siblings # => [subchild1, subchild2]
127
+ def self_and_siblings
128
+ parent ? parent.children : self.class.roots
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,2 @@
1
+ require "active_record/acts/tree"
2
+ ActiveRecord::Base.send :include, ActiveRecord::Acts::Tree
data/rails/init.rb ADDED
@@ -0,0 +1 @@
1
+ require "acts_as_tree"
File without changes
@@ -0,0 +1,269 @@
1
+ $:.reject! { |path| path.include? 'TextMate' }
2
+ require 'test/unit'
3
+
4
+ require 'rubygems'
5
+ require 'active_record'
6
+
7
+ $:.unshift File.dirname(__FILE__) + '/../lib'
8
+ require File.dirname(__FILE__) + '/../lib/active_record/acts/tree'
9
+ require File.dirname(__FILE__) + '/../init'
10
+
11
+ class Test::Unit::TestCase
12
+ def assert_queries(num = 1)
13
+ $query_count = 0
14
+ yield
15
+ ensure
16
+ assert_equal num, $query_count, "#{$query_count} instead of #{num} queries were executed."
17
+ end
18
+
19
+ def assert_no_queries(&block)
20
+ assert_queries(0, &block)
21
+ end
22
+ end
23
+
24
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :dbfile => ":memory:")
25
+
26
+ # AR keeps printing annoying schema statements
27
+ $stdout = StringIO.new
28
+
29
+ def setup_db
30
+ ActiveRecord::Base.logger
31
+ ActiveRecord::Schema.define(:version => 1) do
32
+ create_table :mixins do |t|
33
+ t.column :type, :string
34
+ t.column :parent_id, :integer
35
+ end
36
+ end
37
+ end
38
+
39
+ def teardown_db
40
+ ActiveRecord::Base.connection.tables.each do |table|
41
+ ActiveRecord::Base.connection.drop_table(table)
42
+ end
43
+ end
44
+
45
+ class Mixin < ActiveRecord::Base
46
+ end
47
+
48
+ class TreeMixin < Mixin
49
+ acts_as_tree :foreign_key => "parent_id", :order => "id"
50
+ end
51
+
52
+ class TreeMixinWithoutOrder < Mixin
53
+ acts_as_tree :foreign_key => "parent_id"
54
+ end
55
+
56
+ class RecursivelyCascadedTreeMixin < Mixin
57
+ acts_as_tree :foreign_key => "parent_id"
58
+ has_one :first_child, :class_name => 'RecursivelyCascadedTreeMixin', :foreign_key => :parent_id
59
+ end
60
+
61
+ class TreeMixinNullify < Mixin
62
+ acts_as_tree :foreign_key => "parent_id", :order => "id", :dependent => :nullify
63
+ end
64
+
65
+ class TreeTest < Test::Unit::TestCase
66
+
67
+ def setup
68
+ setup_db
69
+ @root1 = TreeMixin.create!
70
+ @root_child1 = TreeMixin.create! :parent_id => @root1.id
71
+ @child1_child = TreeMixin.create! :parent_id => @root_child1.id
72
+ @root_child2 = TreeMixin.create! :parent_id => @root1.id
73
+ @root2 = TreeMixin.create!
74
+ @root3 = TreeMixin.create!
75
+ end
76
+
77
+ def teardown
78
+ teardown_db
79
+ end
80
+
81
+ def test_children
82
+ assert_equal @root1.children, [@root_child1, @root_child2]
83
+ assert_equal @root_child1.children, [@child1_child]
84
+ assert_equal @child1_child.children, []
85
+ assert_equal @root_child2.children, []
86
+ end
87
+
88
+ def test_parent
89
+ assert_equal @root_child1.parent, @root1
90
+ assert_equal @root_child1.parent, @root_child2.parent
91
+ assert_nil @root1.parent
92
+ end
93
+
94
+ def test_nullify
95
+ root4 = TreeMixinNullify.create!
96
+ root4_child = TreeMixinNullify.create! :parent_id => root4.id
97
+ assert_equal 2, TreeMixinNullify.count
98
+ assert_equal root4.id, root4_child.parent_id
99
+ root4.destroy
100
+ assert_equal 1, TreeMixinNullify.count
101
+ assert_nil root4_child.reload.parent_id
102
+ end
103
+
104
+ def test_delete
105
+ assert_equal 6, TreeMixin.count
106
+ @root1.destroy
107
+ assert_equal 2, TreeMixin.count
108
+ @root2.destroy
109
+ @root3.destroy
110
+ assert_equal 0, TreeMixin.count
111
+ end
112
+
113
+ def test_insert
114
+ @extra = @root1.children.create
115
+
116
+ assert @extra
117
+
118
+ assert_equal @extra.parent, @root1
119
+
120
+ assert_equal 3, @root1.children.size
121
+ assert @root1.children.include?(@extra)
122
+ assert @root1.children.include?(@root_child1)
123
+ assert @root1.children.include?(@root_child2)
124
+ end
125
+
126
+ def test_ancestors
127
+ assert_equal [], @root1.ancestors
128
+ assert_equal [@root1], @root_child1.ancestors
129
+ assert_equal [@root_child1, @root1], @child1_child.ancestors
130
+ assert_equal [@root1], @root_child2.ancestors
131
+ assert_equal [], @root2.ancestors
132
+ assert_equal [], @root3.ancestors
133
+ end
134
+
135
+ def test_root
136
+ assert_equal @root1, TreeMixin.root
137
+ assert_equal @root1, @root1.root
138
+ assert_equal @root1, @root_child1.root
139
+ assert_equal @root1, @child1_child.root
140
+ assert_equal @root1, @root_child2.root
141
+ assert_equal @root2, @root2.root
142
+ assert_equal @root3, @root3.root
143
+ end
144
+
145
+ def test_roots
146
+ assert_equal [@root1, @root2, @root3], TreeMixin.roots
147
+ end
148
+
149
+ def test_siblings
150
+ assert_equal [@root2, @root3], @root1.siblings
151
+ assert_equal [@root_child2], @root_child1.siblings
152
+ assert_equal [], @child1_child.siblings
153
+ assert_equal [@root_child1], @root_child2.siblings
154
+ assert_equal [@root1, @root3], @root2.siblings
155
+ assert_equal [@root1, @root2], @root3.siblings
156
+ end
157
+
158
+ def test_self_and_siblings
159
+ assert_equal [@root1, @root2, @root3], @root1.self_and_siblings
160
+ assert_equal [@root_child1, @root_child2], @root_child1.self_and_siblings
161
+ assert_equal [@child1_child], @child1_child.self_and_siblings
162
+ assert_equal [@root_child1, @root_child2], @root_child2.self_and_siblings
163
+ assert_equal [@root1, @root2, @root3], @root2.self_and_siblings
164
+ assert_equal [@root1, @root2, @root3], @root3.self_and_siblings
165
+ end
166
+
167
+ def test_all_children
168
+ assert_equal [@root_child1, @child1_child, @root_child2], @root1.all_children
169
+ assert_equal [@child1_child], @root_child1.all_children
170
+ assert_equal [], @child1_child.all_children
171
+ end
172
+
173
+ def test_self_and_all_children
174
+ assert_equal [@root1, @root_child1, @child1_child, @root_child2], @root1.self_and_all_children
175
+ assert_equal [@child1_child], @child1_child.self_and_all_children
176
+ end
177
+
178
+ def test_should_not_reference_self_as_parent_id_on_update
179
+ @root1.parent_id = @root1.id
180
+ assert !@root1.save
181
+ assert @root1.errors.on(:parent_id)
182
+ end
183
+
184
+ def test_should_not_reference_children_as_parent_id_on_update
185
+ @root1.parent_id = @root_child1.id
186
+ assert !@root1.save
187
+ assert @root1.errors.on(:parent_id)
188
+
189
+ @root1.reload
190
+ @root1.parent_id = @child1_child.id
191
+ assert !@root1.save
192
+ assert @root1.errors.on(:parent_id)
193
+ end
194
+
195
+ def test_is_root
196
+ assert @root1.is_root?
197
+ assert !@root_child1.is_root?
198
+ assert !TreeMixin.new.is_root?
199
+ end
200
+ end
201
+
202
+ class TreeTestWithEagerLoading < Test::Unit::TestCase
203
+
204
+ def setup
205
+ teardown_db
206
+ setup_db
207
+ @root1 = TreeMixin.create!
208
+ @root_child1 = TreeMixin.create! :parent_id => @root1.id
209
+ @child1_child = TreeMixin.create! :parent_id => @root_child1.id
210
+ @root_child2 = TreeMixin.create! :parent_id => @root1.id
211
+ @root2 = TreeMixin.create!
212
+ @root3 = TreeMixin.create!
213
+
214
+ @rc1 = RecursivelyCascadedTreeMixin.create!
215
+ @rc2 = RecursivelyCascadedTreeMixin.create! :parent_id => @rc1.id
216
+ @rc3 = RecursivelyCascadedTreeMixin.create! :parent_id => @rc2.id
217
+ @rc4 = RecursivelyCascadedTreeMixin.create! :parent_id => @rc3.id
218
+ end
219
+
220
+ def teardown
221
+ teardown_db
222
+ end
223
+
224
+ def test_eager_association_loading
225
+ roots = TreeMixin.find(:all, :include => :children, :conditions => "mixins.parent_id IS NULL", :order => "mixins.id")
226
+ assert_equal [@root1, @root2, @root3], roots
227
+ assert_no_queries do
228
+ assert_equal 2, roots[0].children.size
229
+ assert_equal 0, roots[1].children.size
230
+ assert_equal 0, roots[2].children.size
231
+ end
232
+ end
233
+
234
+ def test_eager_association_loading_with_recursive_cascading_three_levels_has_many
235
+ root_node = RecursivelyCascadedTreeMixin.find(:first, :include => { :children => { :children => :children } }, :order => 'mixins.id')
236
+ assert_equal @rc4, assert_no_queries { root_node.children.first.children.first.children.first }
237
+ end
238
+
239
+ def test_eager_association_loading_with_recursive_cascading_three_levels_has_one
240
+ root_node = RecursivelyCascadedTreeMixin.find(:first, :include => { :first_child => { :first_child => :first_child } }, :order => 'mixins.id')
241
+ assert_equal @rc4, assert_no_queries { root_node.first_child.first_child.first_child }
242
+ end
243
+
244
+ def test_eager_association_loading_with_recursive_cascading_three_levels_belongs_to
245
+ leaf_node = RecursivelyCascadedTreeMixin.find(:first, :include => { :parent => { :parent => :parent } }, :order => 'mixins.id DESC')
246
+ assert_equal @rc1, assert_no_queries { leaf_node.parent.parent.parent }
247
+ end
248
+ end
249
+
250
+ class TreeTestWithoutOrder < Test::Unit::TestCase
251
+
252
+ def setup
253
+ setup_db
254
+ @root1 = TreeMixinWithoutOrder.create!
255
+ @root2 = TreeMixinWithoutOrder.create!
256
+ end
257
+
258
+ def teardown
259
+ teardown_db
260
+ end
261
+
262
+ def test_root
263
+ assert [@root1, @root2].include?(TreeMixinWithoutOrder.root)
264
+ end
265
+
266
+ def test_roots
267
+ assert_equal [], [@root1, @root2] - TreeMixinWithoutOrder.roots
268
+ end
269
+ end
data/test/database.yml ADDED
File without changes
File without changes
File without changes
data/test/schema.rb ADDED
File without changes
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: aunderwo-acts_as_tree
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.2
5
+ platform: ruby
6
+ authors:
7
+ - RailsJedi
8
+ - David Heinemeier Hansson
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2008-10-02 00:00:00 -07:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: rails
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "2.1"
24
+ version:
25
+ description: ""
26
+ email: railsjedi@gmail.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - README
33
+ files:
34
+ - README
35
+ - Rakefile
36
+ - acts_as_tree.gemspec
37
+ - init.rb
38
+ - lib/active_record/acts/tree.rb
39
+ - lib/acts_as_tree.rb
40
+ - rails/init.rb
41
+ has_rdoc: true
42
+ homepage: http://github.com/aunderwo/acts_as_tree
43
+ post_install_message:
44
+ rdoc_options:
45
+ - --main
46
+ - README
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ version:
61
+ requirements: []
62
+
63
+ rubyforge_project:
64
+ rubygems_version: 1.2.0
65
+ signing_key:
66
+ specification_version: 2
67
+ summary: Allows ActiveRecord Models to be easily structured as a tree
68
+ test_files:
69
+ - test/abstract_unit.rb
70
+ - test/acts_as_tree_test.rb
71
+ - test/database.yml
72
+ - test/fixtures/mixin.rb
73
+ - test/fixtures/mixins.yml
74
+ - test/schema.rb