##// END OF EJS Templates
import awesome_nested_set 2.1.5...
Toshi MARUYAMA -
r12402:6f78b3a4080f
parent child
Show More
@@ -1,14 +1,17
1 before_install: gem install bundler --pre
1 2 notifications:
2 3 email:
3 4 - parndt@gmail.com
4 5 env:
5 6 - DB=sqlite3
6 7 - DB=sqlite3mem
7 - DB=postgresql
8 - DB=mysql
9 8 rvm:
10 9 - 1.8.7
11 10 - 1.9.2
12 11 - 1.9.3
13 - rbx-2.0
14 - jruby No newline at end of file
12 - rbx
13 - jruby
14 gemfile:
15 - gemfiles/Gemfile.rails-3.0.rb
16 - gemfiles/Gemfile.rails-3.1.rb
17 - gemfiles/Gemfile.rails-3.2.rb No newline at end of file
@@ -1,3 +1,41
1 2.1.5
2 * Worked around issues where AR#association wasn't present on Rails 3.0.x. [Philip Arndt]
3 * Adds option 'order_column' which defaults to 'left_column_name'. [gudata]
4 * Added moving with order functionality. [Sytse Sijbrandij]
5 * Use tablename in all select queries. [Mikhail Dieterle]
6 * Made sure all descendants' depths are updated when moving parent, not just immediate child. [Phil Thompson]
7 * Add documentation of the callbacks. [Tobias Maier]
8
9 2.1.4
10 * nested_set_options accept both Class & AR Relation. [Semyon Perepelitsa]
11 * Reduce the number of queries triggered by the canonical usage of `i.level` in the `nested_set` helpers. [thedarkone]
12 * Specifically require active_record [Bogdan Gusiev]
13 * compute_level now checks for a non nil association target. [Joel Nimety]
14
15 2.1.3
16 * Update child depth when parent node is moved. [Amanda Wagener]
17 * Added move_to_child_with_index. [Ben Zhang]
18 * Optimised self_and_descendants for when there's an index on lft. [Mark Torrance]
19 * Added support for an unsaved record to return the right 'root'. [Philip Arndt]
20
21 2.1.2
22 * Fixed regressions introduced. [Philip Arndt]
23
24 2.1.1
25 * Added 'depth' which indicates how many levels deep the node is.
26 This only works when you have a column called 'depth' in your table,
27 otherwise it doesn't set itself. [Philip Arndt]
28 * Rails 3.2 support added. [Gabriel Sobrinho]
29 * Oracle compatibility added. [Pikender Sharma]
30 * Adding row locking to deletion, locking source of pivot values, and adding retry on collisions. [Markus J. Q. Roberts]
31 * Added method and helper for sorting children by column. [bluegod]
32 * Fixed .all_roots_valid? to work with Postgres. [Joshua Clayton]
33 * Made compatible with polymorphic belongs_to. [Graham Randall]
34 * Added in the association callbacks to the children :has_many association. [Michael Deering]
35 * Modified helper to allow using array of objects as argument. [Rahmat Budiharso]
36 * Fixed cases where we were calling attr_protected. [Jacob Swanner]
37 * Fixed nil cases involving lft and rgt. [Stuart Coyle] and [Patrick Morgan]
38
1 39 2.0.2
2 40 * Fixed deprecation warning under Rails 3.1 [Philip Arndt]
3 41 * Converted Test::Unit matchers to RSpec. [Uģis Ozols]
@@ -16,7 +16,8 This is a new implementation of nested set based off of BetterNestedSet that fix
16 16
17 17 == Usage
18 18
19 To make use of awesome_nested_set, your model needs to have 3 fields: lft, rgt, and parent_id:
19 To make use of awesome_nested_set, your model needs to have 3 fields: lft, rgt, and parent_id.
20 You can also have an optional field: depth:
20 21
21 22 class CreateCategories < ActiveRecord::Migration
22 23 def self.up
@@ -25,6 +26,7 To make use of awesome_nested_set, your model needs to have 3 fields: lft, rgt,
25 26 t.integer :parent_id
26 27 t.integer :lft
27 28 t.integer :rgt
29 t.integer :depth # this is optional.
28 30 end
29 31 end
30 32
@@ -41,6 +43,57 Enable the nested set functionality by declaring acts_as_nested_set on your mode
41 43
42 44 Run `rake rdoc` to generate the API docs and see CollectiveIdea::Acts::NestedSet for more info.
43 45
46 == Callbacks
47
48 There are three callbacks called when moving a node. `before_move`, `after_move` and `around_move`.
49
50 class Category < ActiveRecord::Base
51 acts_as_nested_set
52
53 after_move :rebuild_slug
54 around_move :da_fancy_things_around
55
56 private
57
58 def rebuild_slug
59 # do whatever
60 end
61
62 def da_fancy_things_around
63 # do something...
64 yield # actually moves
65 # do something else...
66 end
67 end
68
69 Beside this there are also hooks to act on the newly added or removed children.
70
71 class Category < ActiveRecord::Base
72 acts_as_nested_set :before_add => :do_before_add_stuff,
73 :after_add => :do_after_add_stuff,
74 :before_remove => :do_before_remove_stuff,
75 :after_remove => :do_after_remove_stuff
76
77 private
78
79 def do_before_add_stuff(child_node)
80 # do whatever with the child
81 end
82
83 def do_after_add_stuff(child_node)
84 # do whatever with the child
85 end
86
87 def do_before_remove_stuff(child_node)
88 # do whatever with the child
89 end
90
91 def do_after_remove_stuff(child_node)
92 # do whatever with the child
93 end
94 end
95
96
44 97 == Protecting attributes from mass assignment
45 98
46 99 It's generally best to "white list" the attributes that can be used in mass assignment:
@@ -86,13 +139,13 You can learn more about nested sets at: http://threebit.net/tutorials/nestedset
86 139 If you find what you might think is a bug:
87 140
88 141 1. Check the GitHub issue tracker to see if anyone else has had the same issue.
89 http://github.com/collectiveidea/awesome_nested_set/issues/
142 https://github.com/collectiveidea/awesome_nested_set/issues/
90 143 2. If you don't see anything, create an issue with information on how to reproduce it.
91 144
92 145 If you want to contribute an enhancement or a fix:
93 146
94 1. Fork the project on github.
95 http://github.com/collectiveidea/awesome_nested_set/
147 1. Fork the project on GitHub.
148 https://github.com/collectiveidea/awesome_nested_set/
96 149 2. Make your changes with tests.
97 150 3. Commit the changes without making changes to the Rakefile, VERSION, or any other files that aren't related to your enhancement or fix
98 151 4. Send a pull request.
@@ -19,4 +19,5 Gem::Specification.new do |s|
19 19 s.rubygems_version = %q{1.3.6}
20 20 s.summary = %q{An awesome nested set implementation for Active Record}
21 21 s.add_runtime_dependency 'activerecord', '>= 3.0.0'
22 s.add_development_dependency 'rspec-rails', '~> 2.8'
22 23 end
@@ -1,4 +1,5
1 1 require 'awesome_nested_set/awesome_nested_set'
2 require 'active_record'
2 3 ActiveRecord::Base.send :extend, CollectiveIdea::Acts::NestedSet
3 4
4 5 if defined?(ActionView)
@@ -23,6 +23,7 module CollectiveIdea #:nodoc:
23 23 # * +:parent_column+ - specifies the column name to use for keeping the position integer (default: parent_id)
24 24 # * +:left_column+ - column name for left boundry data, default "lft"
25 25 # * +:right_column+ - column name for right boundry data, default "rgt"
26 # * +:depth_column+ - column name for the depth data, default "depth"
26 27 # * +:scope+ - restricts what is to be considered a list. Given a symbol, it'll attach "_id"
27 28 # (if it hasn't been already) and use that as the foreign key restriction. You
28 29 # can also pass an array to scope by multiple attributes.
@@ -34,6 +35,8 module CollectiveIdea #:nodoc:
34 35 # * +:counter_cache+ adds a counter cache for the number of children.
35 36 # defaults to false.
36 37 # Example: <tt>acts_as_nested_set :counter_cache => :children_count</tt>
38 # * +:order_column+ on which column to do sorting, by default it is the left_column_name
39 # Example: <tt>acts_as_nested_set :order_column => :position</tt>
37 40 #
38 41 # See CollectiveIdea::Acts::NestedSet::Model::ClassMethods for a list of class methods and
39 42 # CollectiveIdea::Acts::NestedSet::Model for a list of instance methods added
@@ -43,9 +46,10 module CollectiveIdea #:nodoc:
43 46 :parent_column => 'parent_id',
44 47 :left_column => 'lft',
45 48 :right_column => 'rgt',
49 :depth_column => 'depth',
46 50 :dependent => :delete_all, # or :destroy
47 :counter_cache => false,
48 :order => 'id'
51 :polymorphic => false,
52 :counter_cache => false
49 53 }.merge(options)
50 54
51 55 if options[:scope].is_a?(Symbol) && options[:scope].to_s !~ /_id$/
@@ -60,26 +64,34 module CollectiveIdea #:nodoc:
60 64 extend Columns
61 65
62 66 belongs_to :parent, :class_name => self.base_class.to_s,
67 :foreign_key => parent_column_name,
68 :counter_cache => options[:counter_cache],
69 :inverse_of => (:children unless options[:polymorphic]),
70 :polymorphic => options[:polymorphic]
71
72 has_many_children_options = {
73 :class_name => self.base_class.to_s,
63 74 :foreign_key => parent_column_name,
64 :counter_cache => options[:counter_cache],
65 :inverse_of => :children
66 has_many :children, :class_name => self.base_class.to_s,
67 :foreign_key => parent_column_name, :order => left_column_name,
68 :inverse_of => :parent,
69 :before_add => options[:before_add],
70 :after_add => options[:after_add],
71 :before_remove => options[:before_remove],
72 :after_remove => options[:after_remove]
75 :order => order_column,
76 :inverse_of => (:parent unless options[:polymorphic]),
77 }
78
79 # Add callbacks, if they were supplied.. otherwise, we don't want them.
80 [:before_add, :after_add, :before_remove, :after_remove].each do |ar_callback|
81 has_many_children_options.update(ar_callback => options[ar_callback]) if options[ar_callback]
82 end
83
84 has_many :children, has_many_children_options
73 85
74 86 attr_accessor :skip_before_destroy
75 87
76 88 before_create :set_default_left_and_right
77 89 before_save :store_new_parent
78 after_save :move_to_new_parent
90 after_save :move_to_new_parent, :set_depth!
79 91 before_destroy :destroy_descendants
80 92
81 93 # no assignment to structure fields
82 [left_column_name, right_column_name].each do |column|
94 [left_column_name, right_column_name, depth_column_name].each do |column|
83 95 module_eval <<-"end_eval", __FILE__, __LINE__
84 96 def #{column}=(x)
85 97 raise ActiveRecord::ActiveRecordError, "Unauthorized assignment to #{column}: it's an internal field handled by acts_as_nested_set code, use move_to_* methods instead."
@@ -93,6 +105,10 module CollectiveIdea #:nodoc:
93 105 module Model
94 106 extend ActiveSupport::Concern
95 107
108 included do
109 delegate :quoted_table_name, :to => self
110 end
111
96 112 module ClassMethods
97 113 # Returns the first root
98 114 def root
@@ -100,11 +116,11 module CollectiveIdea #:nodoc:
100 116 end
101 117
102 118 def roots
103 where(parent_column_name => nil).order(quoted_left_column_name)
119 where(parent_column_name => nil).order(quoted_left_column_full_name)
104 120 end
105 121
106 122 def leaves
107 where("#{quoted_right_column_name} - #{quoted_left_column_name} = 1").order(quoted_left_column_name)
123 where("#{quoted_right_column_full_name} - #{quoted_left_column_full_name} = 1").order(quoted_left_column_full_name)
108 124 end
109 125
110 126 def valid?
@@ -112,16 +128,19 module CollectiveIdea #:nodoc:
112 128 end
113 129
114 130 def left_and_rights_valid?
115 joins("LEFT OUTER JOIN #{quoted_table_name} AS parent ON " +
116 "#{quoted_table_name}.#{quoted_parent_column_name} = parent.#{primary_key}").
131 ## AS clause not supported in Oracle in FROM clause for aliasing table name
132 joins("LEFT OUTER JOIN #{quoted_table_name}" +
133 (connection.adapter_name.match(/Oracle/).nil? ? " AS " : " ") +
134 "parent ON " +
135 "#{quoted_parent_column_full_name} = parent.#{primary_key}").
117 136 where(
118 "#{quoted_table_name}.#{quoted_left_column_name} IS NULL OR " +
119 "#{quoted_table_name}.#{quoted_right_column_name} IS NULL OR " +
120 "#{quoted_table_name}.#{quoted_left_column_name} >= " +
121 "#{quoted_table_name}.#{quoted_right_column_name} OR " +
122 "(#{quoted_table_name}.#{quoted_parent_column_name} IS NOT NULL AND " +
123 "(#{quoted_table_name}.#{quoted_left_column_name} <= parent.#{quoted_left_column_name} OR " +
124 "#{quoted_table_name}.#{quoted_right_column_name} >= parent.#{quoted_right_column_name}))"
137 "#{quoted_left_column_full_name} IS NULL OR " +
138 "#{quoted_right_column_full_name} IS NULL OR " +
139 "#{quoted_left_column_full_name} >= " +
140 "#{quoted_right_column_full_name} OR " +
141 "(#{quoted_parent_column_full_name} IS NOT NULL AND " +
142 "(#{quoted_left_column_full_name} <= parent.#{quoted_left_column_name} OR " +
143 "#{quoted_right_column_full_name} >= parent.#{quoted_right_column_name}))"
125 144 ).count == 0
126 145 end
127 146
@@ -129,7 +148,7 module CollectiveIdea #:nodoc:
129 148 scope_string = Array(acts_as_nested_set_options[:scope]).map do |c|
130 149 connection.quote_column_name(c)
131 150 end.push(nil).join(", ")
132 [quoted_left_column_name, quoted_right_column_name].all? do |column|
151 [quoted_left_column_full_name, quoted_right_column_full_name].all? do |column|
133 152 # No duplicates
134 153 select("#{scope_string}#{column}, COUNT(#{column})").
135 154 group("#{scope_string}#{column}").
@@ -141,7 +160,7 module CollectiveIdea #:nodoc:
141 160 # Wrapper for each_root_valid? that can deal with scope.
142 161 def all_roots_valid?
143 162 if acts_as_nested_set_options[:scope]
144 roots.group(scope_column_names).group_by{|record| scope_column_names.collect{|col| record.send(col.to_sym)}}.all? do |scope, grouped_roots|
163 roots.group_by {|record| scope_column_names.collect {|col| record.send(col.to_sym) } }.all? do |scope, grouped_roots|
145 164 each_root_valid?(grouped_roots)
146 165 end
147 166 else
@@ -179,14 +198,14 module CollectiveIdea #:nodoc:
179 198 # set left
180 199 node[left_column_name] = indices[scope.call(node)] += 1
181 200 # find
182 where(["#{quoted_parent_column_name} = ? #{scope.call(node)}", node]).order(acts_as_nested_set_options[:order]).each{|n| set_left_and_rights.call(n) }
201 where(["#{quoted_parent_column_full_name} = ? #{scope.call(node)}", node]).order("#{quoted_left_column_full_name}, #{quoted_right_column_full_name}, id").each{|n| set_left_and_rights.call(n) }
183 202 # set right
184 203 node[right_column_name] = indices[scope.call(node)] += 1
185 204 node.save!(:validate => validate_nodes)
186 205 end
187 206
188 207 # Find root node(s)
189 root_nodes = where("#{quoted_parent_column_name} IS NULL").order(acts_as_nested_set_options[:order]).each do |root_node|
208 root_nodes = where("#{quoted_parent_column_full_name} IS NULL").order("#{quoted_left_column_full_name}, #{quoted_right_column_full_name}, id").each do |root_node|
190 209 # setup index for this scope
191 210 indices[scope.call(root_node)] ||= 0
192 211 set_left_and_rights.call(root_node)
@@ -205,7 +224,7 module CollectiveIdea #:nodoc:
205 224 path = [nil]
206 225 objects.each do |o|
207 226 if o.parent_id != path.last
208 # we are on a new level, did we decent or ascent?
227 # we are on a new level, did we descend or ascend?
209 228 if path.include?(o.parent_id)
210 229 # remove wrong wrong tailing paths elements
211 230 path.pop while path.last != o.parent_id
@@ -216,13 +235,56 module CollectiveIdea #:nodoc:
216 235 yield(o, path.length - 1)
217 236 end
218 237 end
238
239 # Same as each_with_level - Accepts a string as a second argument to sort the list
240 # Example:
241 # Category.each_with_level(Category.root.self_and_descendants, :sort_by_this_column) do |o, level|
242 def sorted_each_with_level(objects, order)
243 path = [nil]
244 children = []
245 objects.each do |o|
246 children << o if o.leaf?
247 if o.parent_id != path.last
248 if !children.empty? && !o.leaf?
249 children.sort_by! &order
250 children.each { |c| yield(c, path.length-1) }
251 children = []
252 end
253 # we are on a new level, did we decent or ascent?
254 if path.include?(o.parent_id)
255 # remove wrong wrong tailing paths elements
256 path.pop while path.last != o.parent_id
257 else
258 path << o.parent_id
259 end
260 end
261 yield(o,path.length-1) if !o.leaf?
262 end
263 if !children.empty?
264 children.sort_by! &order
265 children.each { |c| yield(c, path.length-1) }
266 end
267 end
268
269 def associate_parents(objects)
270 if objects.all?{|o| o.respond_to?(:association)}
271 id_indexed = objects.index_by(&:id)
272 objects.each do |object|
273 if !(association = object.association(:parent)).loaded? && (parent = id_indexed[object.parent_id])
274 association.target = parent
275 association.set_inverse_instance(parent)
276 end
277 end
278 else
279 objects
280 end
281 end
219 282 end
220 283
221 284 # 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.
222 285 #
223 286 # category.self_and_descendants.count
224 287 # category.ancestors.find(:all, :conditions => "name like '%foo%'")
225
226 288 # Value of the parent column
227 289 def parent_id
228 290 self[parent_column_name]
@@ -243,24 +305,33 module CollectiveIdea #:nodoc:
243 305 parent_id.nil?
244 306 end
245 307
308 # Returns true if this is the end of a branch.
246 309 def leaf?
247 new_record? || (right - left == 1)
310 persisted? && right.to_i - left.to_i == 1
248 311 end
249 312
250 313 # Returns true is this is a child node
251 314 def child?
252 !parent_id.nil?
315 !root?
253 316 end
254 317
255 318 # Returns root
256 319 def root
257 self_and_ancestors.where(parent_column_name => nil).first
320 if persisted?
321 self_and_ancestors.where(parent_column_name => nil).first
322 else
323 if parent_id && current_parent = nested_set_scope.find(parent_id)
324 current_parent.root
325 else
326 self
327 end
328 end
258 329 end
259 330
260 331 # Returns the array of all parents and self
261 332 def self_and_ancestors
262 333 nested_set_scope.where([
263 "#{self.class.quoted_table_name}.#{quoted_left_column_name} <= ? AND #{self.class.quoted_table_name}.#{quoted_right_column_name} >= ?", left, right
334 "#{quoted_left_column_full_name} <= ? AND #{quoted_right_column_full_name} >= ?", left, right
264 335 ])
265 336 end
266 337
@@ -281,19 +352,20 module CollectiveIdea #:nodoc:
281 352
282 353 # Returns a set of all of its nested children which do not have children
283 354 def leaves
284 descendants.where("#{self.class.quoted_table_name}.#{quoted_right_column_name} - #{self.class.quoted_table_name}.#{quoted_left_column_name} = 1")
355 descendants.where("#{quoted_right_column_full_name} - #{quoted_left_column_full_name} = 1")
285 356 end
286 357
287 358 # Returns the level of this object in the tree
288 359 # root level is 0
289 360 def level
290 parent_id.nil? ? 0 : ancestors.count
361 parent_id.nil? ? 0 : compute_level
291 362 end
292 363
293 364 # Returns a set of itself and all of its nested children
294 365 def self_and_descendants
295 366 nested_set_scope.where([
296 "#{self.class.quoted_table_name}.#{quoted_left_column_name} >= ? AND #{self.class.quoted_table_name}.#{quoted_right_column_name} <= ?", left, right
367 "#{quoted_left_column_full_name} >= ? AND #{quoted_left_column_full_name} < ?", left, right
368 # using _left_ for both sides here lets us benefit from an index on that column if one exists
297 369 ])
298 370 end
299 371
@@ -327,13 +399,13 module CollectiveIdea #:nodoc:
327 399
328 400 # Find the first sibling to the left
329 401 def left_sibling
330 siblings.where(["#{self.class.quoted_table_name}.#{quoted_left_column_name} < ?", left]).
331 order("#{self.class.quoted_table_name}.#{quoted_left_column_name} DESC").last
402 siblings.where(["#{quoted_left_column_full_name} < ?", left]).
403 order("#{quoted_left_column_full_name} DESC").last
332 404 end
333 405
334 406 # Find the first sibling to the right
335 407 def right_sibling
336 siblings.where(["#{self.class.quoted_table_name}.#{quoted_left_column_name} > ?", left]).first
408 siblings.where(["#{quoted_left_column_full_name} > ?", left]).first
337 409 end
338 410
339 411 # Shorthand method for finding the left sibling and moving to the left of it.
@@ -361,11 +433,44 module CollectiveIdea #:nodoc:
361 433 move_to node, :child
362 434 end
363 435
436 # Move the node to the child of another node with specify index (you can pass id only)
437 def move_to_child_with_index(node, index)
438 if node.children.empty?
439 move_to_child_of(node)
440 elsif node.children.count == index
441 move_to_right_of(node.children.last)
442 else
443 move_to_left_of(node.children[index])
444 end
445 end
446
364 447 # Move the node to root nodes
365 448 def move_to_root
366 449 move_to nil, :root
367 450 end
368 451
452 # Order children in a nested set by an attribute
453 # Can order by any attribute class that uses the Comparable mixin, for example a string or integer
454 # Usage example when sorting categories alphabetically: @new_category.move_to_ordered_child_of(@root, "name")
455 def move_to_ordered_child_of(parent, order_attribute, ascending = true)
456 self.move_to_root and return unless parent
457 left = nil # This is needed, at least for the tests.
458 parent.children.each do |n| # Find the node immediately to the left of this node.
459 if ascending
460 left = n if n.send(order_attribute) < self.send(order_attribute)
461 else
462 left = n if n.send(order_attribute) > self.send(order_attribute)
463 end
464 end
465 self.move_to_child_of(parent)
466 return unless parent.children.count > 1 # Only need to order if there are multiple children.
467 if left # Self has a left neighbor.
468 self.move_to_right_of(left)
469 else # Self is the left most node.
470 self.move_to_left_of(parent.children[0])
471 end
472 end
473
369 474 def move_possible?(target)
370 475 self != target && # Can't target self
371 476 same_scope?(target) && # can't be in different scopes
@@ -381,6 +486,14 module CollectiveIdea #:nodoc:
381 486 end
382 487
383 488 protected
489 def compute_level
490 node, nesting = self, 0
491 while (association = node.association(:parent)).loaded? && association.target
492 nesting += 1
493 node = node.parent
494 end if node.respond_to? :association
495 node == self ? ancestors.count : node.level + nesting
496 end
384 497
385 498 def without_self(scope)
386 499 scope.where(["#{self.class.quoted_table_name}.#{self.class.primary_key} != ?", self])
@@ -390,12 +503,12 module CollectiveIdea #:nodoc:
390 503 # the base ActiveRecord class, using the :scope declared in the acts_as_nested_set
391 504 # declaration.
392 505 def nested_set_scope(options = {})
393 options = {:order => "#{self.class.quoted_table_name}.#{quoted_left_column_name}"}.merge(options)
506 options = {:order => quoted_left_column_full_name}.merge(options)
394 507 scopes = Array(acts_as_nested_set_options[:scope])
395 508 options[:conditions] = scopes.inject({}) do |conditions,attr|
396 509 conditions.merge attr => self[attr]
397 510 end unless scopes.empty?
398 self.class.base_class.scoped options
511 self.class.base_class.unscoped.scoped options
399 512 end
400 513
401 514 def store_new_parent
@@ -411,9 +524,20 module CollectiveIdea #:nodoc:
411 524 end
412 525 end
413 526
527 def set_depth!
528 if nested_set_scope.column_names.map(&:to_s).include?(depth_column_name.to_s)
529 in_tenacious_transaction do
530 reload
531
532 nested_set_scope.where(:id => id).update_all(["#{quoted_depth_column_name} = ?", level])
533 end
534 self[depth_column_name.to_sym] = self.level
535 end
536 end
537
414 538 # on creation, set automatically lft and rgt to the end of the tree
415 539 def set_default_left_and_right
416 highest_right_row = nested_set_scope(:order => "#{quoted_right_column_name} desc").limit(1).lock(true).first
540 highest_right_row = nested_set_scope(:order => "#{quoted_right_column_full_name} desc").limit(1).lock(true).first
417 541 maxright = highest_right_row ? (highest_right_row[right_column_name] || 0) : 0
418 542 # adds the new node to the right of all existing nodes
419 543 self[left_column_name] = maxright + 1
@@ -443,11 +567,8 module CollectiveIdea #:nodoc:
443 567 in_tenacious_transaction do
444 568 reload_nested_set
445 569 # select the rows in the model that extend past the deletion point and apply a lock
446 nested_set_scope.
447 select("id").
448 where("#{quoted_left_column_name} >= ?", left).
449 lock(true).
450 all
570 nested_set_scope.where(["#{quoted_left_column_full_name} >= ?", left]).
571 select(id).lock(true)
451 572
452 573 if acts_as_nested_set_options[:dependent] == :destroy
453 574 descendants.each do |model|
@@ -455,25 +576,21 module CollectiveIdea #:nodoc:
455 576 model.destroy
456 577 end
457 578 else
458 nested_set_scope.delete_all(
459 ["#{quoted_left_column_name} > ? AND #{quoted_right_column_name} < ?",
460 left, right]
461 )
579 nested_set_scope.where(["#{quoted_left_column_name} > ? AND #{quoted_right_column_name} < ?", left, right]).
580 delete_all
462 581 end
463 582
464 583 # update lefts and rights for remaining nodes
465 584 diff = right - left + 1
466 nested_set_scope.update_all(
467 ["#{quoted_left_column_name} = (#{quoted_left_column_name} - ?)", diff],
468 ["#{quoted_left_column_name} > ?", right]
585 nested_set_scope.where(["#{quoted_left_column_full_name} > ?", right]).update_all(
586 ["#{quoted_left_column_name} = (#{quoted_left_column_name} - ?)", diff]
469 587 )
470 nested_set_scope.update_all(
471 ["#{quoted_right_column_name} = (#{quoted_right_column_name} - ?)", diff],
472 ["#{quoted_right_column_name} > ?", right]
473 )
474
475 reload
476 # Don't allow multiple calls to destroy to corrupt the set
588
589 nested_set_scope.where(["#{quoted_right_column_full_name} > ?", right]).update_all(
590 ["#{quoted_right_column_name} = (#{quoted_right_column_name} - ?)", diff]
591 )
592
593 # Don't allow multiple calls to destroy to corrupt the set
477 594 self.skip_before_destroy = true
478 595 end
479 596 end
@@ -481,7 +598,7 reload
481 598 # reload left, right, and parent
482 599 def reload_nested_set
483 600 reload(
484 :select => "#{quoted_left_column_name}, #{quoted_right_column_name}, #{quoted_parent_column_name}",
601 :select => "#{quoted_left_column_full_name}, #{quoted_right_column_full_name}, #{quoted_parent_column_full_name}",
485 602 :lock => true
486 603 )
487 604 end
@@ -526,7 +643,7 reload
526 643
527 644 # select the rows in the model between a and d, and apply a lock
528 645 self.class.base_class.select('id').lock(true).where(
529 ["#{quoted_left_column_name} >= :a and #{quoted_right_column_name} <= :d", {:a => a, :d => d}]
646 ["#{quoted_left_column_full_name} >= :a and #{quoted_right_column_full_name} <= :d", {:a => a, :d => d}]
530 647 )
531 648
532 649 new_parent = case position
@@ -555,6 +672,8 reload
555 672 ])
556 673 end
557 674 target.reload_nested_set if target
675 self.set_depth!
676 self.descendants.each(&:save)
558 677 self.reload_nested_set
559 678 end
560 679 end
@@ -571,10 +690,18 reload
571 690 acts_as_nested_set_options[:right_column]
572 691 end
573 692
693 def depth_column_name
694 acts_as_nested_set_options[:depth_column]
695 end
696
574 697 def parent_column_name
575 698 acts_as_nested_set_options[:parent_column]
576 699 end
577 700
701 def order_column
702 acts_as_nested_set_options[:order_column] || left_column_name
703 end
704
578 705 def scope_column_names
579 706 Array(acts_as_nested_set_options[:scope])
580 707 end
@@ -587,6 +714,10 reload
587 714 connection.quote_column_name(right_column_name)
588 715 end
589 716
717 def quoted_depth_column_name
718 connection.quote_column_name(depth_column_name)
719 end
720
590 721 def quoted_parent_column_name
591 722 connection.quote_column_name(parent_column_name)
592 723 end
@@ -594,6 +725,18 reload
594 725 def quoted_scope_column_names
595 726 scope_column_names.collect {|column_name| connection.quote_column_name(column_name) }
596 727 end
728
729 def quoted_left_column_full_name
730 "#{quoted_table_name}.#{quoted_left_column_name}"
731 end
732
733 def quoted_right_column_full_name
734 "#{quoted_table_name}.#{quoted_right_column_name}"
735 end
736
737 def quoted_parent_column_full_name
738 "#{quoted_table_name}.#{quoted_parent_column_name}"
739 end
597 740 end
598 741
599 742 end
@@ -1,3 +1,4
1 # -*- coding: utf-8 -*-
1 2 module CollectiveIdea #:nodoc:
2 3 module Acts #:nodoc:
3 4 module NestedSet #:nodoc:
@@ -24,12 +25,12 module CollectiveIdea #:nodoc:
24 25 if class_or_item.is_a? Array
25 26 items = class_or_item.reject { |e| !e.root? }
26 27 else
27 class_or_item = class_or_item.roots if class_or_item.is_a?(Class)
28 class_or_item = class_or_item.roots if class_or_item.respond_to?(:scoped)
28 29 items = Array(class_or_item)
29 30 end
30 31 result = []
31 32 items.each do |root|
32 result += root.self_and_descendants.map do |i|
33 result += root.class.associate_parents(root.self_and_descendants).map do |i|
33 34 if mover.nil? || mover.new_record? || mover.move_possible?(i)
34 35 [yield(i), i.id]
35 36 end
@@ -37,7 +38,51 module CollectiveIdea #:nodoc:
37 38 end
38 39 result
39 40 end
40
41
42 # Returns options for select as nested_set_options, sorted by an specific column
43 # It requires passing a string with the name of the column to sort the set with
44 # You can exclude some items from the tree.
45 # You can pass a block receiving an item and returning the string displayed in the select.
46 #
47 # == Params
48 # * +class_or_item+ - Class name or top level times
49 # * +:column+ - Column to sort the set (this will sort each children for all root elements)
50 # * +mover+ - The item that is being move, used to exlude impossible moves
51 # * +&block+ - a block that will be used to display: { |item| ... item.name }
52 #
53 # == Usage
54 #
55 # <%= f.select :parent_id, nested_set_options(Category, :sort_by_this_column, @category) {|i|
56 # "#{'–' * i.level} #{i.name}"
57 # }) %>
58 #
59 def sorted_nested_set_options(class_or_item, order, mover = nil)
60 if class_or_item.is_a? Array
61 items = class_or_item.reject { |e| !e.root? }
62 else
63 class_or_item = class_or_item.roots if class_or_item.is_a?(Class)
64 items = Array(class_or_item)
65 end
66 result = []
67 children = []
68 items.each do |root|
69 root.class.associate_parents(root.self_and_descendants).map do |i|
70 if mover.nil? || mover.new_record? || mover.move_possible?(i)
71 if !i.leaf?
72 children.sort_by! &order
73 children.each { |c| result << [yield(c), c.id] }
74 children = []
75 result << [yield(i), i.id]
76 else
77 children << i
78 end
79 end
80 end.compact
81 end
82 children.sort_by! &order
83 children.each { |c| result << [yield(c), c.id] }
84 result
85 end
41 86 end
42 87 end
43 88 end
@@ -1,3 +1,3
1 1 module AwesomeNestedSet
2 VERSION = '2.1.0' unless defined?(::AwesomeNestedSet::VERSION)
2 VERSION = '2.1.5' unless defined?(::AwesomeNestedSet::VERSION)
3 3 end
@@ -17,7 +17,7 describe "Helper" do
17 17 ['- Child 3', 5],
18 18 [" Top Level 2", 6]
19 19 ]
20 actual = nested_set_options(Category) do |c|
20 actual = nested_set_options(Category.scoped) do |c|
21 21 "#{'-' * c.level} #{c.name}"
22 22 end
23 23 actual.should == expected
@@ -30,6 +30,34 describe "Helper" do
30 30 ['- Child 3', 5],
31 31 [" Top Level 2", 6]
32 32 ]
33 actual = nested_set_options(Category.scoped, categories(:child_2)) do |c|
34 "#{'-' * c.level} #{c.name}"
35 end
36 actual.should == expected
37 end
38
39 it "test_nested_set_options_with_class_as_argument" do
40 expected = [
41 [" Top Level", 1],
42 ["- Child 1", 2],
43 ['- Child 2', 3],
44 ['-- Child 2.1', 4],
45 ['- Child 3', 5],
46 [" Top Level 2", 6]
47 ]
48 actual = nested_set_options(Category) do |c|
49 "#{'-' * c.level} #{c.name}"
50 end
51 actual.should == expected
52 end
53
54 it "test_nested_set_options_with_class_as_argument_with_mover" do
55 expected = [
56 [" Top Level", 1],
57 ["- Child 1", 2],
58 ['- Child 3', 5],
59 [" Top Level 2", 6]
60 ]
33 61 actual = nested_set_options(Category, categories(:child_2)) do |c|
34 62 "#{'-' * c.level} #{c.name}"
35 63 end
@@ -36,6 +36,13 describe "AwesomeNestedSet" do
36 36 RenamedColumns.new.right_column_name.should == 'black'
37 37 end
38 38
39 it "has a depth_column_name" do
40 Default.depth_column_name.should == 'depth'
41 Default.new.depth_column_name.should == 'depth'
42 RenamedColumns.depth_column_name.should == 'pitch'
43 RenamedColumns.depth_column_name.should == 'pitch'
44 end
45
39 46 it "should have parent_column_name" do
40 47 Default.parent_column_name.should == 'parent_id'
41 48 Default.new.parent_column_name.should == 'parent_id'
@@ -55,7 +62,7 describe "AwesomeNestedSet" do
55 62 Broken.create!
56 63 end
57 64 end
58
65
59 66 it "quoted_left_column_name" do
60 67 quoted = Default.connection.quote_column_name('lft')
61 68 Default.quoted_left_column_name.should == quoted
@@ -68,6 +75,12 describe "AwesomeNestedSet" do
68 75 Default.new.quoted_right_column_name.should == quoted
69 76 end
70 77
78 it "quoted_depth_column_name" do
79 quoted = Default.connection.quote_column_name('depth')
80 Default.quoted_depth_column_name.should == quoted
81 Default.new.quoted_depth_column_name.should == quoted
82 end
83
71 84 it "left_column_protected_from_assignment" do
72 85 lambda {
73 86 Category.new.lft = 1
@@ -80,6 +93,12 describe "AwesomeNestedSet" do
80 93 }.should raise_exception(ActiveRecord::ActiveRecordError)
81 94 end
82 95
96 it "depth_column_protected_from_assignment" do
97 lambda {
98 Category.new.depth = 1
99 }.should raise_exception(ActiveRecord::ActiveRecordError)
100 end
101
83 102 it "scoped_appends_id" do
84 103 ScopedCategory.acts_as_nested_set_options[:scope].should == :organization_id
85 104 end
@@ -96,6 +115,16 describe "AwesomeNestedSet" do
96 115 categories(:child_3).root.should == categories(:top_level)
97 116 end
98 117
118 it "root when not persisted and parent_column_name value is self" do
119 new_category = Category.new
120 new_category.root.should == new_category
121 end
122
123 it "root when not persisted and parent_column_name value is set" do
124 last_category = Category.last
125 Category.new(Default.parent_column_name => last_category.id).root.should == last_category.root
126 end
127
99 128 it "root?" do
100 129 categories(:top_level).root?.should be_true
101 130 categories(:top_level_2).root?.should be_true
@@ -159,10 +188,66 describe "AwesomeNestedSet" do
159 188 categories(:top_level).leaves.should == leaves
160 189 end
161 190
162 it "level" do
163 categories(:top_level).level.should == 0
164 categories(:child_1).level.should == 1
165 categories(:child_2_1).level.should == 2
191 describe "level" do
192 it "returns the correct level" do
193 categories(:top_level).level.should == 0
194 categories(:child_1).level.should == 1
195 categories(:child_2_1).level.should == 2
196 end
197
198 context "given parent associations are loaded" do
199 it "returns the correct level" do
200 child = categories(:child_1)
201 if child.respond_to?(:association)
202 child.association(:parent).load_target
203 child.parent.association(:parent).load_target
204 child.level.should == 1
205 else
206 pending 'associations not used where child#association is not a method'
207 end
208 end
209 end
210 end
211
212 describe "depth" do
213 let(:lawyers) { Category.create!(:name => "lawyers") }
214 let(:us) { Category.create!(:name => "United States") }
215 let(:new_york) { Category.create!(:name => "New York") }
216 let(:patent) { Category.create!(:name => "Patent Law") }
217
218 before(:each) do
219 # lawyers > us > new_york > patent
220 us.move_to_child_of(lawyers)
221 new_york.move_to_child_of(us)
222 patent.move_to_child_of(new_york)
223 [lawyers, us, new_york, patent].each(&:reload)
224 end
225
226 it "updates depth when moved into child position" do
227 lawyers.depth.should == 0
228 us.depth.should == 1
229 new_york.depth.should == 2
230 patent.depth.should == 3
231 end
232
233 it "updates depth of all descendants when parent is moved" do
234 # lawyers
235 # us > new_york > patent
236 us.move_to_right_of(lawyers)
237 [lawyers, us, new_york, patent].each(&:reload)
238 us.depth.should == 0
239 new_york.depth.should == 1
240 patent.depth.should == 2
241 end
242 end
243
244 it "depth is magic and does not apply when column is missing" do
245 lambda { NoDepth.create!(:name => "shallow") }.should_not raise_error
246 lambda { NoDepth.first.save }.should_not raise_error
247 lambda { NoDepth.rebuild! }.should_not raise_error
248
249 NoDepth.method_defined?(:depth).should be_false
250 NoDepth.first.respond_to?(:depth).should be_false
166 251 end
167 252
168 253 it "has_children?" do
@@ -171,15 +256,20 describe "AwesomeNestedSet" do
171 256 categories(:top_level).children.empty?.should be_false
172 257 end
173 258
174 it "self_and_descendents" do
259 it "self_and_descendants" do
175 260 parent = categories(:top_level)
176 self_and_descendants = [parent, categories(:child_1), categories(:child_2),
177 categories(:child_2_1), categories(:child_3)]
261 self_and_descendants = [
262 parent,
263 categories(:child_1),
264 categories(:child_2),
265 categories(:child_2_1),
266 categories(:child_3)
267 ]
178 268 self_and_descendants.should == parent.self_and_descendants
179 269 self_and_descendants.count.should == parent.self_and_descendants.count
180 270 end
181 271
182 it "descendents" do
272 it "descendants" do
183 273 lawyers = Category.create!(:name => "lawyers")
184 274 us = Category.create!(:name => "United States")
185 275 us.move_to_child_of(lawyers)
@@ -192,10 +282,14 describe "AwesomeNestedSet" do
192 282 lawyers.descendants.size.should == 2
193 283 end
194 284
195 it "self_and_descendents" do
285 it "self_and_descendants" do
196 286 parent = categories(:top_level)
197 descendants = [categories(:child_1), categories(:child_2),
198 categories(:child_2_1), categories(:child_3)]
287 descendants = [
288 categories(:child_1),
289 categories(:child_2),
290 categories(:child_2_1),
291 categories(:child_3)
292 ]
199 293 descendants.should == parent.descendants
200 294 end
201 295
@@ -350,6 +444,43 describe "AwesomeNestedSet" do
350 444 Category.valid?.should be_true
351 445 end
352 446
447 describe "#move_to_child_with_index" do
448 it "move to a node without child" do
449 categories(:child_1).move_to_child_with_index(categories(:child_3), 0)
450 categories(:child_3).id.should == categories(:child_1).parent_id
451 categories(:child_1).left.should == 7
452 categories(:child_1).right.should == 8
453 categories(:child_3).left.should == 6
454 categories(:child_3).right.should == 9
455 Category.valid?.should be_true
456 end
457
458 it "move to a node to the left child" do
459 categories(:child_1).move_to_child_with_index(categories(:child_2), 0)
460 categories(:child_1).parent_id.should == categories(:child_2).id
461 categories(:child_2_1).left.should == 5
462 categories(:child_2_1).right.should == 6
463 categories(:child_1).left.should == 3
464 categories(:child_1).right.should == 4
465 categories(:child_2).reload
466 categories(:child_2).left.should == 2
467 categories(:child_2).right.should == 7
468 end
469
470 it "move to a node to the right child" do
471 categories(:child_1).move_to_child_with_index(categories(:child_2), 1)
472 categories(:child_1).parent_id.should == categories(:child_2).id
473 categories(:child_2_1).left.should == 3
474 categories(:child_2_1).right.should == 4
475 categories(:child_1).left.should == 5
476 categories(:child_1).right.should == 6
477 categories(:child_2).reload
478 categories(:child_2).left.should == 2
479 categories(:child_2).right.should == 7
480 end
481
482 end
483
353 484 it "move_to_child_of_appends_to_end" do
354 485 child = Category.create! :name => 'New Child'
355 486 child.move_to_child_of categories(:top_level)
@@ -444,6 +575,32 describe "AwesomeNestedSet" do
444 575 Category.roots.last.to_text.should == output
445 576 end
446 577
578 it "should_move_to_ordered_child" do
579 node1 = Category.create(:name => 'Node-1')
580 node2 = Category.create(:name => 'Node-2')
581 node3 = Category.create(:name => 'Node-3')
582
583 node2.move_to_ordered_child_of(node1, "name")
584
585 assert_equal node1, node2.parent
586 assert_equal 1, node1.children.count
587
588 node3.move_to_ordered_child_of(node1, "name", true) # acending
589
590 assert_equal node1, node3.parent
591 assert_equal 2, node1.children.count
592 assert_equal node2.name, node1.children[0].name
593 assert_equal node3.name, node1.children[1].name
594
595 node3.move_to_ordered_child_of(node1, "name", false) # decending
596 node1.reload
597
598 assert_equal node1, node3.parent
599 assert_equal 2, node1.children.count
600 assert_equal node3.name, node1.children[0].name
601 assert_equal node2.name, node1.children[1].name
602 end
603
447 604 it "should be able to rebuild without validating each record" do
448 605 root1 = Category.create(:name => 'Root1')
449 606 root2 = Category.create(:name => 'Root2')
@@ -617,7 +774,15 describe "AwesomeNestedSet" do
617 774 end
618 775
619 776 it "quoting_of_multi_scope_column_names" do
620 ["\"notable_id\"", "\"notable_type\""].should == Note.quoted_scope_column_names
777 ## Proper Array Assignment for different DBs as per their quoting column behavior
778 if Note.connection.adapter_name.match(/Oracle/)
779 expected_quoted_scope_column_names = ["\"NOTABLE_ID\"", "\"NOTABLE_TYPE\""]
780 elsif Note.connection.adapter_name.match(/Mysql/)
781 expected_quoted_scope_column_names = ["`notable_id`", "`notable_type`"]
782 else
783 expected_quoted_scope_column_names = ["\"notable_id\"", "\"notable_type\""]
784 end
785 expected_quoted_scope_column_names.should == Note.quoted_scope_column_names
621 786 end
622 787
623 788 it "equal_in_same_scope" do
@@ -730,7 +895,8 describe "AwesomeNestedSet" do
730 895 [1, "Child 1"],
731 896 [1, "Child 2"],
732 897 [2, "Child 2.1"],
733 [1, "Child 3" ]]
898 [1, "Child 3" ]
899 ]
734 900
735 901 check_structure(Category.root.self_and_descendants, levels)
736 902
@@ -756,9 +922,10 describe "AwesomeNestedSet" do
756 922 [2, "Child 1.2"],
757 923 [1, "Child 2"],
758 924 [2, "Child 2.1"],
759 [1, "Child 3" ]]
925 [1, "Child 3" ]
926 ]
760 927
761 check_structure(Category.root.self_and_descendants, levels)
928 check_structure(Category.root.self_and_descendants, levels)
762 929 end
763 930
764 931 it "should not error on a model with attr_accessible" do
@@ -838,4 +1005,78 describe "AwesomeNestedSet" do
838 1005 root.after_remove.should == child
839 1006 end
840 1007 end
1008
1009 describe 'creating roots with a default scope ordering' do
1010 it "assigns rgt and lft correctly" do
1011 alpha = Order.create(:name => 'Alpha')
1012 gamma = Order.create(:name => 'Gamma')
1013 omega = Order.create(:name => 'Omega')
1014
1015 alpha.lft.should == 1
1016 alpha.rgt.should == 2
1017 gamma.lft.should == 3
1018 gamma.rgt.should == 4
1019 omega.lft.should == 5
1020 omega.rgt.should == 6
1021 end
1022 end
1023
1024 describe 'moving node from one scoped tree to another' do
1025 xit "moves single node correctly" do
1026 root1 = Note.create!(:body => "A-1", :notable_id => 4, :notable_type => 'Category')
1027 child1_1 = Note.create!(:body => "B-1", :notable_id => 4, :notable_type => 'Category')
1028 child1_2 = Note.create!(:body => "C-1", :notable_id => 4, :notable_type => 'Category')
1029 child1_1.move_to_child_of root1
1030 child1_2.move_to_child_of root1
1031
1032 root2 = Note.create!(:body => "A-2", :notable_id => 5, :notable_type => 'Category')
1033 child2_1 = Note.create!(:body => "B-2", :notable_id => 5, :notable_type => 'Category')
1034 child2_2 = Note.create!(:body => "C-2", :notable_id => 5, :notable_type => 'Category')
1035 child2_1.move_to_child_of root2
1036 child2_2.move_to_child_of root2
1037
1038 child1_1.update_attributes!(:notable_id => 5)
1039 child1_1.move_to_child_of root2
1040
1041 root1.children.should == [child1_2]
1042 root2.children.should == [child2_1, child2_2, child1_1]
1043
1044 Note.valid?.should == true
1045 end
1046
1047 xit "moves node with children correctly" do
1048 root1 = Note.create!(:body => "A-1", :notable_id => 4, :notable_type => 'Category')
1049 child1_1 = Note.create!(:body => "B-1", :notable_id => 4, :notable_type => 'Category')
1050 child1_2 = Note.create!(:body => "C-1", :notable_id => 4, :notable_type => 'Category')
1051 child1_1.move_to_child_of root1
1052 child1_2.move_to_child_of child1_1
1053
1054 root2 = Note.create!(:body => "A-2", :notable_id => 5, :notable_type => 'Category')
1055 child2_1 = Note.create!(:body => "B-2", :notable_id => 5, :notable_type => 'Category')
1056 child2_2 = Note.create!(:body => "C-2", :notable_id => 5, :notable_type => 'Category')
1057 child2_1.move_to_child_of root2
1058 child2_2.move_to_child_of root2
1059
1060 child1_1.update_attributes!(:notable_id => 5)
1061 child1_1.move_to_child_of root2
1062
1063 root1.children.should == []
1064 root2.children.should == [child2_1, child2_2, child1_1]
1065 child1_1.children should == [child1_2]
1066 root2.siblings.should == [child2_1, child2_2, child1_1, child1_2]
1067
1068 Note.valid?.should == true
1069 end
1070 end
1071
1072 describe 'specifying custom sort column' do
1073 it "should sort by the default sort column" do
1074 Category.order_column.should == 'lft'
1075 end
1076
1077 it "should sort by custom sort column" do
1078 OrderedCategory.acts_as_nested_set_options[:order_column].should == 'name'
1079 OrderedCategory.order_column.should == 'name'
1080 end
1081 end
841 1082 end
@@ -14,5 +14,12 mysql:
14 14 adapter: mysql2
15 15 host: localhost
16 16 username: root
17 password:
18 database: awesome_nested_set_plugin_test No newline at end of file
17 password:
18 database: awesome_nested_set_plugin_test
19 ## Add DB Configuration to run Oracle tests
20 oracle:
21 adapter: oracle_enhanced
22 host: localhost
23 username: awesome_nested_set_dev
24 password:
25 database: xe
@@ -5,6 +5,7 ActiveRecord::Schema.define(:version => 0) do
5 5 t.column :parent_id, :integer
6 6 t.column :lft, :integer
7 7 t.column :rgt, :integer
8 t.column :depth, :integer
8 9 t.column :organization_id, :integer
9 10 end
10 11
@@ -17,6 +18,7 ActiveRecord::Schema.define(:version => 0) do
17 18 t.column :parent_id, :integer
18 19 t.column :lft, :integer
19 20 t.column :rgt, :integer
21 t.column :depth, :integer
20 22 t.column :notable_id, :integer
21 23 t.column :notable_type, :string
22 24 end
@@ -26,6 +28,7 ActiveRecord::Schema.define(:version => 0) do
26 28 t.column :mother_id, :integer
27 29 t.column :red, :integer
28 30 t.column :black, :integer
31 t.column :pitch, :integer
29 32 end
30 33
31 34 create_table :things, :force => true do |t|
@@ -33,13 +36,30 ActiveRecord::Schema.define(:version => 0) do
33 36 t.column :parent_id, :integer
34 37 t.column :lft, :integer
35 38 t.column :rgt, :integer
39 t.column :depth, :integer
36 40 t.column :children_count, :integer
37 41 end
38
42
39 43 create_table :brokens, :force => true do |t|
40 44 t.column :name, :string
41 45 t.column :parent_id, :integer
42 46 t.column :lft, :integer
43 47 t.column :rgt, :integer
48 t.column :depth, :integer
49 end
50
51 create_table :orders, :force => true do |t|
52 t.column :name, :string
53 t.column :parent_id, :integer
54 t.column :lft, :integer
55 t.column :rgt, :integer
56 t.column :depth, :integer
57 end
58
59 create_table :no_depths, :force => true do |t|
60 t.column :name, :string
61 t.column :parent_id, :integer
62 t.column :lft, :integer
63 t.column :rgt, :integer
44 64 end
45 65 end
@@ -12,8 +12,16 class ScopedCategory < ActiveRecord::Base
12 12 acts_as_nested_set :scope => :organization
13 13 end
14 14
15 class OrderedCategory < ActiveRecord::Base
16 self.table_name = 'categories'
17 acts_as_nested_set :order_column => 'name'
18 end
19
15 20 class RenamedColumns < ActiveRecord::Base
16 acts_as_nested_set :parent_column => 'mother_id', :left_column => 'red', :right_column => 'black'
21 acts_as_nested_set :parent_column => 'mother_id',
22 :left_column => 'red',
23 :right_column => 'black',
24 :depth_column => 'pitch'
17 25 end
18 26
19 27 class Category < ActiveRecord::Base
@@ -69,4 +77,14 end
69 77
70 78 class Broken < ActiveRecord::Base
71 79 acts_as_nested_set
72 end No newline at end of file
80 end
81
82 class Order < ActiveRecord::Base
83 acts_as_nested_set
84
85 default_scope order(:name)
86 end
87
88 class NoDepth < ActiveRecord::Base
89 acts_as_nested_set
90 end
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
This diff has been collapsed as it changes many lines, (603 lines changed) Show them Hide them
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now