##// END OF EJS Templates
Patch ActiveRecord::Errors#full_messages so that it contains custom values error messages....
Jean-Philippe Lang -
r2454:a64b8695c8c2
parent child
Show More
@@ -1,50 +1,80
1 1
2 2 require 'activerecord'
3 3
4 4 module ActiveRecord
5 5 class Base
6 6 include Redmine::I18n
7 7
8 8 # Translate attribute names for validation errors display
9 9 def self.human_attribute_name(attr)
10 10 l("field_#{attr.to_s.gsub(/_id$/, '')}")
11 11 end
12 12 end
13 13 end
14 14
15 module ActiveRecord
16 class Errors
17 def full_messages(options = {})
18 full_messages = []
19
20 @errors.each_key do |attr|
21 @errors[attr].each do |message|
22 next unless message
23
24 if attr == "base"
25 full_messages << message
26 elsif attr == "custom_values"
27 # Replace the generic "custom values is invalid"
28 # with the errors on custom values
29 @base.custom_values.each do |value|
30 value.errors.each do |attr, msg|
31 full_messages << value.custom_field.name + ' ' + msg
32 end
33 end
34 else
35 attr_name = @base.class.human_attribute_name(attr)
36 full_messages << attr_name + ' ' + message
37 end
38 end
39 end
40 full_messages
41 end
42 end
43 end
44
15 45 module ActionView
16 46 module Helpers
17 47 module DateHelper
18 48 # distance_of_time_in_words breaks when difference is greater than 30 years
19 49 def distance_of_date_in_words(from_date, to_date = 0, options = {})
20 50 from_date = from_date.to_date if from_date.respond_to?(:to_date)
21 51 to_date = to_date.to_date if to_date.respond_to?(:to_date)
22 52 distance_in_days = (to_date - from_date).abs
23 53
24 54 I18n.with_options :locale => options[:locale], :scope => :'datetime.distance_in_words' do |locale|
25 55 case distance_in_days
26 56 when 0..60 then locale.t :x_days, :count => distance_in_days
27 57 when 61..720 then locale.t :about_x_months, :count => (distance_in_days / 30).round
28 58 else locale.t :over_x_years, :count => (distance_in_days / 365).round
29 59 end
30 60 end
31 61 end
32 62 end
33 63 end
34 64 end
35 65
36 66 ActionView::Base.field_error_proc = Proc.new{ |html_tag, instance| "#{html_tag}" }
37 67
38 68 # Adds :async_smtp and :async_sendmail delivery methods
39 69 # to perform email deliveries asynchronously
40 70 module AsynchronousMailer
41 71 %w(smtp sendmail).each do |type|
42 72 define_method("perform_delivery_async_#{type}") do |mail|
43 73 Thread.start do
44 74 send "perform_delivery_#{type}", mail
45 75 end
46 76 end
47 77 end
48 78 end
49 79
50 80 ActionMailer::Base.send :include, AsynchronousMailer
@@ -1,231 +1,244
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 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.dirname(__FILE__) + '/../test_helper'
19 19
20 20 class IssueTest < Test::Unit::TestCase
21 21 fixtures :projects, :users, :members,
22 22 :trackers, :projects_trackers,
23 23 :issue_statuses, :issue_categories,
24 24 :enumerations,
25 25 :issues,
26 26 :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values,
27 27 :time_entries
28 28
29 29 def test_create
30 30 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => Enumeration.priorities.first, :subject => 'test_create', :description => 'IssueTest#test_create', :estimated_hours => '1:30')
31 31 assert issue.save
32 32 issue.reload
33 33 assert_equal 1.5, issue.estimated_hours
34 34 end
35 35
36 36 def test_create_minimal
37 37 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => Enumeration.priorities.first, :subject => 'test_create')
38 38 assert issue.save
39 39 assert issue.description.nil?
40 40 end
41 41
42 42 def test_create_with_required_custom_field
43 43 field = IssueCustomField.find_by_name('Database')
44 44 field.update_attribute(:is_required, true)
45 45
46 46 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')
47 47 assert issue.available_custom_fields.include?(field)
48 48 # No value for the custom field
49 49 assert !issue.save
50 50 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
51 51 # Blank value
52 52 issue.custom_field_values = { field.id => '' }
53 53 assert !issue.save
54 54 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
55 55 # Invalid value
56 56 issue.custom_field_values = { field.id => 'SQLServer' }
57 57 assert !issue.save
58 58 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
59 59 # Valid value
60 60 issue.custom_field_values = { field.id => 'PostgreSQL' }
61 61 assert issue.save
62 62 issue.reload
63 63 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
64 64 end
65 65
66 def test_errors_full_messages_should_include_custom_fields_errors
67 field = IssueCustomField.find_by_name('Database')
68
69 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')
70 assert issue.available_custom_fields.include?(field)
71 # Invalid value
72 issue.custom_field_values = { field.id => 'SQLServer' }
73
74 assert !issue.valid?
75 assert_equal 1, issue.errors.full_messages.size
76 assert_equal "Database #{I18n.translate('activerecord.errors.messages.inclusion')}", issue.errors.full_messages.first
77 end
78
66 79 def test_update_issue_with_required_custom_field
67 80 field = IssueCustomField.find_by_name('Database')
68 81 field.update_attribute(:is_required, true)
69 82
70 83 issue = Issue.find(1)
71 84 assert_nil issue.custom_value_for(field)
72 85 assert issue.available_custom_fields.include?(field)
73 86 # No change to custom values, issue can be saved
74 87 assert issue.save
75 88 # Blank value
76 89 issue.custom_field_values = { field.id => '' }
77 90 assert !issue.save
78 91 # Valid value
79 92 issue.custom_field_values = { field.id => 'PostgreSQL' }
80 93 assert issue.save
81 94 issue.reload
82 95 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
83 96 end
84 97
85 98 def test_should_not_update_attributes_if_custom_fields_validation_fails
86 99 issue = Issue.find(1)
87 100 field = IssueCustomField.find_by_name('Database')
88 101 assert issue.available_custom_fields.include?(field)
89 102
90 103 issue.custom_field_values = { field.id => 'Invalid' }
91 104 issue.subject = 'Should be not be saved'
92 105 assert !issue.save
93 106
94 107 issue.reload
95 108 assert_equal "Can't print recipes", issue.subject
96 109 end
97 110
98 111 def test_should_not_recreate_custom_values_objects_on_update
99 112 field = IssueCustomField.find_by_name('Database')
100 113
101 114 issue = Issue.find(1)
102 115 issue.custom_field_values = { field.id => 'PostgreSQL' }
103 116 assert issue.save
104 117 custom_value = issue.custom_value_for(field)
105 118 issue.reload
106 119 issue.custom_field_values = { field.id => 'MySQL' }
107 120 assert issue.save
108 121 issue.reload
109 122 assert_equal custom_value.id, issue.custom_value_for(field).id
110 123 end
111 124
112 125 def test_category_based_assignment
113 126 issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => Enumeration.priorities.first, :subject => 'Assignment test', :description => 'Assignment test', :category_id => 1)
114 127 assert_equal IssueCategory.find(1).assigned_to, issue.assigned_to
115 128 end
116 129
117 130 def test_copy
118 131 issue = Issue.new.copy_from(1)
119 132 assert issue.save
120 133 issue.reload
121 134 orig = Issue.find(1)
122 135 assert_equal orig.subject, issue.subject
123 136 assert_equal orig.tracker, issue.tracker
124 137 assert_equal orig.custom_values.first.value, issue.custom_values.first.value
125 138 end
126 139
127 140 def test_should_close_duplicates
128 141 # Create 3 issues
129 142 issue1 = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :priority => Enumeration.priorities.first, :subject => 'Duplicates test', :description => 'Duplicates test')
130 143 assert issue1.save
131 144 issue2 = issue1.clone
132 145 assert issue2.save
133 146 issue3 = issue1.clone
134 147 assert issue3.save
135 148
136 149 # 2 is a dupe of 1
137 150 IssueRelation.create(:issue_from => issue2, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
138 151 # And 3 is a dupe of 2
139 152 IssueRelation.create(:issue_from => issue3, :issue_to => issue2, :relation_type => IssueRelation::TYPE_DUPLICATES)
140 153 # And 3 is a dupe of 1 (circular duplicates)
141 154 IssueRelation.create(:issue_from => issue3, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
142 155
143 156 assert issue1.reload.duplicates.include?(issue2)
144 157
145 158 # Closing issue 1
146 159 issue1.init_journal(User.find(:first), "Closing issue1")
147 160 issue1.status = IssueStatus.find :first, :conditions => {:is_closed => true}
148 161 assert issue1.save
149 162 # 2 and 3 should be also closed
150 163 assert issue2.reload.closed?
151 164 assert issue3.reload.closed?
152 165 end
153 166
154 167 def test_should_not_close_duplicated_issue
155 168 # Create 3 issues
156 169 issue1 = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :priority => Enumeration.priorities.first, :subject => 'Duplicates test', :description => 'Duplicates test')
157 170 assert issue1.save
158 171 issue2 = issue1.clone
159 172 assert issue2.save
160 173
161 174 # 2 is a dupe of 1
162 175 IssueRelation.create(:issue_from => issue2, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
163 176 # 2 is a dup of 1 but 1 is not a duplicate of 2
164 177 assert !issue2.reload.duplicates.include?(issue1)
165 178
166 179 # Closing issue 2
167 180 issue2.init_journal(User.find(:first), "Closing issue2")
168 181 issue2.status = IssueStatus.find :first, :conditions => {:is_closed => true}
169 182 assert issue2.save
170 183 # 1 should not be also closed
171 184 assert !issue1.reload.closed?
172 185 end
173 186
174 187 def test_move_to_another_project_with_same_category
175 188 issue = Issue.find(1)
176 189 assert issue.move_to(Project.find(2))
177 190 issue.reload
178 191 assert_equal 2, issue.project_id
179 192 # Category changes
180 193 assert_equal 4, issue.category_id
181 194 # Make sure time entries were move to the target project
182 195 assert_equal 2, issue.time_entries.first.project_id
183 196 end
184 197
185 198 def test_move_to_another_project_without_same_category
186 199 issue = Issue.find(2)
187 200 assert issue.move_to(Project.find(2))
188 201 issue.reload
189 202 assert_equal 2, issue.project_id
190 203 # Category cleared
191 204 assert_nil issue.category_id
192 205 end
193 206
194 207 def test_copy_to_the_same_project
195 208 issue = Issue.find(1)
196 209 copy = nil
197 210 assert_difference 'Issue.count' do
198 211 copy = issue.move_to(issue.project, nil, :copy => true)
199 212 end
200 213 assert_kind_of Issue, copy
201 214 assert_equal issue.project, copy.project
202 215 assert_equal "125", copy.custom_value_for(2).value
203 216 end
204 217
205 218 def test_copy_to_another_project_and_tracker
206 219 issue = Issue.find(1)
207 220 copy = nil
208 221 assert_difference 'Issue.count' do
209 222 copy = issue.move_to(Project.find(3), Tracker.find(2), :copy => true)
210 223 end
211 224 assert_kind_of Issue, copy
212 225 assert_equal Project.find(3), copy.project
213 226 assert_equal Tracker.find(2), copy.tracker
214 227 # Custom field #2 is not associated with target tracker
215 228 assert_nil copy.custom_value_for(2)
216 229 end
217 230
218 231 def test_issue_destroy
219 232 Issue.find(1).destroy
220 233 assert_nil Issue.find_by_id(1)
221 234 assert_nil TimeEntry.find_by_issue_id(1)
222 235 end
223 236
224 237 def test_overdue
225 238 assert Issue.new(:due_date => 1.day.ago.to_date).overdue?
226 239 assert !Issue.new(:due_date => Date.today).overdue?
227 240 assert !Issue.new(:due_date => 1.day.from_now.to_date).overdue?
228 241 assert !Issue.new(:due_date => nil).overdue?
229 242 assert !Issue.new(:due_date => 1.day.ago.to_date, :status => IssueStatus.find(:first, :conditions => {:is_closed => true})).overdue?
230 243 end
231 244 end
General Comments 0
You need to be logged in to leave comments. Login now