awesome_nested_set 2.1.4 → 2.1.5

Sign up to get free protection for your applications and to get access to all the features.
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