aunderwo-acts_as_tree 1.0.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/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