shuber-awesome_nested_set 1.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/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2007 [name of plugin creator]
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.rdoc ADDED
@@ -0,0 +1,64 @@
1
+ = AwesomeNestedSet
2
+
3
+ Awesome Nested Set is an implementation of the nested set pattern for ActiveRecord models. It is replacement for acts_as_nested_set and BetterNestedSet, but awesomer.
4
+
5
+ == What makes this so awesome?
6
+
7
+ This is a new implementation of nested set based off of BetterNestedSet that fixes some bugs, removes tons of duplication, adds a few useful methods, and adds STI support.
8
+
9
+ == Installation
10
+
11
+ If you are on Rails 2.1 or later:
12
+
13
+ script/plugin install git://github.com/collectiveidea/awesome_nested_set.git
14
+
15
+ == Usage
16
+
17
+ To make use of awesome_nested_set, your model needs to have 3 fields: lft, rgt, and parent_id:
18
+
19
+ class CreateCategories < ActiveRecord::Migration
20
+ def self.up
21
+ create_table :categories do |t|
22
+ t.string :name
23
+ t.integer :parent_id
24
+ t.integer :lft
25
+ t.integer :rgt
26
+ end
27
+ end
28
+
29
+ def self.down
30
+ drop_table :categories
31
+ end
32
+ end
33
+
34
+ Enable the nested set functionality by declaring acts_as_nested_set on your model
35
+
36
+ class Category < ActiveRecord::Base
37
+ acts_as_nested_set
38
+ end
39
+
40
+ Run `rake rdoc` to generate the API docs and see CollectiveIdea::Acts::NestedSet::SingletonMethods for more info.
41
+
42
+ == View Helper
43
+
44
+ The view helper is called #nested_set_options.
45
+
46
+ Example usage:
47
+
48
+ <%= f.select :parent_id, nested_set_options(Category, @category) {|i| "#{'-' * i.level} #{i.name}" } %>
49
+
50
+ <%= select_tag 'parent_id', options_for_select(nested_set_options(Category) {|i| "#{'-' * i.level} #{i.name}" } ) %>
51
+
52
+ See CollectiveIdea::Acts::NestedSet::Helper for more information about the helpers.
53
+
54
+ == References
55
+
56
+ You can learn more about nested sets at:
57
+
58
+ http://www.dbmsmag.com/9603d06.html
59
+ http://threebit.net/tutorials/nestedset/tutorial1.html
60
+ http://api.rubyonrails.com/classes/ActiveRecord/Acts/NestedSet/ClassMethods.html
61
+ http://opensource.symetrie.com/trac/better_nested_set/
62
+
63
+
64
+ Copyright (c) 2008 Collective Idea, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,46 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+ require 'rake/gempackagetask'
5
+ require 'rcov/rcovtask'
6
+ require "load_multi_rails_rake_tasks"
7
+
8
+ spec = eval(File.read("#{File.dirname(__FILE__)}/awesome_nested_set.gemspec"))
9
+ PKG_NAME = spec.name
10
+ PKG_VERSION = spec.version
11
+
12
+ Rake::GemPackageTask.new(spec) do |pkg|
13
+ pkg.need_zip = true
14
+ pkg.need_tar = true
15
+ end
16
+
17
+
18
+ desc 'Default: run unit tests.'
19
+ task :default => :test
20
+
21
+ desc 'Test the awesome_nested_set plugin.'
22
+ Rake::TestTask.new(:test) do |t|
23
+ t.libs << 'lib'
24
+ t.pattern = 'test/**/*_test.rb'
25
+ t.verbose = true
26
+ end
27
+
28
+ desc 'Generate documentation for the awesome_nested_set plugin.'
29
+ Rake::RDocTask.new(:rdoc) do |rdoc|
30
+ rdoc.rdoc_dir = 'rdoc'
31
+ rdoc.title = 'AwesomeNestedSet'
32
+ rdoc.options << '--line-numbers' << '--inline-source'
33
+ rdoc.rdoc_files.include('README.rdoc')
34
+ rdoc.rdoc_files.include('lib/**/*.rb')
35
+ end
36
+
37
+ namespace :test do
38
+ desc "just rcov minus html output"
39
+ Rcov::RcovTask.new(:coverage) do |t|
40
+ # t.libs << 'test'
41
+ t.test_files = FileList['test/**/*_test.rb']
42
+ t.output_dir = 'coverage'
43
+ t.verbose = true
44
+ t.rcov_opts = %w(--exclude test,/usr/lib/ruby,/Library/Ruby,lib/awesome_nested_set/named_scope.rb --sort coverage)
45
+ end
46
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__) + "/rails/init"
@@ -0,0 +1,546 @@
1
+ module CollectiveIdea #:nodoc:
2
+ module Acts #:nodoc:
3
+ module NestedSet #:nodoc:
4
+ def self.included(base)
5
+ base.extend(SingletonMethods)
6
+ end
7
+
8
+ # This acts provides Nested Set functionality. Nested Set is a smart way to implement
9
+ # an _ordered_ tree, with the added feature that you can select the children and all of their
10
+ # descendants with a single query. The drawback is that insertion or move need some complex
11
+ # sql queries. But everything is done here by this module!
12
+ #
13
+ # Nested sets are appropriate each time you want either an orderd tree (menus,
14
+ # commercial categories) or an efficient way of querying big trees (threaded posts).
15
+ #
16
+ # == API
17
+ #
18
+ # Methods names are aligned with acts_as_tree as much as possible, to make replacment from one
19
+ # by another easier, except for the creation:
20
+ #
21
+ # in acts_as_tree:
22
+ # item.children.create(:name => "child1")
23
+ #
24
+ # in acts_as_nested_set:
25
+ # # adds a new item at the "end" of the tree, i.e. with child.left = max(tree.right)+1
26
+ # child = MyClass.new(:name => "child1")
27
+ # child.save
28
+ # # now move the item to its right place
29
+ # child.move_to_child_of my_item
30
+ #
31
+ # You can pass an id or an object to:
32
+ # * <tt>#move_to_child_of</tt>
33
+ # * <tt>#move_to_right_of</tt>
34
+ # * <tt>#move_to_left_of</tt>
35
+ #
36
+ module SingletonMethods
37
+ # Configuration options are:
38
+ #
39
+ # * +:parent_column+ - specifies the column name to use for keeping the position integer (default: parent_id)
40
+ # * +:left_column+ - column name for left boundry data, default "lft"
41
+ # * +:right_column+ - column name for right boundry data, default "rgt"
42
+ # * +:scope+ - restricts what is to be considered a list. Given a symbol, it'll attach "_id"
43
+ # (if it hasn't been already) and use that as the foreign key restriction. You
44
+ # can also pass an array to scope by multiple attributes.
45
+ # Example: <tt>acts_as_nested_set :scope => [:notable_id, :notable_type]</tt>
46
+ # * +:dependent+ - behavior for cascading destroy. If set to :destroy, all the
47
+ # child objects are destroyed alongside this object by calling their destroy
48
+ # method. If set to :delete_all (default), all the child objects are deleted
49
+ # without calling their destroy method.
50
+ #
51
+ # See CollectiveIdea::Acts::NestedSet::ClassMethods for a list of class methods and
52
+ # CollectiveIdea::Acts::NestedSet::InstanceMethods for a list of instance methods added
53
+ # to acts_as_nested_set models
54
+ def acts_as_nested_set(options = {})
55
+ options = {
56
+ :parent_column => 'parent_id',
57
+ :left_column => 'lft',
58
+ :right_column => 'rgt',
59
+ :dependent => :delete_all, # or :destroy
60
+ }.merge(options)
61
+
62
+ if options[:scope].is_a?(Symbol) && options[:scope].to_s !~ /_id$/
63
+ options[:scope] = "#{options[:scope]}_id".intern
64
+ end
65
+
66
+ write_inheritable_attribute :acts_as_nested_set_options, options
67
+ class_inheritable_reader :acts_as_nested_set_options
68
+
69
+ include Comparable
70
+ include Columns
71
+ include InstanceMethods
72
+ extend Columns
73
+ extend ClassMethods
74
+
75
+ # no bulk assignment
76
+ attr_protected left_column_name.intern,
77
+ right_column_name.intern,
78
+ parent_column_name.intern
79
+
80
+ before_create :set_default_left_and_right
81
+ before_destroy :prune_from_tree
82
+
83
+ # no assignment to structure fields
84
+ [left_column_name, right_column_name, parent_column_name].each do |column|
85
+ module_eval <<-"end_eval", __FILE__, __LINE__
86
+ def #{column}=(x)
87
+ raise ActiveRecord::ActiveRecordError, "Unauthorized assignment to #{column}: it's an internal field handled by acts_as_nested_set code, use move_to_* methods instead."
88
+ end
89
+ end_eval
90
+ end
91
+
92
+ named_scope :roots, :conditions => {parent_column_name => nil}, :order => quoted_left_column_name
93
+ named_scope :leaves, :conditions => "#{quoted_right_column_name} - #{quoted_left_column_name} = 1", :order => quoted_left_column_name
94
+ if self.respond_to?(:define_callbacks)
95
+ define_callbacks("before_move", "after_move")
96
+ end
97
+
98
+
99
+ end
100
+
101
+ end
102
+
103
+ module ClassMethods
104
+
105
+ # Returns the first root
106
+ def root
107
+ roots.find(:first)
108
+ end
109
+
110
+ def valid?
111
+ left_and_rights_valid? && no_duplicates_for_columns? && all_roots_valid?
112
+ end
113
+
114
+ def left_and_rights_valid?
115
+ count(
116
+ :joins => "LEFT OUTER JOIN #{quoted_table_name} AS parent ON " +
117
+ "#{quoted_table_name}.#{quoted_parent_column_name} = parent.#{primary_key}",
118
+ :conditions =>
119
+ "#{quoted_table_name}.#{quoted_left_column_name} IS NULL OR " +
120
+ "#{quoted_table_name}.#{quoted_right_column_name} IS NULL OR " +
121
+ "#{quoted_table_name}.#{quoted_left_column_name} >= " +
122
+ "#{quoted_table_name}.#{quoted_right_column_name} OR " +
123
+ "(#{quoted_table_name}.#{quoted_parent_column_name} IS NOT NULL AND " +
124
+ "(#{quoted_table_name}.#{quoted_left_column_name} <= parent.#{quoted_left_column_name} OR " +
125
+ "#{quoted_table_name}.#{quoted_right_column_name} >= parent.#{quoted_right_column_name}))"
126
+ ) == 0
127
+ end
128
+
129
+ def no_duplicates_for_columns?
130
+ scope_string = Array(acts_as_nested_set_options[:scope]).map do |c|
131
+ connection.quote_column_name(c)
132
+ end.push(nil).join(", ")
133
+ [quoted_left_column_name, quoted_right_column_name].all? do |column|
134
+ # No duplicates
135
+ find(:first,
136
+ :select => "#{scope_string}#{column}, COUNT(#{column})",
137
+ :group => "#{scope_string}#{column}
138
+ HAVING COUNT(#{column}) > 1").nil?
139
+ end
140
+ end
141
+
142
+ # Wrapper for each_root_valid? that can deal with scope.
143
+ def all_roots_valid?
144
+ if acts_as_nested_set_options[:scope]
145
+ roots(:group => scope_column_names).group_by{|record| scope_column_names.collect{|col| record.send(col.to_sym)}}.all? do |scope, grouped_roots|
146
+ each_root_valid?(grouped_roots)
147
+ end
148
+ else
149
+ each_root_valid?(roots)
150
+ end
151
+ end
152
+
153
+ def each_root_valid?(roots_to_validate)
154
+ left = right = 0
155
+ roots_to_validate.all? do |root|
156
+ returning(root.left > left && root.right > right) do
157
+ left = root.left
158
+ right = root.right
159
+ end
160
+ end
161
+ end
162
+
163
+ # Rebuilds the left & rights if unset or invalid. Also very useful for converting from acts_as_tree.
164
+ def rebuild!
165
+ # Don't rebuild a valid tree.
166
+ return true if valid?
167
+
168
+ scope = lambda{}
169
+ if acts_as_nested_set_options[:scope]
170
+ scope = lambda{|node|
171
+ scope_column_names.inject(""){|str, column_name|
172
+ str << "AND #{connection.quote_column_name(column_name)} = #{connection.quote(node.send(column_name.to_sym))} "
173
+ }
174
+ }
175
+ end
176
+ indices = {}
177
+
178
+ set_left_and_rights = lambda do |node|
179
+ # set left
180
+ node[left_column_name] = indices[scope.call(node)] += 1
181
+ # find
182
+ find(:all, :conditions => ["#{quoted_parent_column_name} = ? #{scope.call(node)}", node], :order => "#{quoted_left_column_name}, #{quoted_right_column_name}, id").each{|n| set_left_and_rights.call(n) }
183
+ # set right
184
+ node[right_column_name] = indices[scope.call(node)] += 1
185
+ node.save!
186
+ end
187
+
188
+ # Find root node(s)
189
+ root_nodes = find(:all, :conditions => "#{quoted_parent_column_name} IS NULL", :order => "#{quoted_left_column_name}, #{quoted_right_column_name}, id").each do |root_node|
190
+ # setup index for this scope
191
+ indices[scope.call(root_node)] ||= 0
192
+ set_left_and_rights.call(root_node)
193
+ end
194
+ end
195
+ end
196
+
197
+ # Mixed into both classes and instances to provide easy access to the column names
198
+ module Columns
199
+ def left_column_name
200
+ acts_as_nested_set_options[:left_column]
201
+ end
202
+
203
+ def right_column_name
204
+ acts_as_nested_set_options[:right_column]
205
+ end
206
+
207
+ def parent_column_name
208
+ acts_as_nested_set_options[:parent_column]
209
+ end
210
+
211
+ def scope_column_names
212
+ Array(acts_as_nested_set_options[:scope])
213
+ end
214
+
215
+ def quoted_left_column_name
216
+ connection.quote_column_name(left_column_name)
217
+ end
218
+
219
+ def quoted_right_column_name
220
+ connection.quote_column_name(right_column_name)
221
+ end
222
+
223
+ def quoted_parent_column_name
224
+ connection.quote_column_name(parent_column_name)
225
+ end
226
+
227
+ def quoted_scope_column_names
228
+ scope_column_names.collect {|column_name| connection.quote_column_name(column_name) }
229
+ end
230
+ end
231
+
232
+ # Any instance method that returns a collection makes use of Rails 2.1's named_scope (which is bundled for Rails 2.0), so it can be treated as a finder.
233
+ #
234
+ # category.self_and_descendants.count
235
+ # category.ancestors.find(:all, :conditions => "name like '%foo%'")
236
+ module InstanceMethods
237
+ # Value of the parent column
238
+ def parent_id
239
+ self[parent_column_name]
240
+ end
241
+
242
+ # Value of the left column
243
+ def left
244
+ self[left_column_name]
245
+ end
246
+
247
+ # Value of the right column
248
+ def right
249
+ self[right_column_name]
250
+ end
251
+
252
+ # Returns true if this is a root node.
253
+ def root?
254
+ parent_id.nil?
255
+ end
256
+
257
+ def leaf?
258
+ right - left == 1
259
+ end
260
+
261
+ # Returns true is this is a child node
262
+ def child?
263
+ !parent_id.nil?
264
+ end
265
+
266
+ # order by left column
267
+ def <=>(x)
268
+ left <=> x.left
269
+ end
270
+
271
+ # Redefine to act like active record
272
+ def ==(comparison_object)
273
+ comparison_object.equal?(self) ||
274
+ (comparison_object.instance_of?(self.class) &&
275
+ comparison_object.id == id &&
276
+ !comparison_object.new_record?)
277
+ end
278
+
279
+ # Returns root
280
+ def root
281
+ self_and_ancestors.find(:first)
282
+ end
283
+
284
+ # Returns the immediate parent
285
+ def parent
286
+ nested_set_scope.find_by_id(parent_id) if parent_id
287
+ end
288
+
289
+ # Returns the array of all parents and self
290
+ def self_and_ancestors
291
+ nested_set_scope.scoped :conditions => [
292
+ "#{self.class.table_name}.#{quoted_left_column_name} <= ? AND #{self.class.table_name}.#{quoted_right_column_name} >= ?", left, right
293
+ ]
294
+ end
295
+
296
+ # Returns an array of all parents
297
+ def ancestors
298
+ without_self self_and_ancestors
299
+ end
300
+
301
+ # Returns the array of all children of the parent, including self
302
+ def self_and_siblings
303
+ nested_set_scope.scoped :conditions => {parent_column_name => parent_id}
304
+ end
305
+
306
+ # Returns the array of all children of the parent, except self
307
+ def siblings
308
+ without_self self_and_siblings
309
+ end
310
+
311
+ # Returns a set of all of its nested children which do not have children
312
+ def leaves
313
+ descendants.scoped :conditions => "#{self.class.table_name}.#{quoted_right_column_name} - #{self.class.table_name}.#{quoted_left_column_name} = 1"
314
+ end
315
+
316
+ # Returns the level of this object in the tree
317
+ # root level is 0
318
+ def level
319
+ parent_id.nil? ? 0 : ancestors.count
320
+ end
321
+
322
+ # Returns a set of itself and all of its nested children
323
+ def self_and_descendants
324
+ nested_set_scope.scoped :conditions => [
325
+ "#{self.class.table_name}.#{quoted_left_column_name} >= ? AND #{self.class.table_name}.#{quoted_right_column_name} <= ?", left, right
326
+ ]
327
+ end
328
+
329
+ # Returns a set of all of its children and nested children
330
+ def descendants
331
+ without_self self_and_descendants
332
+ end
333
+
334
+ # Returns a set of only this entry's immediate children
335
+ def children
336
+ nested_set_scope.scoped :conditions => {parent_column_name => self}
337
+ end
338
+
339
+ def is_descendant_of?(other)
340
+ other.left < self.left && self.left < other.right && same_scope?(other)
341
+ end
342
+
343
+ def is_or_is_descendant_of?(other)
344
+ other.left <= self.left && self.left < other.right && same_scope?(other)
345
+ end
346
+
347
+ def is_ancestor_of?(other)
348
+ self.left < other.left && other.left < self.right && same_scope?(other)
349
+ end
350
+
351
+ def is_or_is_ancestor_of?(other)
352
+ self.left <= other.left && other.left < self.right && same_scope?(other)
353
+ end
354
+
355
+ # Check if other model is in the same scope
356
+ def same_scope?(other)
357
+ Array(acts_as_nested_set_options[:scope]).all? do |attr|
358
+ self.send(attr) == other.send(attr)
359
+ end
360
+ end
361
+
362
+ # Find the first sibling to the left
363
+ def left_sibling
364
+ siblings.find(:first, :conditions => ["#{self.class.table_name}.#{quoted_left_column_name} < ?", left],
365
+ :order => "#{self.class.table_name}.#{quoted_left_column_name} DESC")
366
+ end
367
+
368
+ # Find the first sibling to the right
369
+ def right_sibling
370
+ siblings.find(:first, :conditions => ["#{self.class.table_name}.#{quoted_left_column_name} > ?", left])
371
+ end
372
+
373
+ # Shorthand method for finding the left sibling and moving to the left of it.
374
+ def move_left
375
+ move_to_left_of left_sibling
376
+ end
377
+
378
+ # Shorthand method for finding the right sibling and moving to the right of it.
379
+ def move_right
380
+ move_to_right_of right_sibling
381
+ end
382
+
383
+ # Move the node to the left of another node (you can pass id only)
384
+ def move_to_left_of(node)
385
+ move_to node, :left
386
+ end
387
+
388
+ # Move the node to the left of another node (you can pass id only)
389
+ def move_to_right_of(node)
390
+ move_to node, :right
391
+ end
392
+
393
+ # Move the node to the child of another node (you can pass id only)
394
+ def move_to_child_of(node)
395
+ move_to node, :child
396
+ end
397
+
398
+ # Move the node to root nodes
399
+ def move_to_root
400
+ move_to nil, :root
401
+ end
402
+
403
+ def move_possible?(target)
404
+ self != target && # Can't target self
405
+ same_scope?(target) && # can't be in different scopes
406
+ # !(left..right).include?(target.left..target.right) # this needs tested more
407
+ # detect impossible move
408
+ !((left <= target.left && right >= target.left) or (left <= target.right && right >= target.right))
409
+ end
410
+
411
+ def to_text
412
+ self_and_descendants.map do |node|
413
+ "#{'*'*(node.level+1)} #{node.id} #{node.to_s} (#{node.parent_id}, #{node.left}, #{node.right})"
414
+ end.join("\n")
415
+ end
416
+
417
+ protected
418
+
419
+ def without_self(scope)
420
+ scope.scoped :conditions => ["#{self.class.table_name}.#{self.class.primary_key} != ?", self]
421
+ end
422
+
423
+ # All nested set queries should use this nested_set_scope, which performs finds on
424
+ # the base ActiveRecord class, using the :scope declared in the acts_as_nested_set
425
+ # declaration.
426
+ def nested_set_scope
427
+ options = {:order => quoted_left_column_name}
428
+ scopes = Array(acts_as_nested_set_options[:scope])
429
+ options[:conditions] = scopes.inject({}) do |conditions,attr|
430
+ conditions.merge attr => self[attr]
431
+ end unless scopes.empty?
432
+ self.class.base_class.scoped options
433
+ end
434
+
435
+ # on creation, set automatically lft and rgt to the end of the tree
436
+ def set_default_left_and_right
437
+ maxright = nested_set_scope.maximum(right_column_name) || 0
438
+ # adds the new node to the right of all existing nodes
439
+ self[left_column_name] = maxright + 1
440
+ self[right_column_name] = maxright + 2
441
+ end
442
+
443
+ # Prunes a branch off of the tree, shifting all of the elements on the right
444
+ # back to the left so the counts still work.
445
+ def prune_from_tree
446
+ return if right.nil? || left.nil?
447
+ diff = right - left + 1
448
+
449
+ delete_method = acts_as_nested_set_options[:dependent] == :destroy ?
450
+ :destroy_all : :delete_all
451
+
452
+ self.class.base_class.transaction do
453
+ nested_set_scope.send(delete_method,
454
+ ["#{quoted_left_column_name} > ? AND #{quoted_right_column_name} < ?",
455
+ left, right]
456
+ )
457
+ nested_set_scope.update_all(
458
+ ["#{quoted_left_column_name} = (#{quoted_left_column_name} - ?)", diff],
459
+ ["#{quoted_left_column_name} >= ?", right]
460
+ )
461
+ nested_set_scope.update_all(
462
+ ["#{quoted_right_column_name} = (#{quoted_right_column_name} - ?)", diff],
463
+ ["#{quoted_right_column_name} >= ?", right]
464
+ )
465
+ end
466
+ end
467
+
468
+ # reload left, right, and parent
469
+ def reload_nested_set
470
+ reload(:select => "#{quoted_left_column_name}, " +
471
+ "#{quoted_right_column_name}, #{quoted_parent_column_name}")
472
+ end
473
+
474
+ def move_to(target, position)
475
+ raise ActiveRecord::ActiveRecordError, "You cannot move a new node" if self.new_record?
476
+ return if callback(:before_move) == false
477
+ transaction do
478
+ if target.is_a? self.class.base_class
479
+ target.reload_nested_set
480
+ elsif position != :root
481
+ # load object if node is not an object
482
+ target = nested_set_scope.find(target)
483
+ end
484
+ self.reload_nested_set
485
+
486
+ unless position == :root || move_possible?(target)
487
+ raise ActiveRecord::ActiveRecordError, "Impossible move, target node cannot be inside moved tree."
488
+ end
489
+
490
+ bound = case position
491
+ when :child; target[right_column_name]
492
+ when :left; target[left_column_name]
493
+ when :right; target[right_column_name] + 1
494
+ when :root; 1
495
+ else raise ActiveRecord::ActiveRecordError, "Position should be :child, :left, :right or :root ('#{position}' received)."
496
+ end
497
+
498
+ if bound > self[right_column_name]
499
+ bound = bound - 1
500
+ other_bound = self[right_column_name] + 1
501
+ else
502
+ other_bound = self[left_column_name] - 1
503
+ end
504
+
505
+ # there would be no change
506
+ # return if bound == self[right_column_name] || bound == self[left_column_name]
507
+
508
+ # we have defined the boundaries of two non-overlapping intervals,
509
+ # so sorting puts both the intervals and their boundaries in order
510
+ a, b, c, d = [self[left_column_name], self[right_column_name], bound, other_bound].sort
511
+
512
+ new_parent = case position
513
+ when :child; target.id
514
+ when :root; nil
515
+ else target[parent_column_name]
516
+ end
517
+
518
+ self.class.base_class.update_all([
519
+ "#{quoted_left_column_name} = CASE " +
520
+ "WHEN #{quoted_left_column_name} BETWEEN :a AND :b " +
521
+ "THEN #{quoted_left_column_name} + :d - :b " +
522
+ "WHEN #{quoted_left_column_name} BETWEEN :c AND :d " +
523
+ "THEN #{quoted_left_column_name} + :a - :c " +
524
+ "ELSE #{quoted_left_column_name} END, " +
525
+ "#{quoted_right_column_name} = CASE " +
526
+ "WHEN #{quoted_right_column_name} BETWEEN :a AND :b " +
527
+ "THEN #{quoted_right_column_name} + :d - :b " +
528
+ "WHEN #{quoted_right_column_name} BETWEEN :c AND :d " +
529
+ "THEN #{quoted_right_column_name} + :a - :c " +
530
+ "ELSE #{quoted_right_column_name} END, " +
531
+ "#{quoted_parent_column_name} = CASE " +
532
+ "WHEN #{self.class.base_class.primary_key} = :id THEN :new_parent " +
533
+ "ELSE #{quoted_parent_column_name} END",
534
+ {:a => a, :b => b, :c => c, :d => d, :id => self.id, :new_parent => new_parent}
535
+ ], nested_set_scope.proxy_options[:conditions])
536
+ end
537
+ target.reload_nested_set if target
538
+ self.reload_nested_set
539
+ callback(:after_move)
540
+ end
541
+
542
+ end
543
+
544
+ end
545
+ end
546
+ end