awesome_nested_set 2.1.4 → 2.1.5

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/CHANGELOG CHANGED
@@ -1,3 +1,11 @@
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
+
1
9
  2.1.4
2
10
  * nested_set_options accept both Class & AR Relation. [Semyon Perepelitsa]
3
11
  * Reduce the number of queries triggered by the canonical usage of `i.level` in the `nested_set` helpers. [thedarkone]
@@ -41,4 +49,4 @@
41
49
  * Expect Rails 3
42
50
  * Changed how callbacks work. Returning false in a before_move action does not block save operations. Use a validation or exception in the callback if you need that.
43
51
  * Switched to RSpec
44
- * Remove use of Comparable
52
+ * Remove use of Comparable
@@ -43,6 +43,57 @@ Enable the nested set functionality by declaring acts_as_nested_set on your mode
43
43
 
44
44
  Run `rake rdoc` to generate the API docs and see CollectiveIdea::Acts::NestedSet for more info.
45
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
+
46
97
  == Protecting attributes from mass assignment
47
98
 
48
99
  It's generally best to "white list" the attributes that can be used in mass assignment:
@@ -35,6 +35,8 @@ module CollectiveIdea #:nodoc:
35
35
  # * +:counter_cache+ adds a counter cache for the number of children.
36
36
  # defaults to false.
37
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>
38
40
  #
39
41
  # See CollectiveIdea::Acts::NestedSet::Model::ClassMethods for a list of class methods and
40
42
  # CollectiveIdea::Acts::NestedSet::Model for a list of instance methods added
@@ -70,7 +72,7 @@ module CollectiveIdea #:nodoc:
70
72
  has_many_children_options = {
71
73
  :class_name => self.base_class.to_s,
72
74
  :foreign_key => parent_column_name,
73
- :order => left_column_name,
75
+ :order => order_column,
74
76
  :inverse_of => (:parent unless options[:polymorphic]),
75
77
  }
76
78
 
@@ -103,6 +105,10 @@ module CollectiveIdea #:nodoc:
103
105
  module Model
104
106
  extend ActiveSupport::Concern
105
107
 
108
+ included do
109
+ delegate :quoted_table_name, :to => self
110
+ end
111
+
106
112
  module ClassMethods
107
113
  # Returns the first root
108
114
  def root
@@ -110,11 +116,11 @@ module CollectiveIdea #:nodoc:
110
116
  end
111
117
 
112
118
  def roots
113
- where(parent_column_name => nil).order(quoted_left_column_name)
119
+ where(parent_column_name => nil).order(quoted_left_column_full_name)
114
120
  end
115
121
 
116
122
  def leaves
117
- 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)
118
124
  end
119
125
 
120
126
  def valid?
@@ -126,15 +132,15 @@ module CollectiveIdea #:nodoc:
126
132
  joins("LEFT OUTER JOIN #{quoted_table_name}" +
127
133
  (connection.adapter_name.match(/Oracle/).nil? ? " AS " : " ") +
128
134
  "parent ON " +
129
- "#{quoted_table_name}.#{quoted_parent_column_name} = parent.#{primary_key}").
135
+ "#{quoted_parent_column_full_name} = parent.#{primary_key}").
130
136
  where(
131
- "#{quoted_table_name}.#{quoted_left_column_name} IS NULL OR " +
132
- "#{quoted_table_name}.#{quoted_right_column_name} IS NULL OR " +
133
- "#{quoted_table_name}.#{quoted_left_column_name} >= " +
134
- "#{quoted_table_name}.#{quoted_right_column_name} OR " +
135
- "(#{quoted_table_name}.#{quoted_parent_column_name} IS NOT NULL AND " +
136
- "(#{quoted_table_name}.#{quoted_left_column_name} <= parent.#{quoted_left_column_name} OR " +
137
- "#{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}))"
138
144
  ).count == 0
139
145
  end
140
146
 
@@ -142,7 +148,7 @@ module CollectiveIdea #:nodoc:
142
148
  scope_string = Array(acts_as_nested_set_options[:scope]).map do |c|
143
149
  connection.quote_column_name(c)
144
150
  end.push(nil).join(", ")
145
- [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|
146
152
  # No duplicates
147
153
  select("#{scope_string}#{column}, COUNT(#{column})").
148
154
  group("#{scope_string}#{column}").
@@ -192,14 +198,14 @@ module CollectiveIdea #:nodoc:
192
198
  # set left
193
199
  node[left_column_name] = indices[scope.call(node)] += 1
194
200
  # find
195
- where(["#{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) }
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) }
196
202
  # set right
197
203
  node[right_column_name] = indices[scope.call(node)] += 1
198
204
  node.save!(:validate => validate_nodes)
199
205
  end
200
206
 
201
207
  # Find root node(s)
202
- root_nodes = where("#{quoted_parent_column_name} IS NULL").order("#{quoted_left_column_name}, #{quoted_right_column_name}, id").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|
203
209
  # setup index for this scope
204
210
  indices[scope.call(root_node)] ||= 0
205
211
  set_left_and_rights.call(root_node)
@@ -261,12 +267,16 @@ module CollectiveIdea #:nodoc:
261
267
  end
262
268
 
263
269
  def associate_parents(objects)
264
- id_indexed = objects.index_by(&:id)
265
- objects.each do |object|
266
- if !(association = object.association(:parent)).loaded? && (parent = id_indexed[object.parent_id])
267
- association.target = parent
268
- association.set_inverse_instance(parent)
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
269
277
  end
278
+ else
279
+ objects
270
280
  end
271
281
  end
272
282
  end
@@ -321,7 +331,7 @@ module CollectiveIdea #:nodoc:
321
331
  # Returns the array of all parents and self
322
332
  def self_and_ancestors
323
333
  nested_set_scope.where([
324
- "#{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
325
335
  ])
326
336
  end
327
337
 
@@ -342,7 +352,7 @@ module CollectiveIdea #:nodoc:
342
352
 
343
353
  # Returns a set of all of its nested children which do not have children
344
354
  def leaves
345
- 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")
346
356
  end
347
357
 
348
358
  # Returns the level of this object in the tree
@@ -354,7 +364,7 @@ module CollectiveIdea #:nodoc:
354
364
  # Returns a set of itself and all of its nested children
355
365
  def self_and_descendants
356
366
  nested_set_scope.where([
357
- "#{self.class.quoted_table_name}.#{quoted_left_column_name} >= ? AND #{self.class.quoted_table_name}.#{quoted_left_column_name} < ?", left, right
367
+ "#{quoted_left_column_full_name} >= ? AND #{quoted_left_column_full_name} < ?", left, right
358
368
  # using _left_ for both sides here lets us benefit from an index on that column if one exists
359
369
  ])
360
370
  end
@@ -389,13 +399,13 @@ module CollectiveIdea #:nodoc:
389
399
 
390
400
  # Find the first sibling to the left
391
401
  def left_sibling
392
- siblings.where(["#{self.class.quoted_table_name}.#{quoted_left_column_name} < ?", left]).
393
- 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
394
404
  end
395
405
 
396
406
  # Find the first sibling to the right
397
407
  def right_sibling
398
- siblings.where(["#{self.class.quoted_table_name}.#{quoted_left_column_name} > ?", left]).first
408
+ siblings.where(["#{quoted_left_column_full_name} > ?", left]).first
399
409
  end
400
410
 
401
411
  # Shorthand method for finding the left sibling and moving to the left of it.
@@ -439,6 +449,28 @@ module CollectiveIdea #:nodoc:
439
449
  move_to nil, :root
440
450
  end
441
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
+
442
474
  def move_possible?(target)
443
475
  self != target && # Can't target self
444
476
  same_scope?(target) && # can't be in different scopes
@@ -459,7 +491,7 @@ module CollectiveIdea #:nodoc:
459
491
  while (association = node.association(:parent)).loaded? && association.target
460
492
  nesting += 1
461
493
  node = node.parent
462
- end
494
+ end if node.respond_to? :association
463
495
  node == self ? ancestors.count : node.level + nesting
464
496
  end
465
497
 
@@ -471,7 +503,7 @@ module CollectiveIdea #:nodoc:
471
503
  # the base ActiveRecord class, using the :scope declared in the acts_as_nested_set
472
504
  # declaration.
473
505
  def nested_set_scope(options = {})
474
- options = {:order => quoted_left_column_name}.merge(options)
506
+ options = {:order => quoted_left_column_full_name}.merge(options)
475
507
  scopes = Array(acts_as_nested_set_options[:scope])
476
508
  options[:conditions] = scopes.inject({}) do |conditions,attr|
477
509
  conditions.merge attr => self[attr]
@@ -505,7 +537,7 @@ module CollectiveIdea #:nodoc:
505
537
 
506
538
  # on creation, set automatically lft and rgt to the end of the tree
507
539
  def set_default_left_and_right
508
- 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
509
541
  maxright = highest_right_row ? (highest_right_row[right_column_name] || 0) : 0
510
542
  # adds the new node to the right of all existing nodes
511
543
  self[left_column_name] = maxright + 1
@@ -535,7 +567,7 @@ module CollectiveIdea #:nodoc:
535
567
  in_tenacious_transaction do
536
568
  reload_nested_set
537
569
  # select the rows in the model that extend past the deletion point and apply a lock
538
- nested_set_scope.where(["#{quoted_left_column_name} >= ?", left]).
570
+ nested_set_scope.where(["#{quoted_left_column_full_name} >= ?", left]).
539
571
  select(id).lock(true)
540
572
 
541
573
  if acts_as_nested_set_options[:dependent] == :destroy
@@ -550,11 +582,11 @@ module CollectiveIdea #:nodoc:
550
582
 
551
583
  # update lefts and rights for remaining nodes
552
584
  diff = right - left + 1
553
- nested_set_scope.where(["#{quoted_left_column_name} > ?", right]).update_all(
585
+ nested_set_scope.where(["#{quoted_left_column_full_name} > ?", right]).update_all(
554
586
  ["#{quoted_left_column_name} = (#{quoted_left_column_name} - ?)", diff]
555
587
  )
556
588
 
557
- nested_set_scope.where(["#{quoted_right_column_name} > ?", right]).update_all(
589
+ nested_set_scope.where(["#{quoted_right_column_full_name} > ?", right]).update_all(
558
590
  ["#{quoted_right_column_name} = (#{quoted_right_column_name} - ?)", diff]
559
591
  )
560
592
 
@@ -566,7 +598,7 @@ module CollectiveIdea #:nodoc:
566
598
  # reload left, right, and parent
567
599
  def reload_nested_set
568
600
  reload(
569
- :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}",
570
602
  :lock => true
571
603
  )
572
604
  end
@@ -611,7 +643,7 @@ module CollectiveIdea #:nodoc:
611
643
 
612
644
  # select the rows in the model between a and d, and apply a lock
613
645
  self.class.base_class.select('id').lock(true).where(
614
- ["#{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}]
615
647
  )
616
648
 
617
649
  new_parent = case position
@@ -641,7 +673,7 @@ module CollectiveIdea #:nodoc:
641
673
  end
642
674
  target.reload_nested_set if target
643
675
  self.set_depth!
644
- self.children.each(&:save)
676
+ self.descendants.each(&:save)
645
677
  self.reload_nested_set
646
678
  end
647
679
  end
@@ -666,6 +698,10 @@ module CollectiveIdea #:nodoc:
666
698
  acts_as_nested_set_options[:parent_column]
667
699
  end
668
700
 
701
+ def order_column
702
+ acts_as_nested_set_options[:order_column] || left_column_name
703
+ end
704
+
669
705
  def scope_column_names
670
706
  Array(acts_as_nested_set_options[:scope])
671
707
  end
@@ -689,9 +725,20 @@ module CollectiveIdea #:nodoc:
689
725
  def quoted_scope_column_names
690
726
  scope_column_names.collect {|column_name| connection.quote_column_name(column_name) }
691
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
692
740
  end
693
741
 
694
742
  end
695
743
  end
696
744
  end
697
-
@@ -1,3 +1,3 @@
1
1
  module AwesomeNestedSet
2
- VERSION = '2.1.4' unless defined?(::AwesomeNestedSet::VERSION)
3
- end
2
+ VERSION = '2.1.5' unless defined?(::AwesomeNestedSet::VERSION)
3
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: awesome_nested_set
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.4
4
+ version: 2.1.5
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2012-08-21 00:00:00.000000000 Z
14
+ date: 2012-09-26 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: activerecord
@@ -29,6 +29,22 @@ dependencies:
29
29
  - - ! '>='
30
30
  - !ruby/object:Gem::Version
31
31
  version: 3.0.0
32
+ - !ruby/object:Gem::Dependency
33
+ name: rspec-rails
34
+ requirement: !ruby/object:Gem::Requirement
35
+ none: false
36
+ requirements:
37
+ - - ~>
38
+ - !ruby/object:Gem::Version
39
+ version: '2.8'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '2.8'
32
48
  description: An awesome nested set implementation for Active Record
33
49
  email: info@collectiveidea.com
34
50
  executables: []
@@ -67,7 +83,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
67
83
  version: '0'
68
84
  requirements: []
69
85
  rubyforge_project:
70
- rubygems_version: 1.8.22
86
+ rubygems_version: 1.8.24
71
87
  signing_key:
72
88
  specification_version: 3
73
89
  summary: An awesome nested set implementation for Active Record