##// END OF EJS Templates
Extracts custom field values validation from CustomValue so that they can be validated globally from the customized object (#1189)....
Jean-Philippe Lang -
r8597:83e7ee6729cd
parent child
Show More
@@ -0,0 +1,50
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 class CustomFieldValue
19 attr_accessor :custom_field, :customized, :value
20
21 def custom_field_id
22 custom_field.id
23 end
24
25 def true?
26 self.value == '1'
27 end
28
29 def editable?
30 custom_field.editable?
31 end
32
33 def visible?
34 custom_field.visible?
35 end
36
37 def required?
38 custom_field.is_required?
39 end
40
41 def to_s
42 value.to_s
43 end
44
45 def validate_value
46 custom_field.validate_field_value(value).each do |message|
47 customized.errors.add(:base, custom_field.name + ' ' + message)
48 end
49 end
50 end
@@ -1,7 +1,7
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2011 Jean-Philippe Lang
4 # Copyright (C) 2006-2012 Jean-Philippe Lang
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
@@ -61,8 +61,7 module CustomFieldsHelper
61 def custom_field_label_tag(name, custom_value)
61 def custom_field_label_tag(name, custom_value)
62 content_tag "label", h(custom_value.custom_field.name) +
62 content_tag "label", h(custom_value.custom_field.name) +
63 (custom_value.custom_field.is_required? ? " <span class=\"required\">*</span>".html_safe : ""),
63 (custom_value.custom_field.is_required? ? " <span class=\"required\">*</span>".html_safe : ""),
64 :for => "#{name}_custom_field_values_#{custom_value.custom_field.id}",
64 :for => "#{name}_custom_field_values_#{custom_value.custom_field.id}"
65 :class => (custom_value.errors.empty? ? nil : "error" )
66 end
65 end
67
66
68 # Return custom field tag with its label tag
67 # Return custom field tag with its label tag
@@ -1,5 +1,5
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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
@@ -27,7 +27,7 class CustomField < ActiveRecord::Base
27 validates_length_of :name, :maximum => 30
27 validates_length_of :name, :maximum => 30
28 validates_inclusion_of :field_format, :in => Redmine::CustomFieldFormat.available_formats
28 validates_inclusion_of :field_format, :in => Redmine::CustomFieldFormat.available_formats
29
29
30 validate :validate_values
30 validate :validate_custom_field
31 before_validation :set_searchable
31 before_validation :set_searchable
32
32
33 def initialize(attributes=nil, *args)
33 def initialize(attributes=nil, *args)
@@ -41,7 +41,7 class CustomField < ActiveRecord::Base
41 true
41 true
42 end
42 end
43
43
44 def validate_values
44 def validate_custom_field
45 if self.field_format == "list"
45 if self.field_format == "list"
46 errors.add(:possible_values, :blank) if self.possible_values.nil? || self.possible_values.empty?
46 errors.add(:possible_values, :blank) if self.possible_values.nil? || self.possible_values.empty?
47 errors.add(:possible_values, :invalid) unless self.possible_values.is_a? Array
47 errors.add(:possible_values, :invalid) unless self.possible_values.is_a? Array
@@ -55,10 +55,9 class CustomField < ActiveRecord::Base
55 end
55 end
56 end
56 end
57
57
58 # validate default value
58 unless valid_field_value?(default_value)
59 v = CustomValue.new(:custom_field => self.clone, :value => default_value, :customized => nil)
59 errors.add(:default_value, :invalid)
60 v.custom_field.is_required = false
60 end
61 errors.add(:default_value, :invalid) unless v.valid?
62 end
61 end
63
62
64 def possible_values_options(obj=nil)
63 def possible_values_options(obj=nil)
@@ -161,4 +160,45 class CustomField < ActiveRecord::Base
161 def type_name
160 def type_name
162 nil
161 nil
163 end
162 end
163
164 # Returns the error message for the given value
165 # or an empty array if value is a valid value for the custom field
166 def validate_field_value(value)
167 errs = []
168 if is_required? && value.blank?
169 errs << ::I18n.t('activerecord.errors.messages.blank')
170 end
171 errs += validate_field_value_format(value)
172 errs
173 end
174
175 # Returns true if value is a valid value for the custom field
176 def valid_field_value?(value)
177 validate_field_value(value).empty?
178 end
179
180 protected
181
182 # Returns the error message for the given value regarding its format
183 def validate_field_value_format(value)
184 errs = []
185 if value.present?
186 errs << ::I18n.t('activerecord.errors.messages.invalid') unless regexp.blank? or value =~ Regexp.new(regexp)
187 errs << ::I18n.t('activerecord.errors.messages.too_short', :count => min_length) if min_length > 0 and value.length < min_length
188 errs << ::I18n.t('activerecord.errors.messages.too_long', :count => max_length) if max_length > 0 and value.length > max_length
189
190 # Format specific validations
191 case field_format
192 when 'int'
193 errs << ::I18n.t('activerecord.errors.messages.not_a_number') unless value =~ /^[+-]?\d+$/
194 when 'float'
195 begin; Kernel.Float(value); rescue; errs << ::I18n.t('activerecord.errors.messages.invalid') end
196 when 'date'
197 errs << ::I18n.t('activerecord.errors.messages.not_a_date') unless value =~ /^\d{4}-\d{2}-\d{2}$/ && begin; value.to_date; rescue; false end
198 when 'list'
199 errs << ::I18n.t('activerecord.errors.messages.inclusion') unless possible_values.include?(value)
200 end
201 end
202 errs
203 end
164 end
204 end
@@ -1,5 +1,5
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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
@@ -19,8 +19,6 class CustomValue < ActiveRecord::Base
19 belongs_to :custom_field
19 belongs_to :custom_field
20 belongs_to :customized, :polymorphic => true
20 belongs_to :customized, :polymorphic => true
21
21
22 validate :validate_custom_value
23
24 def initialize(attributes=nil, *args)
22 def initialize(attributes=nil, *args)
25 super
23 super
26 if new_record? && custom_field && (customized_type.blank? || (customized && customized.new_record?))
24 if new_record? && custom_field && (customized_type.blank? || (customized && customized.new_record?))
@@ -48,27 +46,4 class CustomValue < ActiveRecord::Base
48 def to_s
46 def to_s
49 value.to_s
47 value.to_s
50 end
48 end
51
52 protected
53 def validate_custom_value
54 if value.blank?
55 errors.add(:value, :blank) if custom_field.is_required? and value.blank?
56 else
57 errors.add(:value, :invalid) unless custom_field.regexp.blank? or value =~ Regexp.new(custom_field.regexp)
58 errors.add(:value, :too_short, :count => custom_field.min_length) if custom_field.min_length > 0 and value.length < custom_field.min_length
59 errors.add(:value, :too_long, :count => custom_field.max_length) if custom_field.max_length > 0 and value.length > custom_field.max_length
60
61 # Format specific validations
62 case custom_field.field_format
63 when 'int'
64 errors.add(:value, :not_a_number) unless value =~ /^[+-]?\d+$/
65 when 'float'
66 begin; Kernel.Float(value); rescue; errors.add(:value, :invalid) end
67 when 'date'
68 errors.add(:value, :not_a_date) unless value =~ /^\d{4}-\d{2}-\d{2}$/ && begin; value.to_date; rescue; false end
69 when 'list'
70 errors.add(:value, :inclusion) unless custom_field.possible_values.include?(value)
71 end
72 end
73 end
74 end
49 end
@@ -16,36 +16,6 module ActiveRecord
16 end
16 end
17 end
17 end
18
18
19 module ActiveRecord
20 class Errors
21 def full_messages(options = {})
22 full_messages = []
23
24 @errors.each_key do |attr|
25 @errors[attr].each do |message|
26 next unless message
27
28 if attr == "base"
29 full_messages << message
30 elsif attr == "custom_values"
31 # Replace the generic "custom values is invalid"
32 # with the errors on custom values
33 @base.custom_values.each do |value|
34 value.errors.each do |attr, msg|
35 full_messages << value.custom_field.name + ' ' + msg
36 end
37 end
38 else
39 attr_name = @base.class.human_attribute_name(attr)
40 full_messages << attr_name + ' ' + message.to_s
41 end
42 end
43 end
44 full_messages
45 end
46 end
47 end
48
49 module ActionView
19 module ActionView
50 module Helpers
20 module Helpers
51 module DateHelper
21 module DateHelper
@@ -1,5 +1,5
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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
@@ -1308,17 +1308,18 class IssuesControllerTest < ActionController::TestCase
1308 field.update_attribute(:is_required, true)
1308 field.update_attribute(:is_required, true)
1309
1309
1310 @request.session[:user_id] = 2
1310 @request.session[:user_id] = 2
1311 post :create, :project_id => 1,
1311 assert_no_difference 'Issue.count' do
1312 :issue => {:tracker_id => 1,
1312 post :create, :project_id => 1,
1313 :subject => 'This is the test_new issue',
1313 :issue => {:tracker_id => 1,
1314 :description => 'This is the description',
1314 :subject => 'This is the test_new issue',
1315 :priority_id => 5}
1315 :description => 'This is the description',
1316 :priority_id => 5}
1317 end
1316 assert_response :success
1318 assert_response :success
1317 assert_template 'new'
1319 assert_template 'new'
1318 issue = assigns(:issue)
1320 issue = assigns(:issue)
1319 assert_not_nil issue
1321 assert_not_nil issue
1320 assert_equal I18n.translate('activerecord.errors.messages.invalid'),
1322 assert_error_tag :content => /Database can't be blank/
1321 issue.errors[:custom_values].to_s
1322 end
1323 end
1323
1324
1324 def test_post_create_with_watchers
1325 def test_post_create_with_watchers
@@ -1,5 +1,5
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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
@@ -44,6 +44,14 class CustomFieldTest < ActiveSupport::TestCase
44 assert field.save
44 assert field.save
45 end
45 end
46
46
47 def test_default_value_should_be_validated
48 field = CustomField.new(:name => 'Test', :field_format => 'int')
49 field.default_value = 'abc'
50 assert !field.valid?
51 field.default_value = '6'
52 assert field.valid?
53 end
54
47 def test_possible_values_should_accept_an_array
55 def test_possible_values_should_accept_an_array
48 field = CustomField.new
56 field = CustomField.new
49 field.possible_values = ["One value", ""]
57 field.possible_values = ["One value", ""]
@@ -85,4 +93,75 class CustomFieldTest < ActiveSupport::TestCase
85 def test_new_subclass_instance_with_non_subclass_name_should_return_nil
93 def test_new_subclass_instance_with_non_subclass_name_should_return_nil
86 assert_nil CustomField.new_subclass_instance('Project')
94 assert_nil CustomField.new_subclass_instance('Project')
87 end
95 end
96
97 def test_string_field_validation_with_blank_value
98 f = CustomField.new(:field_format => 'string')
99
100 assert f.valid_field_value?(nil)
101 assert f.valid_field_value?('')
102
103 f.is_required = true
104 assert !f.valid_field_value?(nil)
105 assert !f.valid_field_value?('')
106 end
107
108 def test_string_field_validation_with_min_and_max_lengths
109 f = CustomField.new(:field_format => 'string', :min_length => 2, :max_length => 5)
110
111 assert f.valid_field_value?(nil)
112 assert f.valid_field_value?('')
113 assert f.valid_field_value?('a' * 2)
114 assert !f.valid_field_value?('a')
115 assert !f.valid_field_value?('a' * 6)
116 end
117
118 def test_string_field_validation_with_regexp
119 f = CustomField.new(:field_format => 'string', :regexp => '^[A-Z0-9]*$')
120
121 assert f.valid_field_value?(nil)
122 assert f.valid_field_value?('')
123 assert f.valid_field_value?('ABC')
124 assert !f.valid_field_value?('abc')
125 end
126
127 def test_date_field_validation
128 f = CustomField.new(:field_format => 'date')
129
130 assert f.valid_field_value?(nil)
131 assert f.valid_field_value?('')
132 assert f.valid_field_value?('1975-07-14')
133 assert !f.valid_field_value?('1975-07-33')
134 assert !f.valid_field_value?('abc')
135 end
136
137 def test_list_field_validation
138 f = CustomField.new(:field_format => 'list', :possible_values => ['value1', 'value2'])
139
140 assert f.valid_field_value?(nil)
141 assert f.valid_field_value?('')
142 assert f.valid_field_value?('value2')
143 assert !f.valid_field_value?('abc')
144 end
145
146 def test_int_field_validation
147 f = CustomField.new(:field_format => 'int')
148
149 assert f.valid_field_value?(nil)
150 assert f.valid_field_value?('')
151 assert f.valid_field_value?('123')
152 assert f.valid_field_value?('+123')
153 assert f.valid_field_value?('-123')
154 assert !f.valid_field_value?('6abc')
155 end
156
157 def test_float_field_validation
158 f = CustomField.new(:field_format => 'float')
159
160 assert f.valid_field_value?(nil)
161 assert f.valid_field_value?('')
162 assert f.valid_field_value?('11.2')
163 assert f.valid_field_value?('-6.250')
164 assert f.valid_field_value?('5')
165 assert !f.valid_field_value?('6abc')
166 end
88 end
167 end
@@ -1,5 +1,5
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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
@@ -20,92 +20,6 require File.expand_path('../../test_helper', __FILE__)
20 class CustomValueTest < ActiveSupport::TestCase
20 class CustomValueTest < ActiveSupport::TestCase
21 fixtures :custom_fields, :custom_values, :users
21 fixtures :custom_fields, :custom_values, :users
22
22
23 def test_string_field_validation_with_blank_value
24 f = CustomField.new(:field_format => 'string')
25 v = CustomValue.new(:custom_field => f)
26
27 v.value = nil
28 assert v.valid?
29 v.value = ''
30 assert v.valid?
31
32 f.is_required = true
33 v.value = nil
34 assert !v.valid?
35 v.value = ''
36 assert !v.valid?
37 end
38
39 def test_string_field_validation_with_min_and_max_lengths
40 f = CustomField.new(:field_format => 'string', :min_length => 2, :max_length => 5)
41 v = CustomValue.new(:custom_field => f, :value => '')
42 assert v.valid?
43 v.value = 'a'
44 assert !v.valid?
45 v.value = 'a' * 2
46 assert v.valid?
47 v.value = 'a' * 6
48 assert !v.valid?
49 end
50
51 def test_string_field_validation_with_regexp
52 f = CustomField.new(:field_format => 'string', :regexp => '^[A-Z0-9]*$')
53 v = CustomValue.new(:custom_field => f, :value => '')
54 assert v.valid?
55 v.value = 'abc'
56 assert !v.valid?
57 v.value = 'ABC'
58 assert v.valid?
59 end
60
61 def test_date_field_validation
62 f = CustomField.new(:field_format => 'date')
63 v = CustomValue.new(:custom_field => f, :value => '')
64 assert v.valid?
65 v.value = 'abc'
66 assert !v.valid?
67 v.value = '1975-07-33'
68 assert !v.valid?
69 v.value = '1975-07-14'
70 assert v.valid?
71 end
72
73 def test_list_field_validation
74 f = CustomField.new(:field_format => 'list', :possible_values => ['value1', 'value2'])
75 v = CustomValue.new(:custom_field => f, :value => '')
76 assert v.valid?
77 v.value = 'abc'
78 assert !v.valid?
79 v.value = 'value2'
80 assert v.valid?
81 end
82
83 def test_int_field_validation
84 f = CustomField.new(:field_format => 'int')
85 v = CustomValue.new(:custom_field => f, :value => '')
86 assert v.valid?
87 v.value = 'abc'
88 assert !v.valid?
89 v.value = '123'
90 assert v.valid?
91 v.value = '+123'
92 assert v.valid?
93 v.value = '-123'
94 assert v.valid?
95 end
96
97 def test_float_field_validation
98 v = CustomValue.new(:customized => User.find(:first), :custom_field => UserCustomField.find_by_name('Money'))
99 v.value = '11.2'
100 assert v.save
101 v.value = ''
102 assert v.save
103 v.value = '-6.250'
104 assert v.save
105 v.value = '6a'
106 assert !v.save
107 end
108
109 def test_default_value
23 def test_default_value
110 field = CustomField.find_by_default_value('Default string')
24 field = CustomField.find_by_default_value('Default string')
111 assert_not_nil field
25 assert_not_nil field
@@ -1,5 +1,5
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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
@@ -29,6 +29,8 class IssueTest < ActiveSupport::TestCase
29 :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values,
29 :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values,
30 :time_entries
30 :time_entries
31
31
32 include Redmine::I18n
33
32 def test_create
34 def test_create
33 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
35 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
34 :status_id => 1, :priority => IssuePriority.all.first,
36 :status_id => 1, :priority => IssuePriority.all.first,
@@ -48,6 +50,7 class IssueTest < ActiveSupport::TestCase
48 end
50 end
49
51
50 def test_create_with_required_custom_field
52 def test_create_with_required_custom_field
53 set_language_if_valid 'en'
51 field = IssueCustomField.find_by_name('Database')
54 field = IssueCustomField.find_by_name('Database')
52 field.update_attribute(:is_required, true)
55 field.update_attribute(:is_required, true)
53
56
@@ -57,18 +60,15 class IssueTest < ActiveSupport::TestCase
57 assert issue.available_custom_fields.include?(field)
60 assert issue.available_custom_fields.include?(field)
58 # No value for the custom field
61 # No value for the custom field
59 assert !issue.save
62 assert !issue.save
60 assert_equal I18n.translate('activerecord.errors.messages.invalid'),
63 assert_equal "Database can't be blank", issue.errors[:base].to_s
61 issue.errors[:custom_values].to_s
62 # Blank value
64 # Blank value
63 issue.custom_field_values = { field.id => '' }
65 issue.custom_field_values = { field.id => '' }
64 assert !issue.save
66 assert !issue.save
65 assert_equal I18n.translate('activerecord.errors.messages.invalid'),
67 assert_equal "Database can't be blank", issue.errors[:base].to_s
66 issue.errors[:custom_values].to_s
67 # Invalid value
68 # Invalid value
68 issue.custom_field_values = { field.id => 'SQLServer' }
69 issue.custom_field_values = { field.id => 'SQLServer' }
69 assert !issue.save
70 assert !issue.save
70 assert_equal I18n.translate('activerecord.errors.messages.invalid'),
71 assert_equal "Database is not included in the list", issue.errors[:base].to_s
71 issue.errors[:custom_values].to_s
72 # Valid value
72 # Valid value
73 issue.custom_field_values = { field.id => 'PostgreSQL' }
73 issue.custom_field_values = { field.id => 'PostgreSQL' }
74 assert issue.save
74 assert issue.save
@@ -327,8 +327,7 class IssueTest < ActiveSupport::TestCase
327 attributes['tracker_id'] = '1'
327 attributes['tracker_id'] = '1'
328 issue = Issue.new(:project => Project.find(1))
328 issue = Issue.new(:project => Project.find(1))
329 issue.attributes = attributes
329 issue.attributes = attributes
330 assert_not_nil issue.custom_value_for(1)
330 assert_equal 'MySQL', issue.custom_field_value(1)
331 assert_equal 'MySQL', issue.custom_value_for(1).value
332 end
331 end
333
332
334 def test_should_update_issue_with_disabled_tracker
333 def test_should_update_issue_with_disabled_tracker
@@ -1,5 +1,5
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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
@@ -20,6 +20,8 require File.expand_path('../../test_helper', __FILE__)
20 class TimeEntryActivityTest < ActiveSupport::TestCase
20 class TimeEntryActivityTest < ActiveSupport::TestCase
21 fixtures :enumerations, :time_entries
21 fixtures :enumerations, :time_entries
22
22
23 include Redmine::I18n
24
23 def test_should_be_an_enumeration
25 def test_should_be_an_enumeration
24 assert TimeEntryActivity.ancestors.include?(Enumeration)
26 assert TimeEntryActivity.ancestors.include?(Enumeration)
25 end
27 end
@@ -44,13 +46,13 class TimeEntryActivityTest < ActiveSupport::TestCase
44 end
46 end
45
47
46 def test_create_without_required_custom_field_should_fail
48 def test_create_without_required_custom_field_should_fail
49 set_language_if_valid 'en'
47 field = TimeEntryActivityCustomField.find_by_name('Billable')
50 field = TimeEntryActivityCustomField.find_by_name('Billable')
48 field.update_attribute(:is_required, true)
51 field.update_attribute(:is_required, true)
49
52
50 e = TimeEntryActivity.new(:name => 'Custom Data')
53 e = TimeEntryActivity.new(:name => 'Custom Data')
51 assert !e.save
54 assert !e.save
52 assert_equal I18n.translate('activerecord.errors.messages.invalid'),
55 assert_equal "Billable can't be blank", e.errors[:base].to_s
53 e.errors[:custom_values].to_s
54 end
56 end
55
57
56 def test_create_with_required_custom_field_should_succeed
58 def test_create_with_required_custom_field_should_succeed
@@ -62,7 +64,8 class TimeEntryActivityTest < ActiveSupport::TestCase
62 assert e.save
64 assert e.save
63 end
65 end
64
66
65 def test_update_issue_with_required_custom_field_change
67 def test_update_with_required_custom_field_change
68 set_language_if_valid 'en'
66 field = TimeEntryActivityCustomField.find_by_name('Billable')
69 field = TimeEntryActivityCustomField.find_by_name('Billable')
67 field.update_attribute(:is_required, true)
70 field.update_attribute(:is_required, true)
68
71
@@ -73,7 +76,7 class TimeEntryActivityTest < ActiveSupport::TestCase
73 # Blanking custom field, save should fail
76 # Blanking custom field, save should fail
74 e.custom_field_values = {field.id => ""}
77 e.custom_field_values = {field.id => ""}
75 assert !e.save
78 assert !e.save
76 assert e.errors[:custom_values]
79 assert_equal "Billable can't be blank", e.errors[:base].to_s
77
80
78 # Update custom field to valid value, save should succeed
81 # Update custom field to valid value, save should succeed
79 e.custom_field_values = {field.id => "0"}
82 e.custom_field_values = {field.id => "0"}
@@ -81,6 +84,5 class TimeEntryActivityTest < ActiveSupport::TestCase
81 e.reload
84 e.reload
82 assert_equal "0", e.custom_value_for(field).value
85 assert_equal "0", e.custom_value_for(field).value
83 end
86 end
84
85 end
87 end
86
88
@@ -1,5 +1,5
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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
@@ -30,12 +30,10 module Redmine
30 has_many :custom_values, :as => :customized,
30 has_many :custom_values, :as => :customized,
31 :include => :custom_field,
31 :include => :custom_field,
32 :order => "#{CustomField.table_name}.position",
32 :order => "#{CustomField.table_name}.position",
33 :dependent => :delete_all
33 :dependent => :delete_all,
34 before_validation { |customized| customized.custom_field_values if customized.new_record? }
34 :validate => false
35 # Trigger validation only if custom values were changed
36 validates_associated :custom_values, :on => :update, :if => Proc.new { |customized| customized.custom_field_values_changed? }
37 send :include, Redmine::Acts::Customizable::InstanceMethods
35 send :include, Redmine::Acts::Customizable::InstanceMethods
38 # Save custom values when saving the customized object
36 validate :validate_custom_field_values
39 after_save :save_custom_field_values
37 after_save :save_custom_field_values
40 end
38 end
41 end
39 end
@@ -66,15 +64,28 module Redmine
66 # Sets the values of the object's custom fields
64 # Sets the values of the object's custom fields
67 # values is a hash like {'1' => 'foo', 2 => 'bar'}
65 # values is a hash like {'1' => 'foo', 2 => 'bar'}
68 def custom_field_values=(values)
66 def custom_field_values=(values)
69 @custom_field_values_changed = true
70 values = values.stringify_keys
67 values = values.stringify_keys
71 custom_field_values.each do |custom_value|
68
72 custom_value.value = values[custom_value.custom_field_id.to_s] if values.has_key?(custom_value.custom_field_id.to_s)
69 custom_field_values.each do |custom_field_value|
73 end if values.is_a?(Hash)
70 key = custom_field_value.custom_field_id.to_s
71 if values.has_key?(key)
72 value = values[key]
73 custom_field_value.value = value
74 end
75 end
76 @custom_field_values_changed = true
74 end
77 end
75
78
76 def custom_field_values
79 def custom_field_values
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) }
80 @custom_field_values ||= available_custom_fields.collect do |field|
81 x = CustomFieldValue.new
82 x.custom_field = field
83 x.customized = self
84 cv = custom_values.detect { |v| v.custom_field == field }
85 cv ||= custom_values.build(:customized => self, :custom_field => field, :value => nil)
86 x.value = cv.value
87 x
88 end
78 end
89 end
79
90
80 def visible_custom_field_values
91 def visible_custom_field_values
@@ -90,18 +101,34 module Redmine
90 custom_values.detect {|v| v.custom_field_id == field_id }
101 custom_values.detect {|v| v.custom_field_id == field_id }
91 end
102 end
92
103
104 def custom_field_value(c)
105 field_id = (c.is_a?(CustomField) ? c.id : c.to_i)
106 custom_field_values.detect {|v| v.custom_field_id == field_id }.try(:value)
107 end
108
109 def validate_custom_field_values
110 if new_record? || custom_field_values_changed?
111 custom_field_values.each(&:validate_value)
112 end
113 end
114
93 def save_custom_field_values
115 def save_custom_field_values
94 self.custom_values = custom_field_values
116 target_custom_values = []
95 custom_field_values.each(&:save)
117 custom_field_values.each do |custom_field_value|
118 target = custom_values.detect {|cv| cv.custom_field == custom_field_value.custom_field}
119 target ||= custom_values.build(:customized => self, :custom_field => custom_field_value.custom_field)
120 target.value = custom_field_value.value
121 target_custom_values << target
122 end
123 self.custom_values = target_custom_values
124 custom_values.each(&:save)
96 @custom_field_values_changed = false
125 @custom_field_values_changed = false
97 @custom_field_values = nil
126 true
98 end
127 end
99
128
100 def reset_custom_values!
129 def reset_custom_values!
101 @custom_field_values = nil
130 @custom_field_values = nil
102 @custom_field_values_changed = true
131 @custom_field_values_changed = true
103 values = custom_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
104 custom_values.each {|cv| cv.destroy unless custom_field_values.include?(cv)}
105 end
132 end
106
133
107 module ClassMethods
134 module ClassMethods
General Comments 0
You need to be logged in to leave comments. Login now