##// END OF EJS Templates
Fixed that projects are not ordered alphabetically after renaming project (#11508)....
Jean-Philippe Lang -
r10004:e52219f09d23
parent child
Show More
@@ -81,6 +81,7 class Project < ActiveRecord::Base
81 # reserved words
81 # reserved words
82 validates_exclusion_of :identifier, :in => %w( new )
82 validates_exclusion_of :identifier, :in => %w( new )
83
83
84 after_save :update_position_under_parent, :if => Proc.new {|project| project.name_changed?}
84 before_destroy :delete_all_members
85 before_destroy :delete_all_members
85
86
86 scope :has_module, lambda { |mod| { :conditions => ["#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s] } }
87 scope :has_module, lambda { |mod| { :conditions => ["#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s] } }
@@ -383,22 +384,7 class Project < ActiveRecord::Base
383 # Nothing to do
384 # Nothing to do
384 true
385 true
385 elsif p.nil? || (p.active? && move_possible?(p))
386 elsif p.nil? || (p.active? && move_possible?(p))
386 # Insert the project so that target's children or root projects stay alphabetically sorted
387 set_or_update_position_under(p)
387 sibs = (p.nil? ? self.class.roots : p.children)
388 to_be_inserted_before = sibs.detect {|c| c.name.to_s.downcase > name.to_s.downcase }
389 if to_be_inserted_before
390 move_to_left_of(to_be_inserted_before)
391 elsif p.nil?
392 if sibs.empty?
393 # move_to_root adds the project in first (ie. left) position
394 move_to_root
395 else
396 move_to_right_of(sibs.last) unless self == sibs.last
397 end
398 else
399 # move_to_child_of adds the project in last (ie.right) position
400 move_to_child_of(p)
401 end
402 Issue.update_versions_from_hierarchy_change(self)
388 Issue.update_versions_from_hierarchy_change(self)
403 true
389 true
404 else
390 else
@@ -943,4 +929,28 class Project < ActiveRecord::Base
943 end
929 end
944 update_attribute :status, STATUS_ARCHIVED
930 update_attribute :status, STATUS_ARCHIVED
945 end
931 end
932
933 def update_position_under_parent
934 set_or_update_position_under(parent)
935 end
936
937 # Inserts/moves the project so that target's children or root projects stay alphabetically sorted
938 def set_or_update_position_under(target_parent)
939 sibs = (target_parent.nil? ? self.class.roots : target_parent.children)
940 to_be_inserted_before = sibs.sort_by {|c| c.name.to_s.downcase}.detect {|c| c.name.to_s.downcase > name.to_s.downcase }
941
942 if to_be_inserted_before
943 move_to_left_of(to_be_inserted_before)
944 elsif target_parent.nil?
945 if sibs.empty?
946 # move_to_root adds the project in first (ie. left) position
947 move_to_root
948 else
949 move_to_right_of(sibs.last) unless self == sibs.last
950 end
951 else
952 # move_to_child_of adds the project in last (ie.right) position
953 move_to_child_of(target_parent)
954 end
955 end
946 end
956 end
@@ -19,95 +19,108 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class ProjectNestedSetTest < ActiveSupport::TestCase
20 class ProjectNestedSetTest < ActiveSupport::TestCase
21
21
22 context "nested set" do
22 def setup
23 setup do
23 Project.delete_all
24 Project.delete_all
25
26 @a = Project.create!(:name => 'Project A', :identifier => 'projecta')
27 @a1 = Project.create!(:name => 'Project A1', :identifier => 'projecta1')
28 @a1.set_parent!(@a)
29 @a2 = Project.create!(:name => 'Project A2', :identifier => 'projecta2')
30 @a2.set_parent!(@a)
31
32 @b = Project.create!(:name => 'Project B', :identifier => 'projectb')
33 @b1 = Project.create!(:name => 'Project B1', :identifier => 'projectb1')
34 @b1.set_parent!(@b)
35 @b11 = Project.create!(:name => 'Project B11', :identifier => 'projectb11')
36 @b11.set_parent!(@b1)
37 @b2 = Project.create!(:name => 'Project B2', :identifier => 'projectb2')
38 @b2.set_parent!(@b)
39
40 @c = Project.create!(:name => 'Project C', :identifier => 'projectc')
41 @c1 = Project.create!(:name => 'Project C1', :identifier => 'projectc1')
42 @c1.set_parent!(@c)
43
44 [@a, @a1, @a2, @b, @b1, @b11, @b2, @c, @c1].each(&:reload)
45 end
46
24
47 context "#create" do
25 @a = Project.create!(:name => 'A', :identifier => 'projecta')
48 should "build valid tree" do
26 @a1 = Project.create!(:name => 'A1', :identifier => 'projecta1')
49 assert_nested_set_values({
27 @a1.set_parent!(@a)
50 @a => [nil, 1, 6],
28 @a2 = Project.create!(:name => 'A2', :identifier => 'projecta2')
51 @a1 => [@a.id, 2, 3],
29 @a2.set_parent!(@a)
52 @a2 => [@a.id, 4, 5],
53 @b => [nil, 7, 14],
54 @b1 => [@b.id, 8, 11],
55 @b11 => [@b1.id,9, 10],
56 @b2 => [@b.id,12, 13],
57 @c => [nil, 15, 18],
58 @c1 => [@c.id,16, 17]
59 })
60 end
61 end
62
30
63 context "#set_parent!" do
31 @b = Project.create!(:name => 'B', :identifier => 'projectb')
64 should "keep valid tree" do
32 @b1 = Project.create!(:name => 'B1', :identifier => 'projectb1')
65 assert_no_difference 'Project.count' do
33 @b1.set_parent!(@b)
66 Project.find_by_name('Project B1').set_parent!(Project.find_by_name('Project A2'))
34 @b11 = Project.create!(:name => 'B11', :identifier => 'projectb11')
67 end
35 @b11.set_parent!(@b1)
68 assert_nested_set_values({
36 @b2 = Project.create!(:name => 'B2', :identifier => 'projectb2')
69 @a => [nil, 1, 10],
37 @b2.set_parent!(@b)
70 @a2 => [@a.id, 4, 9],
38
71 @b1 => [@a2.id,5, 8],
39 @c = Project.create!(:name => 'C', :identifier => 'projectc')
72 @b11 => [@b1.id,6, 7],
40 @c1 = Project.create!(:name => 'C1', :identifier => 'projectc1')
73 @b => [nil, 11, 14],
41 @c1.set_parent!(@c)
74 @c => [nil, 15, 18]
42
75 })
43 @a, @a1, @a2, @b, @b1, @b11, @b2, @c, @c1 = *(Project.all.sort_by(&:name))
76 end
44 end
45
46 def test_valid_tree
47 assert_valid_nested_set
48 end
49
50 def test_moving_a_child_to_a_different_parent_should_keep_valid_tree
51 assert_no_difference 'Project.count' do
52 Project.find_by_name('B1').set_parent!(Project.find_by_name('A2'))
77 end
53 end
54 assert_valid_nested_set
55 end
78
56
79 context "#destroy" do
57 def test_renaming_a_root_to_first_position_should_update_nested_set_order
80 context "a root with children" do
58 @c.name = '1'
81 should "not mess up the tree" do
59 @c.save!
82 assert_difference 'Project.count', -4 do
60 assert_valid_nested_set
83 Project.find_by_name('Project B').destroy
61 end
84 end
85 assert_nested_set_values({
86 @a => [nil, 1, 6],
87 @a1 => [@a.id, 2, 3],
88 @a2 => [@a.id, 4, 5],
89 @c => [nil, 7, 10],
90 @c1 => [@c.id, 8, 9]
91 })
92 end
93 end
94
62
95 context "a child with children" do
63 def test_renaming_a_root_to_middle_position_should_update_nested_set_order
96 should "not mess up the tree" do
64 @a.name = 'BA'
97 assert_difference 'Project.count', -2 do
65 @a.save!
98 Project.find_by_name('Project B1').destroy
66 assert_valid_nested_set
99 end
67 end
100 assert_nested_set_values({
68
101 @a => [nil, 1, 6],
69 def test_renaming_a_root_to_last_position_should_update_nested_set_order
102 @b => [nil, 7, 10],
70 @a.name = 'D'
103 @b2 => [@b.id, 8, 9],
71 @a.save!
104 @c => [nil, 11, 14]
72 assert_valid_nested_set
105 })
73 end
106 end
74
107 end
75 def test_renaming_a_root_to_same_position_should_update_nested_set_order
76 @c.name = 'D'
77 @c.save!
78 assert_valid_nested_set
79 end
80
81 def test_renaming_a_child_should_update_nested_set_order
82 @a1.name = 'A3'
83 @a1.save!
84 assert_valid_nested_set
85 end
86
87 def test_renaming_a_child_with_child_should_update_nested_set_order
88 @b1.name = 'B3'
89 @b1.save!
90 assert_valid_nested_set
91 end
92
93 def test_adding_a_root_to_first_position_should_update_nested_set_order
94 project = Project.create!(:name => '1', :identifier => 'projectba')
95 assert_valid_nested_set
96 end
97
98 def test_adding_a_root_to_middle_position_should_update_nested_set_order
99 project = Project.create!(:name => 'BA', :identifier => 'projectba')
100 assert_valid_nested_set
101 end
102
103 def test_adding_a_root_to_last_position_should_update_nested_set_order
104 project = Project.create!(:name => 'Z', :identifier => 'projectba')
105 assert_valid_nested_set
106 end
107
108 def test_destroying_a_root_with_children_should_keep_valid_tree
109 assert_difference 'Project.count', -4 do
110 Project.find_by_name('B').destroy
108 end
111 end
112 assert_valid_nested_set
109 end
113 end
110
114
115 def test_destroying_a_child_with_children_should_keep_valid_tree
116 assert_difference 'Project.count', -2 do
117 Project.find_by_name('B1').destroy
118 end
119 assert_valid_nested_set
120 end
121
122 private
123
111 def assert_nested_set_values(h)
124 def assert_nested_set_values(h)
112 assert Project.valid?
125 assert Project.valid?
113 h.each do |project, expected|
126 h.each do |project, expected|
@@ -115,4 +128,40 class ProjectNestedSetTest < ActiveSupport::TestCase
115 assert_equal expected, [project.parent_id, project.lft, project.rgt], "Unexpected nested set values for #{project.name}"
128 assert_equal expected, [project.parent_id, project.lft, project.rgt], "Unexpected nested set values for #{project.name}"
116 end
129 end
117 end
130 end
131
132 def assert_valid_nested_set
133 projects = Project.all
134 lft_rgt = projects.map {|p| [p.lft, p.rgt]}.flatten
135 assert_equal projects.size * 2, lft_rgt.uniq.size
136 assert_equal 1, lft_rgt.min
137 assert_equal projects.size * 2, lft_rgt.max
138
139 projects.each do |project|
140 # lft should always be < rgt
141 assert project.lft < project.rgt, "lft=#{project.lft} was not < rgt=#{project.rgt} for project #{project.name}"
142 if project.parent_id
143 # child lft/rgt values must be greater/lower
144 assert_not_nil project.parent, "parent was nil for project #{project.name}"
145 assert project.lft > project.parent.lft, "lft=#{project.lft} was not > parent.lft=#{project.parent.lft} for project #{project.name}"
146 assert project.rgt < project.parent.rgt, "rgt=#{project.rgt} was not < parent.rgt=#{project.parent.rgt} for project #{project.name}"
147 end
148 # no overlapping lft/rgt values
149 overlapping = projects.detect {|other|
150 other != project && (
151 (other.lft > project.lft && other.lft < project.rgt && other.rgt > project.rgt) ||
152 (other.rgt > project.lft && other.rgt < project.rgt && other.lft < project.lft)
153 )
154 }
155 assert_nil overlapping, (overlapping && "Project #{overlapping.name} (#{overlapping.lft}/#{overlapping.rgt}) overlapped #{project.name} (#{project.lft}/#{project.rgt})")
156 end
157
158 # root projects sorted alphabetically
159 assert_equal Project.roots.map(&:name).sort, Project.roots.sort_by(&:lft).map(&:name), "Root projects were not properly sorted"
160 projects.each do |project|
161 if project.children.any?
162 # sibling projects sorted alphabetically
163 assert_equal project.children.map(&:name).sort, project.children.order('lft').map(&:name), "Project #{project.name}'s children were not properly sorted"
164 end
165 end
166 end
118 end
167 end
General Comments 0
You need to be logged in to leave comments. Login now