##// END OF EJS Templates
Adds .rebuild_single_tree! to rebuild a single tree (#24167)....
Jean-Philippe Lang -
r15729:b58cf253838b
parent child
Show More
@@ -1,200 +1,210
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 module Redmine
18 module Redmine
19 module NestedSet
19 module NestedSet
20 module IssueNestedSet
20 module IssueNestedSet
21 def self.included(base)
21 def self.included(base)
22 base.class_eval do
22 base.class_eval do
23 belongs_to :parent, :class_name => self.name
23 belongs_to :parent, :class_name => self.name
24
24
25 before_create :add_to_nested_set, :if => lambda {|issue| issue.parent.present?}
25 before_create :add_to_nested_set, :if => lambda {|issue| issue.parent.present?}
26 after_create :add_as_root, :if => lambda {|issue| issue.parent.blank?}
26 after_create :add_as_root, :if => lambda {|issue| issue.parent.blank?}
27 before_update :handle_parent_change, :if => lambda {|issue| issue.parent_id_changed?}
27 before_update :handle_parent_change, :if => lambda {|issue| issue.parent_id_changed?}
28 before_destroy :destroy_children
28 before_destroy :destroy_children
29 end
29 end
30 base.extend ClassMethods
30 base.extend ClassMethods
31 base.send :include, Redmine::NestedSet::Traversing
31 base.send :include, Redmine::NestedSet::Traversing
32 end
32 end
33
33
34 private
34 private
35
35
36 def target_lft
36 def target_lft
37 scope_for_max_rgt = self.class.where(:root_id => root_id).where(:parent_id => parent_id)
37 scope_for_max_rgt = self.class.where(:root_id => root_id).where(:parent_id => parent_id)
38 if id
38 if id
39 scope_for_max_rgt = scope_for_max_rgt.where("id < ?", id)
39 scope_for_max_rgt = scope_for_max_rgt.where("id < ?", id)
40 end
40 end
41 max_rgt = scope_for_max_rgt.maximum(:rgt)
41 max_rgt = scope_for_max_rgt.maximum(:rgt)
42 if max_rgt
42 if max_rgt
43 max_rgt + 1
43 max_rgt + 1
44 elsif parent
44 elsif parent
45 parent.lft + 1
45 parent.lft + 1
46 else
46 else
47 1
47 1
48 end
48 end
49 end
49 end
50
50
51 def add_to_nested_set(lock=true)
51 def add_to_nested_set(lock=true)
52 lock_nested_set if lock
52 lock_nested_set if lock
53 parent.send :reload_nested_set_values
53 parent.send :reload_nested_set_values
54 self.root_id = parent.root_id
54 self.root_id = parent.root_id
55 self.lft = target_lft
55 self.lft = target_lft
56 self.rgt = lft + 1
56 self.rgt = lft + 1
57 self.class.where(:root_id => root_id).where("lft >= ? OR rgt >= ?", lft, lft).update_all([
57 self.class.where(:root_id => root_id).where("lft >= ? OR rgt >= ?", lft, lft).update_all([
58 "lft = CASE WHEN lft >= :lft THEN lft + 2 ELSE lft END, " +
58 "lft = CASE WHEN lft >= :lft THEN lft + 2 ELSE lft END, " +
59 "rgt = CASE WHEN rgt >= :lft THEN rgt + 2 ELSE rgt END",
59 "rgt = CASE WHEN rgt >= :lft THEN rgt + 2 ELSE rgt END",
60 {:lft => lft}
60 {:lft => lft}
61 ])
61 ])
62 end
62 end
63
63
64 def add_as_root
64 def add_as_root
65 self.root_id = id
65 self.root_id = id
66 self.lft = 1
66 self.lft = 1
67 self.rgt = 2
67 self.rgt = 2
68 self.class.where(:id => id).update_all(:root_id => root_id, :lft => lft, :rgt => rgt)
68 self.class.where(:id => id).update_all(:root_id => root_id, :lft => lft, :rgt => rgt)
69 end
69 end
70
70
71 def handle_parent_change
71 def handle_parent_change
72 lock_nested_set
72 lock_nested_set
73 reload_nested_set_values
73 reload_nested_set_values
74 if parent_id_was
74 if parent_id_was
75 remove_from_nested_set
75 remove_from_nested_set
76 end
76 end
77 if parent
77 if parent
78 move_to_nested_set
78 move_to_nested_set
79 end
79 end
80 reload_nested_set_values
80 reload_nested_set_values
81 end
81 end
82
82
83 def move_to_nested_set
83 def move_to_nested_set
84 if parent
84 if parent
85 previous_root_id = root_id
85 previous_root_id = root_id
86 self.root_id = parent.root_id
86 self.root_id = parent.root_id
87
87
88 lft_after_move = target_lft
88 lft_after_move = target_lft
89 self.class.where(:root_id => parent.root_id).update_all([
89 self.class.where(:root_id => parent.root_id).update_all([
90 "lft = CASE WHEN lft >= :lft THEN lft + :shift ELSE lft END, " +
90 "lft = CASE WHEN lft >= :lft THEN lft + :shift ELSE lft END, " +
91 "rgt = CASE WHEN rgt >= :lft THEN rgt + :shift ELSE rgt END",
91 "rgt = CASE WHEN rgt >= :lft THEN rgt + :shift ELSE rgt END",
92 {:lft => lft_after_move, :shift => (rgt - lft + 1)}
92 {:lft => lft_after_move, :shift => (rgt - lft + 1)}
93 ])
93 ])
94
94
95 self.class.where(:root_id => previous_root_id).update_all([
95 self.class.where(:root_id => previous_root_id).update_all([
96 "root_id = :root_id, lft = lft + :shift, rgt = rgt + :shift",
96 "root_id = :root_id, lft = lft + :shift, rgt = rgt + :shift",
97 {:root_id => parent.root_id, :shift => lft_after_move - lft}
97 {:root_id => parent.root_id, :shift => lft_after_move - lft}
98 ])
98 ])
99
99
100 self.lft, self.rgt = lft_after_move, (rgt - lft + lft_after_move)
100 self.lft, self.rgt = lft_after_move, (rgt - lft + lft_after_move)
101 parent.send :reload_nested_set_values
101 parent.send :reload_nested_set_values
102 end
102 end
103 end
103 end
104
104
105 def remove_from_nested_set
105 def remove_from_nested_set
106 self.class.where(:root_id => root_id).where("lft >= ? AND rgt <= ?", lft, rgt).
106 self.class.where(:root_id => root_id).where("lft >= ? AND rgt <= ?", lft, rgt).
107 update_all(["root_id = :id, lft = lft - :shift, rgt = rgt - :shift", {:id => id, :shift => lft - 1}])
107 update_all(["root_id = :id, lft = lft - :shift, rgt = rgt - :shift", {:id => id, :shift => lft - 1}])
108
108
109 self.class.where(:root_id => root_id).update_all([
109 self.class.where(:root_id => root_id).update_all([
110 "lft = CASE WHEN lft >= :lft THEN lft - :shift ELSE lft END, " +
110 "lft = CASE WHEN lft >= :lft THEN lft - :shift ELSE lft END, " +
111 "rgt = CASE WHEN rgt >= :lft THEN rgt - :shift ELSE rgt END",
111 "rgt = CASE WHEN rgt >= :lft THEN rgt - :shift ELSE rgt END",
112 {:lft => lft, :shift => rgt - lft + 1}
112 {:lft => lft, :shift => rgt - lft + 1}
113 ])
113 ])
114 self.root_id = id
114 self.root_id = id
115 self.lft, self.rgt = 1, (rgt - lft + 1)
115 self.lft, self.rgt = 1, (rgt - lft + 1)
116 end
116 end
117
117
118 def destroy_children
118 def destroy_children
119 unless @without_nested_set_update
119 unless @without_nested_set_update
120 lock_nested_set
120 lock_nested_set
121 reload_nested_set_values
121 reload_nested_set_values
122 end
122 end
123 children.each {|c| c.send :destroy_without_nested_set_update}
123 children.each {|c| c.send :destroy_without_nested_set_update}
124 reload
124 reload
125 unless @without_nested_set_update
125 unless @without_nested_set_update
126 self.class.where(:root_id => root_id).where("lft > ? OR rgt > ?", lft, lft).update_all([
126 self.class.where(:root_id => root_id).where("lft > ? OR rgt > ?", lft, lft).update_all([
127 "lft = CASE WHEN lft > :lft THEN lft - :shift ELSE lft END, " +
127 "lft = CASE WHEN lft > :lft THEN lft - :shift ELSE lft END, " +
128 "rgt = CASE WHEN rgt > :lft THEN rgt - :shift ELSE rgt END",
128 "rgt = CASE WHEN rgt > :lft THEN rgt - :shift ELSE rgt END",
129 {:lft => lft, :shift => rgt - lft + 1}
129 {:lft => lft, :shift => rgt - lft + 1}
130 ])
130 ])
131 end
131 end
132 end
132 end
133
133
134 def destroy_without_nested_set_update
134 def destroy_without_nested_set_update
135 @without_nested_set_update = true
135 @without_nested_set_update = true
136 destroy
136 destroy
137 end
137 end
138
138
139 def reload_nested_set_values
139 def reload_nested_set_values
140 self.root_id, self.lft, self.rgt = self.class.where(:id => id).pluck(:root_id, :lft, :rgt).first
140 self.root_id, self.lft, self.rgt = self.class.where(:id => id).pluck(:root_id, :lft, :rgt).first
141 end
141 end
142
142
143 def save_nested_set_values
143 def save_nested_set_values
144 self.class.where(:id => id).update_all(:root_id => root_id, :lft => lft, :rgt => rgt)
144 self.class.where(:id => id).update_all(:root_id => root_id, :lft => lft, :rgt => rgt)
145 end
145 end
146
146
147 def move_possible?(issue)
147 def move_possible?(issue)
148 new_record? || !is_or_is_ancestor_of?(issue)
148 new_record? || !is_or_is_ancestor_of?(issue)
149 end
149 end
150
150
151 def lock_nested_set
151 def lock_nested_set
152 if self.class.connection.adapter_name =~ /sqlserver/i
152 if self.class.connection.adapter_name =~ /sqlserver/i
153 lock = "WITH (ROWLOCK HOLDLOCK UPDLOCK)"
153 lock = "WITH (ROWLOCK HOLDLOCK UPDLOCK)"
154 # Custom lock for SQLServer
154 # Custom lock for SQLServer
155 # This can be problematic if root_id or parent root_id changes
155 # This can be problematic if root_id or parent root_id changes
156 # before locking
156 # before locking
157 sets_to_lock = [root_id, parent.try(:root_id)].compact.uniq
157 sets_to_lock = [root_id, parent.try(:root_id)].compact.uniq
158 self.class.reorder(:id).where(:root_id => sets_to_lock).lock(lock).ids
158 self.class.reorder(:id).where(:root_id => sets_to_lock).lock(lock).ids
159 else
159 else
160 sets_to_lock = [id, parent_id].compact
160 sets_to_lock = [id, parent_id].compact
161 self.class.reorder(:id).where("root_id IN (SELECT root_id FROM #{self.class.table_name} WHERE id IN (?))", sets_to_lock).lock.ids
161 self.class.reorder(:id).where("root_id IN (SELECT root_id FROM #{self.class.table_name} WHERE id IN (?))", sets_to_lock).lock.ids
162 end
162 end
163 end
163 end
164
164
165 def nested_set_scope
165 def nested_set_scope
166 self.class.order(:lft).where(:root_id => root_id)
166 self.class.order(:lft).where(:root_id => root_id)
167 end
167 end
168
168
169 def same_nested_set_scope?(issue)
169 def same_nested_set_scope?(issue)
170 root_id == issue.root_id
170 root_id == issue.root_id
171 end
171 end
172
172
173 module ClassMethods
173 module ClassMethods
174 def rebuild_tree!
174 def rebuild_tree!
175 transaction do
175 transaction do
176 reorder(:id).lock.ids
176 reorder(:id).lock.ids
177 update_all(:root_id => nil, :lft => nil, :rgt => nil)
177 update_all(:root_id => nil, :lft => nil, :rgt => nil)
178 where(:parent_id => nil).update_all(["root_id = id, lft = ?, rgt = ?", 1, 2])
178 where(:parent_id => nil).update_all(["root_id = id, lft = ?, rgt = ?", 1, 2])
179 roots_with_children = joins("JOIN #{table_name} parent ON parent.id = #{table_name}.parent_id AND parent.id = parent.root_id").distinct.pluck("parent.id")
179 roots_with_children = joins("JOIN #{table_name} parent ON parent.id = #{table_name}.parent_id AND parent.id = parent.root_id").distinct.pluck("parent.id")
180 roots_with_children.each do |root_id|
180 roots_with_children.each do |root_id|
181 rebuild_nodes(root_id)
181 rebuild_nodes(root_id)
182 end
182 end
183 end
183 end
184 end
184 end
185
185
186 def rebuild_single_tree!(root_id)
187 root = Issue.where(:parent_id => nil).find(root_id)
188 transaction do
189 where(root_id: root_id).reorder(:id).lock.ids
190 where(root_id: root_id).update_all(:lft => nil, :rgt => nil)
191 where(root_id: root_id, parent_id: nil).update_all(["lft = ?, rgt = ?", 1, 2])
192 rebuild_nodes(root_id)
193 end
194 end
195
186 private
196 private
187
197
188 def rebuild_nodes(parent_id = nil)
198 def rebuild_nodes(parent_id = nil)
189 nodes = where(:parent_id => parent_id, :rgt => nil, :lft => nil).order(:id).to_a
199 nodes = where(:parent_id => parent_id, :rgt => nil, :lft => nil).order(:id).to_a
190
200
191 nodes.each do |node|
201 nodes.each do |node|
192 node.send :add_to_nested_set, false
202 node.send :add_to_nested_set, false
193 node.send :save_nested_set_values
203 node.send :save_nested_set_values
194 rebuild_nodes node.id
204 rebuild_nodes node.id
195 end
205 end
196 end
206 end
197 end
207 end
198 end
208 end
199 end
209 end
200 end
210 end
@@ -1,309 +1,328
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class IssueNestedSetTest < ActiveSupport::TestCase
20 class IssueNestedSetTest < ActiveSupport::TestCase
21 fixtures :projects, :users, :roles,
21 fixtures :projects, :users, :roles,
22 :trackers, :projects_trackers,
22 :trackers, :projects_trackers,
23 :issue_statuses, :issue_categories, :issue_relations,
23 :issue_statuses, :issue_categories, :issue_relations,
24 :enumerations,
24 :enumerations,
25 :issues
25 :issues
26
26
27 def test_new_record_is_leaf
27 def test_new_record_is_leaf
28 i = Issue.new
28 i = Issue.new
29 assert i.leaf?
29 assert i.leaf?
30 end
30 end
31
31
32 def test_create_root_issue
32 def test_create_root_issue
33 lft1 = new_issue_lft
33 lft1 = new_issue_lft
34 issue1 = Issue.generate!
34 issue1 = Issue.generate!
35 lft2 = new_issue_lft
35 lft2 = new_issue_lft
36 issue2 = Issue.generate!
36 issue2 = Issue.generate!
37 issue1.reload
37 issue1.reload
38 issue2.reload
38 issue2.reload
39 assert_equal [issue1.id, nil, lft1, lft1 + 1], [issue1.root_id, issue1.parent_id, issue1.lft, issue1.rgt]
39 assert_equal [issue1.id, nil, lft1, lft1 + 1], [issue1.root_id, issue1.parent_id, issue1.lft, issue1.rgt]
40 assert_equal [issue2.id, nil, lft2, lft2 + 1], [issue2.root_id, issue2.parent_id, issue2.lft, issue2.rgt]
40 assert_equal [issue2.id, nil, lft2, lft2 + 1], [issue2.root_id, issue2.parent_id, issue2.lft, issue2.rgt]
41 end
41 end
42
42
43 def test_create_child_issue
43 def test_create_child_issue
44 lft = new_issue_lft
44 lft = new_issue_lft
45 parent = Issue.generate!
45 parent = Issue.generate!
46 child = parent.generate_child!
46 child = parent.generate_child!
47 parent.reload
47 parent.reload
48 child.reload
48 child.reload
49 assert_equal [parent.id, nil, lft, lft + 3], [parent.root_id, parent.parent_id, parent.lft, parent.rgt]
49 assert_equal [parent.id, nil, lft, lft + 3], [parent.root_id, parent.parent_id, parent.lft, parent.rgt]
50 assert_equal [parent.id, parent.id, lft + 1, lft + 2], [child.root_id, child.parent_id, child.lft, child.rgt]
50 assert_equal [parent.id, parent.id, lft + 1, lft + 2], [child.root_id, child.parent_id, child.lft, child.rgt]
51 end
51 end
52
52
53 def test_creating_a_child_in_a_subproject_should_validate
53 def test_creating_a_child_in_a_subproject_should_validate
54 issue = Issue.generate!
54 issue = Issue.generate!
55 child = Issue.new(:project_id => 3, :tracker_id => 2, :author_id => 1,
55 child = Issue.new(:project_id => 3, :tracker_id => 2, :author_id => 1,
56 :subject => 'child', :parent_issue_id => issue.id)
56 :subject => 'child', :parent_issue_id => issue.id)
57 assert_save child
57 assert_save child
58 assert_equal issue, child.reload.parent
58 assert_equal issue, child.reload.parent
59 end
59 end
60
60
61 def test_creating_a_child_in_an_invalid_project_should_not_validate
61 def test_creating_a_child_in_an_invalid_project_should_not_validate
62 issue = Issue.generate!
62 issue = Issue.generate!
63 child = Issue.new(:project_id => 2, :tracker_id => 1, :author_id => 1,
63 child = Issue.new(:project_id => 2, :tracker_id => 1, :author_id => 1,
64 :subject => 'child', :parent_issue_id => issue.id)
64 :subject => 'child', :parent_issue_id => issue.id)
65 assert !child.save
65 assert !child.save
66 assert_not_equal [], child.errors[:parent_issue_id]
66 assert_not_equal [], child.errors[:parent_issue_id]
67 end
67 end
68
68
69 def test_move_a_root_to_child
69 def test_move_a_root_to_child
70 lft = new_issue_lft
70 lft = new_issue_lft
71 parent1 = Issue.generate!
71 parent1 = Issue.generate!
72 parent2 = Issue.generate!
72 parent2 = Issue.generate!
73 child = parent1.generate_child!
73 child = parent1.generate_child!
74 parent2.parent_issue_id = parent1.id
74 parent2.parent_issue_id = parent1.id
75 parent2.save!
75 parent2.save!
76 child.reload
76 child.reload
77 parent1.reload
77 parent1.reload
78 parent2.reload
78 parent2.reload
79 assert_equal [parent1.id, lft, lft + 5], [parent1.root_id, parent1.lft, parent1.rgt]
79 assert_equal [parent1.id, lft, lft + 5], [parent1.root_id, parent1.lft, parent1.rgt]
80 assert_equal [parent1.id, lft + 1, lft + 2], [parent2.root_id, parent2.lft, parent2.rgt]
80 assert_equal [parent1.id, lft + 1, lft + 2], [parent2.root_id, parent2.lft, parent2.rgt]
81 assert_equal [parent1.id, lft + 3, lft + 4], [child.root_id, child.lft, child.rgt]
81 assert_equal [parent1.id, lft + 3, lft + 4], [child.root_id, child.lft, child.rgt]
82 end
82 end
83
83
84 def test_move_a_child_to_root
84 def test_move_a_child_to_root
85 lft1 = new_issue_lft
85 lft1 = new_issue_lft
86 parent1 = Issue.generate!
86 parent1 = Issue.generate!
87 lft2 = new_issue_lft
87 lft2 = new_issue_lft
88 parent2 = Issue.generate!
88 parent2 = Issue.generate!
89 lft3 = new_issue_lft
89 lft3 = new_issue_lft
90 child = parent1.generate_child!
90 child = parent1.generate_child!
91 child.parent_issue_id = nil
91 child.parent_issue_id = nil
92 child.save!
92 child.save!
93 child.reload
93 child.reload
94 parent1.reload
94 parent1.reload
95 parent2.reload
95 parent2.reload
96 assert_equal [parent1.id, lft1, lft1 + 1], [parent1.root_id, parent1.lft, parent1.rgt]
96 assert_equal [parent1.id, lft1, lft1 + 1], [parent1.root_id, parent1.lft, parent1.rgt]
97 assert_equal [parent2.id, lft2, lft2 + 1], [parent2.root_id, parent2.lft, parent2.rgt]
97 assert_equal [parent2.id, lft2, lft2 + 1], [parent2.root_id, parent2.lft, parent2.rgt]
98 assert_equal [child.id, lft3, lft3 + 1], [child.root_id, child.lft, child.rgt]
98 assert_equal [child.id, lft3, lft3 + 1], [child.root_id, child.lft, child.rgt]
99 end
99 end
100
100
101 def test_move_a_child_to_another_issue
101 def test_move_a_child_to_another_issue
102 lft1 = new_issue_lft
102 lft1 = new_issue_lft
103 parent1 = Issue.generate!
103 parent1 = Issue.generate!
104 lft2 = new_issue_lft
104 lft2 = new_issue_lft
105 parent2 = Issue.generate!
105 parent2 = Issue.generate!
106 child = parent1.generate_child!
106 child = parent1.generate_child!
107 child.parent_issue_id = parent2.id
107 child.parent_issue_id = parent2.id
108 child.save!
108 child.save!
109 child.reload
109 child.reload
110 parent1.reload
110 parent1.reload
111 parent2.reload
111 parent2.reload
112 assert_equal [parent1.id, lft1, lft1 + 1], [parent1.root_id, parent1.lft, parent1.rgt]
112 assert_equal [parent1.id, lft1, lft1 + 1], [parent1.root_id, parent1.lft, parent1.rgt]
113 assert_equal [parent2.id, lft2, lft2 + 3], [parent2.root_id, parent2.lft, parent2.rgt]
113 assert_equal [parent2.id, lft2, lft2 + 3], [parent2.root_id, parent2.lft, parent2.rgt]
114 assert_equal [parent2.id, lft2 + 1, lft2 + 2], [child.root_id, child.lft, child.rgt]
114 assert_equal [parent2.id, lft2 + 1, lft2 + 2], [child.root_id, child.lft, child.rgt]
115 end
115 end
116
116
117 def test_move_a_child_with_descendants_to_another_issue
117 def test_move_a_child_with_descendants_to_another_issue
118 lft1 = new_issue_lft
118 lft1 = new_issue_lft
119 parent1 = Issue.generate!
119 parent1 = Issue.generate!
120 lft2 = new_issue_lft
120 lft2 = new_issue_lft
121 parent2 = Issue.generate!
121 parent2 = Issue.generate!
122 child = parent1.generate_child!
122 child = parent1.generate_child!
123 grandchild = child.generate_child!
123 grandchild = child.generate_child!
124 parent1.reload
124 parent1.reload
125 parent2.reload
125 parent2.reload
126 child.reload
126 child.reload
127 grandchild.reload
127 grandchild.reload
128 assert_equal [parent1.id, lft1, lft1 + 5], [parent1.root_id, parent1.lft, parent1.rgt]
128 assert_equal [parent1.id, lft1, lft1 + 5], [parent1.root_id, parent1.lft, parent1.rgt]
129 assert_equal [parent2.id, lft2, lft2 + 1], [parent2.root_id, parent2.lft, parent2.rgt]
129 assert_equal [parent2.id, lft2, lft2 + 1], [parent2.root_id, parent2.lft, parent2.rgt]
130 assert_equal [parent1.id, lft1 + 1, lft1 + 4], [child.root_id, child.lft, child.rgt]
130 assert_equal [parent1.id, lft1 + 1, lft1 + 4], [child.root_id, child.lft, child.rgt]
131 assert_equal [parent1.id, lft1 + 2, lft1 + 3], [grandchild.root_id, grandchild.lft, grandchild.rgt]
131 assert_equal [parent1.id, lft1 + 2, lft1 + 3], [grandchild.root_id, grandchild.lft, grandchild.rgt]
132 child.reload.parent_issue_id = parent2.id
132 child.reload.parent_issue_id = parent2.id
133 child.save!
133 child.save!
134 child.reload
134 child.reload
135 grandchild.reload
135 grandchild.reload
136 parent1.reload
136 parent1.reload
137 parent2.reload
137 parent2.reload
138 assert_equal [parent1.id, lft1, lft1 + 1], [parent1.root_id, parent1.lft, parent1.rgt]
138 assert_equal [parent1.id, lft1, lft1 + 1], [parent1.root_id, parent1.lft, parent1.rgt]
139 assert_equal [parent2.id, lft2, lft2 + 5], [parent2.root_id, parent2.lft, parent2.rgt]
139 assert_equal [parent2.id, lft2, lft2 + 5], [parent2.root_id, parent2.lft, parent2.rgt]
140 assert_equal [parent2.id, lft2 + 1, lft2 + 4], [child.root_id, child.lft, child.rgt]
140 assert_equal [parent2.id, lft2 + 1, lft2 + 4], [child.root_id, child.lft, child.rgt]
141 assert_equal [parent2.id, lft2 + 2, lft2 + 3], [grandchild.root_id, grandchild.lft, grandchild.rgt]
141 assert_equal [parent2.id, lft2 + 2, lft2 + 3], [grandchild.root_id, grandchild.lft, grandchild.rgt]
142 end
142 end
143
143
144 def test_move_a_child_with_descendants_to_another_project
144 def test_move_a_child_with_descendants_to_another_project
145 lft1 = new_issue_lft
145 lft1 = new_issue_lft
146 parent1 = Issue.generate!
146 parent1 = Issue.generate!
147 child = parent1.generate_child!
147 child = parent1.generate_child!
148 grandchild = child.generate_child!
148 grandchild = child.generate_child!
149 lft4 = new_issue_lft
149 lft4 = new_issue_lft
150 child.reload
150 child.reload
151 child.project = Project.find(2)
151 child.project = Project.find(2)
152 assert child.save
152 assert child.save
153 child.reload
153 child.reload
154 grandchild.reload
154 grandchild.reload
155 parent1.reload
155 parent1.reload
156 assert_equal [1, parent1.id, lft1, lft1 + 1], [parent1.project_id, parent1.root_id, parent1.lft, parent1.rgt]
156 assert_equal [1, parent1.id, lft1, lft1 + 1], [parent1.project_id, parent1.root_id, parent1.lft, parent1.rgt]
157 assert_equal [2, child.id, lft4, lft4 + 3],
157 assert_equal [2, child.id, lft4, lft4 + 3],
158 [child.project_id, child.root_id, child.lft, child.rgt]
158 [child.project_id, child.root_id, child.lft, child.rgt]
159 assert_equal [2, child.id, lft4 + 1, lft4 + 2],
159 assert_equal [2, child.id, lft4 + 1, lft4 + 2],
160 [grandchild.project_id, grandchild.root_id, grandchild.lft, grandchild.rgt]
160 [grandchild.project_id, grandchild.root_id, grandchild.lft, grandchild.rgt]
161 end
161 end
162
162
163 def test_moving_an_issue_to_a_descendant_should_not_validate
163 def test_moving_an_issue_to_a_descendant_should_not_validate
164 parent1 = Issue.generate!
164 parent1 = Issue.generate!
165 parent2 = Issue.generate!
165 parent2 = Issue.generate!
166 child = parent1.generate_child!
166 child = parent1.generate_child!
167 grandchild = child.generate_child!
167 grandchild = child.generate_child!
168
168
169 child.reload
169 child.reload
170 child.parent_issue_id = grandchild.id
170 child.parent_issue_id = grandchild.id
171 assert !child.save
171 assert !child.save
172 assert_not_equal [], child.errors[:parent_issue_id]
172 assert_not_equal [], child.errors[:parent_issue_id]
173 end
173 end
174
174
175 def test_updating_a_root_issue_should_not_trigger_update_nested_set_attributes_on_parent_change
175 def test_updating_a_root_issue_should_not_trigger_update_nested_set_attributes_on_parent_change
176 issue = Issue.find(Issue.generate!.id)
176 issue = Issue.find(Issue.generate!.id)
177 issue.parent_issue_id = ""
177 issue.parent_issue_id = ""
178 issue.expects(:update_nested_set_attributes_on_parent_change).never
178 issue.expects(:update_nested_set_attributes_on_parent_change).never
179 issue.save!
179 issue.save!
180 end
180 end
181
181
182 def test_updating_a_child_issue_should_not_trigger_update_nested_set_attributes_on_parent_change
182 def test_updating_a_child_issue_should_not_trigger_update_nested_set_attributes_on_parent_change
183 issue = Issue.find(Issue.generate!(:parent_issue_id => 1).id)
183 issue = Issue.find(Issue.generate!(:parent_issue_id => 1).id)
184 issue.parent_issue_id = "1"
184 issue.parent_issue_id = "1"
185 issue.expects(:update_nested_set_attributes_on_parent_change).never
185 issue.expects(:update_nested_set_attributes_on_parent_change).never
186 issue.save!
186 issue.save!
187 end
187 end
188
188
189 def test_moving_a_root_issue_should_trigger_update_nested_set_attributes_on_parent_change
189 def test_moving_a_root_issue_should_trigger_update_nested_set_attributes_on_parent_change
190 issue = Issue.find(Issue.generate!.id)
190 issue = Issue.find(Issue.generate!.id)
191 issue.parent_issue_id = "1"
191 issue.parent_issue_id = "1"
192 issue.expects(:update_nested_set_attributes_on_parent_change).once
192 issue.expects(:update_nested_set_attributes_on_parent_change).once
193 issue.save!
193 issue.save!
194 end
194 end
195
195
196 def test_moving_a_child_issue_to_another_parent_should_trigger_update_nested_set_attributes_on_parent_change
196 def test_moving_a_child_issue_to_another_parent_should_trigger_update_nested_set_attributes_on_parent_change
197 issue = Issue.find(Issue.generate!(:parent_issue_id => 1).id)
197 issue = Issue.find(Issue.generate!(:parent_issue_id => 1).id)
198 issue.parent_issue_id = "2"
198 issue.parent_issue_id = "2"
199 issue.expects(:update_nested_set_attributes_on_parent_change).once
199 issue.expects(:update_nested_set_attributes_on_parent_change).once
200 issue.save!
200 issue.save!
201 end
201 end
202
202
203 def test_moving_a_child_issue_to_root_should_trigger_update_nested_set_attributes_on_parent_change
203 def test_moving_a_child_issue_to_root_should_trigger_update_nested_set_attributes_on_parent_change
204 issue = Issue.find(Issue.generate!(:parent_issue_id => 1).id)
204 issue = Issue.find(Issue.generate!(:parent_issue_id => 1).id)
205 issue.parent_issue_id = ""
205 issue.parent_issue_id = ""
206 issue.expects(:update_nested_set_attributes_on_parent_change).once
206 issue.expects(:update_nested_set_attributes_on_parent_change).once
207 issue.save!
207 issue.save!
208 end
208 end
209
209
210 def test_destroy_should_destroy_children
210 def test_destroy_should_destroy_children
211 lft1 = new_issue_lft
211 lft1 = new_issue_lft
212 issue1 = Issue.generate!
212 issue1 = Issue.generate!
213 issue2 = Issue.generate!
213 issue2 = Issue.generate!
214 issue3 = issue2.generate_child!
214 issue3 = issue2.generate_child!
215 issue4 = issue1.generate_child!
215 issue4 = issue1.generate_child!
216 issue3.init_journal(User.find(2))
216 issue3.init_journal(User.find(2))
217 issue3.subject = 'child with journal'
217 issue3.subject = 'child with journal'
218 issue3.save!
218 issue3.save!
219 assert_difference 'Issue.count', -2 do
219 assert_difference 'Issue.count', -2 do
220 assert_difference 'Journal.count', -1 do
220 assert_difference 'Journal.count', -1 do
221 assert_difference 'JournalDetail.count', -1 do
221 assert_difference 'JournalDetail.count', -1 do
222 Issue.find(issue2.id).destroy
222 Issue.find(issue2.id).destroy
223 end
223 end
224 end
224 end
225 end
225 end
226 issue1.reload
226 issue1.reload
227 issue4.reload
227 issue4.reload
228 assert !Issue.exists?(issue2.id)
228 assert !Issue.exists?(issue2.id)
229 assert !Issue.exists?(issue3.id)
229 assert !Issue.exists?(issue3.id)
230 assert_equal [issue1.id, lft1, lft1 + 3], [issue1.root_id, issue1.lft, issue1.rgt]
230 assert_equal [issue1.id, lft1, lft1 + 3], [issue1.root_id, issue1.lft, issue1.rgt]
231 assert_equal [issue1.id, lft1 + 1, lft1 + 2], [issue4.root_id, issue4.lft, issue4.rgt]
231 assert_equal [issue1.id, lft1 + 1, lft1 + 2], [issue4.root_id, issue4.lft, issue4.rgt]
232 end
232 end
233
233
234 def test_destroy_child_should_update_parent
234 def test_destroy_child_should_update_parent
235 lft1 = new_issue_lft
235 lft1 = new_issue_lft
236 issue = Issue.generate!
236 issue = Issue.generate!
237 child1 = issue.generate_child!
237 child1 = issue.generate_child!
238 child2 = issue.generate_child!
238 child2 = issue.generate_child!
239 issue.reload
239 issue.reload
240 assert_equal [issue.id, lft1, lft1 + 5], [issue.root_id, issue.lft, issue.rgt]
240 assert_equal [issue.id, lft1, lft1 + 5], [issue.root_id, issue.lft, issue.rgt]
241 child2.reload.destroy
241 child2.reload.destroy
242 issue.reload
242 issue.reload
243 assert_equal [issue.id, lft1, lft1 + 3], [issue.root_id, issue.lft, issue.rgt]
243 assert_equal [issue.id, lft1, lft1 + 3], [issue.root_id, issue.lft, issue.rgt]
244 end
244 end
245
245
246 def test_destroy_parent_issue_updated_during_children_destroy
246 def test_destroy_parent_issue_updated_during_children_destroy
247 parent = Issue.generate!
247 parent = Issue.generate!
248 parent.generate_child!(:start_date => Date.today)
248 parent.generate_child!(:start_date => Date.today)
249 parent.generate_child!(:start_date => 2.days.from_now)
249 parent.generate_child!(:start_date => 2.days.from_now)
250
250
251 assert_difference 'Issue.count', -3 do
251 assert_difference 'Issue.count', -3 do
252 Issue.find(parent.id).destroy
252 Issue.find(parent.id).destroy
253 end
253 end
254 end
254 end
255
255
256 def test_destroy_child_issue_with_children
256 def test_destroy_child_issue_with_children
257 root = Issue.generate!
257 root = Issue.generate!
258 child = root.generate_child!
258 child = root.generate_child!
259 leaf = child.generate_child!
259 leaf = child.generate_child!
260 leaf.init_journal(User.find(2))
260 leaf.init_journal(User.find(2))
261 leaf.subject = 'leaf with journal'
261 leaf.subject = 'leaf with journal'
262 leaf.save!
262 leaf.save!
263
263
264 assert_difference 'Issue.count', -2 do
264 assert_difference 'Issue.count', -2 do
265 assert_difference 'Journal.count', -1 do
265 assert_difference 'Journal.count', -1 do
266 assert_difference 'JournalDetail.count', -1 do
266 assert_difference 'JournalDetail.count', -1 do
267 Issue.find(child.id).destroy
267 Issue.find(child.id).destroy
268 end
268 end
269 end
269 end
270 end
270 end
271
271
272 root = Issue.find(root.id)
272 root = Issue.find(root.id)
273 assert root.leaf?, "Root issue is not a leaf (lft: #{root.lft}, rgt: #{root.rgt})"
273 assert root.leaf?, "Root issue is not a leaf (lft: #{root.lft}, rgt: #{root.rgt})"
274 end
274 end
275
275
276 def test_destroy_issue_with_grand_child
276 def test_destroy_issue_with_grand_child
277 lft1 = new_issue_lft
277 lft1 = new_issue_lft
278 parent = Issue.generate!
278 parent = Issue.generate!
279 issue = parent.generate_child!
279 issue = parent.generate_child!
280 child = issue.generate_child!
280 child = issue.generate_child!
281 grandchild1 = child.generate_child!
281 grandchild1 = child.generate_child!
282 grandchild2 = child.generate_child!
282 grandchild2 = child.generate_child!
283 assert_difference 'Issue.count', -4 do
283 assert_difference 'Issue.count', -4 do
284 Issue.find(issue.id).destroy
284 Issue.find(issue.id).destroy
285 parent.reload
285 parent.reload
286 assert_equal [lft1, lft1 + 1], [parent.lft, parent.rgt]
286 assert_equal [lft1, lft1 + 1], [parent.lft, parent.rgt]
287 end
287 end
288 end
288 end
289
289
290 def test_project_copy_should_copy_issue_tree
290 def test_project_copy_should_copy_issue_tree
291 p = Project.create!(:name => 'Tree copy', :identifier => 'tree-copy', :tracker_ids => [1, 2])
291 p = Project.create!(:name => 'Tree copy', :identifier => 'tree-copy', :tracker_ids => [1, 2])
292 i1 = Issue.generate!(:project => p, :subject => 'i1')
292 i1 = Issue.generate!(:project => p, :subject => 'i1')
293 i2 = i1.generate_child!(:project => p, :subject => 'i2')
293 i2 = i1.generate_child!(:project => p, :subject => 'i2')
294 i3 = i1.generate_child!(:project => p, :subject => 'i3')
294 i3 = i1.generate_child!(:project => p, :subject => 'i3')
295 i4 = i2.generate_child!(:project => p, :subject => 'i4')
295 i4 = i2.generate_child!(:project => p, :subject => 'i4')
296 i5 = Issue.generate!(:project => p, :subject => 'i5')
296 i5 = Issue.generate!(:project => p, :subject => 'i5')
297 c = Project.new(:name => 'Copy', :identifier => 'copy', :tracker_ids => [1, 2])
297 c = Project.new(:name => 'Copy', :identifier => 'copy', :tracker_ids => [1, 2])
298 c.copy(p, :only => 'issues')
298 c.copy(p, :only => 'issues')
299 c.reload
299 c.reload
300
300
301 assert_equal 5, c.issues.count
301 assert_equal 5, c.issues.count
302 ic1, ic2, ic3, ic4, ic5 = c.issues.order('subject').to_a
302 ic1, ic2, ic3, ic4, ic5 = c.issues.order('subject').to_a
303 assert ic1.root?
303 assert ic1.root?
304 assert_equal ic1, ic2.parent
304 assert_equal ic1, ic2.parent
305 assert_equal ic1, ic3.parent
305 assert_equal ic1, ic3.parent
306 assert_equal ic2, ic4.parent
306 assert_equal ic2, ic4.parent
307 assert ic5.root?
307 assert ic5.root?
308 end
308 end
309
310 def test_rebuild_single_tree
311 i1 = Issue.generate!
312 i2 = i1.generate_child!
313 i3 = i1.generate_child!
314 Issue.update_all(:lft => 7, :rgt => 7)
315
316 Issue.rebuild_single_tree!(i1.id)
317
318 i1.reload
319 assert_equal [1, 6], [i1.lft, i1.rgt]
320 i2.reload
321 assert_equal [2, 3], [i2.lft, i2.rgt]
322 i3.reload
323 assert_equal [4, 5], [i3.lft, i3.rgt]
324
325 other = Issue.find(1)
326 assert_equal [7, 7], [other.lft, other.rgt]
327 end
309 end
328 end
General Comments 0
You need to be logged in to leave comments. Login now