ggoodale-awesome_nested_set 1.1

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,68 @@
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, adds STI support and adds 2 very helpful view helpers for select boxes.
8
+
9
+ == Installation
10
+
11
+ If you are on edge rails:
12
+
13
+ script/plugin install git://github.com/collectiveidea/awesome_nested_set.git
14
+
15
+ If you are not on edge rails:
16
+
17
+ git clone git://github.com/collectiveidea/awesome_nested_set.git vendor/plugins/awesome_nested_set
18
+
19
+ == Usage
20
+
21
+ To make use of awesome_nested_set, your model needs to have 3 fields: lft, rgt, and parent_id:
22
+
23
+ class CreateCategories < ActiveRecord::Migration
24
+ def self.up
25
+ create_table :categories do |t|
26
+ t.string :name
27
+ t.integer :parent_id
28
+ t.integer :lft
29
+ t.integer :rgt
30
+ end
31
+ end
32
+
33
+ def self.down
34
+ drop_table :categories
35
+ end
36
+ end
37
+
38
+ Enable the nested set functionality by declaring acts_as_nested_set on your model
39
+
40
+ class Category < ActiveRecord::Base
41
+ acts_as_nested_set
42
+ end
43
+
44
+ Run `rake rdoc` to generate the API docs and see CollectiveIdea::Acts::NestedSet::SingletonMethods for more info.
45
+
46
+ == View Helper
47
+
48
+ The view helper is called #nested_set_options.
49
+
50
+ Example usage:
51
+
52
+ <%= f.select :parent_id, nested_set_options(Category, @category) {|i| "#{'�' * i.level} #{i.name}" }) %>
53
+
54
+ <%= select_tag 'parent_id', options_for_select(nested_set_options(Category) {|i| "#{'�' * i.level} #{i.name}" } ) %>
55
+
56
+ See CollectiveIdea::Acts::NestedSet::Helper for more information about the helpers.
57
+
58
+ == References
59
+
60
+ You can learn more about nested sets at:
61
+
62
+ http://www.dbmsmag.com/9603d06.html
63
+ http://threebit.net/tutorials/nestedset/tutorial1.html
64
+ http://api.rubyonrails.com/classes/ActiveRecord/Acts/NestedSet/ClassMethods.html
65
+ http://opensource.symetrie.com/trac/better_nested_set/
66
+
67
+
68
+ 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,534 @@
1
+ module CollectiveIdea
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 InstanceMethods
70
+ include Comparable
71
+ include Columns
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
+
95
+ end
96
+
97
+ end
98
+
99
+ module ClassMethods
100
+
101
+ # Returns the first root
102
+ def root
103
+ roots.find(:first)
104
+ end
105
+
106
+ def valid?
107
+ left_and_rights_valid? && no_duplicates_for_columns? && all_roots_valid?
108
+ end
109
+
110
+ def left_and_rights_valid?
111
+ count(
112
+ :joins => "LEFT OUTER JOIN #{quoted_table_name} AS parent ON " +
113
+ "#{quoted_table_name}.#{quoted_parent_column_name} = parent.#{primary_key}",
114
+ :conditions =>
115
+ "#{quoted_table_name}.#{quoted_left_column_name} IS NULL OR " +
116
+ "#{quoted_table_name}.#{quoted_right_column_name} IS NULL OR " +
117
+ "#{quoted_table_name}.#{quoted_left_column_name} >= " +
118
+ "#{quoted_table_name}.#{quoted_right_column_name} OR " +
119
+ "(#{quoted_table_name}.#{quoted_parent_column_name} IS NOT NULL AND " +
120
+ "(#{quoted_table_name}.#{quoted_left_column_name} <= parent.#{quoted_left_column_name} OR " +
121
+ "#{quoted_table_name}.#{quoted_right_column_name} >= parent.#{quoted_right_column_name}))"
122
+ ) == 0
123
+ end
124
+
125
+ def no_duplicates_for_columns?
126
+ scope_string = Array(acts_as_nested_set_options[:scope]).map do |c|
127
+ connection.quote_column_name(c)
128
+ end.push(nil).join(", ")
129
+ [quoted_left_column_name, quoted_right_column_name].all? do |column|
130
+ # No duplicates
131
+ find(:first,
132
+ :select => "#{scope_string}#{column}, COUNT(#{column})",
133
+ :group => "#{scope_string}#{column}
134
+ HAVING COUNT(#{column}) > 1").nil?
135
+ end
136
+ end
137
+
138
+ # Wrapper for each_root_valid? that can deal with scope.
139
+ def all_roots_valid?
140
+ if acts_as_nested_set_options[:scope]
141
+ roots(:group => scope_column_names).group_by{|record| scope_column_names.collect{|col| record.send(col.to_sym)}}.all? do |scope, grouped_roots|
142
+ each_root_valid?(grouped_roots)
143
+ end
144
+ else
145
+ each_root_valid?(roots)
146
+ end
147
+ end
148
+
149
+ def each_root_valid?(roots_to_validate)
150
+ left = right = 0
151
+ roots_to_validate.all? do |root|
152
+ returning(root.left > left && root.right > right) do
153
+ left = root.left
154
+ right = root.right
155
+ end
156
+ end
157
+ end
158
+
159
+ # Rebuilds the left & rights if unset or invalid. Also very useful for converting from acts_as_tree.
160
+ def rebuild!
161
+ # Don't rebuild a valid tree.
162
+ return true if valid?
163
+
164
+ scope = lambda{}
165
+ if acts_as_nested_set_options[:scope]
166
+ scope = lambda{|node|
167
+ scope_column_names.inject(""){|str, column_name|
168
+ str << "AND #{connection.quote_column_name(column_name)} = #{connection.quote(node.send(column_name.to_sym))} "
169
+ }
170
+ }
171
+ end
172
+ indices = {}
173
+
174
+ set_left_and_rights = lambda do |node|
175
+ # set left
176
+ node[left_column_name] = indices[scope.call(node)] += 1
177
+ # find
178
+ 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) }
179
+ # set right
180
+ node[right_column_name] = indices[scope.call(node)] += 1
181
+ node.save!
182
+ end
183
+
184
+ # Find root node(s)
185
+ 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|
186
+ # setup index for this scope
187
+ indices[scope.call(root_node)] ||= 0
188
+ set_left_and_rights.call(root_node)
189
+ end
190
+ end
191
+ end
192
+
193
+ # Mixed into both classes and instances to provide easy access to the column names
194
+ module Columns
195
+ def left_column_name
196
+ acts_as_nested_set_options[:left_column]
197
+ end
198
+
199
+ def right_column_name
200
+ acts_as_nested_set_options[:right_column]
201
+ end
202
+
203
+ def parent_column_name
204
+ acts_as_nested_set_options[:parent_column]
205
+ end
206
+
207
+ def scope_column_names
208
+ acts_as_nested_set_options[:scope].to_a
209
+ end
210
+
211
+ def quoted_left_column_name
212
+ connection.quote_column_name(left_column_name)
213
+ end
214
+
215
+ def quoted_right_column_name
216
+ connection.quote_column_name(right_column_name)
217
+ end
218
+
219
+ def quoted_parent_column_name
220
+ connection.quote_column_name(parent_column_name)
221
+ end
222
+
223
+ def quoted_scope_column_names
224
+ scope_column_names.collect {|column_name| connection.quote_column_name(column_name) }
225
+ end
226
+ end
227
+
228
+ # 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.
229
+ #
230
+ # category.self_and_descendants.count
231
+ # category.ancestors.find(:all, :conditions => "name like '%foo%'")
232
+ module InstanceMethods
233
+ # Value of the parent column
234
+ def parent_id
235
+ self[parent_column_name]
236
+ end
237
+
238
+ # Value of the left column
239
+ def left
240
+ self[left_column_name]
241
+ end
242
+
243
+ # Value of the right column
244
+ def right
245
+ self[right_column_name]
246
+ end
247
+
248
+ # Returns true if this is a root node.
249
+ def root?
250
+ parent_id.nil?
251
+ end
252
+
253
+ def leaf?
254
+ right - left == 1
255
+ end
256
+
257
+ # Returns true is this is a child node
258
+ def child?
259
+ !parent_id.nil?
260
+ end
261
+
262
+ # order by left column
263
+ def <=>(x)
264
+ left <=> x.left
265
+ end
266
+
267
+ # Returns root
268
+ def root
269
+ self_and_ancestors.find(:first)
270
+ end
271
+
272
+ # Returns the immediate parent
273
+ def parent
274
+ nested_set_scope.find_by_id(parent_id) if parent_id
275
+ end
276
+
277
+ # Returns the array of all parents and self
278
+ def self_and_ancestors
279
+ nested_set_scope.scoped :conditions => [
280
+ "#{self.class.table_name}.#{quoted_left_column_name} <= ? AND #{self.class.table_name}.#{quoted_right_column_name} >= ?", left, right
281
+ ]
282
+ end
283
+
284
+ # Returns an array of all parents
285
+ def ancestors
286
+ without_self self_and_ancestors
287
+ end
288
+
289
+ # Returns the array of all children of the parent, including self
290
+ def self_and_siblings
291
+ nested_set_scope.scoped :conditions => {parent_column_name => parent_id}
292
+ end
293
+
294
+ # Returns the array of all children of the parent, except self
295
+ def siblings
296
+ without_self self_and_siblings
297
+ end
298
+
299
+ # Returns a set of all of its nested children which do not have children
300
+ def leaves
301
+ descendants.scoped :conditions => "#{self.class.table_name}.#{quoted_right_column_name} - #{self.class.table_name}.#{quoted_left_column_name} = 1"
302
+ end
303
+
304
+ # Returns the level of this object in the tree
305
+ # root level is 0
306
+ def level
307
+ parent_id.nil? ? 0 : ancestors.count
308
+ end
309
+
310
+ # Returns a set of itself and all of its nested children
311
+ def self_and_descendants
312
+ nested_set_scope.scoped :conditions => [
313
+ "#{self.class.table_name}.#{quoted_left_column_name} >= ? AND #{self.class.table_name}.#{quoted_right_column_name} <= ?", left, right
314
+ ]
315
+ end
316
+
317
+ # Returns a set of all of its children and nested children
318
+ def descendants
319
+ without_self self_and_descendants
320
+ end
321
+
322
+ # Returns a set of only this entry's immediate children
323
+ def children
324
+ nested_set_scope.scoped :conditions => {parent_column_name => self}
325
+ end
326
+
327
+ def is_descendant_of?(other)
328
+ other.left < self.left && self.left < other.right && same_scope?(other)
329
+ end
330
+
331
+ def is_or_is_descendant_of?(other)
332
+ other.left <= self.left && self.left < other.right && same_scope?(other)
333
+ end
334
+
335
+ def is_ancestor_of?(other)
336
+ self.left < other.left && other.left < self.right && same_scope?(other)
337
+ end
338
+
339
+ def is_or_is_ancestor_of?(other)
340
+ self.left <= other.left && other.left < self.right && same_scope?(other)
341
+ end
342
+
343
+ # Check if other model is in the same scope
344
+ def same_scope?(other)
345
+ Array(acts_as_nested_set_options[:scope]).all? do |attr|
346
+ self.send(attr) == other.send(attr)
347
+ end
348
+ end
349
+
350
+ # Find the first sibling to the left
351
+ def left_sibling
352
+ siblings.find(:first, :conditions => ["#{self.class.table_name}.#{quoted_left_column_name} < ?", left],
353
+ :order => "#{self.class.table_name}.#{quoted_left_column_name} DESC")
354
+ end
355
+
356
+ # Find the first sibling to the right
357
+ def right_sibling
358
+ siblings.find(:first, :conditions => ["#{self.class.table_name}.#{quoted_left_column_name} > ?", left],
359
+ :order => quoted_left_column_name)
360
+ end
361
+
362
+ # Shorthand method for finding the left sibling and moving to the left of it.
363
+ def move_left
364
+ move_to_left_of left_sibling
365
+ end
366
+
367
+ # Shorthand method for finding the right sibling and moving to the right of it.
368
+ def move_right
369
+ move_to_right_of right_sibling
370
+ end
371
+
372
+ # Move the node to the left of another node (you can pass id only)
373
+ def move_to_left_of(node)
374
+ move_to node, :left
375
+ end
376
+
377
+ # Move the node to the left of another node (you can pass id only)
378
+ def move_to_right_of(node)
379
+ move_to node, :right
380
+ end
381
+
382
+ # Move the node to the child of another node (you can pass id only)
383
+ def move_to_child_of(node)
384
+ move_to node, :child
385
+ end
386
+
387
+ # Move the node to root nodes
388
+ def move_to_root
389
+ move_to nil, :root
390
+ end
391
+
392
+ def move_possible?(target)
393
+ self != target && # Can't target self
394
+ same_scope?(target) && # can't be in different scopes
395
+ # !(left..right).include?(target.left..target.right) # this needs tested more
396
+ # detect impossible move
397
+ !((left <= target.left && right >= target.left) or (left <= target.right && right >= target.right))
398
+ end
399
+
400
+ def to_text
401
+ self_and_descendants.map do |node|
402
+ "#{'*'*(node.level+1)} #{node.id} #{node.to_s} (#{node.parent_id}, #{node.left}, #{node.right})"
403
+ end.join("\n")
404
+ end
405
+
406
+ protected
407
+
408
+ def without_self(scope)
409
+ scope.scoped :conditions => ["#{self.class.table_name}.#{self.class.primary_key} != ?", self]
410
+ end
411
+
412
+ # All nested set queries should use this nested_set_scope, which performs finds on
413
+ # the base ActiveRecord class, using the :scope declared in the acts_as_nested_set
414
+ # declaration.
415
+ def nested_set_scope
416
+ options = {:order => quoted_left_column_name}
417
+ scopes = Array(acts_as_nested_set_options[:scope])
418
+ options[:conditions] = scopes.inject({}) do |conditions,attr|
419
+ conditions.merge attr => self[attr]
420
+ end unless scopes.empty?
421
+ self.class.base_class.scoped options
422
+ end
423
+
424
+ # on creation, set automatically lft and rgt to the end of the tree
425
+ def set_default_left_and_right
426
+ maxright = nested_set_scope.maximum(right_column_name) || 0
427
+ # adds the new node to the right of all existing nodes
428
+ self[left_column_name] = maxright + 1
429
+ self[right_column_name] = maxright + 2
430
+ end
431
+
432
+ # Prunes a branch off of the tree, shifting all of the elements on the right
433
+ # back to the left so the counts still work.
434
+ def prune_from_tree
435
+ return if right.nil? || left.nil?
436
+ diff = right - left + 1
437
+
438
+ delete_method = acts_as_nested_set_options[:dependent] == :destroy ?
439
+ :destroy_all : :delete_all
440
+
441
+ self.class.base_class.transaction do
442
+ nested_set_scope.send(delete_method,
443
+ ["#{quoted_left_column_name} > ? AND #{quoted_right_column_name} < ?",
444
+ left, right]
445
+ )
446
+ nested_set_scope.update_all(
447
+ ["#{quoted_left_column_name} = (#{quoted_left_column_name} - ?)", diff],
448
+ ["#{quoted_left_column_name} >= ?", right]
449
+ )
450
+ nested_set_scope.update_all(
451
+ ["#{quoted_right_column_name} = (#{quoted_right_column_name} - ?)", diff],
452
+ ["#{quoted_right_column_name} >= ?", right]
453
+ )
454
+ end
455
+ end
456
+
457
+ # reload left, right, and parent
458
+ def reload_nested_set
459
+ reload(:select => "#{quoted_left_column_name}, " +
460
+ "#{quoted_right_column_name}, #{quoted_parent_column_name}")
461
+ end
462
+
463
+ def move_to(target, position)
464
+ raise ActiveRecord::ActiveRecordError, "You cannot move a new node" if self.new_record?
465
+
466
+ transaction do
467
+ if target.is_a? self.class.base_class
468
+ target.reload_nested_set
469
+ elsif position != :root
470
+ # load object if node is not an object
471
+ target = nested_set_scope.find(target)
472
+ end
473
+ self.reload_nested_set
474
+
475
+ unless position == :root || move_possible?(target)
476
+ raise ActiveRecord::ActiveRecordError, "Impossible move, target node cannot be inside moved tree."
477
+ end
478
+
479
+ bound = case position
480
+ when :child; target[right_column_name]
481
+ when :left; target[left_column_name]
482
+ when :right; target[right_column_name] + 1
483
+ when :root; 1
484
+ else raise ActiveRecord::ActiveRecordError, "Position should be :child, :left, :right or :root ('#{position}' received)."
485
+ end
486
+
487
+ if bound > self[right_column_name]
488
+ bound = bound - 1
489
+ other_bound = self[right_column_name] + 1
490
+ else
491
+ other_bound = self[left_column_name] - 1
492
+ end
493
+
494
+ # there would be no change
495
+ return if bound == self[right_column_name] || bound == self[left_column_name]
496
+
497
+ # we have defined the boundaries of two non-overlapping intervals,
498
+ # so sorting puts both the intervals and their boundaries in order
499
+ a, b, c, d = [self[left_column_name], self[right_column_name], bound, other_bound].sort
500
+
501
+ new_parent = case position
502
+ when :child; target.id
503
+ when :root; nil
504
+ else target[parent_column_name]
505
+ end
506
+
507
+ self.class.base_class.update_all([
508
+ "#{quoted_left_column_name} = CASE " +
509
+ "WHEN #{quoted_left_column_name} BETWEEN :a AND :b " +
510
+ "THEN #{quoted_left_column_name} + :d - :b " +
511
+ "WHEN #{quoted_left_column_name} BETWEEN :c AND :d " +
512
+ "THEN #{quoted_left_column_name} + :a - :c " +
513
+ "ELSE #{quoted_left_column_name} END, " +
514
+ "#{quoted_right_column_name} = CASE " +
515
+ "WHEN #{quoted_right_column_name} BETWEEN :a AND :b " +
516
+ "THEN #{quoted_right_column_name} + :d - :b " +
517
+ "WHEN #{quoted_right_column_name} BETWEEN :c AND :d " +
518
+ "THEN #{quoted_right_column_name} + :a - :c " +
519
+ "ELSE #{quoted_right_column_name} END, " +
520
+ "#{quoted_parent_column_name} = CASE " +
521
+ "WHEN #{self.class.base_class.primary_key} = :id THEN :new_parent " +
522
+ "ELSE #{quoted_parent_column_name} END",
523
+ {:a => a, :b => b, :c => c, :d => d, :id => self.id, :new_parent => new_parent}
524
+ ], nested_set_scope.proxy_options[:conditions])
525
+ end
526
+ target.reload_nested_set if target
527
+ self.reload_nested_set
528
+ end
529
+
530
+ end
531
+
532
+ end
533
+ end
534
+ end