##// END OF EJS Templates
Merged r8103 from trunk (#9737)....
Jean-Philippe Lang -
r8037:1848fcd91e07
parent child
Show More
@@ -1,1024 +1,1040
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class IssueTest < ActiveSupport::TestCase
21 21 fixtures :projects, :users, :members, :member_roles, :roles,
22 22 :trackers, :projects_trackers,
23 23 :enabled_modules,
24 24 :versions,
25 25 :issue_statuses, :issue_categories, :issue_relations, :workflows,
26 26 :enumerations,
27 27 :issues,
28 28 :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values,
29 29 :time_entries
30 30
31 31 def test_create
32 32 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'test_create', :description => 'IssueTest#test_create', :estimated_hours => '1:30')
33 33 assert issue.save
34 34 issue.reload
35 35 assert_equal 1.5, issue.estimated_hours
36 36 end
37 37
38 38 def test_create_minimal
39 39 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'test_create')
40 40 assert issue.save
41 41 assert issue.description.nil?
42 42 end
43 43
44 44 def test_create_with_required_custom_field
45 45 field = IssueCustomField.find_by_name('Database')
46 46 field.update_attribute(:is_required, true)
47 47
48 48 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => 'test_create', :description => 'IssueTest#test_create_with_required_custom_field')
49 49 assert issue.available_custom_fields.include?(field)
50 50 # No value for the custom field
51 51 assert !issue.save
52 52 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
53 53 # Blank value
54 54 issue.custom_field_values = { field.id => '' }
55 55 assert !issue.save
56 56 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
57 57 # Invalid value
58 58 issue.custom_field_values = { field.id => 'SQLServer' }
59 59 assert !issue.save
60 60 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
61 61 # Valid value
62 62 issue.custom_field_values = { field.id => 'PostgreSQL' }
63 63 assert issue.save
64 64 issue.reload
65 65 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
66 66 end
67 67
68 68 def assert_visibility_match(user, issues)
69 69 assert_equal issues.collect(&:id).sort, Issue.all.select {|issue| issue.visible?(user)}.collect(&:id).sort
70 70 end
71 71
72 72 def test_visible_scope_for_anonymous
73 73 # Anonymous user should see issues of public projects only
74 74 issues = Issue.visible(User.anonymous).all
75 75 assert issues.any?
76 76 assert_nil issues.detect {|issue| !issue.project.is_public?}
77 77 assert_nil issues.detect {|issue| issue.is_private?}
78 78 assert_visibility_match User.anonymous, issues
79 79 end
80 80
81 81 def test_visible_scope_for_anonymous_with_own_issues_visibility
82 82 Role.anonymous.update_attribute :issues_visibility, 'own'
83 83 Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => User.anonymous.id, :subject => 'Issue by anonymous')
84 84
85 85 issues = Issue.visible(User.anonymous).all
86 86 assert issues.any?
87 87 assert_nil issues.detect {|issue| issue.author != User.anonymous}
88 88 assert_visibility_match User.anonymous, issues
89 89 end
90 90
91 91 def test_visible_scope_for_anonymous_without_view_issues_permissions
92 92 # Anonymous user should not see issues without permission
93 93 Role.anonymous.remove_permission!(:view_issues)
94 94 issues = Issue.visible(User.anonymous).all
95 95 assert issues.empty?
96 96 assert_visibility_match User.anonymous, issues
97 97 end
98 98
99 99 def test_visible_scope_for_non_member
100 100 user = User.find(9)
101 101 assert user.projects.empty?
102 102 # Non member user should see issues of public projects only
103 103 issues = Issue.visible(user).all
104 104 assert issues.any?
105 105 assert_nil issues.detect {|issue| !issue.project.is_public?}
106 106 assert_nil issues.detect {|issue| issue.is_private?}
107 107 assert_visibility_match user, issues
108 108 end
109 109
110 110 def test_visible_scope_for_non_member_with_own_issues_visibility
111 111 Role.non_member.update_attribute :issues_visibility, 'own'
112 112 Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 9, :subject => 'Issue by non member')
113 113 user = User.find(9)
114 114
115 115 issues = Issue.visible(user).all
116 116 assert issues.any?
117 117 assert_nil issues.detect {|issue| issue.author != user}
118 118 assert_visibility_match user, issues
119 119 end
120 120
121 121 def test_visible_scope_for_non_member_without_view_issues_permissions
122 122 # Non member user should not see issues without permission
123 123 Role.non_member.remove_permission!(:view_issues)
124 124 user = User.find(9)
125 125 assert user.projects.empty?
126 126 issues = Issue.visible(user).all
127 127 assert issues.empty?
128 128 assert_visibility_match user, issues
129 129 end
130 130
131 131 def test_visible_scope_for_member
132 132 user = User.find(9)
133 133 # User should see issues of projects for which he has view_issues permissions only
134 134 Role.non_member.remove_permission!(:view_issues)
135 135 Member.create!(:principal => user, :project_id => 3, :role_ids => [2])
136 136 issues = Issue.visible(user).all
137 137 assert issues.any?
138 138 assert_nil issues.detect {|issue| issue.project_id != 3}
139 139 assert_nil issues.detect {|issue| issue.is_private?}
140 140 assert_visibility_match user, issues
141 141 end
142 142
143 143 def test_visible_scope_for_admin
144 144 user = User.find(1)
145 145 user.members.each(&:destroy)
146 146 assert user.projects.empty?
147 147 issues = Issue.visible(user).all
148 148 assert issues.any?
149 149 # Admin should see issues on private projects that he does not belong to
150 150 assert issues.detect {|issue| !issue.project.is_public?}
151 151 # Admin should see private issues of other users
152 152 assert issues.detect {|issue| issue.is_private? && issue.author != user}
153 153 assert_visibility_match user, issues
154 154 end
155 155
156 156 def test_visible_scope_with_project
157 157 project = Project.find(1)
158 158 issues = Issue.visible(User.find(2), :project => project).all
159 159 projects = issues.collect(&:project).uniq
160 160 assert_equal 1, projects.size
161 161 assert_equal project, projects.first
162 162 end
163 163
164 164 def test_visible_scope_with_project_and_subprojects
165 165 project = Project.find(1)
166 166 issues = Issue.visible(User.find(2), :project => project, :with_subprojects => true).all
167 167 projects = issues.collect(&:project).uniq
168 168 assert projects.size > 1
169 169 assert_equal [], projects.select {|p| !p.is_or_is_descendant_of?(project)}
170 170 end
171 171
172 172 def test_visible_and_nested_set_scopes
173 173 assert_equal 0, Issue.find(1).descendants.visible.all.size
174 174 end
175 175
176 176 def test_errors_full_messages_should_include_custom_fields_errors
177 177 field = IssueCustomField.find_by_name('Database')
178 178
179 179 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => 'test_create', :description => 'IssueTest#test_create_with_required_custom_field')
180 180 assert issue.available_custom_fields.include?(field)
181 181 # Invalid value
182 182 issue.custom_field_values = { field.id => 'SQLServer' }
183 183
184 184 assert !issue.valid?
185 185 assert_equal 1, issue.errors.full_messages.size
186 186 assert_equal "Database #{I18n.translate('activerecord.errors.messages.inclusion')}", issue.errors.full_messages.first
187 187 end
188 188
189 189 def test_update_issue_with_required_custom_field
190 190 field = IssueCustomField.find_by_name('Database')
191 191 field.update_attribute(:is_required, true)
192 192
193 193 issue = Issue.find(1)
194 194 assert_nil issue.custom_value_for(field)
195 195 assert issue.available_custom_fields.include?(field)
196 196 # No change to custom values, issue can be saved
197 197 assert issue.save
198 198 # Blank value
199 199 issue.custom_field_values = { field.id => '' }
200 200 assert !issue.save
201 201 # Valid value
202 202 issue.custom_field_values = { field.id => 'PostgreSQL' }
203 203 assert issue.save
204 204 issue.reload
205 205 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
206 206 end
207 207
208 208 def test_should_not_update_attributes_if_custom_fields_validation_fails
209 209 issue = Issue.find(1)
210 210 field = IssueCustomField.find_by_name('Database')
211 211 assert issue.available_custom_fields.include?(field)
212 212
213 213 issue.custom_field_values = { field.id => 'Invalid' }
214 214 issue.subject = 'Should be not be saved'
215 215 assert !issue.save
216 216
217 217 issue.reload
218 218 assert_equal "Can't print recipes", issue.subject
219 219 end
220 220
221 221 def test_should_not_recreate_custom_values_objects_on_update
222 222 field = IssueCustomField.find_by_name('Database')
223 223
224 224 issue = Issue.find(1)
225 225 issue.custom_field_values = { field.id => 'PostgreSQL' }
226 226 assert issue.save
227 227 custom_value = issue.custom_value_for(field)
228 228 issue.reload
229 229 issue.custom_field_values = { field.id => 'MySQL' }
230 230 assert issue.save
231 231 issue.reload
232 232 assert_equal custom_value.id, issue.custom_value_for(field).id
233 233 end
234 234
235 def test_should_not_update_custom_fields_on_changing_tracker_with_different_custom_fields
236 issue = Issue.new(:project_id => 1)
237 issue.attributes = {:tracker_id => 1, :author_id => 1, :status_id => 1, :subject => 'Test', :custom_field_values => {'2' => 'Test'}}
238 issue.save!
239
240 assert !Tracker.find(2).custom_field_ids.include?(2)
241
242 issue = Issue.find(issue.id)
243 issue.attributes = {:tracker_id => 2, :custom_field_values => {'1' => ''}}
244
245 issue = Issue.find(issue.id)
246 custom_value = issue.custom_value_for(2)
247 assert_not_nil custom_value
248 assert_equal 'Test', custom_value.value
249 end
250
235 251 def test_assigning_tracker_id_should_reload_custom_fields_values
236 252 issue = Issue.new(:project => Project.find(1))
237 253 assert issue.custom_field_values.empty?
238 254 issue.tracker_id = 1
239 255 assert issue.custom_field_values.any?
240 256 end
241 257
242 258 def test_assigning_attributes_should_assign_tracker_id_first
243 259 attributes = ActiveSupport::OrderedHash.new
244 260 attributes['custom_field_values'] = { '1' => 'MySQL' }
245 261 attributes['tracker_id'] = '1'
246 262 issue = Issue.new(:project => Project.find(1))
247 263 issue.attributes = attributes
248 264 assert_not_nil issue.custom_value_for(1)
249 265 assert_equal 'MySQL', issue.custom_value_for(1).value
250 266 end
251 267
252 268 def test_should_update_issue_with_disabled_tracker
253 269 p = Project.find(1)
254 270 issue = Issue.find(1)
255 271
256 272 p.trackers.delete(issue.tracker)
257 273 assert !p.trackers.include?(issue.tracker)
258 274
259 275 issue.reload
260 276 issue.subject = 'New subject'
261 277 assert issue.save
262 278 end
263 279
264 280 def test_should_not_set_a_disabled_tracker
265 281 p = Project.find(1)
266 282 p.trackers.delete(Tracker.find(2))
267 283
268 284 issue = Issue.find(1)
269 285 issue.tracker_id = 2
270 286 issue.subject = 'New subject'
271 287 assert !issue.save
272 288 assert_not_nil issue.errors.on(:tracker_id)
273 289 end
274 290
275 291 def test_category_based_assignment
276 292 issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'Assignment test', :description => 'Assignment test', :category_id => 1)
277 293 assert_equal IssueCategory.find(1).assigned_to, issue.assigned_to
278 294 end
279 295
280 296 def test_new_statuses_allowed_to
281 297 Workflow.delete_all
282 298
283 299 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2, :author => false, :assignee => false)
284 300 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3, :author => true, :assignee => false)
285 301 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4, :author => false, :assignee => true)
286 302 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 5, :author => true, :assignee => true)
287 303 status = IssueStatus.find(1)
288 304 role = Role.find(1)
289 305 tracker = Tracker.find(1)
290 306 user = User.find(2)
291 307
292 308 issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1)
293 309 assert_equal [1, 2], issue.new_statuses_allowed_to(user).map(&:id)
294 310
295 311 issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1, :author => user)
296 312 assert_equal [1, 2, 3, 5], issue.new_statuses_allowed_to(user).map(&:id)
297 313
298 314 issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1, :assigned_to => user)
299 315 assert_equal [1, 2, 4, 5], issue.new_statuses_allowed_to(user).map(&:id)
300 316
301 317 issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1, :author => user, :assigned_to => user)
302 318 assert_equal [1, 2, 3, 4, 5], issue.new_statuses_allowed_to(user).map(&:id)
303 319 end
304 320
305 321 def test_copy
306 322 issue = Issue.new.copy_from(1)
307 323 assert issue.save
308 324 issue.reload
309 325 orig = Issue.find(1)
310 326 assert_equal orig.subject, issue.subject
311 327 assert_equal orig.tracker, issue.tracker
312 328 assert_equal "125", issue.custom_value_for(2).value
313 329 end
314 330
315 331 def test_copy_should_copy_status
316 332 orig = Issue.find(8)
317 333 assert orig.status != IssueStatus.default
318 334
319 335 issue = Issue.new.copy_from(orig)
320 336 assert issue.save
321 337 issue.reload
322 338 assert_equal orig.status, issue.status
323 339 end
324 340
325 341 def test_should_close_duplicates
326 342 # Create 3 issues
327 343 issue1 = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'Duplicates test', :description => 'Duplicates test')
328 344 assert issue1.save
329 345 issue2 = issue1.clone
330 346 assert issue2.save
331 347 issue3 = issue1.clone
332 348 assert issue3.save
333 349
334 350 # 2 is a dupe of 1
335 351 IssueRelation.create(:issue_from => issue2, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
336 352 # And 3 is a dupe of 2
337 353 IssueRelation.create(:issue_from => issue3, :issue_to => issue2, :relation_type => IssueRelation::TYPE_DUPLICATES)
338 354 # And 3 is a dupe of 1 (circular duplicates)
339 355 IssueRelation.create(:issue_from => issue3, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
340 356
341 357 assert issue1.reload.duplicates.include?(issue2)
342 358
343 359 # Closing issue 1
344 360 issue1.init_journal(User.find(:first), "Closing issue1")
345 361 issue1.status = IssueStatus.find :first, :conditions => {:is_closed => true}
346 362 assert issue1.save
347 363 # 2 and 3 should be also closed
348 364 assert issue2.reload.closed?
349 365 assert issue3.reload.closed?
350 366 end
351 367
352 368 def test_should_not_close_duplicated_issue
353 369 # Create 3 issues
354 370 issue1 = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'Duplicates test', :description => 'Duplicates test')
355 371 assert issue1.save
356 372 issue2 = issue1.clone
357 373 assert issue2.save
358 374
359 375 # 2 is a dupe of 1
360 376 IssueRelation.create(:issue_from => issue2, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
361 377 # 2 is a dup of 1 but 1 is not a duplicate of 2
362 378 assert !issue2.reload.duplicates.include?(issue1)
363 379
364 380 # Closing issue 2
365 381 issue2.init_journal(User.find(:first), "Closing issue2")
366 382 issue2.status = IssueStatus.find :first, :conditions => {:is_closed => true}
367 383 assert issue2.save
368 384 # 1 should not be also closed
369 385 assert !issue1.reload.closed?
370 386 end
371 387
372 388 def test_assignable_versions
373 389 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 1, :subject => 'New issue')
374 390 assert_equal ['open'], issue.assignable_versions.collect(&:status).uniq
375 391 end
376 392
377 393 def test_should_not_be_able_to_assign_a_new_issue_to_a_closed_version
378 394 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 1, :subject => 'New issue')
379 395 assert !issue.save
380 396 assert_not_nil issue.errors.on(:fixed_version_id)
381 397 end
382 398
383 399 def test_should_not_be_able_to_assign_a_new_issue_to_a_locked_version
384 400 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 2, :subject => 'New issue')
385 401 assert !issue.save
386 402 assert_not_nil issue.errors.on(:fixed_version_id)
387 403 end
388 404
389 405 def test_should_be_able_to_assign_a_new_issue_to_an_open_version
390 406 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 3, :subject => 'New issue')
391 407 assert issue.save
392 408 end
393 409
394 410 def test_should_be_able_to_update_an_issue_assigned_to_a_closed_version
395 411 issue = Issue.find(11)
396 412 assert_equal 'closed', issue.fixed_version.status
397 413 issue.subject = 'Subject changed'
398 414 assert issue.save
399 415 end
400 416
401 417 def test_should_not_be_able_to_reopen_an_issue_assigned_to_a_closed_version
402 418 issue = Issue.find(11)
403 419 issue.status_id = 1
404 420 assert !issue.save
405 421 assert_not_nil issue.errors.on_base
406 422 end
407 423
408 424 def test_should_be_able_to_reopen_and_reassign_an_issue_assigned_to_a_closed_version
409 425 issue = Issue.find(11)
410 426 issue.status_id = 1
411 427 issue.fixed_version_id = 3
412 428 assert issue.save
413 429 end
414 430
415 431 def test_should_be_able_to_reopen_an_issue_assigned_to_a_locked_version
416 432 issue = Issue.find(12)
417 433 assert_equal 'locked', issue.fixed_version.status
418 434 issue.status_id = 1
419 435 assert issue.save
420 436 end
421 437
422 438 def test_move_to_another_project_with_same_category
423 439 issue = Issue.find(1)
424 440 assert issue.move_to_project(Project.find(2))
425 441 issue.reload
426 442 assert_equal 2, issue.project_id
427 443 # Category changes
428 444 assert_equal 4, issue.category_id
429 445 # Make sure time entries were move to the target project
430 446 assert_equal 2, issue.time_entries.first.project_id
431 447 end
432 448
433 449 def test_move_to_another_project_without_same_category
434 450 issue = Issue.find(2)
435 451 assert issue.move_to_project(Project.find(2))
436 452 issue.reload
437 453 assert_equal 2, issue.project_id
438 454 # Category cleared
439 455 assert_nil issue.category_id
440 456 end
441 457
442 458 def test_move_to_another_project_should_clear_fixed_version_when_not_shared
443 459 issue = Issue.find(1)
444 460 issue.update_attribute(:fixed_version_id, 1)
445 461 assert issue.move_to_project(Project.find(2))
446 462 issue.reload
447 463 assert_equal 2, issue.project_id
448 464 # Cleared fixed_version
449 465 assert_equal nil, issue.fixed_version
450 466 end
451 467
452 468 def test_move_to_another_project_should_keep_fixed_version_when_shared_with_the_target_project
453 469 issue = Issue.find(1)
454 470 issue.update_attribute(:fixed_version_id, 4)
455 471 assert issue.move_to_project(Project.find(5))
456 472 issue.reload
457 473 assert_equal 5, issue.project_id
458 474 # Keep fixed_version
459 475 assert_equal 4, issue.fixed_version_id
460 476 end
461 477
462 478 def test_move_to_another_project_should_clear_fixed_version_when_not_shared_with_the_target_project
463 479 issue = Issue.find(1)
464 480 issue.update_attribute(:fixed_version_id, 1)
465 481 assert issue.move_to_project(Project.find(5))
466 482 issue.reload
467 483 assert_equal 5, issue.project_id
468 484 # Cleared fixed_version
469 485 assert_equal nil, issue.fixed_version
470 486 end
471 487
472 488 def test_move_to_another_project_should_keep_fixed_version_when_shared_systemwide
473 489 issue = Issue.find(1)
474 490 issue.update_attribute(:fixed_version_id, 7)
475 491 assert issue.move_to_project(Project.find(2))
476 492 issue.reload
477 493 assert_equal 2, issue.project_id
478 494 # Keep fixed_version
479 495 assert_equal 7, issue.fixed_version_id
480 496 end
481 497
482 498 def test_move_to_another_project_with_disabled_tracker
483 499 issue = Issue.find(1)
484 500 target = Project.find(2)
485 501 target.tracker_ids = [3]
486 502 target.save
487 503 assert_equal false, issue.move_to_project(target)
488 504 issue.reload
489 505 assert_equal 1, issue.project_id
490 506 end
491 507
492 508 def test_copy_to_the_same_project
493 509 issue = Issue.find(1)
494 510 copy = nil
495 511 assert_difference 'Issue.count' do
496 512 copy = issue.move_to_project(issue.project, nil, :copy => true)
497 513 end
498 514 assert_kind_of Issue, copy
499 515 assert_equal issue.project, copy.project
500 516 assert_equal "125", copy.custom_value_for(2).value
501 517 end
502 518
503 519 def test_copy_to_another_project_and_tracker
504 520 issue = Issue.find(1)
505 521 copy = nil
506 522 assert_difference 'Issue.count' do
507 523 copy = issue.move_to_project(Project.find(3), Tracker.find(2), :copy => true)
508 524 end
509 525 copy.reload
510 526 assert_kind_of Issue, copy
511 527 assert_equal Project.find(3), copy.project
512 528 assert_equal Tracker.find(2), copy.tracker
513 529 # Custom field #2 is not associated with target tracker
514 530 assert_nil copy.custom_value_for(2)
515 531 end
516 532
517 533 context "#move_to_project" do
518 534 context "as a copy" do
519 535 setup do
520 536 @issue = Issue.find(1)
521 537 @copy = nil
522 538 end
523 539
524 540 should "not create a journal" do
525 541 @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:assigned_to_id => 3}})
526 542 assert_equal 0, @copy.reload.journals.size
527 543 end
528 544
529 545 should "allow assigned_to changes" do
530 546 @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:assigned_to_id => 3}})
531 547 assert_equal 3, @copy.assigned_to_id
532 548 end
533 549
534 550 should "allow status changes" do
535 551 @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:status_id => 2}})
536 552 assert_equal 2, @copy.status_id
537 553 end
538 554
539 555 should "allow start date changes" do
540 556 date = Date.today
541 557 @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:start_date => date}})
542 558 assert_equal date, @copy.start_date
543 559 end
544 560
545 561 should "allow due date changes" do
546 562 date = Date.today
547 563 @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:due_date => date}})
548 564
549 565 assert_equal date, @copy.due_date
550 566 end
551 567
552 568 should "set current user as author" do
553 569 User.current = User.find(9)
554 570 @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {}})
555 571
556 572 assert_equal User.current, @copy.author
557 573 end
558 574
559 575 should "keep journal notes" do
560 576 date = Date.today
561 577 notes = "Notes added when copying"
562 578 User.current = User.find(9)
563 579 @issue.init_journal(User.current, notes)
564 580 @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:start_date => date}})
565 581
566 582 assert_equal 1, @copy.journals.size
567 583 journal = @copy.journals.first
568 584 assert_equal 0, journal.details.size
569 585 assert_equal notes, journal.notes
570 586 end
571 587 end
572 588 end
573 589
574 590 def test_recipients_should_not_include_users_that_cannot_view_the_issue
575 591 issue = Issue.find(12)
576 592 assert issue.recipients.include?(issue.author.mail)
577 593 # move the issue to a private project
578 594 copy = issue.move_to_project(Project.find(5), Tracker.find(2), :copy => true)
579 595 # author is not a member of project anymore
580 596 assert !copy.recipients.include?(copy.author.mail)
581 597 end
582 598
583 599 def test_watcher_recipients_should_not_include_users_that_cannot_view_the_issue
584 600 user = User.find(3)
585 601 issue = Issue.find(9)
586 602 Watcher.create!(:user => user, :watchable => issue)
587 603 assert issue.watched_by?(user)
588 604 assert !issue.watcher_recipients.include?(user.mail)
589 605 end
590 606
591 607 def test_issue_destroy
592 608 Issue.find(1).destroy
593 609 assert_nil Issue.find_by_id(1)
594 610 assert_nil TimeEntry.find_by_issue_id(1)
595 611 end
596 612
597 613 def test_blocked
598 614 blocked_issue = Issue.find(9)
599 615 blocking_issue = Issue.find(10)
600 616
601 617 assert blocked_issue.blocked?
602 618 assert !blocking_issue.blocked?
603 619 end
604 620
605 621 def test_blocked_issues_dont_allow_closed_statuses
606 622 blocked_issue = Issue.find(9)
607 623
608 624 allowed_statuses = blocked_issue.new_statuses_allowed_to(users(:users_002))
609 625 assert !allowed_statuses.empty?
610 626 closed_statuses = allowed_statuses.select {|st| st.is_closed?}
611 627 assert closed_statuses.empty?
612 628 end
613 629
614 630 def test_unblocked_issues_allow_closed_statuses
615 631 blocking_issue = Issue.find(10)
616 632
617 633 allowed_statuses = blocking_issue.new_statuses_allowed_to(users(:users_002))
618 634 assert !allowed_statuses.empty?
619 635 closed_statuses = allowed_statuses.select {|st| st.is_closed?}
620 636 assert !closed_statuses.empty?
621 637 end
622 638
623 639 def test_rescheduling_an_issue_should_reschedule_following_issue
624 640 issue1 = Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => '-', :start_date => Date.today, :due_date => Date.today + 2)
625 641 issue2 = Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => '-', :start_date => Date.today, :due_date => Date.today + 2)
626 642 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_PRECEDES)
627 643 assert_equal issue1.due_date + 1, issue2.reload.start_date
628 644
629 645 issue1.due_date = Date.today + 5
630 646 issue1.save!
631 647 assert_equal issue1.due_date + 1, issue2.reload.start_date
632 648 end
633 649
634 650 def test_overdue
635 651 assert Issue.new(:due_date => 1.day.ago.to_date).overdue?
636 652 assert !Issue.new(:due_date => Date.today).overdue?
637 653 assert !Issue.new(:due_date => 1.day.from_now.to_date).overdue?
638 654 assert !Issue.new(:due_date => nil).overdue?
639 655 assert !Issue.new(:due_date => 1.day.ago.to_date, :status => IssueStatus.find(:first, :conditions => {:is_closed => true})).overdue?
640 656 end
641 657
642 658 context "#behind_schedule?" do
643 659 should "be false if the issue has no start_date" do
644 660 assert !Issue.new(:start_date => nil, :due_date => 1.day.from_now.to_date, :done_ratio => 0).behind_schedule?
645 661 end
646 662
647 663 should "be false if the issue has no end_date" do
648 664 assert !Issue.new(:start_date => 1.day.from_now.to_date, :due_date => nil, :done_ratio => 0).behind_schedule?
649 665 end
650 666
651 667 should "be false if the issue has more done than it's calendar time" do
652 668 assert !Issue.new(:start_date => 50.days.ago.to_date, :due_date => 50.days.from_now.to_date, :done_ratio => 90).behind_schedule?
653 669 end
654 670
655 671 should "be true if the issue hasn't been started at all" do
656 672 assert Issue.new(:start_date => 1.day.ago.to_date, :due_date => 1.day.from_now.to_date, :done_ratio => 0).behind_schedule?
657 673 end
658 674
659 675 should "be true if the issue has used more calendar time than it's done ratio" do
660 676 assert Issue.new(:start_date => 100.days.ago.to_date, :due_date => Date.today, :done_ratio => 90).behind_schedule?
661 677 end
662 678 end
663 679
664 680 context "#assignable_users" do
665 681 should "be Users" do
666 682 assert_kind_of User, Issue.find(1).assignable_users.first
667 683 end
668 684
669 685 should "include the issue author" do
670 686 project = Project.find(1)
671 687 non_project_member = User.generate!
672 688 issue = Issue.generate_for_project!(project, :author => non_project_member)
673 689
674 690 assert issue.assignable_users.include?(non_project_member)
675 691 end
676 692
677 693 should "include the current assignee" do
678 694 project = Project.find(1)
679 695 user = User.generate!
680 696 issue = Issue.generate_for_project!(project, :assigned_to => user)
681 697 user.lock!
682 698
683 699 assert Issue.find(issue.id).assignable_users.include?(user)
684 700 end
685 701
686 702 should "not show the issue author twice" do
687 703 assignable_user_ids = Issue.find(1).assignable_users.collect(&:id)
688 704 assert_equal 2, assignable_user_ids.length
689 705
690 706 assignable_user_ids.each do |user_id|
691 707 assert_equal 1, assignable_user_ids.select {|i| i == user_id}.length, "User #{user_id} appears more or less than once"
692 708 end
693 709 end
694 710 end
695 711
696 712 def test_create_should_send_email_notification
697 713 ActionMailer::Base.deliveries.clear
698 714 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'test_create', :estimated_hours => '1:30')
699 715
700 716 assert issue.save
701 717 assert_equal 1, ActionMailer::Base.deliveries.size
702 718 end
703 719
704 720 def test_stale_issue_should_not_send_email_notification
705 721 ActionMailer::Base.deliveries.clear
706 722 issue = Issue.find(1)
707 723 stale = Issue.find(1)
708 724
709 725 issue.init_journal(User.find(1))
710 726 issue.subject = 'Subjet update'
711 727 assert issue.save
712 728 assert_equal 1, ActionMailer::Base.deliveries.size
713 729 ActionMailer::Base.deliveries.clear
714 730
715 731 stale.init_journal(User.find(1))
716 732 stale.subject = 'Another subjet update'
717 733 assert_raise ActiveRecord::StaleObjectError do
718 734 stale.save
719 735 end
720 736 assert ActionMailer::Base.deliveries.empty?
721 737 end
722 738
723 739 def test_journalized_description
724 740 IssueCustomField.delete_all
725 741
726 742 i = Issue.first
727 743 old_description = i.description
728 744 new_description = "This is the new description"
729 745
730 746 i.init_journal(User.find(2))
731 747 i.description = new_description
732 748 assert_difference 'Journal.count', 1 do
733 749 assert_difference 'JournalDetail.count', 1 do
734 750 i.save!
735 751 end
736 752 end
737 753
738 754 detail = JournalDetail.first(:order => 'id DESC')
739 755 assert_equal i, detail.journal.journalized
740 756 assert_equal 'attr', detail.property
741 757 assert_equal 'description', detail.prop_key
742 758 assert_equal old_description, detail.old_value
743 759 assert_equal new_description, detail.value
744 760 end
745 761
746 762 def test_blank_descriptions_should_not_be_journalized
747 763 IssueCustomField.delete_all
748 764 Issue.update_all("description = NULL", "id=1")
749 765
750 766 i = Issue.find(1)
751 767 i.init_journal(User.find(2))
752 768 i.subject = "blank description"
753 769 i.description = "\r\n"
754 770
755 771 assert_difference 'Journal.count', 1 do
756 772 assert_difference 'JournalDetail.count', 1 do
757 773 i.save!
758 774 end
759 775 end
760 776 end
761 777
762 778 def test_description_eol_should_be_normalized
763 779 i = Issue.new(:description => "CR \r LF \n CRLF \r\n")
764 780 assert_equal "CR \r\n LF \r\n CRLF \r\n", i.description
765 781 end
766 782
767 783 def test_saving_twice_should_not_duplicate_journal_details
768 784 i = Issue.find(:first)
769 785 i.init_journal(User.find(2), 'Some notes')
770 786 # initial changes
771 787 i.subject = 'New subject'
772 788 i.done_ratio = i.done_ratio + 10
773 789 assert_difference 'Journal.count' do
774 790 assert i.save
775 791 end
776 792 # 1 more change
777 793 i.priority = IssuePriority.find(:first, :conditions => ["id <> ?", i.priority_id])
778 794 assert_no_difference 'Journal.count' do
779 795 assert_difference 'JournalDetail.count', 1 do
780 796 i.save
781 797 end
782 798 end
783 799 # no more change
784 800 assert_no_difference 'Journal.count' do
785 801 assert_no_difference 'JournalDetail.count' do
786 802 i.save
787 803 end
788 804 end
789 805 end
790 806
791 807 def test_all_dependent_issues
792 808 IssueRelation.delete_all
793 809 assert IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => IssueRelation::TYPE_PRECEDES)
794 810 assert IssueRelation.create!(:issue_from => Issue.find(2), :issue_to => Issue.find(3), :relation_type => IssueRelation::TYPE_PRECEDES)
795 811 assert IssueRelation.create!(:issue_from => Issue.find(3), :issue_to => Issue.find(8), :relation_type => IssueRelation::TYPE_PRECEDES)
796 812
797 813 assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort
798 814 end
799 815
800 816 def test_all_dependent_issues_with_persistent_circular_dependency
801 817 IssueRelation.delete_all
802 818 assert IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => IssueRelation::TYPE_PRECEDES)
803 819 assert IssueRelation.create!(:issue_from => Issue.find(2), :issue_to => Issue.find(3), :relation_type => IssueRelation::TYPE_PRECEDES)
804 820 # Validation skipping
805 821 assert IssueRelation.new(:issue_from => Issue.find(3), :issue_to => Issue.find(1), :relation_type => IssueRelation::TYPE_PRECEDES).save(false)
806 822
807 823 assert_equal [2, 3], Issue.find(1).all_dependent_issues.collect(&:id).sort
808 824 end
809 825
810 826 def test_all_dependent_issues_with_persistent_multiple_circular_dependencies
811 827 IssueRelation.delete_all
812 828 assert IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => IssueRelation::TYPE_RELATES)
813 829 assert IssueRelation.create!(:issue_from => Issue.find(2), :issue_to => Issue.find(3), :relation_type => IssueRelation::TYPE_RELATES)
814 830 assert IssueRelation.create!(:issue_from => Issue.find(3), :issue_to => Issue.find(8), :relation_type => IssueRelation::TYPE_RELATES)
815 831 # Validation skipping
816 832 assert IssueRelation.new(:issue_from => Issue.find(8), :issue_to => Issue.find(2), :relation_type => IssueRelation::TYPE_RELATES).save(false)
817 833 assert IssueRelation.new(:issue_from => Issue.find(3), :issue_to => Issue.find(1), :relation_type => IssueRelation::TYPE_RELATES).save(false)
818 834
819 835 assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort
820 836 end
821 837
822 838 context "#done_ratio" do
823 839 setup do
824 840 @issue = Issue.find(1)
825 841 @issue_status = IssueStatus.find(1)
826 842 @issue_status.update_attribute(:default_done_ratio, 50)
827 843 @issue2 = Issue.find(2)
828 844 @issue_status2 = IssueStatus.find(2)
829 845 @issue_status2.update_attribute(:default_done_ratio, 0)
830 846 end
831 847
832 848 context "with Setting.issue_done_ratio using the issue_field" do
833 849 setup do
834 850 Setting.issue_done_ratio = 'issue_field'
835 851 end
836 852
837 853 should "read the issue's field" do
838 854 assert_equal 0, @issue.done_ratio
839 855 assert_equal 30, @issue2.done_ratio
840 856 end
841 857 end
842 858
843 859 context "with Setting.issue_done_ratio using the issue_status" do
844 860 setup do
845 861 Setting.issue_done_ratio = 'issue_status'
846 862 end
847 863
848 864 should "read the Issue Status's default done ratio" do
849 865 assert_equal 50, @issue.done_ratio
850 866 assert_equal 0, @issue2.done_ratio
851 867 end
852 868 end
853 869 end
854 870
855 871 context "#update_done_ratio_from_issue_status" do
856 872 setup do
857 873 @issue = Issue.find(1)
858 874 @issue_status = IssueStatus.find(1)
859 875 @issue_status.update_attribute(:default_done_ratio, 50)
860 876 @issue2 = Issue.find(2)
861 877 @issue_status2 = IssueStatus.find(2)
862 878 @issue_status2.update_attribute(:default_done_ratio, 0)
863 879 end
864 880
865 881 context "with Setting.issue_done_ratio using the issue_field" do
866 882 setup do
867 883 Setting.issue_done_ratio = 'issue_field'
868 884 end
869 885
870 886 should "not change the issue" do
871 887 @issue.update_done_ratio_from_issue_status
872 888 @issue2.update_done_ratio_from_issue_status
873 889
874 890 assert_equal 0, @issue.read_attribute(:done_ratio)
875 891 assert_equal 30, @issue2.read_attribute(:done_ratio)
876 892 end
877 893 end
878 894
879 895 context "with Setting.issue_done_ratio using the issue_status" do
880 896 setup do
881 897 Setting.issue_done_ratio = 'issue_status'
882 898 end
883 899
884 900 should "change the issue's done ratio" do
885 901 @issue.update_done_ratio_from_issue_status
886 902 @issue2.update_done_ratio_from_issue_status
887 903
888 904 assert_equal 50, @issue.read_attribute(:done_ratio)
889 905 assert_equal 0, @issue2.read_attribute(:done_ratio)
890 906 end
891 907 end
892 908 end
893 909
894 910 test "#by_tracker" do
895 911 User.current = User.anonymous
896 912 groups = Issue.by_tracker(Project.find(1))
897 913 assert_equal 3, groups.size
898 914 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
899 915 end
900 916
901 917 test "#by_version" do
902 918 User.current = User.anonymous
903 919 groups = Issue.by_version(Project.find(1))
904 920 assert_equal 3, groups.size
905 921 assert_equal 3, groups.inject(0) {|sum, group| sum + group['total'].to_i}
906 922 end
907 923
908 924 test "#by_priority" do
909 925 User.current = User.anonymous
910 926 groups = Issue.by_priority(Project.find(1))
911 927 assert_equal 4, groups.size
912 928 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
913 929 end
914 930
915 931 test "#by_category" do
916 932 User.current = User.anonymous
917 933 groups = Issue.by_category(Project.find(1))
918 934 assert_equal 2, groups.size
919 935 assert_equal 3, groups.inject(0) {|sum, group| sum + group['total'].to_i}
920 936 end
921 937
922 938 test "#by_assigned_to" do
923 939 User.current = User.anonymous
924 940 groups = Issue.by_assigned_to(Project.find(1))
925 941 assert_equal 2, groups.size
926 942 assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i}
927 943 end
928 944
929 945 test "#by_author" do
930 946 User.current = User.anonymous
931 947 groups = Issue.by_author(Project.find(1))
932 948 assert_equal 4, groups.size
933 949 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
934 950 end
935 951
936 952 test "#by_subproject" do
937 953 User.current = User.anonymous
938 954 groups = Issue.by_subproject(Project.find(1))
939 955 # Private descendant not visible
940 956 assert_equal 1, groups.size
941 957 assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i}
942 958 end
943 959
944 960 context ".allowed_target_projects_on_move" do
945 961 should "return all active projects for admin users" do
946 962 User.current = User.find(1)
947 963 assert_equal Project.active.count, Issue.allowed_target_projects_on_move.size
948 964 end
949 965
950 966 should "return allowed projects for non admin users" do
951 967 User.current = User.find(2)
952 968 Role.non_member.remove_permission! :move_issues
953 969 assert_equal 3, Issue.allowed_target_projects_on_move.size
954 970
955 971 Role.non_member.add_permission! :move_issues
956 972 assert_equal Project.active.count, Issue.allowed_target_projects_on_move.size
957 973 end
958 974 end
959 975
960 976 def test_recently_updated_with_limit_scopes
961 977 #should return the last updated issue
962 978 assert_equal 1, Issue.recently_updated.with_limit(1).length
963 979 assert_equal Issue.find(:first, :order => "updated_on DESC"), Issue.recently_updated.with_limit(1).first
964 980 end
965 981
966 982 def test_on_active_projects_scope
967 983 assert Project.find(2).archive
968 984
969 985 before = Issue.on_active_project.length
970 986 # test inclusion to results
971 987 issue = Issue.generate_for_project!(Project.find(1), :tracker => Project.find(2).trackers.first)
972 988 assert_equal before + 1, Issue.on_active_project.length
973 989
974 990 # Move to an archived project
975 991 issue.project = Project.find(2)
976 992 assert issue.save
977 993 assert_equal before, Issue.on_active_project.length
978 994 end
979 995
980 996 context "Issue#recipients" do
981 997 setup do
982 998 @project = Project.find(1)
983 999 @author = User.generate_with_protected!
984 1000 @assignee = User.generate_with_protected!
985 1001 @issue = Issue.generate_for_project!(@project, :assigned_to => @assignee, :author => @author)
986 1002 end
987 1003
988 1004 should "include project recipients" do
989 1005 assert @project.recipients.present?
990 1006 @project.recipients.each do |project_recipient|
991 1007 assert @issue.recipients.include?(project_recipient)
992 1008 end
993 1009 end
994 1010
995 1011 should "include the author if the author is active" do
996 1012 assert @issue.author, "No author set for Issue"
997 1013 assert @issue.recipients.include?(@issue.author.mail)
998 1014 end
999 1015
1000 1016 should "include the assigned to user if the assigned to user is active" do
1001 1017 assert @issue.assigned_to, "No assigned_to set for Issue"
1002 1018 assert @issue.recipients.include?(@issue.assigned_to.mail)
1003 1019 end
1004 1020
1005 1021 should "not include users who opt out of all email" do
1006 1022 @author.update_attribute(:mail_notification, :none)
1007 1023
1008 1024 assert !@issue.recipients.include?(@issue.author.mail)
1009 1025 end
1010 1026
1011 1027 should "not include the issue author if they are only notified of assigned issues" do
1012 1028 @author.update_attribute(:mail_notification, :only_assigned)
1013 1029
1014 1030 assert !@issue.recipients.include?(@issue.author.mail)
1015 1031 end
1016 1032
1017 1033 should "not include the assigned user if they are only notified of owned issues" do
1018 1034 @assignee.update_attribute(:mail_notification, :only_owner)
1019 1035
1020 1036 assert !@issue.recipients.include?(@issue.assigned_to.mail)
1021 1037 end
1022 1038
1023 1039 end
1024 1040 end
@@ -1,112 +1,112
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 module Redmine
19 19 module Acts
20 20 module Customizable
21 21 def self.included(base)
22 22 base.extend ClassMethods
23 23 end
24 24
25 25 module ClassMethods
26 26 def acts_as_customizable(options = {})
27 27 return if self.included_modules.include?(Redmine::Acts::Customizable::InstanceMethods)
28 28 cattr_accessor :customizable_options
29 29 self.customizable_options = options
30 30 has_many :custom_values, :as => :customized,
31 31 :include => :custom_field,
32 32 :order => "#{CustomField.table_name}.position",
33 33 :dependent => :delete_all
34 34 before_validation_on_create { |customized| customized.custom_field_values }
35 35 # Trigger validation only if custom values were changed
36 36 validates_associated :custom_values, :on => :update, :if => Proc.new { |customized| customized.custom_field_values_changed? }
37 37 send :include, Redmine::Acts::Customizable::InstanceMethods
38 38 # Save custom values when saving the customized object
39 39 after_save :save_custom_field_values
40 40 end
41 41 end
42 42
43 43 module InstanceMethods
44 44 def self.included(base)
45 45 base.extend ClassMethods
46 46 end
47 47
48 48 def available_custom_fields
49 49 CustomField.find(:all, :conditions => "type = '#{self.class.name}CustomField'",
50 50 :order => 'position')
51 51 end
52 52
53 53 # Sets the values of the object's custom fields
54 54 # values is an array like [{'id' => 1, 'value' => 'foo'}, {'id' => 2, 'value' => 'bar'}]
55 55 def custom_fields=(values)
56 56 values_to_hash = values.inject({}) do |hash, v|
57 57 v = v.stringify_keys
58 58 if v['id'] && v.has_key?('value')
59 59 hash[v['id']] = v['value']
60 60 end
61 61 hash
62 62 end
63 63 self.custom_field_values = values_to_hash
64 64 end
65 65
66 66 # Sets the values of the object's custom fields
67 67 # values is a hash like {'1' => 'foo', 2 => 'bar'}
68 68 def custom_field_values=(values)
69 69 @custom_field_values_changed = true
70 70 values = values.stringify_keys
71 71 custom_field_values.each do |custom_value|
72 72 custom_value.value = values[custom_value.custom_field_id.to_s] if values.has_key?(custom_value.custom_field_id.to_s)
73 73 end if values.is_a?(Hash)
74 self.custom_values = custom_field_values
75 74 end
76 75
77 76 def custom_field_values
78 77 @custom_field_values ||= available_custom_fields.collect { |x| custom_values.detect { |v| v.custom_field == x } || custom_values.build(:customized => self, :custom_field => x, :value => nil) }
79 78 end
80 79
81 80 def visible_custom_field_values
82 81 custom_field_values.select(&:visible?)
83 82 end
84 83
85 84 def custom_field_values_changed?
86 85 @custom_field_values_changed == true
87 86 end
88 87
89 88 def custom_value_for(c)
90 89 field_id = (c.is_a?(CustomField) ? c.id : c.to_i)
91 90 custom_values.detect {|v| v.custom_field_id == field_id }
92 91 end
93 92
94 93 def save_custom_field_values
94 self.custom_values = custom_field_values
95 95 custom_field_values.each(&:save)
96 96 @custom_field_values_changed = false
97 97 @custom_field_values = nil
98 98 end
99 99
100 100 def reset_custom_values!
101 101 @custom_field_values = nil
102 102 @custom_field_values_changed = true
103 103 values = custom_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
104 104 custom_values.each {|cv| cv.destroy unless custom_field_values.include?(cv)}
105 105 end
106 106
107 107 module ClassMethods
108 108 end
109 109 end
110 110 end
111 111 end
112 112 end
General Comments 0
You need to be logged in to leave comments. Login now