nested_set 1.6.4 → 1.6.5

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.6.4
1
+ 1.6.5
@@ -25,6 +25,7 @@ module CollectiveIdea #:nodoc:
25
25
  module SingletonMethods
26
26
  # Configuration options are:
27
27
  #
28
+ # * +:primary_key_column+ - specifies the column name to use for keeping the position integer (default: id)
28
29
  # * +:parent_column+ - specifies the column name to use for keeping the position integer (default: parent_id)
29
30
  # * +:left_column+ - column name for left boundry data, default "lft"
30
31
  # * +:right_column+ - column name for right boundry data, default "rgt"
@@ -43,6 +44,7 @@ module CollectiveIdea #:nodoc:
43
44
  # to acts_as_nested_set models
44
45
  def acts_as_nested_set(options = {})
45
46
  options = {
47
+ :primary_key_column => self.primary_key,
46
48
  :parent_column => 'parent_id',
47
49
  :left_column => 'lft',
48
50
  :right_column => 'rgt',
@@ -54,8 +56,8 @@ module CollectiveIdea #:nodoc:
54
56
  options[:scope] = "#{options[:scope]}_id".intern
55
57
  end
56
58
 
57
- write_inheritable_attribute :acts_as_nested_set_options, options
58
- class_inheritable_reader :acts_as_nested_set_options
59
+ class_attribute :acts_as_nested_set_options
60
+ self.acts_as_nested_set_options = options
59
61
 
60
62
  unless self.is_a?(ClassMethods)
61
63
  include Comparable
@@ -142,7 +144,8 @@ module CollectiveIdea #:nodoc:
142
144
  arranged = ActiveSupport::OrderedHash.new
143
145
  insertion_points = [arranged]
144
146
  depth = 0
145
- order(quoted_left_column_name).each_with_level do |node, level|
147
+ order("#{quoted_table_name}.#{quoted_left_column_name}").each_with_level do |node, level|
148
+ next if level > depth && insertion_points.last.keys.last && node.parent_id != insertion_points.last.keys.last.id
146
149
  insertion_points.push insertion_points.last.values.last if level > depth
147
150
  (depth - level).times { insertion_points.pop } if level < depth
148
151
  insertion_points.last.merge! node => ActiveSupport::OrderedHash.new
@@ -175,17 +178,18 @@ module CollectiveIdea #:nodoc:
175
178
  end.push(nil).join(", ")
176
179
  [quoted_left_column_name, quoted_right_column_name].all? do |column|
177
180
  # No duplicates
178
- first(
181
+ unscoped.first(
179
182
  :select => "#{scope_string}#{column}, COUNT(#{column})",
180
- :group => "#{scope_string}#{column}
181
- HAVING COUNT(#{column}) > 1").nil?
183
+ :group => "#{scope_string}#{column}",
184
+ :having => "COUNT(#{column}) > 1"
185
+ ).nil?
182
186
  end
183
187
  end
184
188
 
185
189
  # Wrapper for each_root_valid? that can deal with scope.
186
190
  def all_roots_valid?
187
191
  if acts_as_nested_set_options[:scope]
188
- roots.group(scope_column_names).group_by{|record| scope_column_names.collect{|col| record.send(col.to_sym)}}.all? do |scope, grouped_roots|
192
+ roots.group_by{|record| scope_column_names.collect{|col| record.send(col.to_sym)}}.all? do |scope, grouped_roots|
189
193
  each_root_valid?(grouped_roots)
190
194
  end
191
195
  else
@@ -252,6 +256,14 @@ module CollectiveIdea #:nodoc:
252
256
  end
253
257
  end
254
258
 
259
+ def map_with_level(objects = nil)
260
+ result = []
261
+ each_with_level objects do |object, level|
262
+ result << yield(object, level)
263
+ end
264
+ result
265
+ end
266
+
255
267
  def before_move(*args, &block)
256
268
  set_callback :move, :before, *args, &block
257
269
  end
@@ -283,7 +295,7 @@ module CollectiveIdea #:nodoc:
283
295
  end
284
296
 
285
297
  def order_for_rebuild
286
- "#{quoted_left_column_name}, #{quoted_right_column_name}, id"
298
+ "#{quoted_left_column_name}, #{quoted_right_column_name}, #{primary_key_column_name}"
287
299
  end
288
300
 
289
301
  end
@@ -310,6 +322,10 @@ module CollectiveIdea #:nodoc:
310
322
  acts_as_nested_set_options[:depth_column]
311
323
  end
312
324
 
325
+ def primary_key_column_name
326
+ acts_as_nested_set_options[:primary_key_column]
327
+ end
328
+
313
329
  def quoted_left_column_name
314
330
  connection.quote_column_name(left_column_name)
315
331
  end
@@ -383,6 +399,11 @@ module CollectiveIdea #:nodoc:
383
399
  self_and_ancestors.first
384
400
  end
385
401
 
402
+ # Returns the array of all children and self
403
+ def self_and_children
404
+ nested_set_scope.scoped.where("#{q_parent} = ? or id = ?", id, id)
405
+ end
406
+
386
407
  # Returns the array of all parents and self
387
408
  def self_and_ancestors
388
409
  nested_set_scope.scoped.where("#{q_left} <= ? AND #{q_right} >= ?", left, right)
@@ -496,8 +517,8 @@ module CollectiveIdea #:nodoc:
496
517
  end
497
518
 
498
519
  def to_text
499
- self_and_descendants.map do |node|
500
- "#{'*'*(node.level+1)} #{node.id} #{node.to_s} (#{node.parent_id}, #{node.left}, #{node.right})"
520
+ self.class.map_with_level(self_and_descendants) do |node,level|
521
+ "#{'*'*(level+1)} #{node.id} #{node.to_s} (#{node.parent_id}, #{node.left}, #{node.right})"
501
522
  end.join("\n")
502
523
  end
503
524
 
@@ -511,6 +532,10 @@ module CollectiveIdea #:nodoc:
511
532
  "#{self.class.quoted_table_name}.#{quoted_right_column_name}"
512
533
  end
513
534
 
535
+ def q_parent
536
+ "#{self.class.quoted_table_name}.#{quoted_parent_column_name}"
537
+ end
538
+
514
539
  def without_self(scope)
515
540
  scope.where("#{self.class.quoted_table_name}.#{self.class.primary_key} != ?", self)
516
541
  end
@@ -519,8 +544,8 @@ module CollectiveIdea #:nodoc:
519
544
  # the base ActiveRecord class, using the :scope declared in the acts_as_nested_set
520
545
  # declaration.
521
546
  def nested_set_scope
522
- conditions = Array(acts_as_nested_set_options[:scope]).inject({}) do |conditions, attr|
523
- conditions.merge attr => self[attr]
547
+ conditions = Array(acts_as_nested_set_options[:scope]).inject({}) do |cnd, attr|
548
+ cnd.merge attr => self[attr]
524
549
  end
525
550
 
526
551
  self.class.base_class.order(q_left).where(conditions)
@@ -551,6 +576,7 @@ module CollectiveIdea #:nodoc:
551
576
  # back to the left so the counts still work.
552
577
  def destroy_descendants
553
578
  return if right.nil? || left.nil? || skip_before_destroy
579
+ reload_nested_set
554
580
 
555
581
  self.class.base_class.transaction do
556
582
  if acts_as_nested_set_options[:dependent] == :destroy
@@ -623,6 +649,13 @@ module CollectiveIdea #:nodoc:
623
649
  # so sorting puts both the intervals and their boundaries in order
624
650
  a, b, c, d = [self[left_column_name], self[right_column_name], bound, other_bound].sort
625
651
 
652
+ # select the rows in the model between a and d, and apply a lock
653
+ self.class.base_class.find(:all,
654
+ :select => primary_key_column_name,
655
+ :conditions => ["#{quoted_left_column_name} >= :a and #{quoted_right_column_name} <= :d", {:a => a, :d => d}],
656
+ :lock => true
657
+ )
658
+
626
659
  new_parent = case position
627
660
  when :child; target.id
628
661
  when :root; nil
@@ -11,9 +11,13 @@ module CollectiveIdea #:nodoc:
11
11
  # You can pass a block receiving an item and returning the string displayed in the select.
12
12
  #
13
13
  # == Params
14
- # * +class_or_item+ - Class name or top level times
15
- # * +mover+ - The item that is being move, used to exlude impossible moves
16
- # * +&block+ - a block that will be used to display: { |item| ... item.name }
14
+ # * +class_or_items+ - Class name or top level items
15
+ # * +mover+ - The item that is being move, used to exclude impossible moves
16
+ # * +options+ - hash of additional options
17
+ # * +&block+ - a block that will be used to display: { |item| ... item.name }
18
+ #
19
+ # == Options
20
+ # * +include_root+ - Include root object(s) in output. Default: true
17
21
  #
18
22
  # == Usage
19
23
  #
@@ -21,12 +25,23 @@ module CollectiveIdea #:nodoc:
21
25
  # "#{'–' * level} #{i.name}"
22
26
  # }) %>
23
27
  #
24
- def nested_set_options(class_or_item, mover = nil)
25
- class_or_item = class_or_item.roots if class_or_item.is_a?(Class)
26
- items = Array(class_or_item)
28
+ def nested_set_options(class_or_items, mover = nil, options = {})
29
+ items = case
30
+ when class_or_items.is_a?(Class)
31
+ class_or_items.roots
32
+ when class_or_items.is_a?(Array)
33
+ class_or_items
34
+ else
35
+ [class_or_items]
36
+ end
37
+
38
+ options.assert_valid_keys :include_root
39
+ options.reverse_merge! :include_root => true
40
+
27
41
  result = []
28
42
  items.each do |item|
29
- item.self_and_descendants.each_with_level do |i, level|
43
+ objects = options[:include_root] ? item.self_and_descendants : item.descendants
44
+ objects.each_with_level do |i, level|
30
45
  if mover.nil? || mover.new_record? || mover.move_possible?(i)
31
46
  result.push([yield(i, level), i.id])
32
47
  end
@@ -8,11 +8,11 @@ module CollectiveIdea
8
8
  class Railtie < ::Rails::Railtie
9
9
  config.before_initialize do
10
10
  ActiveSupport.on_load :active_record do
11
- ActiveRecord::Base.send(:include, CollectiveIdea::Acts::NestedSet::Base)
11
+ include CollectiveIdea::Acts::NestedSet::Base
12
12
  end
13
13
 
14
14
  ActiveSupport.on_load :action_view do
15
- ActionView::Base.send(:include, CollectiveIdea::Acts::NestedSet::Helper)
15
+ include CollectiveIdea::Acts::NestedSet::Helper
16
16
  end
17
17
  end
18
18
 
data/nested_set.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{nested_set}
8
- s.version = "1.6.4"
8
+ s.version = "1.6.5"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Brandon Keepers", "Daniel Morrison"]
12
- s.date = %q{2011-02-10}
12
+ s.date = %q{2011-05-24}
13
13
  s.description = %q{An awesome nested set implementation for Active Record}
14
14
  s.email = %q{info@collectiveidea.com}
15
15
  s.extra_rdoc_files = [
@@ -45,7 +45,7 @@ Gem::Specification.new do |s|
45
45
  ]
46
46
  s.homepage = %q{http://github.com/skyeagle/nested_set}
47
47
  s.require_paths = ["lib"]
48
- s.rubygems_version = %q{1.3.7}
48
+ s.rubygems_version = %q{1.6.2}
49
49
  s.summary = %q{An awesome nested set implementation for Active Record}
50
50
  s.test_files = [
51
51
  "test/benchmarks.rb",
@@ -57,7 +57,6 @@ Gem::Specification.new do |s|
57
57
  ]
58
58
 
59
59
  if s.respond_to? :specification_version then
60
- current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
61
60
  s.specification_version = 3
62
61
 
63
62
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
@@ -5,12 +5,22 @@ class Category < ActiveRecord::Base
5
5
  name
6
6
  end
7
7
 
8
- def recurse &block
8
+ def recurse(&block)
9
9
  block.call self, lambda{
10
10
  self.children.each do |child|
11
- child.recurse &block
11
+ child.recurse(&block)
12
12
  end
13
13
  }
14
14
  end
15
15
 
16
16
  end
17
+
18
+ class Category_NoToArray < Category
19
+ def to_a
20
+ raise 'to_a called'
21
+ end
22
+ end
23
+
24
+ class Category_DefaultScope < Category
25
+ default_scope order('categories.id ASC')
26
+ end
@@ -19,6 +19,19 @@ class HelperTest < ActionView::TestCase
19
19
  assert_equal expected, actual
20
20
  end
21
21
 
22
+ def test_nested_set_options_without_root
23
+ expected = [
24
+ [" Child 1", 2],
25
+ [' Child 2', 3],
26
+ ['- Child 2.1', 4],
27
+ [' Child 3', 5]
28
+ ]
29
+ actual = nested_set_options(categories(:top_level), nil, :include_root => false) do |c, level|
30
+ "#{'-' * level} #{c.name}"
31
+ end
32
+ assert_equal expected, actual
33
+ end
34
+
22
35
  def test_nested_set_options_with_mover
23
36
  expected = [
24
37
  [" Top Level", 1],
@@ -100,4 +113,15 @@ class HelperTest < ActionView::TestCase
100
113
  assert_equal html, "<ul><li>Top Level 2</li><li>Top Level</li><ul><li>Child 3</li><li>Child 2</li><ul><li>Child 2.1</li></ul><li>Child 1</li></ul></ul>"
101
114
  end
102
115
 
116
+ def test_nested_set_options_does_not_call_to_a
117
+ expected = [
118
+ ['Child 2', 3],
119
+ ['Child 2.1', 4]
120
+ ]
121
+ actual = nested_set_options Category_NoToArray.find(3) do |c|
122
+ c.name
123
+ end
124
+ assert_equal expected, actual
125
+ end
126
+
103
127
  end
@@ -130,10 +130,10 @@ class NestedSetTest < ActiveSupport::TestCase
130
130
  def test_leaves_class_method
131
131
  assert_equal Category.find(:all, :conditions => "#{Category.right_column_name} - #{Category.left_column_name} = 1"), Category.leaves
132
132
  assert_equal Category.leaves.count, 4
133
- assert (Category.leaves.include? categories(:child_1))
134
- assert (Category.leaves.include? categories(:child_2_1))
135
- assert (Category.leaves.include? categories(:child_3))
136
- assert (Category.leaves.include? categories(:top_level_2))
133
+ assert Category.leaves.include?(categories(:child_1))
134
+ assert Category.leaves.include?(categories(:child_2_1))
135
+ assert Category.leaves.include?(categories(:child_3))
136
+ assert Category.leaves.include?(categories(:top_level_2))
137
137
  end
138
138
 
139
139
  def test_leaf
@@ -147,11 +147,16 @@ class NestedSetTest < ActiveSupport::TestCase
147
147
  assert !Category.new.leaf?
148
148
  end
149
149
 
150
-
151
150
  def test_parent
152
151
  assert_equal categories(:child_2), categories(:child_2_1).parent
153
152
  end
154
153
 
154
+ def test_self_and_chilren
155
+ node = categories(:top_level)
156
+ self_and_children = [node, categories(:child_1), categories(:child_2), categories(:child_3)]
157
+ assert_equal self_and_children, node.self_and_children
158
+ end
159
+
155
160
  def test_self_and_ancestors
156
161
  child = categories(:child_2_1)
157
162
  self_and_ancestors = [categories(:top_level), categories(:child_2), child]
@@ -675,6 +680,12 @@ class NestedSetTest < ActiveSupport::TestCase
675
680
  assert Category.valid?
676
681
  end
677
682
 
683
+ def test_destroy_on_multiple_records_without_reload_does_not_invalidate
684
+ Category.acts_as_nested_set_options[:dependent] = :destroy
685
+ [categories(:child_1), categories(:child_2)].each(&:destroy)
686
+ assert Category.valid?
687
+ end
688
+
678
689
  def test_assigning_parent_id_on_create
679
690
  category = Category.create!(:name => "Child", :parent_id => categories(:child_2).id)
680
691
  assert_equal categories(:child_2), category.parent
@@ -756,6 +767,10 @@ class NestedSetTest < ActiveSupport::TestCase
756
767
  end
757
768
  end
758
769
 
770
+ def test_valid_with_default_scope
771
+ assert Category_DefaultScope.valid?
772
+ end
773
+
759
774
  def test_each_with_level
760
775
  levels = [
761
776
  [0, "Top Level"],
@@ -794,6 +809,20 @@ class NestedSetTest < ActiveSupport::TestCase
794
809
  check_scoped_structure(Category.root.self_and_descendants, levels)
795
810
  end
796
811
 
812
+ def test_map_with_level
813
+ expected = [
814
+ [0, "Top Level"],
815
+ [1, "Child 1"],
816
+ [1, "Child 2"],
817
+ [2, "Child 2.1"],
818
+ [1, "Child 3" ]
819
+ ]
820
+ actual = Category.map_with_level Category.root.self_and_descendants do |i, level|
821
+ [level, i.name]
822
+ end
823
+ assert_equal expected, actual
824
+ end
825
+
797
826
  def test_model_with_attr_accessible
798
827
  model = Class.new(ActiveRecord::Base)
799
828
  model.set_table_name 'categories'
metadata CHANGED
@@ -1,118 +1,91 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: nested_set
3
- version: !ruby/object:Gem::Version
4
- prerelease: false
5
- segments:
6
- - 1
7
- - 6
8
- - 4
9
- version: 1.6.4
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.6.5
5
+ prerelease:
10
6
  platform: ruby
11
- authors:
7
+ authors:
12
8
  - Brandon Keepers
13
9
  - Daniel Morrison
14
10
  autorequire:
15
11
  bindir: bin
16
12
  cert_chain: []
17
-
18
- date: 2011-02-10 00:00:00 +03:00
13
+ date: 2011-05-24 00:00:00.000000000 +04:00
19
14
  default_executable:
20
- dependencies:
21
- - !ruby/object:Gem::Dependency
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
22
17
  name: railties
23
- requirement: &id001 !ruby/object:Gem::Requirement
18
+ requirement: &11153540 !ruby/object:Gem::Requirement
24
19
  none: false
25
- requirements:
26
- - - ">="
27
- - !ruby/object:Gem::Version
28
- segments:
29
- - 3
30
- - 0
31
- - 0
20
+ requirements:
21
+ - - ! '>='
22
+ - !ruby/object:Gem::Version
32
23
  version: 3.0.0
33
24
  type: :runtime
34
25
  prerelease: false
35
- version_requirements: *id001
36
- - !ruby/object:Gem::Dependency
26
+ version_requirements: *11153540
27
+ - !ruby/object:Gem::Dependency
37
28
  name: activerecord
38
- requirement: &id002 !ruby/object:Gem::Requirement
29
+ requirement: &11152400 !ruby/object:Gem::Requirement
39
30
  none: false
40
- requirements:
41
- - - ">="
42
- - !ruby/object:Gem::Version
43
- segments:
44
- - 3
45
- - 0
46
- - 0
31
+ requirements:
32
+ - - ! '>='
33
+ - !ruby/object:Gem::Version
47
34
  version: 3.0.0
48
35
  type: :runtime
49
36
  prerelease: false
50
- version_requirements: *id002
51
- - !ruby/object:Gem::Dependency
37
+ version_requirements: *11152400
38
+ - !ruby/object:Gem::Dependency
52
39
  name: sqlite3-ruby
53
- requirement: &id003 !ruby/object:Gem::Requirement
40
+ requirement: &11151640 !ruby/object:Gem::Requirement
54
41
  none: false
55
- requirements:
56
- - - ">="
57
- - !ruby/object:Gem::Version
58
- segments:
59
- - 0
60
- version: "0"
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
61
46
  type: :development
62
47
  prerelease: false
63
- version_requirements: *id003
64
- - !ruby/object:Gem::Dependency
48
+ version_requirements: *11151640
49
+ - !ruby/object:Gem::Dependency
65
50
  name: actionpack
66
- requirement: &id004 !ruby/object:Gem::Requirement
51
+ requirement: &11148600 !ruby/object:Gem::Requirement
67
52
  none: false
68
- requirements:
69
- - - ">="
70
- - !ruby/object:Gem::Version
71
- segments:
72
- - 3
73
- - 0
74
- - 0
53
+ requirements:
54
+ - - ! '>='
55
+ - !ruby/object:Gem::Version
75
56
  version: 3.0.0
76
57
  type: :development
77
58
  prerelease: false
78
- version_requirements: *id004
79
- - !ruby/object:Gem::Dependency
59
+ version_requirements: *11148600
60
+ - !ruby/object:Gem::Dependency
80
61
  name: bench_press
81
- requirement: &id005 !ruby/object:Gem::Requirement
62
+ requirement: &11147700 !ruby/object:Gem::Requirement
82
63
  none: false
83
- requirements:
84
- - - ">="
85
- - !ruby/object:Gem::Version
86
- segments:
87
- - 0
88
- - 3
89
- - 1
64
+ requirements:
65
+ - - ! '>='
66
+ - !ruby/object:Gem::Version
90
67
  version: 0.3.1
91
68
  type: :development
92
69
  prerelease: false
93
- version_requirements: *id005
94
- - !ruby/object:Gem::Dependency
70
+ version_requirements: *11147700
71
+ - !ruby/object:Gem::Dependency
95
72
  name: jeweler
96
- requirement: &id006 !ruby/object:Gem::Requirement
73
+ requirement: &11146880 !ruby/object:Gem::Requirement
97
74
  none: false
98
- requirements:
99
- - - ">="
100
- - !ruby/object:Gem::Version
101
- segments:
102
- - 0
103
- version: "0"
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
104
79
  type: :development
105
80
  prerelease: false
106
- version_requirements: *id006
81
+ version_requirements: *11146880
107
82
  description: An awesome nested set implementation for Active Record
108
83
  email: info@collectiveidea.com
109
84
  executables: []
110
-
111
85
  extensions: []
112
-
113
- extra_rdoc_files:
86
+ extra_rdoc_files:
114
87
  - README.md
115
- files:
88
+ files:
116
89
  - .autotest
117
90
  - Gemfile
118
91
  - Gemfile.lock
@@ -142,37 +115,32 @@ files:
142
115
  has_rdoc: true
143
116
  homepage: http://github.com/skyeagle/nested_set
144
117
  licenses: []
145
-
146
118
  post_install_message:
147
119
  rdoc_options: []
148
-
149
- require_paths:
120
+ require_paths:
150
121
  - lib
151
- required_ruby_version: !ruby/object:Gem::Requirement
122
+ required_ruby_version: !ruby/object:Gem::Requirement
152
123
  none: false
153
- requirements:
154
- - - ">="
155
- - !ruby/object:Gem::Version
156
- hash: 462466689
157
- segments:
124
+ requirements:
125
+ - - ! '>='
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ segments:
158
129
  - 0
159
- version: "0"
160
- required_rubygems_version: !ruby/object:Gem::Requirement
130
+ hash: -1622325140072455651
131
+ required_rubygems_version: !ruby/object:Gem::Requirement
161
132
  none: false
162
- requirements:
163
- - - ">="
164
- - !ruby/object:Gem::Version
165
- segments:
166
- - 0
167
- version: "0"
133
+ requirements:
134
+ - - ! '>='
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
168
137
  requirements: []
169
-
170
138
  rubyforge_project:
171
- rubygems_version: 1.3.7
139
+ rubygems_version: 1.6.2
172
140
  signing_key:
173
141
  specification_version: 3
174
142
  summary: An awesome nested set implementation for Active Record
175
- test_files:
143
+ test_files:
176
144
  - test/benchmarks.rb
177
145
  - test/db/schema.rb
178
146
  - test/fixtures/category.rb