chrislloyd-eb_nested_set 0.3.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/LICENSE +20 -0
- data/README.md +65 -0
- data/Rakefile +87 -0
- data/init.rb +1 -0
- data/lib/eb_nested_set.rb +349 -0
- data/spec/directory_spec.rb +54 -0
- data/spec/employee_spec.rb +74 -0
- data/spec/nested_set_behavior.rb +586 -0
- data/spec/spec_helper.rb +83 -0
- metadata +64 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 Jonas Nicklas
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
# EvenBetterNestedSet
|
2
|
+
|
3
|
+
A nested set is a datastruture in a database, sort of like a tree, but unlike a tree it allows you to find all descendants of a node with a single query. Loading a deeply nested structure with nested sets is therefore a lot more efficient than using a tree. So what's the disadvantage? Nested sets are a lot harder to maintain, since inserting and moving records requires management and it is easy to corrupt the dataset. Enter: EvenBetterNestedSet. Amount of micromanaging you need to do: 0. EvenBetterNestedSet does it all for you.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Stable:
|
8
|
+
|
9
|
+
[sudo] gem install eb_nested_set
|
10
|
+
|
11
|
+
Edge:
|
12
|
+
|
13
|
+
[sudo] gem install jnicklas-eb_nested_set --source http://gems.github.com
|
14
|
+
|
15
|
+
From source:
|
16
|
+
|
17
|
+
git clone git://github.com/jnicklas/even_better_nested_set.git
|
18
|
+
cd even_better_nested_set
|
19
|
+
rake install
|
20
|
+
|
21
|
+
If you're running Rails, just add it to your environment.rb file
|
22
|
+
|
23
|
+
config.gem 'eb_nested_set'
|
24
|
+
|
25
|
+
You can also install it as a Rails plugin.
|
26
|
+
|
27
|
+
script/plugin install git://github.com/jnicklas/even_better_nested_set.git
|
28
|
+
|
29
|
+
## Declaring nested sets
|
30
|
+
|
31
|
+
This is how you declare a nested set:
|
32
|
+
|
33
|
+
class Directory < ActiveRecord::Base
|
34
|
+
|
35
|
+
acts_as_nested_set
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
The directories table should have the columns 'parent_id', 'left' and 'right'.
|
40
|
+
|
41
|
+
Now just set the parent to wherever you want your node to be located and EvenBetterNestedSet will do the rest for you.
|
42
|
+
|
43
|
+
d = Directory.new
|
44
|
+
|
45
|
+
d.children.create!(:name => 'blah')
|
46
|
+
d.children.create!(:name => 'gurr')
|
47
|
+
d.children.create!(:name => 'doh')
|
48
|
+
|
49
|
+
d.bounds #=> 1..8
|
50
|
+
d.children[1].bounds #=> 4..5
|
51
|
+
d.children[1].name #=> 'gurr'
|
52
|
+
d.children[1].parent #=> d
|
53
|
+
|
54
|
+
c = Directory.create!(:name => 'test', :parent => d.directory[1]
|
55
|
+
|
56
|
+
## Finding with nested sets
|
57
|
+
|
58
|
+
EvenBetterNestedSet will not automatically cache children for you, because it assumes that this is not always the preferred behaviour. If you want to cache children to a nested set, just do:
|
59
|
+
|
60
|
+
d = Directory.find(42)
|
61
|
+
d.cache_nested_set
|
62
|
+
|
63
|
+
or more conveniently:
|
64
|
+
|
65
|
+
d = Directory.find_with_nested_set(42)
|
data/Rakefile
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/gempackagetask'
|
3
|
+
require 'rubygems/specification'
|
4
|
+
require 'date'
|
5
|
+
require 'spec/rake/spectask'
|
6
|
+
|
7
|
+
GEM = "eb_nested_set"
|
8
|
+
GEM_VERSION = "0.3.3"
|
9
|
+
AUTHOR = "Jonas Nicklas"
|
10
|
+
EMAIL = "jonas.nicklas@gmail.com"
|
11
|
+
HOMEPAGE = "http://github.com/jnicklas/even_better_nested_set/tree/master"
|
12
|
+
SUMMARY = "A cool acts_as_nested_set alternative"
|
13
|
+
|
14
|
+
spec = Gem::Specification.new do |s|
|
15
|
+
s.name = GEM
|
16
|
+
s.version = GEM_VERSION
|
17
|
+
s.platform = Gem::Platform::RUBY
|
18
|
+
s.has_rdoc = true
|
19
|
+
s.extra_rdoc_files = ['README.md', 'LICENSE']
|
20
|
+
s.summary = SUMMARY
|
21
|
+
s.description = s.summary
|
22
|
+
s.author = AUTHOR
|
23
|
+
s.email = EMAIL
|
24
|
+
s.homepage = HOMEPAGE
|
25
|
+
s.require_path = 'lib'
|
26
|
+
s.autorequire = GEM
|
27
|
+
s.files = %w(LICENSE README.md Rakefile init.rb) + Dir.glob("{lib,spec}/**/*")
|
28
|
+
end
|
29
|
+
|
30
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
31
|
+
pkg.gem_spec = spec
|
32
|
+
end
|
33
|
+
|
34
|
+
desc "install the plugin locally"
|
35
|
+
task :install => [:package] do
|
36
|
+
sh %{sudo gem install pkg/#{GEM}-#{GEM_VERSION} --no-update-sources}
|
37
|
+
end
|
38
|
+
|
39
|
+
desc "create a gemspec file"
|
40
|
+
task :make_spec do
|
41
|
+
File.open("#{GEM}.gemspec", "w") do |file|
|
42
|
+
file.puts spec.to_ruby
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
namespace :jruby do
|
47
|
+
|
48
|
+
desc "Run :package and install the resulting .gem with jruby"
|
49
|
+
task :install => :package do
|
50
|
+
sh %{#{SUDO} jruby -S gem install pkg/#{GEM}-#{GEM_VERSION}.gem --no-rdoc --no-ri}
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
spec_files = FileList['spec/*_spec.rb']
|
56
|
+
|
57
|
+
desc 'Default: run unit tests.'
|
58
|
+
task :default => 'spec'
|
59
|
+
|
60
|
+
task :specs => :spec
|
61
|
+
desc "Run all examples"
|
62
|
+
Spec::Rake::SpecTask.new('spec') do |t|
|
63
|
+
t.spec_opts = ['--color']
|
64
|
+
t.spec_files = spec_files
|
65
|
+
end
|
66
|
+
|
67
|
+
namespace :spec do
|
68
|
+
desc "Run all examples with RCov"
|
69
|
+
Spec::Rake::SpecTask.new('rcov') do |t|
|
70
|
+
t.spec_files = spec_files
|
71
|
+
t.rcov = true
|
72
|
+
t.rcov_dir = "doc/coverage"
|
73
|
+
t.rcov_opts = ['--exclude', 'spec,rspec-*,rcov-*,gems']
|
74
|
+
t.spec_opts = ['--color']
|
75
|
+
end
|
76
|
+
|
77
|
+
desc "Generate an html report"
|
78
|
+
Spec::Rake::SpecTask.new('report') do |t|
|
79
|
+
t.spec_files = spec_files
|
80
|
+
t.rcov = true
|
81
|
+
t.rcov_dir = "doc/coverage"
|
82
|
+
t.rcov_opts = ['--exclude', 'spec']
|
83
|
+
t.spec_opts = ['--color', "--format", "html:doc/reports/specs.html"]
|
84
|
+
t.fail_on_error = false
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'lib', 'even_better_nested_set')
|
@@ -0,0 +1,349 @@
|
|
1
|
+
module EvenBetterNestedSet
|
2
|
+
|
3
|
+
def self.included(base)
|
4
|
+
super
|
5
|
+
base.extend ClassMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
class NestedSetError < StandardError; end
|
9
|
+
class IllegalAssignmentError < NestedSetError; end
|
10
|
+
|
11
|
+
module NestedSet
|
12
|
+
|
13
|
+
def self.included(base)
|
14
|
+
super
|
15
|
+
base.extend ClassMethods
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
|
20
|
+
attr_accessor :nested_set_options
|
21
|
+
|
22
|
+
def find_last_root
|
23
|
+
find(:first, :order => "#{nested_set_column(:right)} DESC", :conditions => { :parent_id => nil })
|
24
|
+
end
|
25
|
+
|
26
|
+
def find_boundaries(id)
|
27
|
+
query = "SELECT #{nested_set_column(:left)}, #{nested_set_column(:right)}" +
|
28
|
+
"FROM #{quote_db_property(table_name)}" +
|
29
|
+
"WHERE #{quote_db_property(primary_key)} = #{id}"
|
30
|
+
connection.select_rows(query).first
|
31
|
+
end
|
32
|
+
|
33
|
+
def nested_set
|
34
|
+
sort_nodes_to_nested_set(find(:all, :order => "#{nested_set_column(:left)} ASC"))
|
35
|
+
end
|
36
|
+
|
37
|
+
def find_with_nested_set(*args)
|
38
|
+
result = find(*args)
|
39
|
+
if result.respond_to?(:cache_nested_set)
|
40
|
+
result.cache_nested_set
|
41
|
+
elsif result.respond_to?(:each)
|
42
|
+
result.each do |node|
|
43
|
+
node.cache_nested_set
|
44
|
+
end
|
45
|
+
end
|
46
|
+
result
|
47
|
+
end
|
48
|
+
|
49
|
+
def sort_nodes_to_nested_set(nodes)
|
50
|
+
roots = []
|
51
|
+
hashmap = {}
|
52
|
+
for node in nodes.sort_by { |n| n.left }
|
53
|
+
# if the parent is not in the hashmap, parent will be nil, therefore node will be a root node
|
54
|
+
# in that case
|
55
|
+
parent = node.parent_id ? hashmap[node.parent_id] : nil
|
56
|
+
|
57
|
+
# make sure this is called at least once on every node, so leaves know that they have *no* children
|
58
|
+
node.cache_children()
|
59
|
+
|
60
|
+
if parent
|
61
|
+
node.cache_parent(parent)
|
62
|
+
parent.cache_children(node)
|
63
|
+
else
|
64
|
+
roots << node
|
65
|
+
end
|
66
|
+
|
67
|
+
hashmap[node.id] = node
|
68
|
+
end
|
69
|
+
return roots
|
70
|
+
end
|
71
|
+
|
72
|
+
def nested_set_column(name)
|
73
|
+
quote_db_property(nested_set_options[name])
|
74
|
+
end
|
75
|
+
|
76
|
+
# Recalculates the left and right values for the entire tree
|
77
|
+
def recalculate_nested_set
|
78
|
+
transaction do
|
79
|
+
left = 1
|
80
|
+
roots.each do |root|
|
81
|
+
left = root.recalculate_nested_set(left)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def quote_db_property(property)
|
87
|
+
"`#{property}`".gsub('.','`.`')
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
def root?
|
93
|
+
not parent_id?
|
94
|
+
end
|
95
|
+
|
96
|
+
def descendant_of?(node)
|
97
|
+
node.left < self.left && self.right < node.right
|
98
|
+
end
|
99
|
+
|
100
|
+
def root
|
101
|
+
transaction do
|
102
|
+
reload_boundaries
|
103
|
+
@root ||= base_class.roots.find(:first, :conditions => ["#{nested_set_column(:left)} <= ? AND #{nested_set_column(:right)} >= ?", left, right])
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
alias_method :patriarch, :root
|
108
|
+
|
109
|
+
def ancestors(force_reload=false)
|
110
|
+
@ancestors = nil if force_reload
|
111
|
+
@ancestors ||= base_class.find(
|
112
|
+
:all,:conditions => ["#{nested_set_column(:left)} < ? AND #{nested_set_column(:right)} > ?", left, right],
|
113
|
+
:order => "#{nested_set_column(:left)} DESC"
|
114
|
+
)
|
115
|
+
end
|
116
|
+
|
117
|
+
def lineage(force_reload=false)
|
118
|
+
[self, *ancestors(force_reload)]
|
119
|
+
end
|
120
|
+
|
121
|
+
def kin
|
122
|
+
patriarch.family
|
123
|
+
end
|
124
|
+
|
125
|
+
def descendants
|
126
|
+
base_class.descendants(self)
|
127
|
+
end
|
128
|
+
|
129
|
+
def cache_nested_set
|
130
|
+
@cached_children || base_class.sort_nodes_to_nested_set(family)
|
131
|
+
end
|
132
|
+
|
133
|
+
def family
|
134
|
+
[self, *descendants]
|
135
|
+
end
|
136
|
+
|
137
|
+
def family_ids(force_reload=false)
|
138
|
+
return @family_ids unless @family_ids.nil? or force_reload
|
139
|
+
|
140
|
+
transaction do
|
141
|
+
reload_boundaries
|
142
|
+
query = "SELECT id FROM #{self.class.quote_db_property(base_class.table_name)} " +
|
143
|
+
"WHERE #{nested_set_column(:left)} >= #{left} AND #{nested_set_column(:right)} <= #{right} " +
|
144
|
+
"ORDER BY #{nested_set_column(:left)}"
|
145
|
+
@family_ids = base_class.connection.select_values(query).map(&:to_i)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def generation
|
150
|
+
root? ? base_class.roots : parent.children
|
151
|
+
end
|
152
|
+
|
153
|
+
def siblings
|
154
|
+
generation - [self]
|
155
|
+
end
|
156
|
+
|
157
|
+
def level
|
158
|
+
if root?
|
159
|
+
0
|
160
|
+
elsif @ancestors
|
161
|
+
@ancestors.size
|
162
|
+
else
|
163
|
+
base_class.count :conditions => ["#{nested_set_column(:left)} < ? AND #{nested_set_column(:right)} > ?", left, right]
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def bounds
|
168
|
+
left..right
|
169
|
+
end
|
170
|
+
|
171
|
+
def children
|
172
|
+
@cached_children || uncached_children
|
173
|
+
end
|
174
|
+
|
175
|
+
def children?
|
176
|
+
children.empty?
|
177
|
+
end
|
178
|
+
|
179
|
+
def cache_parent(parent) #:nodoc:
|
180
|
+
self.parent = parent
|
181
|
+
end
|
182
|
+
|
183
|
+
def cache_children(*nodes) #:nodoc:
|
184
|
+
@cached_children ||= []
|
185
|
+
@cached_children.push(*nodes)
|
186
|
+
end
|
187
|
+
|
188
|
+
def left
|
189
|
+
read_attribute(self.class.nested_set_options[:left])
|
190
|
+
end
|
191
|
+
|
192
|
+
def right
|
193
|
+
read_attribute(self.class.nested_set_options[:right])
|
194
|
+
end
|
195
|
+
|
196
|
+
def recalculate_nested_set(left)
|
197
|
+
child_left = left + 1
|
198
|
+
children.each do |child|
|
199
|
+
child_left = child.recalculate_nested_set(child_left)
|
200
|
+
end
|
201
|
+
set_boundaries(left, child_left)
|
202
|
+
save_without_validation!
|
203
|
+
|
204
|
+
right + 1
|
205
|
+
end
|
206
|
+
|
207
|
+
protected
|
208
|
+
|
209
|
+
def illegal_nesting
|
210
|
+
if parent_id? and family_ids.include?(parent_id)
|
211
|
+
errors.add(:parent_id, 'cannot move node to its own descendant')
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def remove_node
|
216
|
+
base_class.delete_all ["#{nested_set_column(:left)} > ? AND #{nested_set_column(:right)} < ?", left, right] # TODO: Figure out what to do with children's destroy callbacks
|
217
|
+
|
218
|
+
shift!(-node_width, right)
|
219
|
+
end
|
220
|
+
|
221
|
+
def append_node
|
222
|
+
boundary = 1
|
223
|
+
|
224
|
+
if parent_id?
|
225
|
+
transaction do
|
226
|
+
boundary = parent(true).right
|
227
|
+
shift! 2, boundary
|
228
|
+
end
|
229
|
+
elsif last_root = base_class.find_last_root
|
230
|
+
boundary = last_root.right + 1
|
231
|
+
end
|
232
|
+
|
233
|
+
set_boundaries(boundary, boundary + 1)
|
234
|
+
end
|
235
|
+
|
236
|
+
def move_node
|
237
|
+
if parent_id_changed?
|
238
|
+
transaction do
|
239
|
+
reload_boundaries
|
240
|
+
|
241
|
+
if parent_id.blank? # moved to root
|
242
|
+
shift_difference = base_class.find_last_root.right - left + 1
|
243
|
+
else # moved to non-root
|
244
|
+
new_parent = base_class.find_by_id(parent_id)
|
245
|
+
|
246
|
+
# open up a space
|
247
|
+
boundary = new_parent.right
|
248
|
+
shift! node_width, boundary
|
249
|
+
|
250
|
+
reload_boundaries
|
251
|
+
|
252
|
+
shift_difference = (new_parent.right - left)
|
253
|
+
end
|
254
|
+
# move itself and children into place
|
255
|
+
shift! shift_difference, left, right
|
256
|
+
|
257
|
+
# close up the space that was left behind after move
|
258
|
+
shift! -node_width, left
|
259
|
+
|
260
|
+
reload_boundaries
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def shift!(positions, left_boundary, right_boundary=nil)
|
266
|
+
if right_boundary
|
267
|
+
base_class.update_all "#{nested_set_column(:left)} = (#{nested_set_column(:left)} + #{positions})", ["#{nested_set_column(:left)} >= ? AND #{nested_set_column(:left)} <= ?", left_boundary, right_boundary]
|
268
|
+
base_class.update_all "#{nested_set_column(:right)} = (#{nested_set_column(:right)} + #{positions})", ["#{nested_set_column(:right)} >= ? AND #{nested_set_column(:right)} <= ?", left_boundary, right_boundary]
|
269
|
+
else
|
270
|
+
base_class.update_all "#{nested_set_column(:left)} = (#{nested_set_column(:left)} + #{positions})", ["#{nested_set_column(:left)} >= ?", left_boundary]
|
271
|
+
base_class.update_all "#{nested_set_column(:right)} = (#{nested_set_column(:right)} + #{positions})", ["#{nested_set_column(:right)} >= ?", left_boundary]
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
def node_width
|
276
|
+
right - left + 1
|
277
|
+
end
|
278
|
+
|
279
|
+
def set_boundaries(left, right)
|
280
|
+
write_attribute(self.class.nested_set_options[:left], left)
|
281
|
+
write_attribute(self.class.nested_set_options[:right], right)
|
282
|
+
end
|
283
|
+
|
284
|
+
def reload_boundaries
|
285
|
+
set_boundaries(*base_class.find_boundaries(id))
|
286
|
+
end
|
287
|
+
|
288
|
+
def base_class
|
289
|
+
self.class.base_class
|
290
|
+
end
|
291
|
+
|
292
|
+
def validate_parent_is_within_scope
|
293
|
+
if self.class.nested_set_options[:scope] && parent_id
|
294
|
+
parent.reload # Make sure we are testing the record corresponding to the parent_id
|
295
|
+
if self.send(self.class.nested_set_options[:scope]) != parent.send(self.class.nested_set_options[:scope])
|
296
|
+
errors.add(:parent_id, "cannot be a record with a different #{self.class.nested_set_options[:scope]} to this record")
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
module ClassMethods
|
303
|
+
|
304
|
+
def acts_as_nested_set(options = {})
|
305
|
+
options = { :left => :left, :right => :right }.merge!(options)
|
306
|
+
options[:scope] = "#{options[:scope]}_id" if options[:scope]
|
307
|
+
|
308
|
+
include NestedSet
|
309
|
+
|
310
|
+
self.nested_set_options = options
|
311
|
+
|
312
|
+
class_eval <<-RUBY, __FILE__, __LINE__+1
|
313
|
+
def #{options[:left]}=(left)
|
314
|
+
raise EvenBetterNestedSet::IllegalAssignmentError, "#{options[:left]} is an internal attribute used by EvenBetterNestedSet, do not assign it directly as is may corrupt the data in your database"
|
315
|
+
end
|
316
|
+
|
317
|
+
def #{options[:right]}=(right)
|
318
|
+
raise EvenBetterNestedSet::IllegalAssignmentError, "#{options[:right]} is an internal attribute used by EvenBetterNestedSet, do not assign it directly as is may corrupt the data in your database"
|
319
|
+
end
|
320
|
+
RUBY
|
321
|
+
|
322
|
+
named_scope :roots, :conditions => { :parent_id => nil }, :order => "#{nested_set_column(:left)} asc"
|
323
|
+
|
324
|
+
has_many :uncached_children, :class_name => self.name, :foreign_key => :parent_id, :order => "#{nested_set_column(:left)} asc"
|
325
|
+
protected :uncached_children, :uncached_children=
|
326
|
+
|
327
|
+
belongs_to :parent, :class_name => self.name, :foreign_key => :parent_id
|
328
|
+
|
329
|
+
named_scope :descendants, lambda { |node|
|
330
|
+
left, right = find_boundaries(node.id)
|
331
|
+
{ :conditions => ["#{nested_set_column(:left)} > ? and #{nested_set_column(:right)} < ?", left, right],
|
332
|
+
:order => "#{nested_set_column(:left)} asc" }
|
333
|
+
}
|
334
|
+
|
335
|
+
before_create :append_node
|
336
|
+
before_update :move_node
|
337
|
+
before_destroy :reload
|
338
|
+
after_destroy :remove_node
|
339
|
+
validate_on_update :illegal_nesting
|
340
|
+
validate :validate_parent_is_within_scope
|
341
|
+
|
342
|
+
delegate :nested_set_column, :to => "self.class"
|
343
|
+
end
|
344
|
+
|
345
|
+
end
|
346
|
+
|
347
|
+
end
|
348
|
+
|
349
|
+
ActiveRecord::Base.send(:include, EvenBetterNestedSet) if defined?(ActiveRecord)
|