##// END OF EJS Templates
set_table_name deprecated....
Jean-Philippe Lang -
r9368:f2ec23d80cf4
parent child
Show More
@@ -1,841 +1,841
1 require 'spec_helper'
1 require 'spec_helper'
2
2
3 describe "AwesomeNestedSet" do
3 describe "AwesomeNestedSet" do
4 before(:all) do
4 before(:all) do
5 self.class.fixtures :categories, :departments, :notes, :things, :brokens
5 self.class.fixtures :categories, :departments, :notes, :things, :brokens
6 end
6 end
7
7
8 describe "defaults" do
8 describe "defaults" do
9 it "should have left_column_default" do
9 it "should have left_column_default" do
10 Default.acts_as_nested_set_options[:left_column].should == 'lft'
10 Default.acts_as_nested_set_options[:left_column].should == 'lft'
11 end
11 end
12
12
13 it "should have right_column_default" do
13 it "should have right_column_default" do
14 Default.acts_as_nested_set_options[:right_column].should == 'rgt'
14 Default.acts_as_nested_set_options[:right_column].should == 'rgt'
15 end
15 end
16
16
17 it "should have parent_column_default" do
17 it "should have parent_column_default" do
18 Default.acts_as_nested_set_options[:parent_column].should == 'parent_id'
18 Default.acts_as_nested_set_options[:parent_column].should == 'parent_id'
19 end
19 end
20
20
21 it "should have scope_default" do
21 it "should have scope_default" do
22 Default.acts_as_nested_set_options[:scope].should be_nil
22 Default.acts_as_nested_set_options[:scope].should be_nil
23 end
23 end
24
24
25 it "should have left_column_name" do
25 it "should have left_column_name" do
26 Default.left_column_name.should == 'lft'
26 Default.left_column_name.should == 'lft'
27 Default.new.left_column_name.should == 'lft'
27 Default.new.left_column_name.should == 'lft'
28 RenamedColumns.left_column_name.should == 'red'
28 RenamedColumns.left_column_name.should == 'red'
29 RenamedColumns.new.left_column_name.should == 'red'
29 RenamedColumns.new.left_column_name.should == 'red'
30 end
30 end
31
31
32 it "should have right_column_name" do
32 it "should have right_column_name" do
33 Default.right_column_name.should == 'rgt'
33 Default.right_column_name.should == 'rgt'
34 Default.new.right_column_name.should == 'rgt'
34 Default.new.right_column_name.should == 'rgt'
35 RenamedColumns.right_column_name.should == 'black'
35 RenamedColumns.right_column_name.should == 'black'
36 RenamedColumns.new.right_column_name.should == 'black'
36 RenamedColumns.new.right_column_name.should == 'black'
37 end
37 end
38
38
39 it "should have parent_column_name" do
39 it "should have parent_column_name" do
40 Default.parent_column_name.should == 'parent_id'
40 Default.parent_column_name.should == 'parent_id'
41 Default.new.parent_column_name.should == 'parent_id'
41 Default.new.parent_column_name.should == 'parent_id'
42 RenamedColumns.parent_column_name.should == 'mother_id'
42 RenamedColumns.parent_column_name.should == 'mother_id'
43 RenamedColumns.new.parent_column_name.should == 'mother_id'
43 RenamedColumns.new.parent_column_name.should == 'mother_id'
44 end
44 end
45 end
45 end
46
46
47 it "creation_with_altered_column_names" do
47 it "creation_with_altered_column_names" do
48 lambda {
48 lambda {
49 RenamedColumns.create!()
49 RenamedColumns.create!()
50 }.should_not raise_exception
50 }.should_not raise_exception
51 end
51 end
52
52
53 it "creation when existing record has nil left column" do
53 it "creation when existing record has nil left column" do
54 assert_nothing_raised do
54 assert_nothing_raised do
55 Broken.create!
55 Broken.create!
56 end
56 end
57 end
57 end
58
58
59 it "quoted_left_column_name" do
59 it "quoted_left_column_name" do
60 quoted = Default.connection.quote_column_name('lft')
60 quoted = Default.connection.quote_column_name('lft')
61 Default.quoted_left_column_name.should == quoted
61 Default.quoted_left_column_name.should == quoted
62 Default.new.quoted_left_column_name.should == quoted
62 Default.new.quoted_left_column_name.should == quoted
63 end
63 end
64
64
65 it "quoted_right_column_name" do
65 it "quoted_right_column_name" do
66 quoted = Default.connection.quote_column_name('rgt')
66 quoted = Default.connection.quote_column_name('rgt')
67 Default.quoted_right_column_name.should == quoted
67 Default.quoted_right_column_name.should == quoted
68 Default.new.quoted_right_column_name.should == quoted
68 Default.new.quoted_right_column_name.should == quoted
69 end
69 end
70
70
71 it "left_column_protected_from_assignment" do
71 it "left_column_protected_from_assignment" do
72 lambda {
72 lambda {
73 Category.new.lft = 1
73 Category.new.lft = 1
74 }.should raise_exception(ActiveRecord::ActiveRecordError)
74 }.should raise_exception(ActiveRecord::ActiveRecordError)
75 end
75 end
76
76
77 it "right_column_protected_from_assignment" do
77 it "right_column_protected_from_assignment" do
78 lambda {
78 lambda {
79 Category.new.rgt = 1
79 Category.new.rgt = 1
80 }.should raise_exception(ActiveRecord::ActiveRecordError)
80 }.should raise_exception(ActiveRecord::ActiveRecordError)
81 end
81 end
82
82
83 it "scoped_appends_id" do
83 it "scoped_appends_id" do
84 ScopedCategory.acts_as_nested_set_options[:scope].should == :organization_id
84 ScopedCategory.acts_as_nested_set_options[:scope].should == :organization_id
85 end
85 end
86
86
87 it "roots_class_method" do
87 it "roots_class_method" do
88 Category.roots.should == Category.find_all_by_parent_id(nil)
88 Category.roots.should == Category.find_all_by_parent_id(nil)
89 end
89 end
90
90
91 it "root_class_method" do
91 it "root_class_method" do
92 Category.root.should == categories(:top_level)
92 Category.root.should == categories(:top_level)
93 end
93 end
94
94
95 it "root" do
95 it "root" do
96 categories(:child_3).root.should == categories(:top_level)
96 categories(:child_3).root.should == categories(:top_level)
97 end
97 end
98
98
99 it "root?" do
99 it "root?" do
100 categories(:top_level).root?.should be_true
100 categories(:top_level).root?.should be_true
101 categories(:top_level_2).root?.should be_true
101 categories(:top_level_2).root?.should be_true
102 end
102 end
103
103
104 it "leaves_class_method" do
104 it "leaves_class_method" do
105 Category.find(:all, :conditions => "#{Category.right_column_name} - #{Category.left_column_name} = 1").should == Category.leaves
105 Category.find(:all, :conditions => "#{Category.right_column_name} - #{Category.left_column_name} = 1").should == Category.leaves
106 Category.leaves.count.should == 4
106 Category.leaves.count.should == 4
107 Category.leaves.should include(categories(:child_1))
107 Category.leaves.should include(categories(:child_1))
108 Category.leaves.should include(categories(:child_2_1))
108 Category.leaves.should include(categories(:child_2_1))
109 Category.leaves.should include(categories(:child_3))
109 Category.leaves.should include(categories(:child_3))
110 Category.leaves.should include(categories(:top_level_2))
110 Category.leaves.should include(categories(:top_level_2))
111 end
111 end
112
112
113 it "leaf" do
113 it "leaf" do
114 categories(:child_1).leaf?.should be_true
114 categories(:child_1).leaf?.should be_true
115 categories(:child_2_1).leaf?.should be_true
115 categories(:child_2_1).leaf?.should be_true
116 categories(:child_3).leaf?.should be_true
116 categories(:child_3).leaf?.should be_true
117 categories(:top_level_2).leaf?.should be_true
117 categories(:top_level_2).leaf?.should be_true
118
118
119 categories(:top_level).leaf?.should be_false
119 categories(:top_level).leaf?.should be_false
120 categories(:child_2).leaf?.should be_false
120 categories(:child_2).leaf?.should be_false
121 Category.new.leaf?.should be_false
121 Category.new.leaf?.should be_false
122 end
122 end
123
123
124
124
125 it "parent" do
125 it "parent" do
126 categories(:child_2_1).parent.should == categories(:child_2)
126 categories(:child_2_1).parent.should == categories(:child_2)
127 end
127 end
128
128
129 it "self_and_ancestors" do
129 it "self_and_ancestors" do
130 child = categories(:child_2_1)
130 child = categories(:child_2_1)
131 self_and_ancestors = [categories(:top_level), categories(:child_2), child]
131 self_and_ancestors = [categories(:top_level), categories(:child_2), child]
132 self_and_ancestors.should == child.self_and_ancestors
132 self_and_ancestors.should == child.self_and_ancestors
133 end
133 end
134
134
135 it "ancestors" do
135 it "ancestors" do
136 child = categories(:child_2_1)
136 child = categories(:child_2_1)
137 ancestors = [categories(:top_level), categories(:child_2)]
137 ancestors = [categories(:top_level), categories(:child_2)]
138 ancestors.should == child.ancestors
138 ancestors.should == child.ancestors
139 end
139 end
140
140
141 it "self_and_siblings" do
141 it "self_and_siblings" do
142 child = categories(:child_2)
142 child = categories(:child_2)
143 self_and_siblings = [categories(:child_1), child, categories(:child_3)]
143 self_and_siblings = [categories(:child_1), child, categories(:child_3)]
144 self_and_siblings.should == child.self_and_siblings
144 self_and_siblings.should == child.self_and_siblings
145 lambda do
145 lambda do
146 tops = [categories(:top_level), categories(:top_level_2)]
146 tops = [categories(:top_level), categories(:top_level_2)]
147 assert_equal tops, categories(:top_level).self_and_siblings
147 assert_equal tops, categories(:top_level).self_and_siblings
148 end.should_not raise_exception
148 end.should_not raise_exception
149 end
149 end
150
150
151 it "siblings" do
151 it "siblings" do
152 child = categories(:child_2)
152 child = categories(:child_2)
153 siblings = [categories(:child_1), categories(:child_3)]
153 siblings = [categories(:child_1), categories(:child_3)]
154 siblings.should == child.siblings
154 siblings.should == child.siblings
155 end
155 end
156
156
157 it "leaves" do
157 it "leaves" do
158 leaves = [categories(:child_1), categories(:child_2_1), categories(:child_3)]
158 leaves = [categories(:child_1), categories(:child_2_1), categories(:child_3)]
159 categories(:top_level).leaves.should == leaves
159 categories(:top_level).leaves.should == leaves
160 end
160 end
161
161
162 it "level" do
162 it "level" do
163 categories(:top_level).level.should == 0
163 categories(:top_level).level.should == 0
164 categories(:child_1).level.should == 1
164 categories(:child_1).level.should == 1
165 categories(:child_2_1).level.should == 2
165 categories(:child_2_1).level.should == 2
166 end
166 end
167
167
168 it "has_children?" do
168 it "has_children?" do
169 categories(:child_2_1).children.empty?.should be_true
169 categories(:child_2_1).children.empty?.should be_true
170 categories(:child_2).children.empty?.should be_false
170 categories(:child_2).children.empty?.should be_false
171 categories(:top_level).children.empty?.should be_false
171 categories(:top_level).children.empty?.should be_false
172 end
172 end
173
173
174 it "self_and_descendents" do
174 it "self_and_descendents" do
175 parent = categories(:top_level)
175 parent = categories(:top_level)
176 self_and_descendants = [parent, categories(:child_1), categories(:child_2),
176 self_and_descendants = [parent, categories(:child_1), categories(:child_2),
177 categories(:child_2_1), categories(:child_3)]
177 categories(:child_2_1), categories(:child_3)]
178 self_and_descendants.should == parent.self_and_descendants
178 self_and_descendants.should == parent.self_and_descendants
179 self_and_descendants.count.should == parent.self_and_descendants.count
179 self_and_descendants.count.should == parent.self_and_descendants.count
180 end
180 end
181
181
182 it "descendents" do
182 it "descendents" do
183 lawyers = Category.create!(:name => "lawyers")
183 lawyers = Category.create!(:name => "lawyers")
184 us = Category.create!(:name => "United States")
184 us = Category.create!(:name => "United States")
185 us.move_to_child_of(lawyers)
185 us.move_to_child_of(lawyers)
186 patent = Category.create!(:name => "Patent Law")
186 patent = Category.create!(:name => "Patent Law")
187 patent.move_to_child_of(us)
187 patent.move_to_child_of(us)
188 lawyers.reload
188 lawyers.reload
189
189
190 lawyers.children.size.should == 1
190 lawyers.children.size.should == 1
191 us.children.size.should == 1
191 us.children.size.should == 1
192 lawyers.descendants.size.should == 2
192 lawyers.descendants.size.should == 2
193 end
193 end
194
194
195 it "self_and_descendents" do
195 it "self_and_descendents" do
196 parent = categories(:top_level)
196 parent = categories(:top_level)
197 descendants = [categories(:child_1), categories(:child_2),
197 descendants = [categories(:child_1), categories(:child_2),
198 categories(:child_2_1), categories(:child_3)]
198 categories(:child_2_1), categories(:child_3)]
199 descendants.should == parent.descendants
199 descendants.should == parent.descendants
200 end
200 end
201
201
202 it "children" do
202 it "children" do
203 category = categories(:top_level)
203 category = categories(:top_level)
204 category.children.each {|c| category.id.should == c.parent_id }
204 category.children.each {|c| category.id.should == c.parent_id }
205 end
205 end
206
206
207 it "order_of_children" do
207 it "order_of_children" do
208 categories(:child_2).move_left
208 categories(:child_2).move_left
209 categories(:child_2).should == categories(:top_level).children[0]
209 categories(:child_2).should == categories(:top_level).children[0]
210 categories(:child_1).should == categories(:top_level).children[1]
210 categories(:child_1).should == categories(:top_level).children[1]
211 categories(:child_3).should == categories(:top_level).children[2]
211 categories(:child_3).should == categories(:top_level).children[2]
212 end
212 end
213
213
214 it "is_or_is_ancestor_of?" do
214 it "is_or_is_ancestor_of?" do
215 categories(:top_level).is_or_is_ancestor_of?(categories(:child_1)).should be_true
215 categories(:top_level).is_or_is_ancestor_of?(categories(:child_1)).should be_true
216 categories(:top_level).is_or_is_ancestor_of?(categories(:child_2_1)).should be_true
216 categories(:top_level).is_or_is_ancestor_of?(categories(:child_2_1)).should be_true
217 categories(:child_2).is_or_is_ancestor_of?(categories(:child_2_1)).should be_true
217 categories(:child_2).is_or_is_ancestor_of?(categories(:child_2_1)).should be_true
218 categories(:child_2_1).is_or_is_ancestor_of?(categories(:child_2)).should be_false
218 categories(:child_2_1).is_or_is_ancestor_of?(categories(:child_2)).should be_false
219 categories(:child_1).is_or_is_ancestor_of?(categories(:child_2)).should be_false
219 categories(:child_1).is_or_is_ancestor_of?(categories(:child_2)).should be_false
220 categories(:child_1).is_or_is_ancestor_of?(categories(:child_1)).should be_true
220 categories(:child_1).is_or_is_ancestor_of?(categories(:child_1)).should be_true
221 end
221 end
222
222
223 it "is_ancestor_of?" do
223 it "is_ancestor_of?" do
224 categories(:top_level).is_ancestor_of?(categories(:child_1)).should be_true
224 categories(:top_level).is_ancestor_of?(categories(:child_1)).should be_true
225 categories(:top_level).is_ancestor_of?(categories(:child_2_1)).should be_true
225 categories(:top_level).is_ancestor_of?(categories(:child_2_1)).should be_true
226 categories(:child_2).is_ancestor_of?(categories(:child_2_1)).should be_true
226 categories(:child_2).is_ancestor_of?(categories(:child_2_1)).should be_true
227 categories(:child_2_1).is_ancestor_of?(categories(:child_2)).should be_false
227 categories(:child_2_1).is_ancestor_of?(categories(:child_2)).should be_false
228 categories(:child_1).is_ancestor_of?(categories(:child_2)).should be_false
228 categories(:child_1).is_ancestor_of?(categories(:child_2)).should be_false
229 categories(:child_1).is_ancestor_of?(categories(:child_1)).should be_false
229 categories(:child_1).is_ancestor_of?(categories(:child_1)).should be_false
230 end
230 end
231
231
232 it "is_or_is_ancestor_of_with_scope" do
232 it "is_or_is_ancestor_of_with_scope" do
233 root = ScopedCategory.root
233 root = ScopedCategory.root
234 child = root.children.first
234 child = root.children.first
235 root.is_or_is_ancestor_of?(child).should be_true
235 root.is_or_is_ancestor_of?(child).should be_true
236 child.update_attribute :organization_id, 'different'
236 child.update_attribute :organization_id, 'different'
237 root.is_or_is_ancestor_of?(child).should be_false
237 root.is_or_is_ancestor_of?(child).should be_false
238 end
238 end
239
239
240 it "is_or_is_descendant_of?" do
240 it "is_or_is_descendant_of?" do
241 categories(:child_1).is_or_is_descendant_of?(categories(:top_level)).should be_true
241 categories(:child_1).is_or_is_descendant_of?(categories(:top_level)).should be_true
242 categories(:child_2_1).is_or_is_descendant_of?(categories(:top_level)).should be_true
242 categories(:child_2_1).is_or_is_descendant_of?(categories(:top_level)).should be_true
243 categories(:child_2_1).is_or_is_descendant_of?(categories(:child_2)).should be_true
243 categories(:child_2_1).is_or_is_descendant_of?(categories(:child_2)).should be_true
244 categories(:child_2).is_or_is_descendant_of?(categories(:child_2_1)).should be_false
244 categories(:child_2).is_or_is_descendant_of?(categories(:child_2_1)).should be_false
245 categories(:child_2).is_or_is_descendant_of?(categories(:child_1)).should be_false
245 categories(:child_2).is_or_is_descendant_of?(categories(:child_1)).should be_false
246 categories(:child_1).is_or_is_descendant_of?(categories(:child_1)).should be_true
246 categories(:child_1).is_or_is_descendant_of?(categories(:child_1)).should be_true
247 end
247 end
248
248
249 it "is_descendant_of?" do
249 it "is_descendant_of?" do
250 categories(:child_1).is_descendant_of?(categories(:top_level)).should be_true
250 categories(:child_1).is_descendant_of?(categories(:top_level)).should be_true
251 categories(:child_2_1).is_descendant_of?(categories(:top_level)).should be_true
251 categories(:child_2_1).is_descendant_of?(categories(:top_level)).should be_true
252 categories(:child_2_1).is_descendant_of?(categories(:child_2)).should be_true
252 categories(:child_2_1).is_descendant_of?(categories(:child_2)).should be_true
253 categories(:child_2).is_descendant_of?(categories(:child_2_1)).should be_false
253 categories(:child_2).is_descendant_of?(categories(:child_2_1)).should be_false
254 categories(:child_2).is_descendant_of?(categories(:child_1)).should be_false
254 categories(:child_2).is_descendant_of?(categories(:child_1)).should be_false
255 categories(:child_1).is_descendant_of?(categories(:child_1)).should be_false
255 categories(:child_1).is_descendant_of?(categories(:child_1)).should be_false
256 end
256 end
257
257
258 it "is_or_is_descendant_of_with_scope" do
258 it "is_or_is_descendant_of_with_scope" do
259 root = ScopedCategory.root
259 root = ScopedCategory.root
260 child = root.children.first
260 child = root.children.first
261 child.is_or_is_descendant_of?(root).should be_true
261 child.is_or_is_descendant_of?(root).should be_true
262 child.update_attribute :organization_id, 'different'
262 child.update_attribute :organization_id, 'different'
263 child.is_or_is_descendant_of?(root).should be_false
263 child.is_or_is_descendant_of?(root).should be_false
264 end
264 end
265
265
266 it "same_scope?" do
266 it "same_scope?" do
267 root = ScopedCategory.root
267 root = ScopedCategory.root
268 child = root.children.first
268 child = root.children.first
269 child.same_scope?(root).should be_true
269 child.same_scope?(root).should be_true
270 child.update_attribute :organization_id, 'different'
270 child.update_attribute :organization_id, 'different'
271 child.same_scope?(root).should be_false
271 child.same_scope?(root).should be_false
272 end
272 end
273
273
274 it "left_sibling" do
274 it "left_sibling" do
275 categories(:child_1).should == categories(:child_2).left_sibling
275 categories(:child_1).should == categories(:child_2).left_sibling
276 categories(:child_2).should == categories(:child_3).left_sibling
276 categories(:child_2).should == categories(:child_3).left_sibling
277 end
277 end
278
278
279 it "left_sibling_of_root" do
279 it "left_sibling_of_root" do
280 categories(:top_level).left_sibling.should be_nil
280 categories(:top_level).left_sibling.should be_nil
281 end
281 end
282
282
283 it "left_sibling_without_siblings" do
283 it "left_sibling_without_siblings" do
284 categories(:child_2_1).left_sibling.should be_nil
284 categories(:child_2_1).left_sibling.should be_nil
285 end
285 end
286
286
287 it "left_sibling_of_leftmost_node" do
287 it "left_sibling_of_leftmost_node" do
288 categories(:child_1).left_sibling.should be_nil
288 categories(:child_1).left_sibling.should be_nil
289 end
289 end
290
290
291 it "right_sibling" do
291 it "right_sibling" do
292 categories(:child_3).should == categories(:child_2).right_sibling
292 categories(:child_3).should == categories(:child_2).right_sibling
293 categories(:child_2).should == categories(:child_1).right_sibling
293 categories(:child_2).should == categories(:child_1).right_sibling
294 end
294 end
295
295
296 it "right_sibling_of_root" do
296 it "right_sibling_of_root" do
297 categories(:top_level_2).should == categories(:top_level).right_sibling
297 categories(:top_level_2).should == categories(:top_level).right_sibling
298 categories(:top_level_2).right_sibling.should be_nil
298 categories(:top_level_2).right_sibling.should be_nil
299 end
299 end
300
300
301 it "right_sibling_without_siblings" do
301 it "right_sibling_without_siblings" do
302 categories(:child_2_1).right_sibling.should be_nil
302 categories(:child_2_1).right_sibling.should be_nil
303 end
303 end
304
304
305 it "right_sibling_of_rightmost_node" do
305 it "right_sibling_of_rightmost_node" do
306 categories(:child_3).right_sibling.should be_nil
306 categories(:child_3).right_sibling.should be_nil
307 end
307 end
308
308
309 it "move_left" do
309 it "move_left" do
310 categories(:child_2).move_left
310 categories(:child_2).move_left
311 categories(:child_2).left_sibling.should be_nil
311 categories(:child_2).left_sibling.should be_nil
312 categories(:child_1).should == categories(:child_2).right_sibling
312 categories(:child_1).should == categories(:child_2).right_sibling
313 Category.valid?.should be_true
313 Category.valid?.should be_true
314 end
314 end
315
315
316 it "move_right" do
316 it "move_right" do
317 categories(:child_2).move_right
317 categories(:child_2).move_right
318 categories(:child_2).right_sibling.should be_nil
318 categories(:child_2).right_sibling.should be_nil
319 categories(:child_3).should == categories(:child_2).left_sibling
319 categories(:child_3).should == categories(:child_2).left_sibling
320 Category.valid?.should be_true
320 Category.valid?.should be_true
321 end
321 end
322
322
323 it "move_to_left_of" do
323 it "move_to_left_of" do
324 categories(:child_3).move_to_left_of(categories(:child_1))
324 categories(:child_3).move_to_left_of(categories(:child_1))
325 categories(:child_3).left_sibling.should be_nil
325 categories(:child_3).left_sibling.should be_nil
326 categories(:child_1).should == categories(:child_3).right_sibling
326 categories(:child_1).should == categories(:child_3).right_sibling
327 Category.valid?.should be_true
327 Category.valid?.should be_true
328 end
328 end
329
329
330 it "move_to_right_of" do
330 it "move_to_right_of" do
331 categories(:child_1).move_to_right_of(categories(:child_3))
331 categories(:child_1).move_to_right_of(categories(:child_3))
332 categories(:child_1).right_sibling.should be_nil
332 categories(:child_1).right_sibling.should be_nil
333 categories(:child_3).should == categories(:child_1).left_sibling
333 categories(:child_3).should == categories(:child_1).left_sibling
334 Category.valid?.should be_true
334 Category.valid?.should be_true
335 end
335 end
336
336
337 it "move_to_root" do
337 it "move_to_root" do
338 categories(:child_2).move_to_root
338 categories(:child_2).move_to_root
339 categories(:child_2).parent.should be_nil
339 categories(:child_2).parent.should be_nil
340 categories(:child_2).level.should == 0
340 categories(:child_2).level.should == 0
341 categories(:child_2_1).level.should == 1
341 categories(:child_2_1).level.should == 1
342 categories(:child_2).left.should == 1
342 categories(:child_2).left.should == 1
343 categories(:child_2).right.should == 4
343 categories(:child_2).right.should == 4
344 Category.valid?.should be_true
344 Category.valid?.should be_true
345 end
345 end
346
346
347 it "move_to_child_of" do
347 it "move_to_child_of" do
348 categories(:child_1).move_to_child_of(categories(:child_3))
348 categories(:child_1).move_to_child_of(categories(:child_3))
349 categories(:child_3).id.should == categories(:child_1).parent_id
349 categories(:child_3).id.should == categories(:child_1).parent_id
350 Category.valid?.should be_true
350 Category.valid?.should be_true
351 end
351 end
352
352
353 it "move_to_child_of_appends_to_end" do
353 it "move_to_child_of_appends_to_end" do
354 child = Category.create! :name => 'New Child'
354 child = Category.create! :name => 'New Child'
355 child.move_to_child_of categories(:top_level)
355 child.move_to_child_of categories(:top_level)
356 child.should == categories(:top_level).children.last
356 child.should == categories(:top_level).children.last
357 end
357 end
358
358
359 it "subtree_move_to_child_of" do
359 it "subtree_move_to_child_of" do
360 categories(:child_2).left.should == 4
360 categories(:child_2).left.should == 4
361 categories(:child_2).right.should == 7
361 categories(:child_2).right.should == 7
362
362
363 categories(:child_1).left.should == 2
363 categories(:child_1).left.should == 2
364 categories(:child_1).right.should == 3
364 categories(:child_1).right.should == 3
365
365
366 categories(:child_2).move_to_child_of(categories(:child_1))
366 categories(:child_2).move_to_child_of(categories(:child_1))
367 Category.valid?.should be_true
367 Category.valid?.should be_true
368 categories(:child_1).id.should == categories(:child_2).parent_id
368 categories(:child_1).id.should == categories(:child_2).parent_id
369
369
370 categories(:child_2).left.should == 3
370 categories(:child_2).left.should == 3
371 categories(:child_2).right.should == 6
371 categories(:child_2).right.should == 6
372 categories(:child_1).left.should == 2
372 categories(:child_1).left.should == 2
373 categories(:child_1).right.should == 7
373 categories(:child_1).right.should == 7
374 end
374 end
375
375
376 it "slightly_difficult_move_to_child_of" do
376 it "slightly_difficult_move_to_child_of" do
377 categories(:top_level_2).left.should == 11
377 categories(:top_level_2).left.should == 11
378 categories(:top_level_2).right.should == 12
378 categories(:top_level_2).right.should == 12
379
379
380 # create a new top-level node and move single-node top-level tree inside it.
380 # create a new top-level node and move single-node top-level tree inside it.
381 new_top = Category.create(:name => 'New Top')
381 new_top = Category.create(:name => 'New Top')
382 new_top.left.should == 13
382 new_top.left.should == 13
383 new_top.right.should == 14
383 new_top.right.should == 14
384
384
385 categories(:top_level_2).move_to_child_of(new_top)
385 categories(:top_level_2).move_to_child_of(new_top)
386
386
387 Category.valid?.should be_true
387 Category.valid?.should be_true
388 new_top.id.should == categories(:top_level_2).parent_id
388 new_top.id.should == categories(:top_level_2).parent_id
389
389
390 categories(:top_level_2).left.should == 12
390 categories(:top_level_2).left.should == 12
391 categories(:top_level_2).right.should == 13
391 categories(:top_level_2).right.should == 13
392 new_top.left.should == 11
392 new_top.left.should == 11
393 new_top.right.should == 14
393 new_top.right.should == 14
394 end
394 end
395
395
396 it "difficult_move_to_child_of" do
396 it "difficult_move_to_child_of" do
397 categories(:top_level).left.should == 1
397 categories(:top_level).left.should == 1
398 categories(:top_level).right.should == 10
398 categories(:top_level).right.should == 10
399 categories(:child_2_1).left.should == 5
399 categories(:child_2_1).left.should == 5
400 categories(:child_2_1).right.should == 6
400 categories(:child_2_1).right.should == 6
401
401
402 # create a new top-level node and move an entire top-level tree inside it.
402 # create a new top-level node and move an entire top-level tree inside it.
403 new_top = Category.create(:name => 'New Top')
403 new_top = Category.create(:name => 'New Top')
404 categories(:top_level).move_to_child_of(new_top)
404 categories(:top_level).move_to_child_of(new_top)
405 categories(:child_2_1).reload
405 categories(:child_2_1).reload
406 Category.valid?.should be_true
406 Category.valid?.should be_true
407 new_top.id.should == categories(:top_level).parent_id
407 new_top.id.should == categories(:top_level).parent_id
408
408
409 categories(:top_level).left.should == 4
409 categories(:top_level).left.should == 4
410 categories(:top_level).right.should == 13
410 categories(:top_level).right.should == 13
411 categories(:child_2_1).left.should == 8
411 categories(:child_2_1).left.should == 8
412 categories(:child_2_1).right.should == 9
412 categories(:child_2_1).right.should == 9
413 end
413 end
414
414
415 #rebuild swaps the position of the 2 children when added using move_to_child twice onto same parent
415 #rebuild swaps the position of the 2 children when added using move_to_child twice onto same parent
416 it "move_to_child_more_than_once_per_parent_rebuild" do
416 it "move_to_child_more_than_once_per_parent_rebuild" do
417 root1 = Category.create(:name => 'Root1')
417 root1 = Category.create(:name => 'Root1')
418 root2 = Category.create(:name => 'Root2')
418 root2 = Category.create(:name => 'Root2')
419 root3 = Category.create(:name => 'Root3')
419 root3 = Category.create(:name => 'Root3')
420
420
421 root2.move_to_child_of root1
421 root2.move_to_child_of root1
422 root3.move_to_child_of root1
422 root3.move_to_child_of root1
423
423
424 output = Category.roots.last.to_text
424 output = Category.roots.last.to_text
425 Category.update_all('lft = null, rgt = null')
425 Category.update_all('lft = null, rgt = null')
426 Category.rebuild!
426 Category.rebuild!
427
427
428 Category.roots.last.to_text.should == output
428 Category.roots.last.to_text.should == output
429 end
429 end
430
430
431 # doing move_to_child twice onto same parent from the furthest right first
431 # doing move_to_child twice onto same parent from the furthest right first
432 it "move_to_child_more_than_once_per_parent_outside_in" do
432 it "move_to_child_more_than_once_per_parent_outside_in" do
433 node1 = Category.create(:name => 'Node-1')
433 node1 = Category.create(:name => 'Node-1')
434 node2 = Category.create(:name => 'Node-2')
434 node2 = Category.create(:name => 'Node-2')
435 node3 = Category.create(:name => 'Node-3')
435 node3 = Category.create(:name => 'Node-3')
436
436
437 node2.move_to_child_of node1
437 node2.move_to_child_of node1
438 node3.move_to_child_of node1
438 node3.move_to_child_of node1
439
439
440 output = Category.roots.last.to_text
440 output = Category.roots.last.to_text
441 Category.update_all('lft = null, rgt = null')
441 Category.update_all('lft = null, rgt = null')
442 Category.rebuild!
442 Category.rebuild!
443
443
444 Category.roots.last.to_text.should == output
444 Category.roots.last.to_text.should == output
445 end
445 end
446
446
447 it "should be able to rebuild without validating each record" do
447 it "should be able to rebuild without validating each record" do
448 root1 = Category.create(:name => 'Root1')
448 root1 = Category.create(:name => 'Root1')
449 root2 = Category.create(:name => 'Root2')
449 root2 = Category.create(:name => 'Root2')
450 root3 = Category.create(:name => 'Root3')
450 root3 = Category.create(:name => 'Root3')
451
451
452 root2.move_to_child_of root1
452 root2.move_to_child_of root1
453 root3.move_to_child_of root1
453 root3.move_to_child_of root1
454
454
455 root2.name = nil
455 root2.name = nil
456 root2.save!(:validate => false)
456 root2.save!(:validate => false)
457
457
458 output = Category.roots.last.to_text
458 output = Category.roots.last.to_text
459 Category.update_all('lft = null, rgt = null')
459 Category.update_all('lft = null, rgt = null')
460 Category.rebuild!(false)
460 Category.rebuild!(false)
461
461
462 Category.roots.last.to_text.should == output
462 Category.roots.last.to_text.should == output
463 end
463 end
464
464
465 it "valid_with_null_lefts" do
465 it "valid_with_null_lefts" do
466 Category.valid?.should be_true
466 Category.valid?.should be_true
467 Category.update_all('lft = null')
467 Category.update_all('lft = null')
468 Category.valid?.should be_false
468 Category.valid?.should be_false
469 end
469 end
470
470
471 it "valid_with_null_rights" do
471 it "valid_with_null_rights" do
472 Category.valid?.should be_true
472 Category.valid?.should be_true
473 Category.update_all('rgt = null')
473 Category.update_all('rgt = null')
474 Category.valid?.should be_false
474 Category.valid?.should be_false
475 end
475 end
476
476
477 it "valid_with_missing_intermediate_node" do
477 it "valid_with_missing_intermediate_node" do
478 # Even though child_2_1 will still exist, it is a sign of a sloppy delete, not an invalid tree.
478 # Even though child_2_1 will still exist, it is a sign of a sloppy delete, not an invalid tree.
479 Category.valid?.should be_true
479 Category.valid?.should be_true
480 Category.delete(categories(:child_2).id)
480 Category.delete(categories(:child_2).id)
481 Category.valid?.should be_true
481 Category.valid?.should be_true
482 end
482 end
483
483
484 it "valid_with_overlapping_and_rights" do
484 it "valid_with_overlapping_and_rights" do
485 Category.valid?.should be_true
485 Category.valid?.should be_true
486 categories(:top_level_2)['lft'] = 0
486 categories(:top_level_2)['lft'] = 0
487 categories(:top_level_2).save
487 categories(:top_level_2).save
488 Category.valid?.should be_false
488 Category.valid?.should be_false
489 end
489 end
490
490
491 it "rebuild" do
491 it "rebuild" do
492 Category.valid?.should be_true
492 Category.valid?.should be_true
493 before_text = Category.root.to_text
493 before_text = Category.root.to_text
494 Category.update_all('lft = null, rgt = null')
494 Category.update_all('lft = null, rgt = null')
495 Category.rebuild!
495 Category.rebuild!
496 Category.valid?.should be_true
496 Category.valid?.should be_true
497 before_text.should == Category.root.to_text
497 before_text.should == Category.root.to_text
498 end
498 end
499
499
500 it "move_possible_for_sibling" do
500 it "move_possible_for_sibling" do
501 categories(:child_2).move_possible?(categories(:child_1)).should be_true
501 categories(:child_2).move_possible?(categories(:child_1)).should be_true
502 end
502 end
503
503
504 it "move_not_possible_to_self" do
504 it "move_not_possible_to_self" do
505 categories(:top_level).move_possible?(categories(:top_level)).should be_false
505 categories(:top_level).move_possible?(categories(:top_level)).should be_false
506 end
506 end
507
507
508 it "move_not_possible_to_parent" do
508 it "move_not_possible_to_parent" do
509 categories(:top_level).descendants.each do |descendant|
509 categories(:top_level).descendants.each do |descendant|
510 categories(:top_level).move_possible?(descendant).should be_false
510 categories(:top_level).move_possible?(descendant).should be_false
511 descendant.move_possible?(categories(:top_level)).should be_true
511 descendant.move_possible?(categories(:top_level)).should be_true
512 end
512 end
513 end
513 end
514
514
515 it "is_or_is_ancestor_of?" do
515 it "is_or_is_ancestor_of?" do
516 [:child_1, :child_2, :child_2_1, :child_3].each do |c|
516 [:child_1, :child_2, :child_2_1, :child_3].each do |c|
517 categories(:top_level).is_or_is_ancestor_of?(categories(c)).should be_true
517 categories(:top_level).is_or_is_ancestor_of?(categories(c)).should be_true
518 end
518 end
519 categories(:top_level).is_or_is_ancestor_of?(categories(:top_level_2)).should be_false
519 categories(:top_level).is_or_is_ancestor_of?(categories(:top_level_2)).should be_false
520 end
520 end
521
521
522 it "left_and_rights_valid_with_blank_left" do
522 it "left_and_rights_valid_with_blank_left" do
523 Category.left_and_rights_valid?.should be_true
523 Category.left_and_rights_valid?.should be_true
524 categories(:child_2)[:lft] = nil
524 categories(:child_2)[:lft] = nil
525 categories(:child_2).save(:validate => false)
525 categories(:child_2).save(:validate => false)
526 Category.left_and_rights_valid?.should be_false
526 Category.left_and_rights_valid?.should be_false
527 end
527 end
528
528
529 it "left_and_rights_valid_with_blank_right" do
529 it "left_and_rights_valid_with_blank_right" do
530 Category.left_and_rights_valid?.should be_true
530 Category.left_and_rights_valid?.should be_true
531 categories(:child_2)[:rgt] = nil
531 categories(:child_2)[:rgt] = nil
532 categories(:child_2).save(:validate => false)
532 categories(:child_2).save(:validate => false)
533 Category.left_and_rights_valid?.should be_false
533 Category.left_and_rights_valid?.should be_false
534 end
534 end
535
535
536 it "left_and_rights_valid_with_equal" do
536 it "left_and_rights_valid_with_equal" do
537 Category.left_and_rights_valid?.should be_true
537 Category.left_and_rights_valid?.should be_true
538 categories(:top_level_2)[:lft] = categories(:top_level_2)[:rgt]
538 categories(:top_level_2)[:lft] = categories(:top_level_2)[:rgt]
539 categories(:top_level_2).save(:validate => false)
539 categories(:top_level_2).save(:validate => false)
540 Category.left_and_rights_valid?.should be_false
540 Category.left_and_rights_valid?.should be_false
541 end
541 end
542
542
543 it "left_and_rights_valid_with_left_equal_to_parent" do
543 it "left_and_rights_valid_with_left_equal_to_parent" do
544 Category.left_and_rights_valid?.should be_true
544 Category.left_and_rights_valid?.should be_true
545 categories(:child_2)[:lft] = categories(:top_level)[:lft]
545 categories(:child_2)[:lft] = categories(:top_level)[:lft]
546 categories(:child_2).save(:validate => false)
546 categories(:child_2).save(:validate => false)
547 Category.left_and_rights_valid?.should be_false
547 Category.left_and_rights_valid?.should be_false
548 end
548 end
549
549
550 it "left_and_rights_valid_with_right_equal_to_parent" do
550 it "left_and_rights_valid_with_right_equal_to_parent" do
551 Category.left_and_rights_valid?.should be_true
551 Category.left_and_rights_valid?.should be_true
552 categories(:child_2)[:rgt] = categories(:top_level)[:rgt]
552 categories(:child_2)[:rgt] = categories(:top_level)[:rgt]
553 categories(:child_2).save(:validate => false)
553 categories(:child_2).save(:validate => false)
554 Category.left_and_rights_valid?.should be_false
554 Category.left_and_rights_valid?.should be_false
555 end
555 end
556
556
557 it "moving_dirty_objects_doesnt_invalidate_tree" do
557 it "moving_dirty_objects_doesnt_invalidate_tree" do
558 r1 = Category.create :name => "Test 1"
558 r1 = Category.create :name => "Test 1"
559 r2 = Category.create :name => "Test 2"
559 r2 = Category.create :name => "Test 2"
560 r3 = Category.create :name => "Test 3"
560 r3 = Category.create :name => "Test 3"
561 r4 = Category.create :name => "Test 4"
561 r4 = Category.create :name => "Test 4"
562 nodes = [r1, r2, r3, r4]
562 nodes = [r1, r2, r3, r4]
563
563
564 r2.move_to_child_of(r1)
564 r2.move_to_child_of(r1)
565 Category.valid?.should be_true
565 Category.valid?.should be_true
566
566
567 r3.move_to_child_of(r1)
567 r3.move_to_child_of(r1)
568 Category.valid?.should be_true
568 Category.valid?.should be_true
569
569
570 r4.move_to_child_of(r2)
570 r4.move_to_child_of(r2)
571 Category.valid?.should be_true
571 Category.valid?.should be_true
572 end
572 end
573
573
574 it "multi_scoped_no_duplicates_for_columns?" do
574 it "multi_scoped_no_duplicates_for_columns?" do
575 lambda {
575 lambda {
576 Note.no_duplicates_for_columns?
576 Note.no_duplicates_for_columns?
577 }.should_not raise_exception
577 }.should_not raise_exception
578 end
578 end
579
579
580 it "multi_scoped_all_roots_valid?" do
580 it "multi_scoped_all_roots_valid?" do
581 lambda {
581 lambda {
582 Note.all_roots_valid?
582 Note.all_roots_valid?
583 }.should_not raise_exception
583 }.should_not raise_exception
584 end
584 end
585
585
586 it "multi_scoped" do
586 it "multi_scoped" do
587 note1 = Note.create!(:body => "A", :notable_id => 2, :notable_type => 'Category')
587 note1 = Note.create!(:body => "A", :notable_id => 2, :notable_type => 'Category')
588 note2 = Note.create!(:body => "B", :notable_id => 2, :notable_type => 'Category')
588 note2 = Note.create!(:body => "B", :notable_id => 2, :notable_type => 'Category')
589 note3 = Note.create!(:body => "C", :notable_id => 2, :notable_type => 'Default')
589 note3 = Note.create!(:body => "C", :notable_id => 2, :notable_type => 'Default')
590
590
591 [note1, note2].should == note1.self_and_siblings
591 [note1, note2].should == note1.self_and_siblings
592 [note3].should == note3.self_and_siblings
592 [note3].should == note3.self_and_siblings
593 end
593 end
594
594
595 it "multi_scoped_rebuild" do
595 it "multi_scoped_rebuild" do
596 root = Note.create!(:body => "A", :notable_id => 3, :notable_type => 'Category')
596 root = Note.create!(:body => "A", :notable_id => 3, :notable_type => 'Category')
597 child1 = Note.create!(:body => "B", :notable_id => 3, :notable_type => 'Category')
597 child1 = Note.create!(:body => "B", :notable_id => 3, :notable_type => 'Category')
598 child2 = Note.create!(:body => "C", :notable_id => 3, :notable_type => 'Category')
598 child2 = Note.create!(:body => "C", :notable_id => 3, :notable_type => 'Category')
599
599
600 child1.move_to_child_of root
600 child1.move_to_child_of root
601 child2.move_to_child_of root
601 child2.move_to_child_of root
602
602
603 Note.update_all('lft = null, rgt = null')
603 Note.update_all('lft = null, rgt = null')
604 Note.rebuild!
604 Note.rebuild!
605
605
606 Note.roots.find_by_body('A').should == root
606 Note.roots.find_by_body('A').should == root
607 [child1, child2].should == Note.roots.find_by_body('A').children
607 [child1, child2].should == Note.roots.find_by_body('A').children
608 end
608 end
609
609
610 it "same_scope_with_multi_scopes" do
610 it "same_scope_with_multi_scopes" do
611 lambda {
611 lambda {
612 notes(:scope1).same_scope?(notes(:child_1))
612 notes(:scope1).same_scope?(notes(:child_1))
613 }.should_not raise_exception
613 }.should_not raise_exception
614 notes(:scope1).same_scope?(notes(:child_1)).should be_true
614 notes(:scope1).same_scope?(notes(:child_1)).should be_true
615 notes(:child_1).same_scope?(notes(:scope1)).should be_true
615 notes(:child_1).same_scope?(notes(:scope1)).should be_true
616 notes(:scope1).same_scope?(notes(:scope2)).should be_false
616 notes(:scope1).same_scope?(notes(:scope2)).should be_false
617 end
617 end
618
618
619 it "quoting_of_multi_scope_column_names" do
619 it "quoting_of_multi_scope_column_names" do
620 ["\"notable_id\"", "\"notable_type\""].should == Note.quoted_scope_column_names
620 ["\"notable_id\"", "\"notable_type\""].should == Note.quoted_scope_column_names
621 end
621 end
622
622
623 it "equal_in_same_scope" do
623 it "equal_in_same_scope" do
624 notes(:scope1).should == notes(:scope1)
624 notes(:scope1).should == notes(:scope1)
625 notes(:scope1).should_not == notes(:child_1)
625 notes(:scope1).should_not == notes(:child_1)
626 end
626 end
627
627
628 it "equal_in_different_scopes" do
628 it "equal_in_different_scopes" do
629 notes(:scope1).should_not == notes(:scope2)
629 notes(:scope1).should_not == notes(:scope2)
630 end
630 end
631
631
632 it "delete_does_not_invalidate" do
632 it "delete_does_not_invalidate" do
633 Category.acts_as_nested_set_options[:dependent] = :delete
633 Category.acts_as_nested_set_options[:dependent] = :delete
634 categories(:child_2).destroy
634 categories(:child_2).destroy
635 Category.valid?.should be_true
635 Category.valid?.should be_true
636 end
636 end
637
637
638 it "destroy_does_not_invalidate" do
638 it "destroy_does_not_invalidate" do
639 Category.acts_as_nested_set_options[:dependent] = :destroy
639 Category.acts_as_nested_set_options[:dependent] = :destroy
640 categories(:child_2).destroy
640 categories(:child_2).destroy
641 Category.valid?.should be_true
641 Category.valid?.should be_true
642 end
642 end
643
643
644 it "destroy_multiple_times_does_not_invalidate" do
644 it "destroy_multiple_times_does_not_invalidate" do
645 Category.acts_as_nested_set_options[:dependent] = :destroy
645 Category.acts_as_nested_set_options[:dependent] = :destroy
646 categories(:child_2).destroy
646 categories(:child_2).destroy
647 categories(:child_2).destroy
647 categories(:child_2).destroy
648 Category.valid?.should be_true
648 Category.valid?.should be_true
649 end
649 end
650
650
651 it "assigning_parent_id_on_create" do
651 it "assigning_parent_id_on_create" do
652 category = Category.create!(:name => "Child", :parent_id => categories(:child_2).id)
652 category = Category.create!(:name => "Child", :parent_id => categories(:child_2).id)
653 categories(:child_2).should == category.parent
653 categories(:child_2).should == category.parent
654 categories(:child_2).id.should == category.parent_id
654 categories(:child_2).id.should == category.parent_id
655 category.left.should_not be_nil
655 category.left.should_not be_nil
656 category.right.should_not be_nil
656 category.right.should_not be_nil
657 Category.valid?.should be_true
657 Category.valid?.should be_true
658 end
658 end
659
659
660 it "assigning_parent_on_create" do
660 it "assigning_parent_on_create" do
661 category = Category.create!(:name => "Child", :parent => categories(:child_2))
661 category = Category.create!(:name => "Child", :parent => categories(:child_2))
662 categories(:child_2).should == category.parent
662 categories(:child_2).should == category.parent
663 categories(:child_2).id.should == category.parent_id
663 categories(:child_2).id.should == category.parent_id
664 category.left.should_not be_nil
664 category.left.should_not be_nil
665 category.right.should_not be_nil
665 category.right.should_not be_nil
666 Category.valid?.should be_true
666 Category.valid?.should be_true
667 end
667 end
668
668
669 it "assigning_parent_id_to_nil_on_create" do
669 it "assigning_parent_id_to_nil_on_create" do
670 category = Category.create!(:name => "New Root", :parent_id => nil)
670 category = Category.create!(:name => "New Root", :parent_id => nil)
671 category.parent.should be_nil
671 category.parent.should be_nil
672 category.parent_id.should be_nil
672 category.parent_id.should be_nil
673 category.left.should_not be_nil
673 category.left.should_not be_nil
674 category.right.should_not be_nil
674 category.right.should_not be_nil
675 Category.valid?.should be_true
675 Category.valid?.should be_true
676 end
676 end
677
677
678 it "assigning_parent_id_on_update" do
678 it "assigning_parent_id_on_update" do
679 category = categories(:child_2_1)
679 category = categories(:child_2_1)
680 category.parent_id = categories(:child_3).id
680 category.parent_id = categories(:child_3).id
681 category.save
681 category.save
682 category.reload
682 category.reload
683 categories(:child_3).reload
683 categories(:child_3).reload
684 categories(:child_3).should == category.parent
684 categories(:child_3).should == category.parent
685 categories(:child_3).id.should == category.parent_id
685 categories(:child_3).id.should == category.parent_id
686 Category.valid?.should be_true
686 Category.valid?.should be_true
687 end
687 end
688
688
689 it "assigning_parent_on_update" do
689 it "assigning_parent_on_update" do
690 category = categories(:child_2_1)
690 category = categories(:child_2_1)
691 category.parent = categories(:child_3)
691 category.parent = categories(:child_3)
692 category.save
692 category.save
693 category.reload
693 category.reload
694 categories(:child_3).reload
694 categories(:child_3).reload
695 categories(:child_3).should == category.parent
695 categories(:child_3).should == category.parent
696 categories(:child_3).id.should == category.parent_id
696 categories(:child_3).id.should == category.parent_id
697 Category.valid?.should be_true
697 Category.valid?.should be_true
698 end
698 end
699
699
700 it "assigning_parent_id_to_nil_on_update" do
700 it "assigning_parent_id_to_nil_on_update" do
701 category = categories(:child_2_1)
701 category = categories(:child_2_1)
702 category.parent_id = nil
702 category.parent_id = nil
703 category.save
703 category.save
704 category.parent.should be_nil
704 category.parent.should be_nil
705 category.parent_id.should be_nil
705 category.parent_id.should be_nil
706 Category.valid?.should be_true
706 Category.valid?.should be_true
707 end
707 end
708
708
709 it "creating_child_from_parent" do
709 it "creating_child_from_parent" do
710 category = categories(:child_2).children.create!(:name => "Child")
710 category = categories(:child_2).children.create!(:name => "Child")
711 categories(:child_2).should == category.parent
711 categories(:child_2).should == category.parent
712 categories(:child_2).id.should == category.parent_id
712 categories(:child_2).id.should == category.parent_id
713 category.left.should_not be_nil
713 category.left.should_not be_nil
714 category.right.should_not be_nil
714 category.right.should_not be_nil
715 Category.valid?.should be_true
715 Category.valid?.should be_true
716 end
716 end
717
717
718 def check_structure(entries, structure)
718 def check_structure(entries, structure)
719 structure = structure.dup
719 structure = structure.dup
720 Category.each_with_level(entries) do |category, level|
720 Category.each_with_level(entries) do |category, level|
721 expected_level, expected_name = structure.shift
721 expected_level, expected_name = structure.shift
722 expected_name.should == category.name
722 expected_name.should == category.name
723 expected_level.should == level
723 expected_level.should == level
724 end
724 end
725 end
725 end
726
726
727 it "each_with_level" do
727 it "each_with_level" do
728 levels = [
728 levels = [
729 [0, "Top Level"],
729 [0, "Top Level"],
730 [1, "Child 1"],
730 [1, "Child 1"],
731 [1, "Child 2"],
731 [1, "Child 2"],
732 [2, "Child 2.1"],
732 [2, "Child 2.1"],
733 [1, "Child 3" ]]
733 [1, "Child 3" ]]
734
734
735 check_structure(Category.root.self_and_descendants, levels)
735 check_structure(Category.root.self_and_descendants, levels)
736
736
737 # test some deeper structures
737 # test some deeper structures
738 category = Category.find_by_name("Child 1")
738 category = Category.find_by_name("Child 1")
739 c1 = Category.new(:name => "Child 1.1")
739 c1 = Category.new(:name => "Child 1.1")
740 c2 = Category.new(:name => "Child 1.1.1")
740 c2 = Category.new(:name => "Child 1.1.1")
741 c3 = Category.new(:name => "Child 1.1.1.1")
741 c3 = Category.new(:name => "Child 1.1.1.1")
742 c4 = Category.new(:name => "Child 1.2")
742 c4 = Category.new(:name => "Child 1.2")
743 [c1, c2, c3, c4].each(&:save!)
743 [c1, c2, c3, c4].each(&:save!)
744
744
745 c1.move_to_child_of(category)
745 c1.move_to_child_of(category)
746 c2.move_to_child_of(c1)
746 c2.move_to_child_of(c1)
747 c3.move_to_child_of(c2)
747 c3.move_to_child_of(c2)
748 c4.move_to_child_of(category)
748 c4.move_to_child_of(category)
749
749
750 levels = [
750 levels = [
751 [0, "Top Level"],
751 [0, "Top Level"],
752 [1, "Child 1"],
752 [1, "Child 1"],
753 [2, "Child 1.1"],
753 [2, "Child 1.1"],
754 [3, "Child 1.1.1"],
754 [3, "Child 1.1.1"],
755 [4, "Child 1.1.1.1"],
755 [4, "Child 1.1.1.1"],
756 [2, "Child 1.2"],
756 [2, "Child 1.2"],
757 [1, "Child 2"],
757 [1, "Child 2"],
758 [2, "Child 2.1"],
758 [2, "Child 2.1"],
759 [1, "Child 3" ]]
759 [1, "Child 3" ]]
760
760
761 check_structure(Category.root.self_and_descendants, levels)
761 check_structure(Category.root.self_and_descendants, levels)
762 end
762 end
763
763
764 it "should not error on a model with attr_accessible" do
764 it "should not error on a model with attr_accessible" do
765 model = Class.new(ActiveRecord::Base)
765 model = Class.new(ActiveRecord::Base)
766 model.set_table_name 'categories'
766 model.table_name = 'categories'
767 model.attr_accessible :name
767 model.attr_accessible :name
768 lambda {
768 lambda {
769 model.acts_as_nested_set
769 model.acts_as_nested_set
770 model.new(:name => 'foo')
770 model.new(:name => 'foo')
771 }.should_not raise_exception
771 }.should_not raise_exception
772 end
772 end
773
773
774 describe "before_move_callback" do
774 describe "before_move_callback" do
775 it "should fire the callback" do
775 it "should fire the callback" do
776 categories(:child_2).should_receive(:custom_before_move)
776 categories(:child_2).should_receive(:custom_before_move)
777 categories(:child_2).move_to_root
777 categories(:child_2).move_to_root
778 end
778 end
779
779
780 it "should stop move when callback returns false" do
780 it "should stop move when callback returns false" do
781 Category.test_allows_move = false
781 Category.test_allows_move = false
782 categories(:child_3).move_to_root.should be_false
782 categories(:child_3).move_to_root.should be_false
783 categories(:child_3).root?.should be_false
783 categories(:child_3).root?.should be_false
784 end
784 end
785
785
786 it "should not halt save actions" do
786 it "should not halt save actions" do
787 Category.test_allows_move = false
787 Category.test_allows_move = false
788 categories(:child_3).parent_id = nil
788 categories(:child_3).parent_id = nil
789 categories(:child_3).save.should be_true
789 categories(:child_3).save.should be_true
790 end
790 end
791 end
791 end
792
792
793 describe "counter_cache" do
793 describe "counter_cache" do
794
794
795 it "should allow use of a counter cache for children" do
795 it "should allow use of a counter cache for children" do
796 note1 = things(:parent1)
796 note1 = things(:parent1)
797 note1.children.count.should == 2
797 note1.children.count.should == 2
798 end
798 end
799
799
800 it "should increment the counter cache on create" do
800 it "should increment the counter cache on create" do
801 note1 = things(:parent1)
801 note1 = things(:parent1)
802 note1.children.count.should == 2
802 note1.children.count.should == 2
803 note1[:children_count].should == 2
803 note1[:children_count].should == 2
804 note1.children.create :body => 'Child 3'
804 note1.children.create :body => 'Child 3'
805 note1.children.count.should == 3
805 note1.children.count.should == 3
806 note1.reload
806 note1.reload
807 note1[:children_count].should == 3
807 note1[:children_count].should == 3
808 end
808 end
809
809
810 it "should decrement the counter cache on destroy" do
810 it "should decrement the counter cache on destroy" do
811 note1 = things(:parent1)
811 note1 = things(:parent1)
812 note1.children.count.should == 2
812 note1.children.count.should == 2
813 note1[:children_count].should == 2
813 note1[:children_count].should == 2
814 note1.children.last.destroy
814 note1.children.last.destroy
815 note1.children.count.should == 1
815 note1.children.count.should == 1
816 note1.reload
816 note1.reload
817 note1[:children_count].should == 1
817 note1[:children_count].should == 1
818 end
818 end
819 end
819 end
820
820
821 describe "association callbacks on children" do
821 describe "association callbacks on children" do
822 it "should call the appropriate callbacks on the children :has_many association " do
822 it "should call the appropriate callbacks on the children :has_many association " do
823 root = DefaultWithCallbacks.create
823 root = DefaultWithCallbacks.create
824 root.should_not be_new_record
824 root.should_not be_new_record
825
825
826 child = root.children.build
826 child = root.children.build
827
827
828 root.before_add.should == child
828 root.before_add.should == child
829 root.after_add.should == child
829 root.after_add.should == child
830
830
831 root.before_remove.should_not == child
831 root.before_remove.should_not == child
832 root.after_remove.should_not == child
832 root.after_remove.should_not == child
833
833
834 child.save.should be_true
834 child.save.should be_true
835 root.children.delete(child).should be_true
835 root.children.delete(child).should be_true
836
836
837 root.before_remove.should == child
837 root.before_remove.should == child
838 root.after_remove.should == child
838 root.after_remove.should == child
839 end
839 end
840 end
840 end
841 end
841 end
@@ -1,72 +1,72
1 class Note < ActiveRecord::Base
1 class Note < ActiveRecord::Base
2 acts_as_nested_set :scope => [:notable_id, :notable_type]
2 acts_as_nested_set :scope => [:notable_id, :notable_type]
3 end
3 end
4
4
5 class Default < ActiveRecord::Base
5 class Default < ActiveRecord::Base
6 set_table_name 'categories'
6 self.table_name = 'categories'
7 acts_as_nested_set
7 acts_as_nested_set
8 end
8 end
9
9
10 class ScopedCategory < ActiveRecord::Base
10 class ScopedCategory < ActiveRecord::Base
11 set_table_name 'categories'
11 self.table_name = 'categories'
12 acts_as_nested_set :scope => :organization
12 acts_as_nested_set :scope => :organization
13 end
13 end
14
14
15 class RenamedColumns < ActiveRecord::Base
15 class RenamedColumns < ActiveRecord::Base
16 acts_as_nested_set :parent_column => 'mother_id', :left_column => 'red', :right_column => 'black'
16 acts_as_nested_set :parent_column => 'mother_id', :left_column => 'red', :right_column => 'black'
17 end
17 end
18
18
19 class Category < ActiveRecord::Base
19 class Category < ActiveRecord::Base
20 acts_as_nested_set
20 acts_as_nested_set
21
21
22 validates_presence_of :name
22 validates_presence_of :name
23
23
24 # Setup a callback that we can switch to true or false per-test
24 # Setup a callback that we can switch to true or false per-test
25 set_callback :move, :before, :custom_before_move
25 set_callback :move, :before, :custom_before_move
26 cattr_accessor :test_allows_move
26 cattr_accessor :test_allows_move
27 @@test_allows_move = true
27 @@test_allows_move = true
28 def custom_before_move
28 def custom_before_move
29 @@test_allows_move
29 @@test_allows_move
30 end
30 end
31
31
32 def to_s
32 def to_s
33 name
33 name
34 end
34 end
35
35
36 def recurse &block
36 def recurse &block
37 block.call self, lambda{
37 block.call self, lambda{
38 self.children.each do |child|
38 self.children.each do |child|
39 child.recurse &block
39 child.recurse &block
40 end
40 end
41 }
41 }
42 end
42 end
43 end
43 end
44
44
45 class Thing < ActiveRecord::Base
45 class Thing < ActiveRecord::Base
46 acts_as_nested_set :counter_cache => 'children_count'
46 acts_as_nested_set :counter_cache => 'children_count'
47 end
47 end
48
48
49 class DefaultWithCallbacks < ActiveRecord::Base
49 class DefaultWithCallbacks < ActiveRecord::Base
50
50
51 set_table_name 'categories'
51 self.table_name = 'categories'
52
52
53 attr_accessor :before_add, :after_add, :before_remove, :after_remove
53 attr_accessor :before_add, :after_add, :before_remove, :after_remove
54
54
55 acts_as_nested_set :before_add => :do_before_add_stuff,
55 acts_as_nested_set :before_add => :do_before_add_stuff,
56 :after_add => :do_after_add_stuff,
56 :after_add => :do_after_add_stuff,
57 :before_remove => :do_before_remove_stuff,
57 :before_remove => :do_before_remove_stuff,
58 :after_remove => :do_after_remove_stuff
58 :after_remove => :do_after_remove_stuff
59
59
60 private
60 private
61
61
62 [ :before_add, :after_add, :before_remove, :after_remove ].each do |hook_name|
62 [ :before_add, :after_add, :before_remove, :after_remove ].each do |hook_name|
63 define_method "do_#{hook_name}_stuff" do |child_node|
63 define_method "do_#{hook_name}_stuff" do |child_node|
64 self.send("#{hook_name}=", child_node)
64 self.send("#{hook_name}=", child_node)
65 end
65 end
66 end
66 end
67
67
68 end
68 end
69
69
70 class Broken < ActiveRecord::Base
70 class Broken < ActiveRecord::Base
71 acts_as_nested_set
71 acts_as_nested_set
72 end No newline at end of file
72 end
@@ -1,603 +1,603
1 require File.dirname(__FILE__) + '/test_helper'
1 require File.dirname(__FILE__) + '/test_helper'
2
2
3 class Note < ActiveRecord::Base
3 class Note < ActiveRecord::Base
4 acts_as_nested_set :scope => [:notable_id, :notable_type]
4 acts_as_nested_set :scope => [:notable_id, :notable_type]
5 end
5 end
6
6
7 class AwesomeNestedSetTest < Test::Unit::TestCase
7 class AwesomeNestedSetTest < Test::Unit::TestCase
8
8
9 class Default < ActiveRecord::Base
9 class Default < ActiveRecord::Base
10 acts_as_nested_set
10 acts_as_nested_set
11 set_table_name 'categories'
11 self.table_name = 'categories'
12 end
12 end
13 class Scoped < ActiveRecord::Base
13 class Scoped < ActiveRecord::Base
14 acts_as_nested_set :scope => :organization
14 acts_as_nested_set :scope => :organization
15 set_table_name 'categories'
15 self.table_name = 'categories'
16 end
16 end
17
17
18 def test_left_column_default
18 def test_left_column_default
19 assert_equal 'lft', Default.acts_as_nested_set_options[:left_column]
19 assert_equal 'lft', Default.acts_as_nested_set_options[:left_column]
20 end
20 end
21
21
22 def test_right_column_default
22 def test_right_column_default
23 assert_equal 'rgt', Default.acts_as_nested_set_options[:right_column]
23 assert_equal 'rgt', Default.acts_as_nested_set_options[:right_column]
24 end
24 end
25
25
26 def test_parent_column_default
26 def test_parent_column_default
27 assert_equal 'parent_id', Default.acts_as_nested_set_options[:parent_column]
27 assert_equal 'parent_id', Default.acts_as_nested_set_options[:parent_column]
28 end
28 end
29
29
30 def test_scope_default
30 def test_scope_default
31 assert_nil Default.acts_as_nested_set_options[:scope]
31 assert_nil Default.acts_as_nested_set_options[:scope]
32 end
32 end
33
33
34 def test_left_column_name
34 def test_left_column_name
35 assert_equal 'lft', Default.left_column_name
35 assert_equal 'lft', Default.left_column_name
36 assert_equal 'lft', Default.new.left_column_name
36 assert_equal 'lft', Default.new.left_column_name
37 end
37 end
38
38
39 def test_right_column_name
39 def test_right_column_name
40 assert_equal 'rgt', Default.right_column_name
40 assert_equal 'rgt', Default.right_column_name
41 assert_equal 'rgt', Default.new.right_column_name
41 assert_equal 'rgt', Default.new.right_column_name
42 end
42 end
43
43
44 def test_parent_column_name
44 def test_parent_column_name
45 assert_equal 'parent_id', Default.parent_column_name
45 assert_equal 'parent_id', Default.parent_column_name
46 assert_equal 'parent_id', Default.new.parent_column_name
46 assert_equal 'parent_id', Default.new.parent_column_name
47 end
47 end
48
48
49 def test_quoted_left_column_name
49 def test_quoted_left_column_name
50 quoted = Default.connection.quote_column_name('lft')
50 quoted = Default.connection.quote_column_name('lft')
51 assert_equal quoted, Default.quoted_left_column_name
51 assert_equal quoted, Default.quoted_left_column_name
52 assert_equal quoted, Default.new.quoted_left_column_name
52 assert_equal quoted, Default.new.quoted_left_column_name
53 end
53 end
54
54
55 def test_quoted_right_column_name
55 def test_quoted_right_column_name
56 quoted = Default.connection.quote_column_name('rgt')
56 quoted = Default.connection.quote_column_name('rgt')
57 assert_equal quoted, Default.quoted_right_column_name
57 assert_equal quoted, Default.quoted_right_column_name
58 assert_equal quoted, Default.new.quoted_right_column_name
58 assert_equal quoted, Default.new.quoted_right_column_name
59 end
59 end
60
60
61 def test_left_column_protected_from_assignment
61 def test_left_column_protected_from_assignment
62 assert_raises(ActiveRecord::ActiveRecordError) { Category.new.lft = 1 }
62 assert_raises(ActiveRecord::ActiveRecordError) { Category.new.lft = 1 }
63 end
63 end
64
64
65 def test_right_column_protected_from_assignment
65 def test_right_column_protected_from_assignment
66 assert_raises(ActiveRecord::ActiveRecordError) { Category.new.rgt = 1 }
66 assert_raises(ActiveRecord::ActiveRecordError) { Category.new.rgt = 1 }
67 end
67 end
68
68
69 def test_parent_column_protected_from_assignment
69 def test_parent_column_protected_from_assignment
70 assert_raises(ActiveRecord::ActiveRecordError) { Category.new.parent_id = 1 }
70 assert_raises(ActiveRecord::ActiveRecordError) { Category.new.parent_id = 1 }
71 end
71 end
72
72
73 def test_colums_protected_on_initialize
73 def test_colums_protected_on_initialize
74 c = Category.new(:lft => 1, :rgt => 2, :parent_id => 3)
74 c = Category.new(:lft => 1, :rgt => 2, :parent_id => 3)
75 assert_nil c.lft
75 assert_nil c.lft
76 assert_nil c.rgt
76 assert_nil c.rgt
77 assert_nil c.parent_id
77 assert_nil c.parent_id
78 end
78 end
79
79
80 def test_scoped_appends_id
80 def test_scoped_appends_id
81 assert_equal :organization_id, Scoped.acts_as_nested_set_options[:scope]
81 assert_equal :organization_id, Scoped.acts_as_nested_set_options[:scope]
82 end
82 end
83
83
84 def test_roots_class_method
84 def test_roots_class_method
85 assert_equal Category.find_all_by_parent_id(nil), Category.roots
85 assert_equal Category.find_all_by_parent_id(nil), Category.roots
86 end
86 end
87
87
88 def test_root_class_method
88 def test_root_class_method
89 assert_equal categories(:top_level), Category.root
89 assert_equal categories(:top_level), Category.root
90 end
90 end
91
91
92 def test_root
92 def test_root
93 assert_equal categories(:top_level), categories(:child_3).root
93 assert_equal categories(:top_level), categories(:child_3).root
94 end
94 end
95
95
96 def test_root?
96 def test_root?
97 assert categories(:top_level).root?
97 assert categories(:top_level).root?
98 assert categories(:top_level_2).root?
98 assert categories(:top_level_2).root?
99 end
99 end
100
100
101 def test_leaves_class_method
101 def test_leaves_class_method
102 assert_equal Category.find(:all, :conditions => "#{Category.right_column_name} - #{Category.left_column_name} = 1"), Category.leaves
102 assert_equal Category.find(:all, :conditions => "#{Category.right_column_name} - #{Category.left_column_name} = 1"), Category.leaves
103 assert_equal Category.leaves.count, 4
103 assert_equal Category.leaves.count, 4
104 assert (Category.leaves.include? categories(:child_1))
104 assert (Category.leaves.include? categories(:child_1))
105 assert (Category.leaves.include? categories(:child_2_1))
105 assert (Category.leaves.include? categories(:child_2_1))
106 assert (Category.leaves.include? categories(:child_3))
106 assert (Category.leaves.include? categories(:child_3))
107 assert (Category.leaves.include? categories(:top_level_2))
107 assert (Category.leaves.include? categories(:top_level_2))
108 end
108 end
109
109
110 def test_leaf
110 def test_leaf
111 assert categories(:child_1).leaf?
111 assert categories(:child_1).leaf?
112 assert categories(:child_2_1).leaf?
112 assert categories(:child_2_1).leaf?
113 assert categories(:child_3).leaf?
113 assert categories(:child_3).leaf?
114 assert categories(:top_level_2).leaf?
114 assert categories(:top_level_2).leaf?
115
115
116 assert !categories(:top_level).leaf?
116 assert !categories(:top_level).leaf?
117 assert !categories(:child_2).leaf?
117 assert !categories(:child_2).leaf?
118 end
118 end
119
119
120 def test_parent
120 def test_parent
121 assert_equal categories(:child_2), categories(:child_2_1).parent
121 assert_equal categories(:child_2), categories(:child_2_1).parent
122 end
122 end
123
123
124 def test_self_and_ancestors
124 def test_self_and_ancestors
125 child = categories(:child_2_1)
125 child = categories(:child_2_1)
126 self_and_ancestors = [categories(:top_level), categories(:child_2), child]
126 self_and_ancestors = [categories(:top_level), categories(:child_2), child]
127 assert_equal self_and_ancestors, child.self_and_ancestors
127 assert_equal self_and_ancestors, child.self_and_ancestors
128 end
128 end
129
129
130 def test_ancestors
130 def test_ancestors
131 child = categories(:child_2_1)
131 child = categories(:child_2_1)
132 ancestors = [categories(:top_level), categories(:child_2)]
132 ancestors = [categories(:top_level), categories(:child_2)]
133 assert_equal ancestors, child.ancestors
133 assert_equal ancestors, child.ancestors
134 end
134 end
135
135
136 def test_self_and_siblings
136 def test_self_and_siblings
137 child = categories(:child_2)
137 child = categories(:child_2)
138 self_and_siblings = [categories(:child_1), child, categories(:child_3)]
138 self_and_siblings = [categories(:child_1), child, categories(:child_3)]
139 assert_equal self_and_siblings, child.self_and_siblings
139 assert_equal self_and_siblings, child.self_and_siblings
140 assert_nothing_raised do
140 assert_nothing_raised do
141 tops = [categories(:top_level), categories(:top_level_2)]
141 tops = [categories(:top_level), categories(:top_level_2)]
142 assert_equal tops, categories(:top_level).self_and_siblings
142 assert_equal tops, categories(:top_level).self_and_siblings
143 end
143 end
144 end
144 end
145
145
146 def test_siblings
146 def test_siblings
147 child = categories(:child_2)
147 child = categories(:child_2)
148 siblings = [categories(:child_1), categories(:child_3)]
148 siblings = [categories(:child_1), categories(:child_3)]
149 assert_equal siblings, child.siblings
149 assert_equal siblings, child.siblings
150 end
150 end
151
151
152 def test_leaves
152 def test_leaves
153 leaves = [categories(:child_1), categories(:child_2_1), categories(:child_3), categories(:top_level_2)]
153 leaves = [categories(:child_1), categories(:child_2_1), categories(:child_3), categories(:top_level_2)]
154 assert categories(:top_level).leaves, leaves
154 assert categories(:top_level).leaves, leaves
155 end
155 end
156
156
157 def test_level
157 def test_level
158 assert_equal 0, categories(:top_level).level
158 assert_equal 0, categories(:top_level).level
159 assert_equal 1, categories(:child_1).level
159 assert_equal 1, categories(:child_1).level
160 assert_equal 2, categories(:child_2_1).level
160 assert_equal 2, categories(:child_2_1).level
161 end
161 end
162
162
163 def test_has_children?
163 def test_has_children?
164 assert categories(:child_2_1).children.empty?
164 assert categories(:child_2_1).children.empty?
165 assert !categories(:child_2).children.empty?
165 assert !categories(:child_2).children.empty?
166 assert !categories(:top_level).children.empty?
166 assert !categories(:top_level).children.empty?
167 end
167 end
168
168
169 def test_self_and_descendents
169 def test_self_and_descendents
170 parent = categories(:top_level)
170 parent = categories(:top_level)
171 self_and_descendants = [parent, categories(:child_1), categories(:child_2),
171 self_and_descendants = [parent, categories(:child_1), categories(:child_2),
172 categories(:child_2_1), categories(:child_3)]
172 categories(:child_2_1), categories(:child_3)]
173 assert_equal self_and_descendants, parent.self_and_descendants
173 assert_equal self_and_descendants, parent.self_and_descendants
174 assert_equal self_and_descendants, parent.self_and_descendants.count
174 assert_equal self_and_descendants, parent.self_and_descendants.count
175 end
175 end
176
176
177 def test_descendents
177 def test_descendents
178 lawyers = Category.create!(:name => "lawyers")
178 lawyers = Category.create!(:name => "lawyers")
179 us = Category.create!(:name => "United States")
179 us = Category.create!(:name => "United States")
180 us.move_to_child_of(lawyers)
180 us.move_to_child_of(lawyers)
181 patent = Category.create!(:name => "Patent Law")
181 patent = Category.create!(:name => "Patent Law")
182 patent.move_to_child_of(us)
182 patent.move_to_child_of(us)
183 lawyers.reload
183 lawyers.reload
184
184
185 assert_equal 1, lawyers.children.size
185 assert_equal 1, lawyers.children.size
186 assert_equal 1, us.children.size
186 assert_equal 1, us.children.size
187 assert_equal 2, lawyers.descendants.size
187 assert_equal 2, lawyers.descendants.size
188 end
188 end
189
189
190 def test_self_and_descendents
190 def test_self_and_descendents
191 parent = categories(:top_level)
191 parent = categories(:top_level)
192 descendants = [categories(:child_1), categories(:child_2),
192 descendants = [categories(:child_1), categories(:child_2),
193 categories(:child_2_1), categories(:child_3)]
193 categories(:child_2_1), categories(:child_3)]
194 assert_equal descendants, parent.descendants
194 assert_equal descendants, parent.descendants
195 end
195 end
196
196
197 def test_children
197 def test_children
198 category = categories(:top_level)
198 category = categories(:top_level)
199 category.children.each {|c| assert_equal category.id, c.parent_id }
199 category.children.each {|c| assert_equal category.id, c.parent_id }
200 end
200 end
201
201
202 def test_is_or_is_ancestor_of?
202 def test_is_or_is_ancestor_of?
203 assert categories(:top_level).is_or_is_ancestor_of?(categories(:child_1))
203 assert categories(:top_level).is_or_is_ancestor_of?(categories(:child_1))
204 assert categories(:top_level).is_or_is_ancestor_of?(categories(:child_2_1))
204 assert categories(:top_level).is_or_is_ancestor_of?(categories(:child_2_1))
205 assert categories(:child_2).is_or_is_ancestor_of?(categories(:child_2_1))
205 assert categories(:child_2).is_or_is_ancestor_of?(categories(:child_2_1))
206 assert !categories(:child_2_1).is_or_is_ancestor_of?(categories(:child_2))
206 assert !categories(:child_2_1).is_or_is_ancestor_of?(categories(:child_2))
207 assert !categories(:child_1).is_or_is_ancestor_of?(categories(:child_2))
207 assert !categories(:child_1).is_or_is_ancestor_of?(categories(:child_2))
208 assert categories(:child_1).is_or_is_ancestor_of?(categories(:child_1))
208 assert categories(:child_1).is_or_is_ancestor_of?(categories(:child_1))
209 end
209 end
210
210
211 def test_is_ancestor_of?
211 def test_is_ancestor_of?
212 assert categories(:top_level).is_ancestor_of?(categories(:child_1))
212 assert categories(:top_level).is_ancestor_of?(categories(:child_1))
213 assert categories(:top_level).is_ancestor_of?(categories(:child_2_1))
213 assert categories(:top_level).is_ancestor_of?(categories(:child_2_1))
214 assert categories(:child_2).is_ancestor_of?(categories(:child_2_1))
214 assert categories(:child_2).is_ancestor_of?(categories(:child_2_1))
215 assert !categories(:child_2_1).is_ancestor_of?(categories(:child_2))
215 assert !categories(:child_2_1).is_ancestor_of?(categories(:child_2))
216 assert !categories(:child_1).is_ancestor_of?(categories(:child_2))
216 assert !categories(:child_1).is_ancestor_of?(categories(:child_2))
217 assert !categories(:child_1).is_ancestor_of?(categories(:child_1))
217 assert !categories(:child_1).is_ancestor_of?(categories(:child_1))
218 end
218 end
219
219
220 def test_is_or_is_ancestor_of_with_scope
220 def test_is_or_is_ancestor_of_with_scope
221 root = Scoped.root
221 root = Scoped.root
222 child = root.children.first
222 child = root.children.first
223 assert root.is_or_is_ancestor_of?(child)
223 assert root.is_or_is_ancestor_of?(child)
224 child.update_attribute :organization_id, 'different'
224 child.update_attribute :organization_id, 'different'
225 assert !root.is_or_is_ancestor_of?(child)
225 assert !root.is_or_is_ancestor_of?(child)
226 end
226 end
227
227
228 def test_is_or_is_descendant_of?
228 def test_is_or_is_descendant_of?
229 assert categories(:child_1).is_or_is_descendant_of?(categories(:top_level))
229 assert categories(:child_1).is_or_is_descendant_of?(categories(:top_level))
230 assert categories(:child_2_1).is_or_is_descendant_of?(categories(:top_level))
230 assert categories(:child_2_1).is_or_is_descendant_of?(categories(:top_level))
231 assert categories(:child_2_1).is_or_is_descendant_of?(categories(:child_2))
231 assert categories(:child_2_1).is_or_is_descendant_of?(categories(:child_2))
232 assert !categories(:child_2).is_or_is_descendant_of?(categories(:child_2_1))
232 assert !categories(:child_2).is_or_is_descendant_of?(categories(:child_2_1))
233 assert !categories(:child_2).is_or_is_descendant_of?(categories(:child_1))
233 assert !categories(:child_2).is_or_is_descendant_of?(categories(:child_1))
234 assert categories(:child_1).is_or_is_descendant_of?(categories(:child_1))
234 assert categories(:child_1).is_or_is_descendant_of?(categories(:child_1))
235 end
235 end
236
236
237 def test_is_descendant_of?
237 def test_is_descendant_of?
238 assert categories(:child_1).is_descendant_of?(categories(:top_level))
238 assert categories(:child_1).is_descendant_of?(categories(:top_level))
239 assert categories(:child_2_1).is_descendant_of?(categories(:top_level))
239 assert categories(:child_2_1).is_descendant_of?(categories(:top_level))
240 assert categories(:child_2_1).is_descendant_of?(categories(:child_2))
240 assert categories(:child_2_1).is_descendant_of?(categories(:child_2))
241 assert !categories(:child_2).is_descendant_of?(categories(:child_2_1))
241 assert !categories(:child_2).is_descendant_of?(categories(:child_2_1))
242 assert !categories(:child_2).is_descendant_of?(categories(:child_1))
242 assert !categories(:child_2).is_descendant_of?(categories(:child_1))
243 assert !categories(:child_1).is_descendant_of?(categories(:child_1))
243 assert !categories(:child_1).is_descendant_of?(categories(:child_1))
244 end
244 end
245
245
246 def test_is_or_is_descendant_of_with_scope
246 def test_is_or_is_descendant_of_with_scope
247 root = Scoped.root
247 root = Scoped.root
248 child = root.children.first
248 child = root.children.first
249 assert child.is_or_is_descendant_of?(root)
249 assert child.is_or_is_descendant_of?(root)
250 child.update_attribute :organization_id, 'different'
250 child.update_attribute :organization_id, 'different'
251 assert !child.is_or_is_descendant_of?(root)
251 assert !child.is_or_is_descendant_of?(root)
252 end
252 end
253
253
254 def test_same_scope?
254 def test_same_scope?
255 root = Scoped.root
255 root = Scoped.root
256 child = root.children.first
256 child = root.children.first
257 assert child.same_scope?(root)
257 assert child.same_scope?(root)
258 child.update_attribute :organization_id, 'different'
258 child.update_attribute :organization_id, 'different'
259 assert !child.same_scope?(root)
259 assert !child.same_scope?(root)
260 end
260 end
261
261
262 def test_left_sibling
262 def test_left_sibling
263 assert_equal categories(:child_1), categories(:child_2).left_sibling
263 assert_equal categories(:child_1), categories(:child_2).left_sibling
264 assert_equal categories(:child_2), categories(:child_3).left_sibling
264 assert_equal categories(:child_2), categories(:child_3).left_sibling
265 end
265 end
266
266
267 def test_left_sibling_of_root
267 def test_left_sibling_of_root
268 assert_nil categories(:top_level).left_sibling
268 assert_nil categories(:top_level).left_sibling
269 end
269 end
270
270
271 def test_left_sibling_without_siblings
271 def test_left_sibling_without_siblings
272 assert_nil categories(:child_2_1).left_sibling
272 assert_nil categories(:child_2_1).left_sibling
273 end
273 end
274
274
275 def test_left_sibling_of_leftmost_node
275 def test_left_sibling_of_leftmost_node
276 assert_nil categories(:child_1).left_sibling
276 assert_nil categories(:child_1).left_sibling
277 end
277 end
278
278
279 def test_right_sibling
279 def test_right_sibling
280 assert_equal categories(:child_3), categories(:child_2).right_sibling
280 assert_equal categories(:child_3), categories(:child_2).right_sibling
281 assert_equal categories(:child_2), categories(:child_1).right_sibling
281 assert_equal categories(:child_2), categories(:child_1).right_sibling
282 end
282 end
283
283
284 def test_right_sibling_of_root
284 def test_right_sibling_of_root
285 assert_equal categories(:top_level_2), categories(:top_level).right_sibling
285 assert_equal categories(:top_level_2), categories(:top_level).right_sibling
286 assert_nil categories(:top_level_2).right_sibling
286 assert_nil categories(:top_level_2).right_sibling
287 end
287 end
288
288
289 def test_right_sibling_without_siblings
289 def test_right_sibling_without_siblings
290 assert_nil categories(:child_2_1).right_sibling
290 assert_nil categories(:child_2_1).right_sibling
291 end
291 end
292
292
293 def test_right_sibling_of_rightmost_node
293 def test_right_sibling_of_rightmost_node
294 assert_nil categories(:child_3).right_sibling
294 assert_nil categories(:child_3).right_sibling
295 end
295 end
296
296
297 def test_move_left
297 def test_move_left
298 categories(:child_2).move_left
298 categories(:child_2).move_left
299 assert_nil categories(:child_2).left_sibling
299 assert_nil categories(:child_2).left_sibling
300 assert_equal categories(:child_1), categories(:child_2).right_sibling
300 assert_equal categories(:child_1), categories(:child_2).right_sibling
301 assert Category.valid?
301 assert Category.valid?
302 end
302 end
303
303
304 def test_move_right
304 def test_move_right
305 categories(:child_2).move_right
305 categories(:child_2).move_right
306 assert_nil categories(:child_2).right_sibling
306 assert_nil categories(:child_2).right_sibling
307 assert_equal categories(:child_3), categories(:child_2).left_sibling
307 assert_equal categories(:child_3), categories(:child_2).left_sibling
308 assert Category.valid?
308 assert Category.valid?
309 end
309 end
310
310
311 def test_move_to_left_of
311 def test_move_to_left_of
312 categories(:child_3).move_to_left_of(categories(:child_1))
312 categories(:child_3).move_to_left_of(categories(:child_1))
313 assert_nil categories(:child_3).left_sibling
313 assert_nil categories(:child_3).left_sibling
314 assert_equal categories(:child_1), categories(:child_3).right_sibling
314 assert_equal categories(:child_1), categories(:child_3).right_sibling
315 assert Category.valid?
315 assert Category.valid?
316 end
316 end
317
317
318 def test_move_to_right_of
318 def test_move_to_right_of
319 categories(:child_1).move_to_right_of(categories(:child_3))
319 categories(:child_1).move_to_right_of(categories(:child_3))
320 assert_nil categories(:child_1).right_sibling
320 assert_nil categories(:child_1).right_sibling
321 assert_equal categories(:child_3), categories(:child_1).left_sibling
321 assert_equal categories(:child_3), categories(:child_1).left_sibling
322 assert Category.valid?
322 assert Category.valid?
323 end
323 end
324
324
325 def test_move_to_root
325 def test_move_to_root
326 categories(:child_2).move_to_root
326 categories(:child_2).move_to_root
327 assert_nil categories(:child_2).parent
327 assert_nil categories(:child_2).parent
328 assert_equal 0, categories(:child_2).level
328 assert_equal 0, categories(:child_2).level
329 assert_equal 1, categories(:child_2_1).level
329 assert_equal 1, categories(:child_2_1).level
330 assert_equal 1, categories(:child_2).left
330 assert_equal 1, categories(:child_2).left
331 assert_equal 4, categories(:child_2).right
331 assert_equal 4, categories(:child_2).right
332 assert Category.valid?
332 assert Category.valid?
333 end
333 end
334
334
335 def test_move_to_child_of
335 def test_move_to_child_of
336 categories(:child_1).move_to_child_of(categories(:child_3))
336 categories(:child_1).move_to_child_of(categories(:child_3))
337 assert_equal categories(:child_3).id, categories(:child_1).parent_id
337 assert_equal categories(:child_3).id, categories(:child_1).parent_id
338 assert Category.valid?
338 assert Category.valid?
339 end
339 end
340
340
341 def test_move_to_child_of_appends_to_end
341 def test_move_to_child_of_appends_to_end
342 child = Category.create! :name => 'New Child'
342 child = Category.create! :name => 'New Child'
343 child.move_to_child_of categories(:top_level)
343 child.move_to_child_of categories(:top_level)
344 assert_equal child, categories(:top_level).children.last
344 assert_equal child, categories(:top_level).children.last
345 end
345 end
346
346
347 def test_subtree_move_to_child_of
347 def test_subtree_move_to_child_of
348 assert_equal 4, categories(:child_2).left
348 assert_equal 4, categories(:child_2).left
349 assert_equal 7, categories(:child_2).right
349 assert_equal 7, categories(:child_2).right
350
350
351 assert_equal 2, categories(:child_1).left
351 assert_equal 2, categories(:child_1).left
352 assert_equal 3, categories(:child_1).right
352 assert_equal 3, categories(:child_1).right
353
353
354 categories(:child_2).move_to_child_of(categories(:child_1))
354 categories(:child_2).move_to_child_of(categories(:child_1))
355 assert Category.valid?
355 assert Category.valid?
356 assert_equal categories(:child_1).id, categories(:child_2).parent_id
356 assert_equal categories(:child_1).id, categories(:child_2).parent_id
357
357
358 assert_equal 3, categories(:child_2).left
358 assert_equal 3, categories(:child_2).left
359 assert_equal 6, categories(:child_2).right
359 assert_equal 6, categories(:child_2).right
360 assert_equal 2, categories(:child_1).left
360 assert_equal 2, categories(:child_1).left
361 assert_equal 7, categories(:child_1).right
361 assert_equal 7, categories(:child_1).right
362 end
362 end
363
363
364 def test_slightly_difficult_move_to_child_of
364 def test_slightly_difficult_move_to_child_of
365 assert_equal 11, categories(:top_level_2).left
365 assert_equal 11, categories(:top_level_2).left
366 assert_equal 12, categories(:top_level_2).right
366 assert_equal 12, categories(:top_level_2).right
367
367
368 # create a new top-level node and move single-node top-level tree inside it.
368 # create a new top-level node and move single-node top-level tree inside it.
369 new_top = Category.create(:name => 'New Top')
369 new_top = Category.create(:name => 'New Top')
370 assert_equal 13, new_top.left
370 assert_equal 13, new_top.left
371 assert_equal 14, new_top.right
371 assert_equal 14, new_top.right
372
372
373 categories(:top_level_2).move_to_child_of(new_top)
373 categories(:top_level_2).move_to_child_of(new_top)
374
374
375 assert Category.valid?
375 assert Category.valid?
376 assert_equal new_top.id, categories(:top_level_2).parent_id
376 assert_equal new_top.id, categories(:top_level_2).parent_id
377
377
378 assert_equal 12, categories(:top_level_2).left
378 assert_equal 12, categories(:top_level_2).left
379 assert_equal 13, categories(:top_level_2).right
379 assert_equal 13, categories(:top_level_2).right
380 assert_equal 11, new_top.left
380 assert_equal 11, new_top.left
381 assert_equal 14, new_top.right
381 assert_equal 14, new_top.right
382 end
382 end
383
383
384 def test_difficult_move_to_child_of
384 def test_difficult_move_to_child_of
385 assert_equal 1, categories(:top_level).left
385 assert_equal 1, categories(:top_level).left
386 assert_equal 10, categories(:top_level).right
386 assert_equal 10, categories(:top_level).right
387 assert_equal 5, categories(:child_2_1).left
387 assert_equal 5, categories(:child_2_1).left
388 assert_equal 6, categories(:child_2_1).right
388 assert_equal 6, categories(:child_2_1).right
389
389
390 # create a new top-level node and move an entire top-level tree inside it.
390 # create a new top-level node and move an entire top-level tree inside it.
391 new_top = Category.create(:name => 'New Top')
391 new_top = Category.create(:name => 'New Top')
392 categories(:top_level).move_to_child_of(new_top)
392 categories(:top_level).move_to_child_of(new_top)
393 categories(:child_2_1).reload
393 categories(:child_2_1).reload
394 assert Category.valid?
394 assert Category.valid?
395 assert_equal new_top.id, categories(:top_level).parent_id
395 assert_equal new_top.id, categories(:top_level).parent_id
396
396
397 assert_equal 4, categories(:top_level).left
397 assert_equal 4, categories(:top_level).left
398 assert_equal 13, categories(:top_level).right
398 assert_equal 13, categories(:top_level).right
399 assert_equal 8, categories(:child_2_1).left
399 assert_equal 8, categories(:child_2_1).left
400 assert_equal 9, categories(:child_2_1).right
400 assert_equal 9, categories(:child_2_1).right
401 end
401 end
402
402
403 #rebuild swaps the position of the 2 children when added using move_to_child twice onto same parent
403 #rebuild swaps the position of the 2 children when added using move_to_child twice onto same parent
404 def test_move_to_child_more_than_once_per_parent_rebuild
404 def test_move_to_child_more_than_once_per_parent_rebuild
405 root1 = Category.create(:name => 'Root1')
405 root1 = Category.create(:name => 'Root1')
406 root2 = Category.create(:name => 'Root2')
406 root2 = Category.create(:name => 'Root2')
407 root3 = Category.create(:name => 'Root3')
407 root3 = Category.create(:name => 'Root3')
408
408
409 root2.move_to_child_of root1
409 root2.move_to_child_of root1
410 root3.move_to_child_of root1
410 root3.move_to_child_of root1
411
411
412 output = Category.roots.last.to_text
412 output = Category.roots.last.to_text
413 Category.update_all('lft = null, rgt = null')
413 Category.update_all('lft = null, rgt = null')
414 Category.rebuild!
414 Category.rebuild!
415
415
416 assert_equal Category.roots.last.to_text, output
416 assert_equal Category.roots.last.to_text, output
417 end
417 end
418
418
419 # doing move_to_child twice onto same parent from the furthest right first
419 # doing move_to_child twice onto same parent from the furthest right first
420 def test_move_to_child_more_than_once_per_parent_outside_in
420 def test_move_to_child_more_than_once_per_parent_outside_in
421 node1 = Category.create(:name => 'Node-1')
421 node1 = Category.create(:name => 'Node-1')
422 node2 = Category.create(:name => 'Node-2')
422 node2 = Category.create(:name => 'Node-2')
423 node3 = Category.create(:name => 'Node-3')
423 node3 = Category.create(:name => 'Node-3')
424
424
425 node2.move_to_child_of node1
425 node2.move_to_child_of node1
426 node3.move_to_child_of node1
426 node3.move_to_child_of node1
427
427
428 output = Category.roots.last.to_text
428 output = Category.roots.last.to_text
429 Category.update_all('lft = null, rgt = null')
429 Category.update_all('lft = null, rgt = null')
430 Category.rebuild!
430 Category.rebuild!
431
431
432 assert_equal Category.roots.last.to_text, output
432 assert_equal Category.roots.last.to_text, output
433 end
433 end
434
434
435
435
436 def test_valid_with_null_lefts
436 def test_valid_with_null_lefts
437 assert Category.valid?
437 assert Category.valid?
438 Category.update_all('lft = null')
438 Category.update_all('lft = null')
439 assert !Category.valid?
439 assert !Category.valid?
440 end
440 end
441
441
442 def test_valid_with_null_rights
442 def test_valid_with_null_rights
443 assert Category.valid?
443 assert Category.valid?
444 Category.update_all('rgt = null')
444 Category.update_all('rgt = null')
445 assert !Category.valid?
445 assert !Category.valid?
446 end
446 end
447
447
448 def test_valid_with_missing_intermediate_node
448 def test_valid_with_missing_intermediate_node
449 # Even though child_2_1 will still exist, it is a sign of a sloppy delete, not an invalid tree.
449 # Even though child_2_1 will still exist, it is a sign of a sloppy delete, not an invalid tree.
450 assert Category.valid?
450 assert Category.valid?
451 Category.delete(categories(:child_2).id)
451 Category.delete(categories(:child_2).id)
452 assert Category.valid?
452 assert Category.valid?
453 end
453 end
454
454
455 def test_valid_with_overlapping_and_rights
455 def test_valid_with_overlapping_and_rights
456 assert Category.valid?
456 assert Category.valid?
457 categories(:top_level_2)['lft'] = 0
457 categories(:top_level_2)['lft'] = 0
458 categories(:top_level_2).save
458 categories(:top_level_2).save
459 assert !Category.valid?
459 assert !Category.valid?
460 end
460 end
461
461
462 def test_rebuild
462 def test_rebuild
463 assert Category.valid?
463 assert Category.valid?
464 before_text = Category.root.to_text
464 before_text = Category.root.to_text
465 Category.update_all('lft = null, rgt = null')
465 Category.update_all('lft = null, rgt = null')
466 Category.rebuild!
466 Category.rebuild!
467 assert Category.valid?
467 assert Category.valid?
468 assert_equal before_text, Category.root.to_text
468 assert_equal before_text, Category.root.to_text
469 end
469 end
470
470
471 def test_move_possible_for_sibling
471 def test_move_possible_for_sibling
472 assert categories(:child_2).move_possible?(categories(:child_1))
472 assert categories(:child_2).move_possible?(categories(:child_1))
473 end
473 end
474
474
475 def test_move_not_possible_to_self
475 def test_move_not_possible_to_self
476 assert !categories(:top_level).move_possible?(categories(:top_level))
476 assert !categories(:top_level).move_possible?(categories(:top_level))
477 end
477 end
478
478
479 def test_move_not_possible_to_parent
479 def test_move_not_possible_to_parent
480 categories(:top_level).descendants.each do |descendant|
480 categories(:top_level).descendants.each do |descendant|
481 assert !categories(:top_level).move_possible?(descendant)
481 assert !categories(:top_level).move_possible?(descendant)
482 assert descendant.move_possible?(categories(:top_level))
482 assert descendant.move_possible?(categories(:top_level))
483 end
483 end
484 end
484 end
485
485
486 def test_is_or_is_ancestor_of?
486 def test_is_or_is_ancestor_of?
487 [:child_1, :child_2, :child_2_1, :child_3].each do |c|
487 [:child_1, :child_2, :child_2_1, :child_3].each do |c|
488 assert categories(:top_level).is_or_is_ancestor_of?(categories(c))
488 assert categories(:top_level).is_or_is_ancestor_of?(categories(c))
489 end
489 end
490 assert !categories(:top_level).is_or_is_ancestor_of?(categories(:top_level_2))
490 assert !categories(:top_level).is_or_is_ancestor_of?(categories(:top_level_2))
491 end
491 end
492
492
493 def test_left_and_rights_valid_with_blank_left
493 def test_left_and_rights_valid_with_blank_left
494 assert Category.left_and_rights_valid?
494 assert Category.left_and_rights_valid?
495 categories(:child_2)[:lft] = nil
495 categories(:child_2)[:lft] = nil
496 categories(:child_2).save(false)
496 categories(:child_2).save(false)
497 assert !Category.left_and_rights_valid?
497 assert !Category.left_and_rights_valid?
498 end
498 end
499
499
500 def test_left_and_rights_valid_with_blank_right
500 def test_left_and_rights_valid_with_blank_right
501 assert Category.left_and_rights_valid?
501 assert Category.left_and_rights_valid?
502 categories(:child_2)[:rgt] = nil
502 categories(:child_2)[:rgt] = nil
503 categories(:child_2).save(false)
503 categories(:child_2).save(false)
504 assert !Category.left_and_rights_valid?
504 assert !Category.left_and_rights_valid?
505 end
505 end
506
506
507 def test_left_and_rights_valid_with_equal
507 def test_left_and_rights_valid_with_equal
508 assert Category.left_and_rights_valid?
508 assert Category.left_and_rights_valid?
509 categories(:top_level_2)[:lft] = categories(:top_level_2)[:rgt]
509 categories(:top_level_2)[:lft] = categories(:top_level_2)[:rgt]
510 categories(:top_level_2).save(false)
510 categories(:top_level_2).save(false)
511 assert !Category.left_and_rights_valid?
511 assert !Category.left_and_rights_valid?
512 end
512 end
513
513
514 def test_left_and_rights_valid_with_left_equal_to_parent
514 def test_left_and_rights_valid_with_left_equal_to_parent
515 assert Category.left_and_rights_valid?
515 assert Category.left_and_rights_valid?
516 categories(:child_2)[:lft] = categories(:top_level)[:lft]
516 categories(:child_2)[:lft] = categories(:top_level)[:lft]
517 categories(:child_2).save(false)
517 categories(:child_2).save(false)
518 assert !Category.left_and_rights_valid?
518 assert !Category.left_and_rights_valid?
519 end
519 end
520
520
521 def test_left_and_rights_valid_with_right_equal_to_parent
521 def test_left_and_rights_valid_with_right_equal_to_parent
522 assert Category.left_and_rights_valid?
522 assert Category.left_and_rights_valid?
523 categories(:child_2)[:rgt] = categories(:top_level)[:rgt]
523 categories(:child_2)[:rgt] = categories(:top_level)[:rgt]
524 categories(:child_2).save(false)
524 categories(:child_2).save(false)
525 assert !Category.left_and_rights_valid?
525 assert !Category.left_and_rights_valid?
526 end
526 end
527
527
528 def test_moving_dirty_objects_doesnt_invalidate_tree
528 def test_moving_dirty_objects_doesnt_invalidate_tree
529 r1 = Category.create
529 r1 = Category.create
530 r2 = Category.create
530 r2 = Category.create
531 r3 = Category.create
531 r3 = Category.create
532 r4 = Category.create
532 r4 = Category.create
533 nodes = [r1, r2, r3, r4]
533 nodes = [r1, r2, r3, r4]
534
534
535 r2.move_to_child_of(r1)
535 r2.move_to_child_of(r1)
536 assert Category.valid?
536 assert Category.valid?
537
537
538 r3.move_to_child_of(r1)
538 r3.move_to_child_of(r1)
539 assert Category.valid?
539 assert Category.valid?
540
540
541 r4.move_to_child_of(r2)
541 r4.move_to_child_of(r2)
542 assert Category.valid?
542 assert Category.valid?
543 end
543 end
544
544
545 def test_multi_scoped_no_duplicates_for_columns?
545 def test_multi_scoped_no_duplicates_for_columns?
546 assert_nothing_raised do
546 assert_nothing_raised do
547 Note.no_duplicates_for_columns?
547 Note.no_duplicates_for_columns?
548 end
548 end
549 end
549 end
550
550
551 def test_multi_scoped_all_roots_valid?
551 def test_multi_scoped_all_roots_valid?
552 assert_nothing_raised do
552 assert_nothing_raised do
553 Note.all_roots_valid?
553 Note.all_roots_valid?
554 end
554 end
555 end
555 end
556
556
557 def test_multi_scoped
557 def test_multi_scoped
558 note1 = Note.create!(:body => "A", :notable_id => 2, :notable_type => 'Category')
558 note1 = Note.create!(:body => "A", :notable_id => 2, :notable_type => 'Category')
559 note2 = Note.create!(:body => "B", :notable_id => 2, :notable_type => 'Category')
559 note2 = Note.create!(:body => "B", :notable_id => 2, :notable_type => 'Category')
560 note3 = Note.create!(:body => "C", :notable_id => 2, :notable_type => 'Default')
560 note3 = Note.create!(:body => "C", :notable_id => 2, :notable_type => 'Default')
561
561
562 assert_equal [note1, note2], note1.self_and_siblings
562 assert_equal [note1, note2], note1.self_and_siblings
563 assert_equal [note3], note3.self_and_siblings
563 assert_equal [note3], note3.self_and_siblings
564 end
564 end
565
565
566 def test_multi_scoped_rebuild
566 def test_multi_scoped_rebuild
567 root = Note.create!(:body => "A", :notable_id => 3, :notable_type => 'Category')
567 root = Note.create!(:body => "A", :notable_id => 3, :notable_type => 'Category')
568 child1 = Note.create!(:body => "B", :notable_id => 3, :notable_type => 'Category')
568 child1 = Note.create!(:body => "B", :notable_id => 3, :notable_type => 'Category')
569 child2 = Note.create!(:body => "C", :notable_id => 3, :notable_type => 'Category')
569 child2 = Note.create!(:body => "C", :notable_id => 3, :notable_type => 'Category')
570
570
571 child1.move_to_child_of root
571 child1.move_to_child_of root
572 child2.move_to_child_of root
572 child2.move_to_child_of root
573
573
574 Note.update_all('lft = null, rgt = null')
574 Note.update_all('lft = null, rgt = null')
575 Note.rebuild!
575 Note.rebuild!
576
576
577 assert_equal Note.roots.find_by_body('A'), root
577 assert_equal Note.roots.find_by_body('A'), root
578 assert_equal [child1, child2], Note.roots.find_by_body('A').children
578 assert_equal [child1, child2], Note.roots.find_by_body('A').children
579 end
579 end
580
580
581 def test_same_scope_with_multi_scopes
581 def test_same_scope_with_multi_scopes
582 assert_nothing_raised do
582 assert_nothing_raised do
583 notes(:scope1).same_scope?(notes(:child_1))
583 notes(:scope1).same_scope?(notes(:child_1))
584 end
584 end
585 assert notes(:scope1).same_scope?(notes(:child_1))
585 assert notes(:scope1).same_scope?(notes(:child_1))
586 assert notes(:child_1).same_scope?(notes(:scope1))
586 assert notes(:child_1).same_scope?(notes(:scope1))
587 assert !notes(:scope1).same_scope?(notes(:scope2))
587 assert !notes(:scope1).same_scope?(notes(:scope2))
588 end
588 end
589
589
590 def test_quoting_of_multi_scope_column_names
590 def test_quoting_of_multi_scope_column_names
591 assert_equal ["\"notable_id\"", "\"notable_type\""], Note.quoted_scope_column_names
591 assert_equal ["\"notable_id\"", "\"notable_type\""], Note.quoted_scope_column_names
592 end
592 end
593
593
594 def test_equal_in_same_scope
594 def test_equal_in_same_scope
595 assert_equal notes(:scope1), notes(:scope1)
595 assert_equal notes(:scope1), notes(:scope1)
596 assert_not_equal notes(:scope1), notes(:child_1)
596 assert_not_equal notes(:scope1), notes(:child_1)
597 end
597 end
598
598
599 def test_equal_in_different_scopes
599 def test_equal_in_different_scopes
600 assert_not_equal notes(:scope1), notes(:scope2)
600 assert_not_equal notes(:scope1), notes(:scope2)
601 end
601 end
602
602
603 end
603 end
@@ -1,7 +1,7
1 class Developer < ActiveRecord::Base
1 class Developer < ActiveRecord::Base
2 has_and_belongs_to_many :projects
2 has_and_belongs_to_many :projects
3 end
3 end
4
4
5 class DeVeLoPeR < ActiveRecord::Base
5 class DeVeLoPeR < ActiveRecord::Base
6 set_table_name "developers"
6 self.table_name = "developers"
7 end
7 end
@@ -1,512 +1,512
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 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 desc 'Mantis migration script'
18 desc 'Mantis migration script'
19
19
20 require 'active_record'
20 require 'active_record'
21 require 'iconv'
21 require 'iconv'
22 require 'pp'
22 require 'pp'
23
23
24 namespace :redmine do
24 namespace :redmine do
25 task :migrate_from_mantis => :environment do
25 task :migrate_from_mantis => :environment do
26
26
27 module MantisMigrate
27 module MantisMigrate
28
28
29 DEFAULT_STATUS = IssueStatus.default
29 DEFAULT_STATUS = IssueStatus.default
30 assigned_status = IssueStatus.find_by_position(2)
30 assigned_status = IssueStatus.find_by_position(2)
31 resolved_status = IssueStatus.find_by_position(3)
31 resolved_status = IssueStatus.find_by_position(3)
32 feedback_status = IssueStatus.find_by_position(4)
32 feedback_status = IssueStatus.find_by_position(4)
33 closed_status = IssueStatus.find :first, :conditions => { :is_closed => true }
33 closed_status = IssueStatus.find :first, :conditions => { :is_closed => true }
34 STATUS_MAPPING = {10 => DEFAULT_STATUS, # new
34 STATUS_MAPPING = {10 => DEFAULT_STATUS, # new
35 20 => feedback_status, # feedback
35 20 => feedback_status, # feedback
36 30 => DEFAULT_STATUS, # acknowledged
36 30 => DEFAULT_STATUS, # acknowledged
37 40 => DEFAULT_STATUS, # confirmed
37 40 => DEFAULT_STATUS, # confirmed
38 50 => assigned_status, # assigned
38 50 => assigned_status, # assigned
39 80 => resolved_status, # resolved
39 80 => resolved_status, # resolved
40 90 => closed_status # closed
40 90 => closed_status # closed
41 }
41 }
42
42
43 priorities = IssuePriority.all
43 priorities = IssuePriority.all
44 DEFAULT_PRIORITY = priorities[2]
44 DEFAULT_PRIORITY = priorities[2]
45 PRIORITY_MAPPING = {10 => priorities[1], # none
45 PRIORITY_MAPPING = {10 => priorities[1], # none
46 20 => priorities[1], # low
46 20 => priorities[1], # low
47 30 => priorities[2], # normal
47 30 => priorities[2], # normal
48 40 => priorities[3], # high
48 40 => priorities[3], # high
49 50 => priorities[4], # urgent
49 50 => priorities[4], # urgent
50 60 => priorities[5] # immediate
50 60 => priorities[5] # immediate
51 }
51 }
52
52
53 TRACKER_BUG = Tracker.find_by_position(1)
53 TRACKER_BUG = Tracker.find_by_position(1)
54 TRACKER_FEATURE = Tracker.find_by_position(2)
54 TRACKER_FEATURE = Tracker.find_by_position(2)
55
55
56 roles = Role.find(:all, :conditions => {:builtin => 0}, :order => 'position ASC')
56 roles = Role.find(:all, :conditions => {:builtin => 0}, :order => 'position ASC')
57 manager_role = roles[0]
57 manager_role = roles[0]
58 developer_role = roles[1]
58 developer_role = roles[1]
59 DEFAULT_ROLE = roles.last
59 DEFAULT_ROLE = roles.last
60 ROLE_MAPPING = {10 => DEFAULT_ROLE, # viewer
60 ROLE_MAPPING = {10 => DEFAULT_ROLE, # viewer
61 25 => DEFAULT_ROLE, # reporter
61 25 => DEFAULT_ROLE, # reporter
62 40 => DEFAULT_ROLE, # updater
62 40 => DEFAULT_ROLE, # updater
63 55 => developer_role, # developer
63 55 => developer_role, # developer
64 70 => manager_role, # manager
64 70 => manager_role, # manager
65 90 => manager_role # administrator
65 90 => manager_role # administrator
66 }
66 }
67
67
68 CUSTOM_FIELD_TYPE_MAPPING = {0 => 'string', # String
68 CUSTOM_FIELD_TYPE_MAPPING = {0 => 'string', # String
69 1 => 'int', # Numeric
69 1 => 'int', # Numeric
70 2 => 'int', # Float
70 2 => 'int', # Float
71 3 => 'list', # Enumeration
71 3 => 'list', # Enumeration
72 4 => 'string', # Email
72 4 => 'string', # Email
73 5 => 'bool', # Checkbox
73 5 => 'bool', # Checkbox
74 6 => 'list', # List
74 6 => 'list', # List
75 7 => 'list', # Multiselection list
75 7 => 'list', # Multiselection list
76 8 => 'date', # Date
76 8 => 'date', # Date
77 }
77 }
78
78
79 RELATION_TYPE_MAPPING = {1 => IssueRelation::TYPE_RELATES, # related to
79 RELATION_TYPE_MAPPING = {1 => IssueRelation::TYPE_RELATES, # related to
80 2 => IssueRelation::TYPE_RELATES, # parent of
80 2 => IssueRelation::TYPE_RELATES, # parent of
81 3 => IssueRelation::TYPE_RELATES, # child of
81 3 => IssueRelation::TYPE_RELATES, # child of
82 0 => IssueRelation::TYPE_DUPLICATES, # duplicate of
82 0 => IssueRelation::TYPE_DUPLICATES, # duplicate of
83 4 => IssueRelation::TYPE_DUPLICATES # has duplicate
83 4 => IssueRelation::TYPE_DUPLICATES # has duplicate
84 }
84 }
85
85
86 class MantisUser < ActiveRecord::Base
86 class MantisUser < ActiveRecord::Base
87 set_table_name :mantis_user_table
87 self.table_name = :mantis_user_table
88
88
89 def firstname
89 def firstname
90 @firstname = realname.blank? ? username : realname.split.first[0..29]
90 @firstname = realname.blank? ? username : realname.split.first[0..29]
91 @firstname
91 @firstname
92 end
92 end
93
93
94 def lastname
94 def lastname
95 @lastname = realname.blank? ? '-' : realname.split[1..-1].join(' ')[0..29]
95 @lastname = realname.blank? ? '-' : realname.split[1..-1].join(' ')[0..29]
96 @lastname = '-' if @lastname.blank?
96 @lastname = '-' if @lastname.blank?
97 @lastname
97 @lastname
98 end
98 end
99
99
100 def email
100 def email
101 if read_attribute(:email).match(/^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i) &&
101 if read_attribute(:email).match(/^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i) &&
102 !User.find_by_mail(read_attribute(:email))
102 !User.find_by_mail(read_attribute(:email))
103 @email = read_attribute(:email)
103 @email = read_attribute(:email)
104 else
104 else
105 @email = "#{username}@foo.bar"
105 @email = "#{username}@foo.bar"
106 end
106 end
107 end
107 end
108
108
109 def username
109 def username
110 read_attribute(:username)[0..29].gsub(/[^a-zA-Z0-9_\-@\.]/, '-')
110 read_attribute(:username)[0..29].gsub(/[^a-zA-Z0-9_\-@\.]/, '-')
111 end
111 end
112 end
112 end
113
113
114 class MantisProject < ActiveRecord::Base
114 class MantisProject < ActiveRecord::Base
115 set_table_name :mantis_project_table
115 self.table_name = :mantis_project_table
116 has_many :versions, :class_name => "MantisVersion", :foreign_key => :project_id
116 has_many :versions, :class_name => "MantisVersion", :foreign_key => :project_id
117 has_many :categories, :class_name => "MantisCategory", :foreign_key => :project_id
117 has_many :categories, :class_name => "MantisCategory", :foreign_key => :project_id
118 has_many :news, :class_name => "MantisNews", :foreign_key => :project_id
118 has_many :news, :class_name => "MantisNews", :foreign_key => :project_id
119 has_many :members, :class_name => "MantisProjectUser", :foreign_key => :project_id
119 has_many :members, :class_name => "MantisProjectUser", :foreign_key => :project_id
120
120
121 def identifier
121 def identifier
122 read_attribute(:name).gsub(/[^a-z0-9\-]+/, '-').slice(0, Project::IDENTIFIER_MAX_LENGTH)
122 read_attribute(:name).gsub(/[^a-z0-9\-]+/, '-').slice(0, Project::IDENTIFIER_MAX_LENGTH)
123 end
123 end
124 end
124 end
125
125
126 class MantisVersion < ActiveRecord::Base
126 class MantisVersion < ActiveRecord::Base
127 set_table_name :mantis_project_version_table
127 self.table_name = :mantis_project_version_table
128
128
129 def version
129 def version
130 read_attribute(:version)[0..29]
130 read_attribute(:version)[0..29]
131 end
131 end
132
132
133 def description
133 def description
134 read_attribute(:description)[0..254]
134 read_attribute(:description)[0..254]
135 end
135 end
136 end
136 end
137
137
138 class MantisCategory < ActiveRecord::Base
138 class MantisCategory < ActiveRecord::Base
139 set_table_name :mantis_project_category_table
139 self.table_name = :mantis_project_category_table
140 end
140 end
141
141
142 class MantisProjectUser < ActiveRecord::Base
142 class MantisProjectUser < ActiveRecord::Base
143 set_table_name :mantis_project_user_list_table
143 self.table_name = :mantis_project_user_list_table
144 end
144 end
145
145
146 class MantisBug < ActiveRecord::Base
146 class MantisBug < ActiveRecord::Base
147 set_table_name :mantis_bug_table
147 self.table_name = :mantis_bug_table
148 belongs_to :bug_text, :class_name => "MantisBugText", :foreign_key => :bug_text_id
148 belongs_to :bug_text, :class_name => "MantisBugText", :foreign_key => :bug_text_id
149 has_many :bug_notes, :class_name => "MantisBugNote", :foreign_key => :bug_id
149 has_many :bug_notes, :class_name => "MantisBugNote", :foreign_key => :bug_id
150 has_many :bug_files, :class_name => "MantisBugFile", :foreign_key => :bug_id
150 has_many :bug_files, :class_name => "MantisBugFile", :foreign_key => :bug_id
151 has_many :bug_monitors, :class_name => "MantisBugMonitor", :foreign_key => :bug_id
151 has_many :bug_monitors, :class_name => "MantisBugMonitor", :foreign_key => :bug_id
152 end
152 end
153
153
154 class MantisBugText < ActiveRecord::Base
154 class MantisBugText < ActiveRecord::Base
155 set_table_name :mantis_bug_text_table
155 self.table_name = :mantis_bug_text_table
156
156
157 # Adds Mantis steps_to_reproduce and additional_information fields
157 # Adds Mantis steps_to_reproduce and additional_information fields
158 # to description if any
158 # to description if any
159 def full_description
159 def full_description
160 full_description = description
160 full_description = description
161 full_description += "\n\n*Steps to reproduce:*\n\n#{steps_to_reproduce}" unless steps_to_reproduce.blank?
161 full_description += "\n\n*Steps to reproduce:*\n\n#{steps_to_reproduce}" unless steps_to_reproduce.blank?
162 full_description += "\n\n*Additional information:*\n\n#{additional_information}" unless additional_information.blank?
162 full_description += "\n\n*Additional information:*\n\n#{additional_information}" unless additional_information.blank?
163 full_description
163 full_description
164 end
164 end
165 end
165 end
166
166
167 class MantisBugNote < ActiveRecord::Base
167 class MantisBugNote < ActiveRecord::Base
168 set_table_name :mantis_bugnote_table
168 self.table_name = :mantis_bugnote_table
169 belongs_to :bug, :class_name => "MantisBug", :foreign_key => :bug_id
169 belongs_to :bug, :class_name => "MantisBug", :foreign_key => :bug_id
170 belongs_to :bug_note_text, :class_name => "MantisBugNoteText", :foreign_key => :bugnote_text_id
170 belongs_to :bug_note_text, :class_name => "MantisBugNoteText", :foreign_key => :bugnote_text_id
171 end
171 end
172
172
173 class MantisBugNoteText < ActiveRecord::Base
173 class MantisBugNoteText < ActiveRecord::Base
174 set_table_name :mantis_bugnote_text_table
174 self.table_name = :mantis_bugnote_text_table
175 end
175 end
176
176
177 class MantisBugFile < ActiveRecord::Base
177 class MantisBugFile < ActiveRecord::Base
178 set_table_name :mantis_bug_file_table
178 self.table_name = :mantis_bug_file_table
179
179
180 def size
180 def size
181 filesize
181 filesize
182 end
182 end
183
183
184 def original_filename
184 def original_filename
185 MantisMigrate.encode(filename)
185 MantisMigrate.encode(filename)
186 end
186 end
187
187
188 def content_type
188 def content_type
189 file_type
189 file_type
190 end
190 end
191
191
192 def read(*args)
192 def read(*args)
193 if @read_finished
193 if @read_finished
194 nil
194 nil
195 else
195 else
196 @read_finished = true
196 @read_finished = true
197 content
197 content
198 end
198 end
199 end
199 end
200 end
200 end
201
201
202 class MantisBugRelationship < ActiveRecord::Base
202 class MantisBugRelationship < ActiveRecord::Base
203 set_table_name :mantis_bug_relationship_table
203 self.table_name = :mantis_bug_relationship_table
204 end
204 end
205
205
206 class MantisBugMonitor < ActiveRecord::Base
206 class MantisBugMonitor < ActiveRecord::Base
207 set_table_name :mantis_bug_monitor_table
207 self.table_name = :mantis_bug_monitor_table
208 end
208 end
209
209
210 class MantisNews < ActiveRecord::Base
210 class MantisNews < ActiveRecord::Base
211 set_table_name :mantis_news_table
211 self.table_name = :mantis_news_table
212 end
212 end
213
213
214 class MantisCustomField < ActiveRecord::Base
214 class MantisCustomField < ActiveRecord::Base
215 set_table_name :mantis_custom_field_table
215 self.table_name = :mantis_custom_field_table
216 set_inheritance_column :none
216 set_inheritance_column :none
217 has_many :values, :class_name => "MantisCustomFieldString", :foreign_key => :field_id
217 has_many :values, :class_name => "MantisCustomFieldString", :foreign_key => :field_id
218 has_many :projects, :class_name => "MantisCustomFieldProject", :foreign_key => :field_id
218 has_many :projects, :class_name => "MantisCustomFieldProject", :foreign_key => :field_id
219
219
220 def format
220 def format
221 read_attribute :type
221 read_attribute :type
222 end
222 end
223
223
224 def name
224 def name
225 read_attribute(:name)[0..29]
225 read_attribute(:name)[0..29]
226 end
226 end
227 end
227 end
228
228
229 class MantisCustomFieldProject < ActiveRecord::Base
229 class MantisCustomFieldProject < ActiveRecord::Base
230 set_table_name :mantis_custom_field_project_table
230 self.table_name = :mantis_custom_field_project_table
231 end
231 end
232
232
233 class MantisCustomFieldString < ActiveRecord::Base
233 class MantisCustomFieldString < ActiveRecord::Base
234 set_table_name :mantis_custom_field_string_table
234 self.table_name = :mantis_custom_field_string_table
235 end
235 end
236
236
237
237
238 def self.migrate
238 def self.migrate
239
239
240 # Users
240 # Users
241 print "Migrating users"
241 print "Migrating users"
242 User.delete_all "login <> 'admin'"
242 User.delete_all "login <> 'admin'"
243 users_map = {}
243 users_map = {}
244 users_migrated = 0
244 users_migrated = 0
245 MantisUser.find(:all).each do |user|
245 MantisUser.find(:all).each do |user|
246 u = User.new :firstname => encode(user.firstname),
246 u = User.new :firstname => encode(user.firstname),
247 :lastname => encode(user.lastname),
247 :lastname => encode(user.lastname),
248 :mail => user.email,
248 :mail => user.email,
249 :last_login_on => user.last_visit
249 :last_login_on => user.last_visit
250 u.login = user.username
250 u.login = user.username
251 u.password = 'mantis'
251 u.password = 'mantis'
252 u.status = User::STATUS_LOCKED if user.enabled != 1
252 u.status = User::STATUS_LOCKED if user.enabled != 1
253 u.admin = true if user.access_level == 90
253 u.admin = true if user.access_level == 90
254 next unless u.save!
254 next unless u.save!
255 users_migrated += 1
255 users_migrated += 1
256 users_map[user.id] = u.id
256 users_map[user.id] = u.id
257 print '.'
257 print '.'
258 end
258 end
259 puts
259 puts
260
260
261 # Projects
261 # Projects
262 print "Migrating projects"
262 print "Migrating projects"
263 Project.destroy_all
263 Project.destroy_all
264 projects_map = {}
264 projects_map = {}
265 versions_map = {}
265 versions_map = {}
266 categories_map = {}
266 categories_map = {}
267 MantisProject.find(:all).each do |project|
267 MantisProject.find(:all).each do |project|
268 p = Project.new :name => encode(project.name),
268 p = Project.new :name => encode(project.name),
269 :description => encode(project.description)
269 :description => encode(project.description)
270 p.identifier = project.identifier
270 p.identifier = project.identifier
271 next unless p.save
271 next unless p.save
272 projects_map[project.id] = p.id
272 projects_map[project.id] = p.id
273 p.enabled_module_names = ['issue_tracking', 'news', 'wiki']
273 p.enabled_module_names = ['issue_tracking', 'news', 'wiki']
274 p.trackers << TRACKER_BUG
274 p.trackers << TRACKER_BUG
275 p.trackers << TRACKER_FEATURE
275 p.trackers << TRACKER_FEATURE
276 print '.'
276 print '.'
277
277
278 # Project members
278 # Project members
279 project.members.each do |member|
279 project.members.each do |member|
280 m = Member.new :user => User.find_by_id(users_map[member.user_id]),
280 m = Member.new :user => User.find_by_id(users_map[member.user_id]),
281 :roles => [ROLE_MAPPING[member.access_level] || DEFAULT_ROLE]
281 :roles => [ROLE_MAPPING[member.access_level] || DEFAULT_ROLE]
282 m.project = p
282 m.project = p
283 m.save
283 m.save
284 end
284 end
285
285
286 # Project versions
286 # Project versions
287 project.versions.each do |version|
287 project.versions.each do |version|
288 v = Version.new :name => encode(version.version),
288 v = Version.new :name => encode(version.version),
289 :description => encode(version.description),
289 :description => encode(version.description),
290 :effective_date => (version.date_order ? version.date_order.to_date : nil)
290 :effective_date => (version.date_order ? version.date_order.to_date : nil)
291 v.project = p
291 v.project = p
292 v.save
292 v.save
293 versions_map[version.id] = v.id
293 versions_map[version.id] = v.id
294 end
294 end
295
295
296 # Project categories
296 # Project categories
297 project.categories.each do |category|
297 project.categories.each do |category|
298 g = IssueCategory.new :name => category.category[0,30]
298 g = IssueCategory.new :name => category.category[0,30]
299 g.project = p
299 g.project = p
300 g.save
300 g.save
301 categories_map[category.category] = g.id
301 categories_map[category.category] = g.id
302 end
302 end
303 end
303 end
304 puts
304 puts
305
305
306 # Bugs
306 # Bugs
307 print "Migrating bugs"
307 print "Migrating bugs"
308 Issue.destroy_all
308 Issue.destroy_all
309 issues_map = {}
309 issues_map = {}
310 keep_bug_ids = (Issue.count == 0)
310 keep_bug_ids = (Issue.count == 0)
311 MantisBug.find_each(:batch_size => 200) do |bug|
311 MantisBug.find_each(:batch_size => 200) do |bug|
312 next unless projects_map[bug.project_id] && users_map[bug.reporter_id]
312 next unless projects_map[bug.project_id] && users_map[bug.reporter_id]
313 i = Issue.new :project_id => projects_map[bug.project_id],
313 i = Issue.new :project_id => projects_map[bug.project_id],
314 :subject => encode(bug.summary),
314 :subject => encode(bug.summary),
315 :description => encode(bug.bug_text.full_description),
315 :description => encode(bug.bug_text.full_description),
316 :priority => PRIORITY_MAPPING[bug.priority] || DEFAULT_PRIORITY,
316 :priority => PRIORITY_MAPPING[bug.priority] || DEFAULT_PRIORITY,
317 :created_on => bug.date_submitted,
317 :created_on => bug.date_submitted,
318 :updated_on => bug.last_updated
318 :updated_on => bug.last_updated
319 i.author = User.find_by_id(users_map[bug.reporter_id])
319 i.author = User.find_by_id(users_map[bug.reporter_id])
320 i.category = IssueCategory.find_by_project_id_and_name(i.project_id, bug.category[0,30]) unless bug.category.blank?
320 i.category = IssueCategory.find_by_project_id_and_name(i.project_id, bug.category[0,30]) unless bug.category.blank?
321 i.fixed_version = Version.find_by_project_id_and_name(i.project_id, bug.fixed_in_version) unless bug.fixed_in_version.blank?
321 i.fixed_version = Version.find_by_project_id_and_name(i.project_id, bug.fixed_in_version) unless bug.fixed_in_version.blank?
322 i.status = STATUS_MAPPING[bug.status] || DEFAULT_STATUS
322 i.status = STATUS_MAPPING[bug.status] || DEFAULT_STATUS
323 i.tracker = (bug.severity == 10 ? TRACKER_FEATURE : TRACKER_BUG)
323 i.tracker = (bug.severity == 10 ? TRACKER_FEATURE : TRACKER_BUG)
324 i.id = bug.id if keep_bug_ids
324 i.id = bug.id if keep_bug_ids
325 next unless i.save
325 next unless i.save
326 issues_map[bug.id] = i.id
326 issues_map[bug.id] = i.id
327 print '.'
327 print '.'
328 STDOUT.flush
328 STDOUT.flush
329
329
330 # Assignee
330 # Assignee
331 # Redmine checks that the assignee is a project member
331 # Redmine checks that the assignee is a project member
332 if (bug.handler_id && users_map[bug.handler_id])
332 if (bug.handler_id && users_map[bug.handler_id])
333 i.assigned_to = User.find_by_id(users_map[bug.handler_id])
333 i.assigned_to = User.find_by_id(users_map[bug.handler_id])
334 i.save_with_validation(false)
334 i.save_with_validation(false)
335 end
335 end
336
336
337 # Bug notes
337 # Bug notes
338 bug.bug_notes.each do |note|
338 bug.bug_notes.each do |note|
339 next unless users_map[note.reporter_id]
339 next unless users_map[note.reporter_id]
340 n = Journal.new :notes => encode(note.bug_note_text.note),
340 n = Journal.new :notes => encode(note.bug_note_text.note),
341 :created_on => note.date_submitted
341 :created_on => note.date_submitted
342 n.user = User.find_by_id(users_map[note.reporter_id])
342 n.user = User.find_by_id(users_map[note.reporter_id])
343 n.journalized = i
343 n.journalized = i
344 n.save
344 n.save
345 end
345 end
346
346
347 # Bug files
347 # Bug files
348 bug.bug_files.each do |file|
348 bug.bug_files.each do |file|
349 a = Attachment.new :created_on => file.date_added
349 a = Attachment.new :created_on => file.date_added
350 a.file = file
350 a.file = file
351 a.author = User.find :first
351 a.author = User.find :first
352 a.container = i
352 a.container = i
353 a.save
353 a.save
354 end
354 end
355
355
356 # Bug monitors
356 # Bug monitors
357 bug.bug_monitors.each do |monitor|
357 bug.bug_monitors.each do |monitor|
358 next unless users_map[monitor.user_id]
358 next unless users_map[monitor.user_id]
359 i.add_watcher(User.find_by_id(users_map[monitor.user_id]))
359 i.add_watcher(User.find_by_id(users_map[monitor.user_id]))
360 end
360 end
361 end
361 end
362
362
363 # update issue id sequence if needed (postgresql)
363 # update issue id sequence if needed (postgresql)
364 Issue.connection.reset_pk_sequence!(Issue.table_name) if Issue.connection.respond_to?('reset_pk_sequence!')
364 Issue.connection.reset_pk_sequence!(Issue.table_name) if Issue.connection.respond_to?('reset_pk_sequence!')
365 puts
365 puts
366
366
367 # Bug relationships
367 # Bug relationships
368 print "Migrating bug relations"
368 print "Migrating bug relations"
369 MantisBugRelationship.find(:all).each do |relation|
369 MantisBugRelationship.find(:all).each do |relation|
370 next unless issues_map[relation.source_bug_id] && issues_map[relation.destination_bug_id]
370 next unless issues_map[relation.source_bug_id] && issues_map[relation.destination_bug_id]
371 r = IssueRelation.new :relation_type => RELATION_TYPE_MAPPING[relation.relationship_type]
371 r = IssueRelation.new :relation_type => RELATION_TYPE_MAPPING[relation.relationship_type]
372 r.issue_from = Issue.find_by_id(issues_map[relation.source_bug_id])
372 r.issue_from = Issue.find_by_id(issues_map[relation.source_bug_id])
373 r.issue_to = Issue.find_by_id(issues_map[relation.destination_bug_id])
373 r.issue_to = Issue.find_by_id(issues_map[relation.destination_bug_id])
374 pp r unless r.save
374 pp r unless r.save
375 print '.'
375 print '.'
376 STDOUT.flush
376 STDOUT.flush
377 end
377 end
378 puts
378 puts
379
379
380 # News
380 # News
381 print "Migrating news"
381 print "Migrating news"
382 News.destroy_all
382 News.destroy_all
383 MantisNews.find(:all, :conditions => 'project_id > 0').each do |news|
383 MantisNews.find(:all, :conditions => 'project_id > 0').each do |news|
384 next unless projects_map[news.project_id]
384 next unless projects_map[news.project_id]
385 n = News.new :project_id => projects_map[news.project_id],
385 n = News.new :project_id => projects_map[news.project_id],
386 :title => encode(news.headline[0..59]),
386 :title => encode(news.headline[0..59]),
387 :description => encode(news.body),
387 :description => encode(news.body),
388 :created_on => news.date_posted
388 :created_on => news.date_posted
389 n.author = User.find_by_id(users_map[news.poster_id])
389 n.author = User.find_by_id(users_map[news.poster_id])
390 n.save
390 n.save
391 print '.'
391 print '.'
392 STDOUT.flush
392 STDOUT.flush
393 end
393 end
394 puts
394 puts
395
395
396 # Custom fields
396 # Custom fields
397 print "Migrating custom fields"
397 print "Migrating custom fields"
398 IssueCustomField.destroy_all
398 IssueCustomField.destroy_all
399 MantisCustomField.find(:all).each do |field|
399 MantisCustomField.find(:all).each do |field|
400 f = IssueCustomField.new :name => field.name[0..29],
400 f = IssueCustomField.new :name => field.name[0..29],
401 :field_format => CUSTOM_FIELD_TYPE_MAPPING[field.format],
401 :field_format => CUSTOM_FIELD_TYPE_MAPPING[field.format],
402 :min_length => field.length_min,
402 :min_length => field.length_min,
403 :max_length => field.length_max,
403 :max_length => field.length_max,
404 :regexp => field.valid_regexp,
404 :regexp => field.valid_regexp,
405 :possible_values => field.possible_values.split('|'),
405 :possible_values => field.possible_values.split('|'),
406 :is_required => field.require_report?
406 :is_required => field.require_report?
407 next unless f.save
407 next unless f.save
408 print '.'
408 print '.'
409 STDOUT.flush
409 STDOUT.flush
410 # Trackers association
410 # Trackers association
411 f.trackers = Tracker.find :all
411 f.trackers = Tracker.find :all
412
412
413 # Projects association
413 # Projects association
414 field.projects.each do |project|
414 field.projects.each do |project|
415 f.projects << Project.find_by_id(projects_map[project.project_id]) if projects_map[project.project_id]
415 f.projects << Project.find_by_id(projects_map[project.project_id]) if projects_map[project.project_id]
416 end
416 end
417
417
418 # Values
418 # Values
419 field.values.each do |value|
419 field.values.each do |value|
420 v = CustomValue.new :custom_field_id => f.id,
420 v = CustomValue.new :custom_field_id => f.id,
421 :value => value.value
421 :value => value.value
422 v.customized = Issue.find_by_id(issues_map[value.bug_id]) if issues_map[value.bug_id]
422 v.customized = Issue.find_by_id(issues_map[value.bug_id]) if issues_map[value.bug_id]
423 v.save
423 v.save
424 end unless f.new_record?
424 end unless f.new_record?
425 end
425 end
426 puts
426 puts
427
427
428 puts
428 puts
429 puts "Users: #{users_migrated}/#{MantisUser.count}"
429 puts "Users: #{users_migrated}/#{MantisUser.count}"
430 puts "Projects: #{Project.count}/#{MantisProject.count}"
430 puts "Projects: #{Project.count}/#{MantisProject.count}"
431 puts "Memberships: #{Member.count}/#{MantisProjectUser.count}"
431 puts "Memberships: #{Member.count}/#{MantisProjectUser.count}"
432 puts "Versions: #{Version.count}/#{MantisVersion.count}"
432 puts "Versions: #{Version.count}/#{MantisVersion.count}"
433 puts "Categories: #{IssueCategory.count}/#{MantisCategory.count}"
433 puts "Categories: #{IssueCategory.count}/#{MantisCategory.count}"
434 puts "Bugs: #{Issue.count}/#{MantisBug.count}"
434 puts "Bugs: #{Issue.count}/#{MantisBug.count}"
435 puts "Bug notes: #{Journal.count}/#{MantisBugNote.count}"
435 puts "Bug notes: #{Journal.count}/#{MantisBugNote.count}"
436 puts "Bug files: #{Attachment.count}/#{MantisBugFile.count}"
436 puts "Bug files: #{Attachment.count}/#{MantisBugFile.count}"
437 puts "Bug relations: #{IssueRelation.count}/#{MantisBugRelationship.count}"
437 puts "Bug relations: #{IssueRelation.count}/#{MantisBugRelationship.count}"
438 puts "Bug monitors: #{Watcher.count}/#{MantisBugMonitor.count}"
438 puts "Bug monitors: #{Watcher.count}/#{MantisBugMonitor.count}"
439 puts "News: #{News.count}/#{MantisNews.count}"
439 puts "News: #{News.count}/#{MantisNews.count}"
440 puts "Custom fields: #{IssueCustomField.count}/#{MantisCustomField.count}"
440 puts "Custom fields: #{IssueCustomField.count}/#{MantisCustomField.count}"
441 end
441 end
442
442
443 def self.encoding(charset)
443 def self.encoding(charset)
444 @ic = Iconv.new('UTF-8', charset)
444 @ic = Iconv.new('UTF-8', charset)
445 rescue Iconv::InvalidEncoding
445 rescue Iconv::InvalidEncoding
446 return false
446 return false
447 end
447 end
448
448
449 def self.establish_connection(params)
449 def self.establish_connection(params)
450 constants.each do |const|
450 constants.each do |const|
451 klass = const_get(const)
451 klass = const_get(const)
452 next unless klass.respond_to? 'establish_connection'
452 next unless klass.respond_to? 'establish_connection'
453 klass.establish_connection params
453 klass.establish_connection params
454 end
454 end
455 end
455 end
456
456
457 def self.encode(text)
457 def self.encode(text)
458 @ic.iconv text
458 @ic.iconv text
459 rescue
459 rescue
460 text
460 text
461 end
461 end
462 end
462 end
463
463
464 puts
464 puts
465 if Redmine::DefaultData::Loader.no_data?
465 if Redmine::DefaultData::Loader.no_data?
466 puts "Redmine configuration need to be loaded before importing data."
466 puts "Redmine configuration need to be loaded before importing data."
467 puts "Please, run this first:"
467 puts "Please, run this first:"
468 puts
468 puts
469 puts " rake redmine:load_default_data RAILS_ENV=\"#{ENV['RAILS_ENV']}\""
469 puts " rake redmine:load_default_data RAILS_ENV=\"#{ENV['RAILS_ENV']}\""
470 exit
470 exit
471 end
471 end
472
472
473 puts "WARNING: Your Redmine data will be deleted during this process."
473 puts "WARNING: Your Redmine data will be deleted during this process."
474 print "Are you sure you want to continue ? [y/N] "
474 print "Are you sure you want to continue ? [y/N] "
475 STDOUT.flush
475 STDOUT.flush
476 break unless STDIN.gets.match(/^y$/i)
476 break unless STDIN.gets.match(/^y$/i)
477
477
478 # Default Mantis database settings
478 # Default Mantis database settings
479 db_params = {:adapter => 'mysql',
479 db_params = {:adapter => 'mysql',
480 :database => 'bugtracker',
480 :database => 'bugtracker',
481 :host => 'localhost',
481 :host => 'localhost',
482 :username => 'root',
482 :username => 'root',
483 :password => '' }
483 :password => '' }
484
484
485 puts
485 puts
486 puts "Please enter settings for your Mantis database"
486 puts "Please enter settings for your Mantis database"
487 [:adapter, :host, :database, :username, :password].each do |param|
487 [:adapter, :host, :database, :username, :password].each do |param|
488 print "#{param} [#{db_params[param]}]: "
488 print "#{param} [#{db_params[param]}]: "
489 value = STDIN.gets.chomp!
489 value = STDIN.gets.chomp!
490 db_params[param] = value unless value.blank?
490 db_params[param] = value unless value.blank?
491 end
491 end
492
492
493 while true
493 while true
494 print "encoding [UTF-8]: "
494 print "encoding [UTF-8]: "
495 STDOUT.flush
495 STDOUT.flush
496 encoding = STDIN.gets.chomp!
496 encoding = STDIN.gets.chomp!
497 encoding = 'UTF-8' if encoding.blank?
497 encoding = 'UTF-8' if encoding.blank?
498 break if MantisMigrate.encoding encoding
498 break if MantisMigrate.encoding encoding
499 puts "Invalid encoding!"
499 puts "Invalid encoding!"
500 end
500 end
501 puts
501 puts
502
502
503 # Make sure bugs can refer bugs in other projects
503 # Make sure bugs can refer bugs in other projects
504 Setting.cross_project_issue_relations = 1 if Setting.respond_to? 'cross_project_issue_relations'
504 Setting.cross_project_issue_relations = 1 if Setting.respond_to? 'cross_project_issue_relations'
505
505
506 # Turn off email notifications
506 # Turn off email notifications
507 Setting.notified_events = []
507 Setting.notified_events = []
508
508
509 MantisMigrate.establish_connection db_params
509 MantisMigrate.establish_connection db_params
510 MantisMigrate.migrate
510 MantisMigrate.migrate
511 end
511 end
512 end
512 end
@@ -1,768 +1,768
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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 'active_record'
18 require 'active_record'
19 require 'iconv'
19 require 'iconv'
20 require 'pp'
20 require 'pp'
21
21
22 namespace :redmine do
22 namespace :redmine do
23 desc 'Trac migration script'
23 desc 'Trac migration script'
24 task :migrate_from_trac => :environment do
24 task :migrate_from_trac => :environment do
25
25
26 module TracMigrate
26 module TracMigrate
27 TICKET_MAP = []
27 TICKET_MAP = []
28
28
29 DEFAULT_STATUS = IssueStatus.default
29 DEFAULT_STATUS = IssueStatus.default
30 assigned_status = IssueStatus.find_by_position(2)
30 assigned_status = IssueStatus.find_by_position(2)
31 resolved_status = IssueStatus.find_by_position(3)
31 resolved_status = IssueStatus.find_by_position(3)
32 feedback_status = IssueStatus.find_by_position(4)
32 feedback_status = IssueStatus.find_by_position(4)
33 closed_status = IssueStatus.find :first, :conditions => { :is_closed => true }
33 closed_status = IssueStatus.find :first, :conditions => { :is_closed => true }
34 STATUS_MAPPING = {'new' => DEFAULT_STATUS,
34 STATUS_MAPPING = {'new' => DEFAULT_STATUS,
35 'reopened' => feedback_status,
35 'reopened' => feedback_status,
36 'assigned' => assigned_status,
36 'assigned' => assigned_status,
37 'closed' => closed_status
37 'closed' => closed_status
38 }
38 }
39
39
40 priorities = IssuePriority.all
40 priorities = IssuePriority.all
41 DEFAULT_PRIORITY = priorities[0]
41 DEFAULT_PRIORITY = priorities[0]
42 PRIORITY_MAPPING = {'lowest' => priorities[0],
42 PRIORITY_MAPPING = {'lowest' => priorities[0],
43 'low' => priorities[0],
43 'low' => priorities[0],
44 'normal' => priorities[1],
44 'normal' => priorities[1],
45 'high' => priorities[2],
45 'high' => priorities[2],
46 'highest' => priorities[3],
46 'highest' => priorities[3],
47 # ---
47 # ---
48 'trivial' => priorities[0],
48 'trivial' => priorities[0],
49 'minor' => priorities[1],
49 'minor' => priorities[1],
50 'major' => priorities[2],
50 'major' => priorities[2],
51 'critical' => priorities[3],
51 'critical' => priorities[3],
52 'blocker' => priorities[4]
52 'blocker' => priorities[4]
53 }
53 }
54
54
55 TRACKER_BUG = Tracker.find_by_position(1)
55 TRACKER_BUG = Tracker.find_by_position(1)
56 TRACKER_FEATURE = Tracker.find_by_position(2)
56 TRACKER_FEATURE = Tracker.find_by_position(2)
57 DEFAULT_TRACKER = TRACKER_BUG
57 DEFAULT_TRACKER = TRACKER_BUG
58 TRACKER_MAPPING = {'defect' => TRACKER_BUG,
58 TRACKER_MAPPING = {'defect' => TRACKER_BUG,
59 'enhancement' => TRACKER_FEATURE,
59 'enhancement' => TRACKER_FEATURE,
60 'task' => TRACKER_FEATURE,
60 'task' => TRACKER_FEATURE,
61 'patch' =>TRACKER_FEATURE
61 'patch' =>TRACKER_FEATURE
62 }
62 }
63
63
64 roles = Role.find(:all, :conditions => {:builtin => 0}, :order => 'position ASC')
64 roles = Role.find(:all, :conditions => {:builtin => 0}, :order => 'position ASC')
65 manager_role = roles[0]
65 manager_role = roles[0]
66 developer_role = roles[1]
66 developer_role = roles[1]
67 DEFAULT_ROLE = roles.last
67 DEFAULT_ROLE = roles.last
68 ROLE_MAPPING = {'admin' => manager_role,
68 ROLE_MAPPING = {'admin' => manager_role,
69 'developer' => developer_role
69 'developer' => developer_role
70 }
70 }
71
71
72 class ::Time
72 class ::Time
73 class << self
73 class << self
74 alias :real_now :now
74 alias :real_now :now
75 def now
75 def now
76 real_now - @fake_diff.to_i
76 real_now - @fake_diff.to_i
77 end
77 end
78 def fake(time)
78 def fake(time)
79 @fake_diff = real_now - time
79 @fake_diff = real_now - time
80 res = yield
80 res = yield
81 @fake_diff = 0
81 @fake_diff = 0
82 res
82 res
83 end
83 end
84 end
84 end
85 end
85 end
86
86
87 class TracComponent < ActiveRecord::Base
87 class TracComponent < ActiveRecord::Base
88 set_table_name :component
88 self.table_name = :component
89 end
89 end
90
90
91 class TracMilestone < ActiveRecord::Base
91 class TracMilestone < ActiveRecord::Base
92 set_table_name :milestone
92 self.table_name = :milestone
93 # If this attribute is set a milestone has a defined target timepoint
93 # If this attribute is set a milestone has a defined target timepoint
94 def due
94 def due
95 if read_attribute(:due) && read_attribute(:due) > 0
95 if read_attribute(:due) && read_attribute(:due) > 0
96 Time.at(read_attribute(:due)).to_date
96 Time.at(read_attribute(:due)).to_date
97 else
97 else
98 nil
98 nil
99 end
99 end
100 end
100 end
101 # This is the real timepoint at which the milestone has finished.
101 # This is the real timepoint at which the milestone has finished.
102 def completed
102 def completed
103 if read_attribute(:completed) && read_attribute(:completed) > 0
103 if read_attribute(:completed) && read_attribute(:completed) > 0
104 Time.at(read_attribute(:completed)).to_date
104 Time.at(read_attribute(:completed)).to_date
105 else
105 else
106 nil
106 nil
107 end
107 end
108 end
108 end
109
109
110 def description
110 def description
111 # Attribute is named descr in Trac v0.8.x
111 # Attribute is named descr in Trac v0.8.x
112 has_attribute?(:descr) ? read_attribute(:descr) : read_attribute(:description)
112 has_attribute?(:descr) ? read_attribute(:descr) : read_attribute(:description)
113 end
113 end
114 end
114 end
115
115
116 class TracTicketCustom < ActiveRecord::Base
116 class TracTicketCustom < ActiveRecord::Base
117 set_table_name :ticket_custom
117 self.table_name = :ticket_custom
118 end
118 end
119
119
120 class TracAttachment < ActiveRecord::Base
120 class TracAttachment < ActiveRecord::Base
121 set_table_name :attachment
121 self.table_name = :attachment
122 set_inheritance_column :none
122 set_inheritance_column :none
123
123
124 def time; Time.at(read_attribute(:time)) end
124 def time; Time.at(read_attribute(:time)) end
125
125
126 def original_filename
126 def original_filename
127 filename
127 filename
128 end
128 end
129
129
130 def content_type
130 def content_type
131 ''
131 ''
132 end
132 end
133
133
134 def exist?
134 def exist?
135 File.file? trac_fullpath
135 File.file? trac_fullpath
136 end
136 end
137
137
138 def open
138 def open
139 File.open("#{trac_fullpath}", 'rb') {|f|
139 File.open("#{trac_fullpath}", 'rb') {|f|
140 @file = f
140 @file = f
141 yield self
141 yield self
142 }
142 }
143 end
143 end
144
144
145 def read(*args)
145 def read(*args)
146 @file.read(*args)
146 @file.read(*args)
147 end
147 end
148
148
149 def description
149 def description
150 read_attribute(:description).to_s.slice(0,255)
150 read_attribute(:description).to_s.slice(0,255)
151 end
151 end
152
152
153 private
153 private
154 def trac_fullpath
154 def trac_fullpath
155 attachment_type = read_attribute(:type)
155 attachment_type = read_attribute(:type)
156 trac_file = filename.gsub( /[^a-zA-Z0-9\-_\.!~*']/n ) {|x| sprintf('%%%02x', x[0]) }
156 trac_file = filename.gsub( /[^a-zA-Z0-9\-_\.!~*']/n ) {|x| sprintf('%%%02x', x[0]) }
157 "#{TracMigrate.trac_attachments_directory}/#{attachment_type}/#{id}/#{trac_file}"
157 "#{TracMigrate.trac_attachments_directory}/#{attachment_type}/#{id}/#{trac_file}"
158 end
158 end
159 end
159 end
160
160
161 class TracTicket < ActiveRecord::Base
161 class TracTicket < ActiveRecord::Base
162 set_table_name :ticket
162 self.table_name = :ticket
163 set_inheritance_column :none
163 set_inheritance_column :none
164
164
165 # ticket changes: only migrate status changes and comments
165 # ticket changes: only migrate status changes and comments
166 has_many :changes, :class_name => "TracTicketChange", :foreign_key => :ticket
166 has_many :changes, :class_name => "TracTicketChange", :foreign_key => :ticket
167 has_many :attachments, :class_name => "TracAttachment",
167 has_many :attachments, :class_name => "TracAttachment",
168 :finder_sql => "SELECT DISTINCT attachment.* FROM #{TracMigrate::TracAttachment.table_name}" +
168 :finder_sql => "SELECT DISTINCT attachment.* FROM #{TracMigrate::TracAttachment.table_name}" +
169 " WHERE #{TracMigrate::TracAttachment.table_name}.type = 'ticket'" +
169 " WHERE #{TracMigrate::TracAttachment.table_name}.type = 'ticket'" +
170 ' AND #{TracMigrate::TracAttachment.table_name}.id = \'#{TracMigrate::TracAttachment.connection.quote_string(id.to_s)}\''
170 ' AND #{TracMigrate::TracAttachment.table_name}.id = \'#{TracMigrate::TracAttachment.connection.quote_string(id.to_s)}\''
171 has_many :customs, :class_name => "TracTicketCustom", :foreign_key => :ticket
171 has_many :customs, :class_name => "TracTicketCustom", :foreign_key => :ticket
172
172
173 def ticket_type
173 def ticket_type
174 read_attribute(:type)
174 read_attribute(:type)
175 end
175 end
176
176
177 def summary
177 def summary
178 read_attribute(:summary).blank? ? "(no subject)" : read_attribute(:summary)
178 read_attribute(:summary).blank? ? "(no subject)" : read_attribute(:summary)
179 end
179 end
180
180
181 def description
181 def description
182 read_attribute(:description).blank? ? summary : read_attribute(:description)
182 read_attribute(:description).blank? ? summary : read_attribute(:description)
183 end
183 end
184
184
185 def time; Time.at(read_attribute(:time)) end
185 def time; Time.at(read_attribute(:time)) end
186 def changetime; Time.at(read_attribute(:changetime)) end
186 def changetime; Time.at(read_attribute(:changetime)) end
187 end
187 end
188
188
189 class TracTicketChange < ActiveRecord::Base
189 class TracTicketChange < ActiveRecord::Base
190 set_table_name :ticket_change
190 self.table_name = :ticket_change
191
191
192 def time; Time.at(read_attribute(:time)) end
192 def time; Time.at(read_attribute(:time)) end
193 end
193 end
194
194
195 TRAC_WIKI_PAGES = %w(InterMapTxt InterTrac InterWiki RecentChanges SandBox TracAccessibility TracAdmin TracBackup TracBrowser TracCgi TracChangeset \
195 TRAC_WIKI_PAGES = %w(InterMapTxt InterTrac InterWiki RecentChanges SandBox TracAccessibility TracAdmin TracBackup TracBrowser TracCgi TracChangeset \
196 TracEnvironment TracFastCgi TracGuide TracImport TracIni TracInstall TracInterfaceCustomization \
196 TracEnvironment TracFastCgi TracGuide TracImport TracIni TracInstall TracInterfaceCustomization \
197 TracLinks TracLogging TracModPython TracNotification TracPermissions TracPlugins TracQuery \
197 TracLinks TracLogging TracModPython TracNotification TracPermissions TracPlugins TracQuery \
198 TracReports TracRevisionLog TracRoadmap TracRss TracSearch TracStandalone TracSupport TracSyntaxColoring TracTickets \
198 TracReports TracRevisionLog TracRoadmap TracRss TracSearch TracStandalone TracSupport TracSyntaxColoring TracTickets \
199 TracTicketsCustomFields TracTimeline TracUnicode TracUpgrade TracWiki WikiDeletePage WikiFormatting \
199 TracTicketsCustomFields TracTimeline TracUnicode TracUpgrade TracWiki WikiDeletePage WikiFormatting \
200 WikiHtml WikiMacros WikiNewPage WikiPageNames WikiProcessors WikiRestructuredText WikiRestructuredTextLinks \
200 WikiHtml WikiMacros WikiNewPage WikiPageNames WikiProcessors WikiRestructuredText WikiRestructuredTextLinks \
201 CamelCase TitleIndex)
201 CamelCase TitleIndex)
202
202
203 class TracWikiPage < ActiveRecord::Base
203 class TracWikiPage < ActiveRecord::Base
204 set_table_name :wiki
204 self.table_name = :wiki
205 set_primary_key :name
205 set_primary_key :name
206
206
207 has_many :attachments, :class_name => "TracAttachment",
207 has_many :attachments, :class_name => "TracAttachment",
208 :finder_sql => "SELECT DISTINCT attachment.* FROM #{TracMigrate::TracAttachment.table_name}" +
208 :finder_sql => "SELECT DISTINCT attachment.* FROM #{TracMigrate::TracAttachment.table_name}" +
209 " WHERE #{TracMigrate::TracAttachment.table_name}.type = 'wiki'" +
209 " WHERE #{TracMigrate::TracAttachment.table_name}.type = 'wiki'" +
210 ' AND #{TracMigrate::TracAttachment.table_name}.id = \'#{TracMigrate::TracAttachment.connection.quote_string(id.to_s)}\''
210 ' AND #{TracMigrate::TracAttachment.table_name}.id = \'#{TracMigrate::TracAttachment.connection.quote_string(id.to_s)}\''
211
211
212 def self.columns
212 def self.columns
213 # Hides readonly Trac field to prevent clash with AR readonly? method (Rails 2.0)
213 # Hides readonly Trac field to prevent clash with AR readonly? method (Rails 2.0)
214 super.select {|column| column.name.to_s != 'readonly'}
214 super.select {|column| column.name.to_s != 'readonly'}
215 end
215 end
216
216
217 def time; Time.at(read_attribute(:time)) end
217 def time; Time.at(read_attribute(:time)) end
218 end
218 end
219
219
220 class TracPermission < ActiveRecord::Base
220 class TracPermission < ActiveRecord::Base
221 set_table_name :permission
221 self.table_name = :permission
222 end
222 end
223
223
224 class TracSessionAttribute < ActiveRecord::Base
224 class TracSessionAttribute < ActiveRecord::Base
225 set_table_name :session_attribute
225 self.table_name = :session_attribute
226 end
226 end
227
227
228 def self.find_or_create_user(username, project_member = false)
228 def self.find_or_create_user(username, project_member = false)
229 return User.anonymous if username.blank?
229 return User.anonymous if username.blank?
230
230
231 u = User.find_by_login(username)
231 u = User.find_by_login(username)
232 if !u
232 if !u
233 # Create a new user if not found
233 # Create a new user if not found
234 mail = username[0, User::MAIL_LENGTH_LIMIT]
234 mail = username[0, User::MAIL_LENGTH_LIMIT]
235 if mail_attr = TracSessionAttribute.find_by_sid_and_name(username, 'email')
235 if mail_attr = TracSessionAttribute.find_by_sid_and_name(username, 'email')
236 mail = mail_attr.value
236 mail = mail_attr.value
237 end
237 end
238 mail = "#{mail}@foo.bar" unless mail.include?("@")
238 mail = "#{mail}@foo.bar" unless mail.include?("@")
239
239
240 name = username
240 name = username
241 if name_attr = TracSessionAttribute.find_by_sid_and_name(username, 'name')
241 if name_attr = TracSessionAttribute.find_by_sid_and_name(username, 'name')
242 name = name_attr.value
242 name = name_attr.value
243 end
243 end
244 name =~ (/(.*)(\s+\w+)?/)
244 name =~ (/(.*)(\s+\w+)?/)
245 fn = $1.strip
245 fn = $1.strip
246 ln = ($2 || '-').strip
246 ln = ($2 || '-').strip
247
247
248 u = User.new :mail => mail.gsub(/[^-@a-z0-9\.]/i, '-'),
248 u = User.new :mail => mail.gsub(/[^-@a-z0-9\.]/i, '-'),
249 :firstname => fn[0, limit_for(User, 'firstname')],
249 :firstname => fn[0, limit_for(User, 'firstname')],
250 :lastname => ln[0, limit_for(User, 'lastname')]
250 :lastname => ln[0, limit_for(User, 'lastname')]
251
251
252 u.login = username[0, User::LOGIN_LENGTH_LIMIT].gsub(/[^a-z0-9_\-@\.]/i, '-')
252 u.login = username[0, User::LOGIN_LENGTH_LIMIT].gsub(/[^a-z0-9_\-@\.]/i, '-')
253 u.password = 'trac'
253 u.password = 'trac'
254 u.admin = true if TracPermission.find_by_username_and_action(username, 'admin')
254 u.admin = true if TracPermission.find_by_username_and_action(username, 'admin')
255 # finally, a default user is used if the new user is not valid
255 # finally, a default user is used if the new user is not valid
256 u = User.find(:first) unless u.save
256 u = User.find(:first) unless u.save
257 end
257 end
258 # Make sure he is a member of the project
258 # Make sure he is a member of the project
259 if project_member && !u.member_of?(@target_project)
259 if project_member && !u.member_of?(@target_project)
260 role = DEFAULT_ROLE
260 role = DEFAULT_ROLE
261 if u.admin
261 if u.admin
262 role = ROLE_MAPPING['admin']
262 role = ROLE_MAPPING['admin']
263 elsif TracPermission.find_by_username_and_action(username, 'developer')
263 elsif TracPermission.find_by_username_and_action(username, 'developer')
264 role = ROLE_MAPPING['developer']
264 role = ROLE_MAPPING['developer']
265 end
265 end
266 Member.create(:user => u, :project => @target_project, :roles => [role])
266 Member.create(:user => u, :project => @target_project, :roles => [role])
267 u.reload
267 u.reload
268 end
268 end
269 u
269 u
270 end
270 end
271
271
272 # Basic wiki syntax conversion
272 # Basic wiki syntax conversion
273 def self.convert_wiki_text(text)
273 def self.convert_wiki_text(text)
274 # Titles
274 # Titles
275 text = text.gsub(/^(\=+)\s(.+)\s(\=+)/) {|s| "\nh#{$1.length}. #{$2}\n"}
275 text = text.gsub(/^(\=+)\s(.+)\s(\=+)/) {|s| "\nh#{$1.length}. #{$2}\n"}
276 # External Links
276 # External Links
277 text = text.gsub(/\[(http[^\s]+)\s+([^\]]+)\]/) {|s| "\"#{$2}\":#{$1}"}
277 text = text.gsub(/\[(http[^\s]+)\s+([^\]]+)\]/) {|s| "\"#{$2}\":#{$1}"}
278 # Ticket links:
278 # Ticket links:
279 # [ticket:234 Text],[ticket:234 This is a test]
279 # [ticket:234 Text],[ticket:234 This is a test]
280 text = text.gsub(/\[ticket\:([^\ ]+)\ (.+?)\]/, '"\2":/issues/show/\1')
280 text = text.gsub(/\[ticket\:([^\ ]+)\ (.+?)\]/, '"\2":/issues/show/\1')
281 # ticket:1234
281 # ticket:1234
282 # #1 is working cause Redmine uses the same syntax.
282 # #1 is working cause Redmine uses the same syntax.
283 text = text.gsub(/ticket\:([^\ ]+)/, '#\1')
283 text = text.gsub(/ticket\:([^\ ]+)/, '#\1')
284 # Milestone links:
284 # Milestone links:
285 # [milestone:"0.1.0 Mercury" Milestone 0.1.0 (Mercury)]
285 # [milestone:"0.1.0 Mercury" Milestone 0.1.0 (Mercury)]
286 # The text "Milestone 0.1.0 (Mercury)" is not converted,
286 # The text "Milestone 0.1.0 (Mercury)" is not converted,
287 # cause Redmine's wiki does not support this.
287 # cause Redmine's wiki does not support this.
288 text = text.gsub(/\[milestone\:\"([^\"]+)\"\ (.+?)\]/, 'version:"\1"')
288 text = text.gsub(/\[milestone\:\"([^\"]+)\"\ (.+?)\]/, 'version:"\1"')
289 # [milestone:"0.1.0 Mercury"]
289 # [milestone:"0.1.0 Mercury"]
290 text = text.gsub(/\[milestone\:\"([^\"]+)\"\]/, 'version:"\1"')
290 text = text.gsub(/\[milestone\:\"([^\"]+)\"\]/, 'version:"\1"')
291 text = text.gsub(/milestone\:\"([^\"]+)\"/, 'version:"\1"')
291 text = text.gsub(/milestone\:\"([^\"]+)\"/, 'version:"\1"')
292 # milestone:0.1.0
292 # milestone:0.1.0
293 text = text.gsub(/\[milestone\:([^\ ]+)\]/, 'version:\1')
293 text = text.gsub(/\[milestone\:([^\ ]+)\]/, 'version:\1')
294 text = text.gsub(/milestone\:([^\ ]+)/, 'version:\1')
294 text = text.gsub(/milestone\:([^\ ]+)/, 'version:\1')
295 # Internal Links
295 # Internal Links
296 text = text.gsub(/\[\[BR\]\]/, "\n") # This has to go before the rules below
296 text = text.gsub(/\[\[BR\]\]/, "\n") # This has to go before the rules below
297 text = text.gsub(/\[\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
297 text = text.gsub(/\[\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
298 text = text.gsub(/\[wiki:\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
298 text = text.gsub(/\[wiki:\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
299 text = text.gsub(/\[wiki:\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
299 text = text.gsub(/\[wiki:\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
300 text = text.gsub(/\[wiki:([^\s\]]+)\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
300 text = text.gsub(/\[wiki:([^\s\]]+)\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
301 text = text.gsub(/\[wiki:([^\s\]]+)\s(.*)\]/) {|s| "[[#{$1.delete(',./?;|:')}|#{$2.delete(',./?;|:')}]]"}
301 text = text.gsub(/\[wiki:([^\s\]]+)\s(.*)\]/) {|s| "[[#{$1.delete(',./?;|:')}|#{$2.delete(',./?;|:')}]]"}
302
302
303 # Links to pages UsingJustWikiCaps
303 # Links to pages UsingJustWikiCaps
304 text = text.gsub(/([^!]|^)(^| )([A-Z][a-z]+[A-Z][a-zA-Z]+)/, '\\1\\2[[\3]]')
304 text = text.gsub(/([^!]|^)(^| )([A-Z][a-z]+[A-Z][a-zA-Z]+)/, '\\1\\2[[\3]]')
305 # Normalize things that were supposed to not be links
305 # Normalize things that were supposed to not be links
306 # like !NotALink
306 # like !NotALink
307 text = text.gsub(/(^| )!([A-Z][A-Za-z]+)/, '\1\2')
307 text = text.gsub(/(^| )!([A-Z][A-Za-z]+)/, '\1\2')
308 # Revisions links
308 # Revisions links
309 text = text.gsub(/\[(\d+)\]/, 'r\1')
309 text = text.gsub(/\[(\d+)\]/, 'r\1')
310 # Ticket number re-writing
310 # Ticket number re-writing
311 text = text.gsub(/#(\d+)/) do |s|
311 text = text.gsub(/#(\d+)/) do |s|
312 if $1.length < 10
312 if $1.length < 10
313 # TICKET_MAP[$1.to_i] ||= $1
313 # TICKET_MAP[$1.to_i] ||= $1
314 "\##{TICKET_MAP[$1.to_i] || $1}"
314 "\##{TICKET_MAP[$1.to_i] || $1}"
315 else
315 else
316 s
316 s
317 end
317 end
318 end
318 end
319 # We would like to convert the Code highlighting too
319 # We would like to convert the Code highlighting too
320 # This will go into the next line.
320 # This will go into the next line.
321 shebang_line = false
321 shebang_line = false
322 # Reguar expression for start of code
322 # Reguar expression for start of code
323 pre_re = /\{\{\{/
323 pre_re = /\{\{\{/
324 # Code hightlighing...
324 # Code hightlighing...
325 shebang_re = /^\#\!([a-z]+)/
325 shebang_re = /^\#\!([a-z]+)/
326 # Regular expression for end of code
326 # Regular expression for end of code
327 pre_end_re = /\}\}\}/
327 pre_end_re = /\}\}\}/
328
328
329 # Go through the whole text..extract it line by line
329 # Go through the whole text..extract it line by line
330 text = text.gsub(/^(.*)$/) do |line|
330 text = text.gsub(/^(.*)$/) do |line|
331 m_pre = pre_re.match(line)
331 m_pre = pre_re.match(line)
332 if m_pre
332 if m_pre
333 line = '<pre>'
333 line = '<pre>'
334 else
334 else
335 m_sl = shebang_re.match(line)
335 m_sl = shebang_re.match(line)
336 if m_sl
336 if m_sl
337 shebang_line = true
337 shebang_line = true
338 line = '<code class="' + m_sl[1] + '">'
338 line = '<code class="' + m_sl[1] + '">'
339 end
339 end
340 m_pre_end = pre_end_re.match(line)
340 m_pre_end = pre_end_re.match(line)
341 if m_pre_end
341 if m_pre_end
342 line = '</pre>'
342 line = '</pre>'
343 if shebang_line
343 if shebang_line
344 line = '</code>' + line
344 line = '</code>' + line
345 end
345 end
346 end
346 end
347 end
347 end
348 line
348 line
349 end
349 end
350
350
351 # Highlighting
351 # Highlighting
352 text = text.gsub(/'''''([^\s])/, '_*\1')
352 text = text.gsub(/'''''([^\s])/, '_*\1')
353 text = text.gsub(/([^\s])'''''/, '\1*_')
353 text = text.gsub(/([^\s])'''''/, '\1*_')
354 text = text.gsub(/'''/, '*')
354 text = text.gsub(/'''/, '*')
355 text = text.gsub(/''/, '_')
355 text = text.gsub(/''/, '_')
356 text = text.gsub(/__/, '+')
356 text = text.gsub(/__/, '+')
357 text = text.gsub(/~~/, '-')
357 text = text.gsub(/~~/, '-')
358 text = text.gsub(/`/, '@')
358 text = text.gsub(/`/, '@')
359 text = text.gsub(/,,/, '~')
359 text = text.gsub(/,,/, '~')
360 # Lists
360 # Lists
361 text = text.gsub(/^([ ]+)\* /) {|s| '*' * $1.length + " "}
361 text = text.gsub(/^([ ]+)\* /) {|s| '*' * $1.length + " "}
362
362
363 text
363 text
364 end
364 end
365
365
366 def self.migrate
366 def self.migrate
367 establish_connection
367 establish_connection
368
368
369 # Quick database test
369 # Quick database test
370 TracComponent.count
370 TracComponent.count
371
371
372 migrated_components = 0
372 migrated_components = 0
373 migrated_milestones = 0
373 migrated_milestones = 0
374 migrated_tickets = 0
374 migrated_tickets = 0
375 migrated_custom_values = 0
375 migrated_custom_values = 0
376 migrated_ticket_attachments = 0
376 migrated_ticket_attachments = 0
377 migrated_wiki_edits = 0
377 migrated_wiki_edits = 0
378 migrated_wiki_attachments = 0
378 migrated_wiki_attachments = 0
379
379
380 #Wiki system initializing...
380 #Wiki system initializing...
381 @target_project.wiki.destroy if @target_project.wiki
381 @target_project.wiki.destroy if @target_project.wiki
382 @target_project.reload
382 @target_project.reload
383 wiki = Wiki.new(:project => @target_project, :start_page => 'WikiStart')
383 wiki = Wiki.new(:project => @target_project, :start_page => 'WikiStart')
384 wiki_edit_count = 0
384 wiki_edit_count = 0
385
385
386 # Components
386 # Components
387 print "Migrating components"
387 print "Migrating components"
388 issues_category_map = {}
388 issues_category_map = {}
389 TracComponent.find(:all).each do |component|
389 TracComponent.find(:all).each do |component|
390 print '.'
390 print '.'
391 STDOUT.flush
391 STDOUT.flush
392 c = IssueCategory.new :project => @target_project,
392 c = IssueCategory.new :project => @target_project,
393 :name => encode(component.name[0, limit_for(IssueCategory, 'name')])
393 :name => encode(component.name[0, limit_for(IssueCategory, 'name')])
394 next unless c.save
394 next unless c.save
395 issues_category_map[component.name] = c
395 issues_category_map[component.name] = c
396 migrated_components += 1
396 migrated_components += 1
397 end
397 end
398 puts
398 puts
399
399
400 # Milestones
400 # Milestones
401 print "Migrating milestones"
401 print "Migrating milestones"
402 version_map = {}
402 version_map = {}
403 TracMilestone.find(:all).each do |milestone|
403 TracMilestone.find(:all).each do |milestone|
404 print '.'
404 print '.'
405 STDOUT.flush
405 STDOUT.flush
406 # First we try to find the wiki page...
406 # First we try to find the wiki page...
407 p = wiki.find_or_new_page(milestone.name.to_s)
407 p = wiki.find_or_new_page(milestone.name.to_s)
408 p.content = WikiContent.new(:page => p) if p.new_record?
408 p.content = WikiContent.new(:page => p) if p.new_record?
409 p.content.text = milestone.description.to_s
409 p.content.text = milestone.description.to_s
410 p.content.author = find_or_create_user('trac')
410 p.content.author = find_or_create_user('trac')
411 p.content.comments = 'Milestone'
411 p.content.comments = 'Milestone'
412 p.save
412 p.save
413
413
414 v = Version.new :project => @target_project,
414 v = Version.new :project => @target_project,
415 :name => encode(milestone.name[0, limit_for(Version, 'name')]),
415 :name => encode(milestone.name[0, limit_for(Version, 'name')]),
416 :description => nil,
416 :description => nil,
417 :wiki_page_title => milestone.name.to_s,
417 :wiki_page_title => milestone.name.to_s,
418 :effective_date => milestone.completed
418 :effective_date => milestone.completed
419
419
420 next unless v.save
420 next unless v.save
421 version_map[milestone.name] = v
421 version_map[milestone.name] = v
422 migrated_milestones += 1
422 migrated_milestones += 1
423 end
423 end
424 puts
424 puts
425
425
426 # Custom fields
426 # Custom fields
427 # TODO: read trac.ini instead
427 # TODO: read trac.ini instead
428 print "Migrating custom fields"
428 print "Migrating custom fields"
429 custom_field_map = {}
429 custom_field_map = {}
430 TracTicketCustom.find_by_sql("SELECT DISTINCT name FROM #{TracTicketCustom.table_name}").each do |field|
430 TracTicketCustom.find_by_sql("SELECT DISTINCT name FROM #{TracTicketCustom.table_name}").each do |field|
431 print '.'
431 print '.'
432 STDOUT.flush
432 STDOUT.flush
433 # Redmine custom field name
433 # Redmine custom field name
434 field_name = encode(field.name[0, limit_for(IssueCustomField, 'name')]).humanize
434 field_name = encode(field.name[0, limit_for(IssueCustomField, 'name')]).humanize
435 # Find if the custom already exists in Redmine
435 # Find if the custom already exists in Redmine
436 f = IssueCustomField.find_by_name(field_name)
436 f = IssueCustomField.find_by_name(field_name)
437 # Or create a new one
437 # Or create a new one
438 f ||= IssueCustomField.create(:name => encode(field.name[0, limit_for(IssueCustomField, 'name')]).humanize,
438 f ||= IssueCustomField.create(:name => encode(field.name[0, limit_for(IssueCustomField, 'name')]).humanize,
439 :field_format => 'string')
439 :field_format => 'string')
440
440
441 next if f.new_record?
441 next if f.new_record?
442 f.trackers = Tracker.find(:all)
442 f.trackers = Tracker.find(:all)
443 f.projects << @target_project
443 f.projects << @target_project
444 custom_field_map[field.name] = f
444 custom_field_map[field.name] = f
445 end
445 end
446 puts
446 puts
447
447
448 # Trac 'resolution' field as a Redmine custom field
448 # Trac 'resolution' field as a Redmine custom field
449 r = IssueCustomField.find(:first, :conditions => { :name => "Resolution" })
449 r = IssueCustomField.find(:first, :conditions => { :name => "Resolution" })
450 r = IssueCustomField.new(:name => 'Resolution',
450 r = IssueCustomField.new(:name => 'Resolution',
451 :field_format => 'list',
451 :field_format => 'list',
452 :is_filter => true) if r.nil?
452 :is_filter => true) if r.nil?
453 r.trackers = Tracker.find(:all)
453 r.trackers = Tracker.find(:all)
454 r.projects << @target_project
454 r.projects << @target_project
455 r.possible_values = (r.possible_values + %w(fixed invalid wontfix duplicate worksforme)).flatten.compact.uniq
455 r.possible_values = (r.possible_values + %w(fixed invalid wontfix duplicate worksforme)).flatten.compact.uniq
456 r.save!
456 r.save!
457 custom_field_map['resolution'] = r
457 custom_field_map['resolution'] = r
458
458
459 # Tickets
459 # Tickets
460 print "Migrating tickets"
460 print "Migrating tickets"
461 TracTicket.find_each(:batch_size => 200) do |ticket|
461 TracTicket.find_each(:batch_size => 200) do |ticket|
462 print '.'
462 print '.'
463 STDOUT.flush
463 STDOUT.flush
464 i = Issue.new :project => @target_project,
464 i = Issue.new :project => @target_project,
465 :subject => encode(ticket.summary[0, limit_for(Issue, 'subject')]),
465 :subject => encode(ticket.summary[0, limit_for(Issue, 'subject')]),
466 :description => convert_wiki_text(encode(ticket.description)),
466 :description => convert_wiki_text(encode(ticket.description)),
467 :priority => PRIORITY_MAPPING[ticket.priority] || DEFAULT_PRIORITY,
467 :priority => PRIORITY_MAPPING[ticket.priority] || DEFAULT_PRIORITY,
468 :created_on => ticket.time
468 :created_on => ticket.time
469 i.author = find_or_create_user(ticket.reporter)
469 i.author = find_or_create_user(ticket.reporter)
470 i.category = issues_category_map[ticket.component] unless ticket.component.blank?
470 i.category = issues_category_map[ticket.component] unless ticket.component.blank?
471 i.fixed_version = version_map[ticket.milestone] unless ticket.milestone.blank?
471 i.fixed_version = version_map[ticket.milestone] unless ticket.milestone.blank?
472 i.status = STATUS_MAPPING[ticket.status] || DEFAULT_STATUS
472 i.status = STATUS_MAPPING[ticket.status] || DEFAULT_STATUS
473 i.tracker = TRACKER_MAPPING[ticket.ticket_type] || DEFAULT_TRACKER
473 i.tracker = TRACKER_MAPPING[ticket.ticket_type] || DEFAULT_TRACKER
474 i.id = ticket.id unless Issue.exists?(ticket.id)
474 i.id = ticket.id unless Issue.exists?(ticket.id)
475 next unless Time.fake(ticket.changetime) { i.save }
475 next unless Time.fake(ticket.changetime) { i.save }
476 TICKET_MAP[ticket.id] = i.id
476 TICKET_MAP[ticket.id] = i.id
477 migrated_tickets += 1
477 migrated_tickets += 1
478
478
479 # Owner
479 # Owner
480 unless ticket.owner.blank?
480 unless ticket.owner.blank?
481 i.assigned_to = find_or_create_user(ticket.owner, true)
481 i.assigned_to = find_or_create_user(ticket.owner, true)
482 Time.fake(ticket.changetime) { i.save }
482 Time.fake(ticket.changetime) { i.save }
483 end
483 end
484
484
485 # Comments and status/resolution changes
485 # Comments and status/resolution changes
486 ticket.changes.group_by(&:time).each do |time, changeset|
486 ticket.changes.group_by(&:time).each do |time, changeset|
487 status_change = changeset.select {|change| change.field == 'status'}.first
487 status_change = changeset.select {|change| change.field == 'status'}.first
488 resolution_change = changeset.select {|change| change.field == 'resolution'}.first
488 resolution_change = changeset.select {|change| change.field == 'resolution'}.first
489 comment_change = changeset.select {|change| change.field == 'comment'}.first
489 comment_change = changeset.select {|change| change.field == 'comment'}.first
490
490
491 n = Journal.new :notes => (comment_change ? convert_wiki_text(encode(comment_change.newvalue)) : ''),
491 n = Journal.new :notes => (comment_change ? convert_wiki_text(encode(comment_change.newvalue)) : ''),
492 :created_on => time
492 :created_on => time
493 n.user = find_or_create_user(changeset.first.author)
493 n.user = find_or_create_user(changeset.first.author)
494 n.journalized = i
494 n.journalized = i
495 if status_change &&
495 if status_change &&
496 STATUS_MAPPING[status_change.oldvalue] &&
496 STATUS_MAPPING[status_change.oldvalue] &&
497 STATUS_MAPPING[status_change.newvalue] &&
497 STATUS_MAPPING[status_change.newvalue] &&
498 (STATUS_MAPPING[status_change.oldvalue] != STATUS_MAPPING[status_change.newvalue])
498 (STATUS_MAPPING[status_change.oldvalue] != STATUS_MAPPING[status_change.newvalue])
499 n.details << JournalDetail.new(:property => 'attr',
499 n.details << JournalDetail.new(:property => 'attr',
500 :prop_key => 'status_id',
500 :prop_key => 'status_id',
501 :old_value => STATUS_MAPPING[status_change.oldvalue].id,
501 :old_value => STATUS_MAPPING[status_change.oldvalue].id,
502 :value => STATUS_MAPPING[status_change.newvalue].id)
502 :value => STATUS_MAPPING[status_change.newvalue].id)
503 end
503 end
504 if resolution_change
504 if resolution_change
505 n.details << JournalDetail.new(:property => 'cf',
505 n.details << JournalDetail.new(:property => 'cf',
506 :prop_key => custom_field_map['resolution'].id,
506 :prop_key => custom_field_map['resolution'].id,
507 :old_value => resolution_change.oldvalue,
507 :old_value => resolution_change.oldvalue,
508 :value => resolution_change.newvalue)
508 :value => resolution_change.newvalue)
509 end
509 end
510 n.save unless n.details.empty? && n.notes.blank?
510 n.save unless n.details.empty? && n.notes.blank?
511 end
511 end
512
512
513 # Attachments
513 # Attachments
514 ticket.attachments.each do |attachment|
514 ticket.attachments.each do |attachment|
515 next unless attachment.exist?
515 next unless attachment.exist?
516 attachment.open {
516 attachment.open {
517 a = Attachment.new :created_on => attachment.time
517 a = Attachment.new :created_on => attachment.time
518 a.file = attachment
518 a.file = attachment
519 a.author = find_or_create_user(attachment.author)
519 a.author = find_or_create_user(attachment.author)
520 a.container = i
520 a.container = i
521 a.description = attachment.description
521 a.description = attachment.description
522 migrated_ticket_attachments += 1 if a.save
522 migrated_ticket_attachments += 1 if a.save
523 }
523 }
524 end
524 end
525
525
526 # Custom fields
526 # Custom fields
527 custom_values = ticket.customs.inject({}) do |h, custom|
527 custom_values = ticket.customs.inject({}) do |h, custom|
528 if custom_field = custom_field_map[custom.name]
528 if custom_field = custom_field_map[custom.name]
529 h[custom_field.id] = custom.value
529 h[custom_field.id] = custom.value
530 migrated_custom_values += 1
530 migrated_custom_values += 1
531 end
531 end
532 h
532 h
533 end
533 end
534 if custom_field_map['resolution'] && !ticket.resolution.blank?
534 if custom_field_map['resolution'] && !ticket.resolution.blank?
535 custom_values[custom_field_map['resolution'].id] = ticket.resolution
535 custom_values[custom_field_map['resolution'].id] = ticket.resolution
536 end
536 end
537 i.custom_field_values = custom_values
537 i.custom_field_values = custom_values
538 i.save_custom_field_values
538 i.save_custom_field_values
539 end
539 end
540
540
541 # update issue id sequence if needed (postgresql)
541 # update issue id sequence if needed (postgresql)
542 Issue.connection.reset_pk_sequence!(Issue.table_name) if Issue.connection.respond_to?('reset_pk_sequence!')
542 Issue.connection.reset_pk_sequence!(Issue.table_name) if Issue.connection.respond_to?('reset_pk_sequence!')
543 puts
543 puts
544
544
545 # Wiki
545 # Wiki
546 print "Migrating wiki"
546 print "Migrating wiki"
547 if wiki.save
547 if wiki.save
548 TracWikiPage.find(:all, :order => 'name, version').each do |page|
548 TracWikiPage.find(:all, :order => 'name, version').each do |page|
549 # Do not migrate Trac manual wiki pages
549 # Do not migrate Trac manual wiki pages
550 next if TRAC_WIKI_PAGES.include?(page.name)
550 next if TRAC_WIKI_PAGES.include?(page.name)
551 wiki_edit_count += 1
551 wiki_edit_count += 1
552 print '.'
552 print '.'
553 STDOUT.flush
553 STDOUT.flush
554 p = wiki.find_or_new_page(page.name)
554 p = wiki.find_or_new_page(page.name)
555 p.content = WikiContent.new(:page => p) if p.new_record?
555 p.content = WikiContent.new(:page => p) if p.new_record?
556 p.content.text = page.text
556 p.content.text = page.text
557 p.content.author = find_or_create_user(page.author) unless page.author.blank? || page.author == 'trac'
557 p.content.author = find_or_create_user(page.author) unless page.author.blank? || page.author == 'trac'
558 p.content.comments = page.comment
558 p.content.comments = page.comment
559 Time.fake(page.time) { p.new_record? ? p.save : p.content.save }
559 Time.fake(page.time) { p.new_record? ? p.save : p.content.save }
560
560
561 next if p.content.new_record?
561 next if p.content.new_record?
562 migrated_wiki_edits += 1
562 migrated_wiki_edits += 1
563
563
564 # Attachments
564 # Attachments
565 page.attachments.each do |attachment|
565 page.attachments.each do |attachment|
566 next unless attachment.exist?
566 next unless attachment.exist?
567 next if p.attachments.find_by_filename(attachment.filename.gsub(/^.*(\\|\/)/, '').gsub(/[^\w\.\-]/,'_')) #add only once per page
567 next if p.attachments.find_by_filename(attachment.filename.gsub(/^.*(\\|\/)/, '').gsub(/[^\w\.\-]/,'_')) #add only once per page
568 attachment.open {
568 attachment.open {
569 a = Attachment.new :created_on => attachment.time
569 a = Attachment.new :created_on => attachment.time
570 a.file = attachment
570 a.file = attachment
571 a.author = find_or_create_user(attachment.author)
571 a.author = find_or_create_user(attachment.author)
572 a.description = attachment.description
572 a.description = attachment.description
573 a.container = p
573 a.container = p
574 migrated_wiki_attachments += 1 if a.save
574 migrated_wiki_attachments += 1 if a.save
575 }
575 }
576 end
576 end
577 end
577 end
578
578
579 wiki.reload
579 wiki.reload
580 wiki.pages.each do |page|
580 wiki.pages.each do |page|
581 page.content.text = convert_wiki_text(page.content.text)
581 page.content.text = convert_wiki_text(page.content.text)
582 Time.fake(page.content.updated_on) { page.content.save }
582 Time.fake(page.content.updated_on) { page.content.save }
583 end
583 end
584 end
584 end
585 puts
585 puts
586
586
587 puts
587 puts
588 puts "Components: #{migrated_components}/#{TracComponent.count}"
588 puts "Components: #{migrated_components}/#{TracComponent.count}"
589 puts "Milestones: #{migrated_milestones}/#{TracMilestone.count}"
589 puts "Milestones: #{migrated_milestones}/#{TracMilestone.count}"
590 puts "Tickets: #{migrated_tickets}/#{TracTicket.count}"
590 puts "Tickets: #{migrated_tickets}/#{TracTicket.count}"
591 puts "Ticket files: #{migrated_ticket_attachments}/" + TracAttachment.count(:conditions => {:type => 'ticket'}).to_s
591 puts "Ticket files: #{migrated_ticket_attachments}/" + TracAttachment.count(:conditions => {:type => 'ticket'}).to_s
592 puts "Custom values: #{migrated_custom_values}/#{TracTicketCustom.count}"
592 puts "Custom values: #{migrated_custom_values}/#{TracTicketCustom.count}"
593 puts "Wiki edits: #{migrated_wiki_edits}/#{wiki_edit_count}"
593 puts "Wiki edits: #{migrated_wiki_edits}/#{wiki_edit_count}"
594 puts "Wiki files: #{migrated_wiki_attachments}/" + TracAttachment.count(:conditions => {:type => 'wiki'}).to_s
594 puts "Wiki files: #{migrated_wiki_attachments}/" + TracAttachment.count(:conditions => {:type => 'wiki'}).to_s
595 end
595 end
596
596
597 def self.limit_for(klass, attribute)
597 def self.limit_for(klass, attribute)
598 klass.columns_hash[attribute.to_s].limit
598 klass.columns_hash[attribute.to_s].limit
599 end
599 end
600
600
601 def self.encoding(charset)
601 def self.encoding(charset)
602 @ic = Iconv.new('UTF-8', charset)
602 @ic = Iconv.new('UTF-8', charset)
603 rescue Iconv::InvalidEncoding
603 rescue Iconv::InvalidEncoding
604 puts "Invalid encoding!"
604 puts "Invalid encoding!"
605 return false
605 return false
606 end
606 end
607
607
608 def self.set_trac_directory(path)
608 def self.set_trac_directory(path)
609 @@trac_directory = path
609 @@trac_directory = path
610 raise "This directory doesn't exist!" unless File.directory?(path)
610 raise "This directory doesn't exist!" unless File.directory?(path)
611 raise "#{trac_attachments_directory} doesn't exist!" unless File.directory?(trac_attachments_directory)
611 raise "#{trac_attachments_directory} doesn't exist!" unless File.directory?(trac_attachments_directory)
612 @@trac_directory
612 @@trac_directory
613 rescue Exception => e
613 rescue Exception => e
614 puts e
614 puts e
615 return false
615 return false
616 end
616 end
617
617
618 def self.trac_directory
618 def self.trac_directory
619 @@trac_directory
619 @@trac_directory
620 end
620 end
621
621
622 def self.set_trac_adapter(adapter)
622 def self.set_trac_adapter(adapter)
623 return false if adapter.blank?
623 return false if adapter.blank?
624 raise "Unknown adapter: #{adapter}!" unless %w(sqlite sqlite3 mysql postgresql).include?(adapter)
624 raise "Unknown adapter: #{adapter}!" unless %w(sqlite sqlite3 mysql postgresql).include?(adapter)
625 # If adapter is sqlite or sqlite3, make sure that trac.db exists
625 # If adapter is sqlite or sqlite3, make sure that trac.db exists
626 raise "#{trac_db_path} doesn't exist!" if %w(sqlite sqlite3).include?(adapter) && !File.exist?(trac_db_path)
626 raise "#{trac_db_path} doesn't exist!" if %w(sqlite sqlite3).include?(adapter) && !File.exist?(trac_db_path)
627 @@trac_adapter = adapter
627 @@trac_adapter = adapter
628 rescue Exception => e
628 rescue Exception => e
629 puts e
629 puts e
630 return false
630 return false
631 end
631 end
632
632
633 def self.set_trac_db_host(host)
633 def self.set_trac_db_host(host)
634 return nil if host.blank?
634 return nil if host.blank?
635 @@trac_db_host = host
635 @@trac_db_host = host
636 end
636 end
637
637
638 def self.set_trac_db_port(port)
638 def self.set_trac_db_port(port)
639 return nil if port.to_i == 0
639 return nil if port.to_i == 0
640 @@trac_db_port = port.to_i
640 @@trac_db_port = port.to_i
641 end
641 end
642
642
643 def self.set_trac_db_name(name)
643 def self.set_trac_db_name(name)
644 return nil if name.blank?
644 return nil if name.blank?
645 @@trac_db_name = name
645 @@trac_db_name = name
646 end
646 end
647
647
648 def self.set_trac_db_username(username)
648 def self.set_trac_db_username(username)
649 @@trac_db_username = username
649 @@trac_db_username = username
650 end
650 end
651
651
652 def self.set_trac_db_password(password)
652 def self.set_trac_db_password(password)
653 @@trac_db_password = password
653 @@trac_db_password = password
654 end
654 end
655
655
656 def self.set_trac_db_schema(schema)
656 def self.set_trac_db_schema(schema)
657 @@trac_db_schema = schema
657 @@trac_db_schema = schema
658 end
658 end
659
659
660 mattr_reader :trac_directory, :trac_adapter, :trac_db_host, :trac_db_port, :trac_db_name, :trac_db_schema, :trac_db_username, :trac_db_password
660 mattr_reader :trac_directory, :trac_adapter, :trac_db_host, :trac_db_port, :trac_db_name, :trac_db_schema, :trac_db_username, :trac_db_password
661
661
662 def self.trac_db_path; "#{trac_directory}/db/trac.db" end
662 def self.trac_db_path; "#{trac_directory}/db/trac.db" end
663 def self.trac_attachments_directory; "#{trac_directory}/attachments" end
663 def self.trac_attachments_directory; "#{trac_directory}/attachments" end
664
664
665 def self.target_project_identifier(identifier)
665 def self.target_project_identifier(identifier)
666 project = Project.find_by_identifier(identifier)
666 project = Project.find_by_identifier(identifier)
667 if !project
667 if !project
668 # create the target project
668 # create the target project
669 project = Project.new :name => identifier.humanize,
669 project = Project.new :name => identifier.humanize,
670 :description => ''
670 :description => ''
671 project.identifier = identifier
671 project.identifier = identifier
672 puts "Unable to create a project with identifier '#{identifier}'!" unless project.save
672 puts "Unable to create a project with identifier '#{identifier}'!" unless project.save
673 # enable issues and wiki for the created project
673 # enable issues and wiki for the created project
674 project.enabled_module_names = ['issue_tracking', 'wiki']
674 project.enabled_module_names = ['issue_tracking', 'wiki']
675 else
675 else
676 puts
676 puts
677 puts "This project already exists in your Redmine database."
677 puts "This project already exists in your Redmine database."
678 print "Are you sure you want to append data to this project ? [Y/n] "
678 print "Are you sure you want to append data to this project ? [Y/n] "
679 STDOUT.flush
679 STDOUT.flush
680 exit if STDIN.gets.match(/^n$/i)
680 exit if STDIN.gets.match(/^n$/i)
681 end
681 end
682 project.trackers << TRACKER_BUG unless project.trackers.include?(TRACKER_BUG)
682 project.trackers << TRACKER_BUG unless project.trackers.include?(TRACKER_BUG)
683 project.trackers << TRACKER_FEATURE unless project.trackers.include?(TRACKER_FEATURE)
683 project.trackers << TRACKER_FEATURE unless project.trackers.include?(TRACKER_FEATURE)
684 @target_project = project.new_record? ? nil : project
684 @target_project = project.new_record? ? nil : project
685 @target_project.reload
685 @target_project.reload
686 end
686 end
687
687
688 def self.connection_params
688 def self.connection_params
689 if %w(sqlite sqlite3).include?(trac_adapter)
689 if %w(sqlite sqlite3).include?(trac_adapter)
690 {:adapter => trac_adapter,
690 {:adapter => trac_adapter,
691 :database => trac_db_path}
691 :database => trac_db_path}
692 else
692 else
693 {:adapter => trac_adapter,
693 {:adapter => trac_adapter,
694 :database => trac_db_name,
694 :database => trac_db_name,
695 :host => trac_db_host,
695 :host => trac_db_host,
696 :port => trac_db_port,
696 :port => trac_db_port,
697 :username => trac_db_username,
697 :username => trac_db_username,
698 :password => trac_db_password,
698 :password => trac_db_password,
699 :schema_search_path => trac_db_schema
699 :schema_search_path => trac_db_schema
700 }
700 }
701 end
701 end
702 end
702 end
703
703
704 def self.establish_connection
704 def self.establish_connection
705 constants.each do |const|
705 constants.each do |const|
706 klass = const_get(const)
706 klass = const_get(const)
707 next unless klass.respond_to? 'establish_connection'
707 next unless klass.respond_to? 'establish_connection'
708 klass.establish_connection connection_params
708 klass.establish_connection connection_params
709 end
709 end
710 end
710 end
711
711
712 private
712 private
713 def self.encode(text)
713 def self.encode(text)
714 @ic.iconv text
714 @ic.iconv text
715 rescue
715 rescue
716 text
716 text
717 end
717 end
718 end
718 end
719
719
720 puts
720 puts
721 if Redmine::DefaultData::Loader.no_data?
721 if Redmine::DefaultData::Loader.no_data?
722 puts "Redmine configuration need to be loaded before importing data."
722 puts "Redmine configuration need to be loaded before importing data."
723 puts "Please, run this first:"
723 puts "Please, run this first:"
724 puts
724 puts
725 puts " rake redmine:load_default_data RAILS_ENV=\"#{ENV['RAILS_ENV']}\""
725 puts " rake redmine:load_default_data RAILS_ENV=\"#{ENV['RAILS_ENV']}\""
726 exit
726 exit
727 end
727 end
728
728
729 puts "WARNING: a new project will be added to Redmine during this process."
729 puts "WARNING: a new project will be added to Redmine during this process."
730 print "Are you sure you want to continue ? [y/N] "
730 print "Are you sure you want to continue ? [y/N] "
731 STDOUT.flush
731 STDOUT.flush
732 break unless STDIN.gets.match(/^y$/i)
732 break unless STDIN.gets.match(/^y$/i)
733 puts
733 puts
734
734
735 def prompt(text, options = {}, &block)
735 def prompt(text, options = {}, &block)
736 default = options[:default] || ''
736 default = options[:default] || ''
737 while true
737 while true
738 print "#{text} [#{default}]: "
738 print "#{text} [#{default}]: "
739 STDOUT.flush
739 STDOUT.flush
740 value = STDIN.gets.chomp!
740 value = STDIN.gets.chomp!
741 value = default if value.blank?
741 value = default if value.blank?
742 break if yield value
742 break if yield value
743 end
743 end
744 end
744 end
745
745
746 DEFAULT_PORTS = {'mysql' => 3306, 'postgresql' => 5432}
746 DEFAULT_PORTS = {'mysql' => 3306, 'postgresql' => 5432}
747
747
748 prompt('Trac directory') {|directory| TracMigrate.set_trac_directory directory.strip}
748 prompt('Trac directory') {|directory| TracMigrate.set_trac_directory directory.strip}
749 prompt('Trac database adapter (sqlite, sqlite3, mysql, postgresql)', :default => 'sqlite') {|adapter| TracMigrate.set_trac_adapter adapter}
749 prompt('Trac database adapter (sqlite, sqlite3, mysql, postgresql)', :default => 'sqlite') {|adapter| TracMigrate.set_trac_adapter adapter}
750 unless %w(sqlite sqlite3).include?(TracMigrate.trac_adapter)
750 unless %w(sqlite sqlite3).include?(TracMigrate.trac_adapter)
751 prompt('Trac database host', :default => 'localhost') {|host| TracMigrate.set_trac_db_host host}
751 prompt('Trac database host', :default => 'localhost') {|host| TracMigrate.set_trac_db_host host}
752 prompt('Trac database port', :default => DEFAULT_PORTS[TracMigrate.trac_adapter]) {|port| TracMigrate.set_trac_db_port port}
752 prompt('Trac database port', :default => DEFAULT_PORTS[TracMigrate.trac_adapter]) {|port| TracMigrate.set_trac_db_port port}
753 prompt('Trac database name') {|name| TracMigrate.set_trac_db_name name}
753 prompt('Trac database name') {|name| TracMigrate.set_trac_db_name name}
754 prompt('Trac database schema', :default => 'public') {|schema| TracMigrate.set_trac_db_schema schema}
754 prompt('Trac database schema', :default => 'public') {|schema| TracMigrate.set_trac_db_schema schema}
755 prompt('Trac database username') {|username| TracMigrate.set_trac_db_username username}
755 prompt('Trac database username') {|username| TracMigrate.set_trac_db_username username}
756 prompt('Trac database password') {|password| TracMigrate.set_trac_db_password password}
756 prompt('Trac database password') {|password| TracMigrate.set_trac_db_password password}
757 end
757 end
758 prompt('Trac database encoding', :default => 'UTF-8') {|encoding| TracMigrate.encoding encoding}
758 prompt('Trac database encoding', :default => 'UTF-8') {|encoding| TracMigrate.encoding encoding}
759 prompt('Target project identifier') {|identifier| TracMigrate.target_project_identifier identifier}
759 prompt('Target project identifier') {|identifier| TracMigrate.target_project_identifier identifier}
760 puts
760 puts
761
761
762 # Turn off email notifications
762 # Turn off email notifications
763 Setting.notified_events = []
763 Setting.notified_events = []
764
764
765 TracMigrate.migrate
765 TracMigrate.migrate
766 end
766 end
767 end
767 end
768
768
General Comments 0
You need to be logged in to leave comments. Login now