##// END OF EJS Templates
Ability to uncheck "Multiple values" for existing custom fields (#12251)....
Jean-Philippe Lang -
r10937:5c1039a69e93
parent child
Show More
@@ -1,338 +1,355
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 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
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 class CustomField < ActiveRecord::Base
18 class CustomField < ActiveRecord::Base
19 include Redmine::SubclassFactory
19 include Redmine::SubclassFactory
20
20
21 has_many :custom_values, :dependent => :delete_all
21 has_many :custom_values, :dependent => :delete_all
22 acts_as_list :scope => 'type = \'#{self.class}\''
22 acts_as_list :scope => 'type = \'#{self.class}\''
23 serialize :possible_values
23 serialize :possible_values
24
24
25 validates_presence_of :name, :field_format
25 validates_presence_of :name, :field_format
26 validates_uniqueness_of :name, :scope => :type
26 validates_uniqueness_of :name, :scope => :type
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_custom_field
30 validate :validate_custom_field
31 before_validation :set_searchable
31 before_validation :set_searchable
32 after_save :handle_multiplicity_change
32
33
33 scope :sorted, lambda { order("#{table_name}.position ASC") }
34 scope :sorted, lambda { order("#{table_name}.position ASC") }
34
35
35 CUSTOM_FIELDS_TABS = [
36 CUSTOM_FIELDS_TABS = [
36 {:name => 'IssueCustomField', :partial => 'custom_fields/index',
37 {:name => 'IssueCustomField', :partial => 'custom_fields/index',
37 :label => :label_issue_plural},
38 :label => :label_issue_plural},
38 {:name => 'TimeEntryCustomField', :partial => 'custom_fields/index',
39 {:name => 'TimeEntryCustomField', :partial => 'custom_fields/index',
39 :label => :label_spent_time},
40 :label => :label_spent_time},
40 {:name => 'ProjectCustomField', :partial => 'custom_fields/index',
41 {:name => 'ProjectCustomField', :partial => 'custom_fields/index',
41 :label => :label_project_plural},
42 :label => :label_project_plural},
42 {:name => 'VersionCustomField', :partial => 'custom_fields/index',
43 {:name => 'VersionCustomField', :partial => 'custom_fields/index',
43 :label => :label_version_plural},
44 :label => :label_version_plural},
44 {:name => 'UserCustomField', :partial => 'custom_fields/index',
45 {:name => 'UserCustomField', :partial => 'custom_fields/index',
45 :label => :label_user_plural},
46 :label => :label_user_plural},
46 {:name => 'GroupCustomField', :partial => 'custom_fields/index',
47 {:name => 'GroupCustomField', :partial => 'custom_fields/index',
47 :label => :label_group_plural},
48 :label => :label_group_plural},
48 {:name => 'TimeEntryActivityCustomField', :partial => 'custom_fields/index',
49 {:name => 'TimeEntryActivityCustomField', :partial => 'custom_fields/index',
49 :label => TimeEntryActivity::OptionName},
50 :label => TimeEntryActivity::OptionName},
50 {:name => 'IssuePriorityCustomField', :partial => 'custom_fields/index',
51 {:name => 'IssuePriorityCustomField', :partial => 'custom_fields/index',
51 :label => IssuePriority::OptionName},
52 :label => IssuePriority::OptionName},
52 {:name => 'DocumentCategoryCustomField', :partial => 'custom_fields/index',
53 {:name => 'DocumentCategoryCustomField', :partial => 'custom_fields/index',
53 :label => DocumentCategory::OptionName}
54 :label => DocumentCategory::OptionName}
54 ]
55 ]
55
56
56 CUSTOM_FIELDS_NAMES = CUSTOM_FIELDS_TABS.collect{|v| v[:name]}
57 CUSTOM_FIELDS_NAMES = CUSTOM_FIELDS_TABS.collect{|v| v[:name]}
57
58
58 def field_format=(arg)
59 def field_format=(arg)
59 # cannot change format of a saved custom field
60 # cannot change format of a saved custom field
60 super if new_record?
61 super if new_record?
61 end
62 end
62
63
63 def set_searchable
64 def set_searchable
64 # make sure these fields are not searchable
65 # make sure these fields are not searchable
65 self.searchable = false if %w(int float date bool).include?(field_format)
66 self.searchable = false if %w(int float date bool).include?(field_format)
66 # make sure only these fields can have multiple values
67 # make sure only these fields can have multiple values
67 self.multiple = false unless %w(list user version).include?(field_format)
68 self.multiple = false unless %w(list user version).include?(field_format)
68 true
69 true
69 end
70 end
70
71
71 def validate_custom_field
72 def validate_custom_field
72 if self.field_format == "list"
73 if self.field_format == "list"
73 errors.add(:possible_values, :blank) if self.possible_values.nil? || self.possible_values.empty?
74 errors.add(:possible_values, :blank) if self.possible_values.nil? || self.possible_values.empty?
74 errors.add(:possible_values, :invalid) unless self.possible_values.is_a? Array
75 errors.add(:possible_values, :invalid) unless self.possible_values.is_a? Array
75 end
76 end
76
77
77 if regexp.present?
78 if regexp.present?
78 begin
79 begin
79 Regexp.new(regexp)
80 Regexp.new(regexp)
80 rescue
81 rescue
81 errors.add(:regexp, :invalid)
82 errors.add(:regexp, :invalid)
82 end
83 end
83 end
84 end
84
85
85 if default_value.present? && !valid_field_value?(default_value)
86 if default_value.present? && !valid_field_value?(default_value)
86 errors.add(:default_value, :invalid)
87 errors.add(:default_value, :invalid)
87 end
88 end
88 end
89 end
89
90
90 def possible_values_options(obj=nil)
91 def possible_values_options(obj=nil)
91 case field_format
92 case field_format
92 when 'user', 'version'
93 when 'user', 'version'
93 if obj.respond_to?(:project) && obj.project
94 if obj.respond_to?(:project) && obj.project
94 case field_format
95 case field_format
95 when 'user'
96 when 'user'
96 obj.project.users.sort.collect {|u| [u.to_s, u.id.to_s]}
97 obj.project.users.sort.collect {|u| [u.to_s, u.id.to_s]}
97 when 'version'
98 when 'version'
98 obj.project.shared_versions.sort.collect {|u| [u.to_s, u.id.to_s]}
99 obj.project.shared_versions.sort.collect {|u| [u.to_s, u.id.to_s]}
99 end
100 end
100 elsif obj.is_a?(Array)
101 elsif obj.is_a?(Array)
101 obj.collect {|o| possible_values_options(o)}.reduce(:&)
102 obj.collect {|o| possible_values_options(o)}.reduce(:&)
102 else
103 else
103 []
104 []
104 end
105 end
105 when 'bool'
106 when 'bool'
106 [[l(:general_text_Yes), '1'], [l(:general_text_No), '0']]
107 [[l(:general_text_Yes), '1'], [l(:general_text_No), '0']]
107 else
108 else
108 possible_values || []
109 possible_values || []
109 end
110 end
110 end
111 end
111
112
112 def possible_values(obj=nil)
113 def possible_values(obj=nil)
113 case field_format
114 case field_format
114 when 'user', 'version'
115 when 'user', 'version'
115 possible_values_options(obj).collect(&:last)
116 possible_values_options(obj).collect(&:last)
116 when 'bool'
117 when 'bool'
117 ['1', '0']
118 ['1', '0']
118 else
119 else
119 values = super()
120 values = super()
120 if values.is_a?(Array)
121 if values.is_a?(Array)
121 values.each do |value|
122 values.each do |value|
122 value.force_encoding('UTF-8') if value.respond_to?(:force_encoding)
123 value.force_encoding('UTF-8') if value.respond_to?(:force_encoding)
123 end
124 end
124 end
125 end
125 values || []
126 values || []
126 end
127 end
127 end
128 end
128
129
129 # Makes possible_values accept a multiline string
130 # Makes possible_values accept a multiline string
130 def possible_values=(arg)
131 def possible_values=(arg)
131 if arg.is_a?(Array)
132 if arg.is_a?(Array)
132 super(arg.compact.collect(&:strip).select {|v| !v.blank?})
133 super(arg.compact.collect(&:strip).select {|v| !v.blank?})
133 else
134 else
134 self.possible_values = arg.to_s.split(/[\n\r]+/)
135 self.possible_values = arg.to_s.split(/[\n\r]+/)
135 end
136 end
136 end
137 end
137
138
138 def cast_value(value)
139 def cast_value(value)
139 casted = nil
140 casted = nil
140 unless value.blank?
141 unless value.blank?
141 case field_format
142 case field_format
142 when 'string', 'text', 'list'
143 when 'string', 'text', 'list'
143 casted = value
144 casted = value
144 when 'date'
145 when 'date'
145 casted = begin; value.to_date; rescue; nil end
146 casted = begin; value.to_date; rescue; nil end
146 when 'bool'
147 when 'bool'
147 casted = (value == '1' ? true : false)
148 casted = (value == '1' ? true : false)
148 when 'int'
149 when 'int'
149 casted = value.to_i
150 casted = value.to_i
150 when 'float'
151 when 'float'
151 casted = value.to_f
152 casted = value.to_f
152 when 'user', 'version'
153 when 'user', 'version'
153 casted = (value.blank? ? nil : field_format.classify.constantize.find_by_id(value.to_i))
154 casted = (value.blank? ? nil : field_format.classify.constantize.find_by_id(value.to_i))
154 end
155 end
155 end
156 end
156 casted
157 casted
157 end
158 end
158
159
159 def value_from_keyword(keyword, customized)
160 def value_from_keyword(keyword, customized)
160 possible_values_options = possible_values_options(customized)
161 possible_values_options = possible_values_options(customized)
161 if possible_values_options.present?
162 if possible_values_options.present?
162 keyword = keyword.to_s.downcase
163 keyword = keyword.to_s.downcase
163 if v = possible_values_options.detect {|text, id| text.downcase == keyword}
164 if v = possible_values_options.detect {|text, id| text.downcase == keyword}
164 if v.is_a?(Array)
165 if v.is_a?(Array)
165 v.last
166 v.last
166 else
167 else
167 v
168 v
168 end
169 end
169 end
170 end
170 else
171 else
171 keyword
172 keyword
172 end
173 end
173 end
174 end
174
175
175 # Returns a ORDER BY clause that can used to sort customized
176 # Returns a ORDER BY clause that can used to sort customized
176 # objects by their value of the custom field.
177 # objects by their value of the custom field.
177 # Returns nil if the custom field can not be used for sorting.
178 # Returns nil if the custom field can not be used for sorting.
178 def order_statement
179 def order_statement
179 return nil if multiple?
180 return nil if multiple?
180 case field_format
181 case field_format
181 when 'string', 'text', 'list', 'date', 'bool'
182 when 'string', 'text', 'list', 'date', 'bool'
182 # COALESCE is here to make sure that blank and NULL values are sorted equally
183 # COALESCE is here to make sure that blank and NULL values are sorted equally
183 "COALESCE(#{join_alias}.value, '')"
184 "COALESCE(#{join_alias}.value, '')"
184 when 'int', 'float'
185 when 'int', 'float'
185 # Make the database cast values into numeric
186 # Make the database cast values into numeric
186 # Postgresql will raise an error if a value can not be casted!
187 # Postgresql will raise an error if a value can not be casted!
187 # CustomValue validations should ensure that it doesn't occur
188 # CustomValue validations should ensure that it doesn't occur
188 "CAST(CASE #{join_alias}.value WHEN '' THEN '0' ELSE #{join_alias}.value END AS decimal(30,3))"
189 "CAST(CASE #{join_alias}.value WHEN '' THEN '0' ELSE #{join_alias}.value END AS decimal(30,3))"
189 when 'user', 'version'
190 when 'user', 'version'
190 value_class.fields_for_order_statement(value_join_alias)
191 value_class.fields_for_order_statement(value_join_alias)
191 else
192 else
192 nil
193 nil
193 end
194 end
194 end
195 end
195
196
196 # Returns a GROUP BY clause that can used to group by custom value
197 # Returns a GROUP BY clause that can used to group by custom value
197 # Returns nil if the custom field can not be used for grouping.
198 # Returns nil if the custom field can not be used for grouping.
198 def group_statement
199 def group_statement
199 return nil if multiple?
200 return nil if multiple?
200 case field_format
201 case field_format
201 when 'list', 'date', 'bool', 'int'
202 when 'list', 'date', 'bool', 'int'
202 order_statement
203 order_statement
203 when 'user', 'version'
204 when 'user', 'version'
204 "COALESCE(#{join_alias}.value, '')"
205 "COALESCE(#{join_alias}.value, '')"
205 else
206 else
206 nil
207 nil
207 end
208 end
208 end
209 end
209
210
210 def join_for_order_statement
211 def join_for_order_statement
211 case field_format
212 case field_format
212 when 'user', 'version'
213 when 'user', 'version'
213 "LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" +
214 "LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" +
214 " ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" +
215 " ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" +
215 " AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" +
216 " AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" +
216 " AND #{join_alias}.custom_field_id = #{id}" +
217 " AND #{join_alias}.custom_field_id = #{id}" +
217 " AND #{join_alias}.value <> ''" +
218 " AND #{join_alias}.value <> ''" +
218 " AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" +
219 " AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" +
219 " WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" +
220 " WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" +
220 " AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" +
221 " AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" +
221 " AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)" +
222 " AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)" +
222 " LEFT OUTER JOIN #{value_class.table_name} #{value_join_alias}" +
223 " LEFT OUTER JOIN #{value_class.table_name} #{value_join_alias}" +
223 " ON CAST(CASE #{join_alias}.value WHEN '' THEN '0' ELSE #{join_alias}.value END AS decimal(30,0)) = #{value_join_alias}.id"
224 " ON CAST(CASE #{join_alias}.value WHEN '' THEN '0' ELSE #{join_alias}.value END AS decimal(30,0)) = #{value_join_alias}.id"
224 when 'int', 'float'
225 when 'int', 'float'
225 "LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" +
226 "LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" +
226 " ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" +
227 " ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" +
227 " AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" +
228 " AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" +
228 " AND #{join_alias}.custom_field_id = #{id}" +
229 " AND #{join_alias}.custom_field_id = #{id}" +
229 " AND #{join_alias}.value <> ''" +
230 " AND #{join_alias}.value <> ''" +
230 " AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" +
231 " AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" +
231 " WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" +
232 " WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" +
232 " AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" +
233 " AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" +
233 " AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)"
234 " AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)"
234 when 'string', 'text', 'list', 'date', 'bool'
235 when 'string', 'text', 'list', 'date', 'bool'
235 "LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" +
236 "LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" +
236 " ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" +
237 " ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" +
237 " AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" +
238 " AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" +
238 " AND #{join_alias}.custom_field_id = #{id}" +
239 " AND #{join_alias}.custom_field_id = #{id}" +
239 " AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" +
240 " AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" +
240 " WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" +
241 " WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" +
241 " AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" +
242 " AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" +
242 " AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)"
243 " AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)"
243 else
244 else
244 nil
245 nil
245 end
246 end
246 end
247 end
247
248
248 def join_alias
249 def join_alias
249 "cf_#{id}"
250 "cf_#{id}"
250 end
251 end
251
252
252 def value_join_alias
253 def value_join_alias
253 join_alias + "_" + field_format
254 join_alias + "_" + field_format
254 end
255 end
255
256
256 def <=>(field)
257 def <=>(field)
257 position <=> field.position
258 position <=> field.position
258 end
259 end
259
260
260 # Returns the class that values represent
261 # Returns the class that values represent
261 def value_class
262 def value_class
262 case field_format
263 case field_format
263 when 'user', 'version'
264 when 'user', 'version'
264 field_format.classify.constantize
265 field_format.classify.constantize
265 else
266 else
266 nil
267 nil
267 end
268 end
268 end
269 end
269
270
270 def self.customized_class
271 def self.customized_class
271 self.name =~ /^(.+)CustomField$/
272 self.name =~ /^(.+)CustomField$/
272 begin; $1.constantize; rescue nil; end
273 begin; $1.constantize; rescue nil; end
273 end
274 end
274
275
275 # to move in project_custom_field
276 # to move in project_custom_field
276 def self.for_all
277 def self.for_all
277 where(:is_for_all => true).order('position').all
278 where(:is_for_all => true).order('position').all
278 end
279 end
279
280
280 def type_name
281 def type_name
281 nil
282 nil
282 end
283 end
283
284
284 # Returns the error messages for the given value
285 # Returns the error messages for the given value
285 # or an empty array if value is a valid value for the custom field
286 # or an empty array if value is a valid value for the custom field
286 def validate_field_value(value)
287 def validate_field_value(value)
287 errs = []
288 errs = []
288 if value.is_a?(Array)
289 if value.is_a?(Array)
289 if !multiple?
290 if !multiple?
290 errs << ::I18n.t('activerecord.errors.messages.invalid')
291 errs << ::I18n.t('activerecord.errors.messages.invalid')
291 end
292 end
292 if is_required? && value.detect(&:present?).nil?
293 if is_required? && value.detect(&:present?).nil?
293 errs << ::I18n.t('activerecord.errors.messages.blank')
294 errs << ::I18n.t('activerecord.errors.messages.blank')
294 end
295 end
295 value.each {|v| errs += validate_field_value_format(v)}
296 value.each {|v| errs += validate_field_value_format(v)}
296 else
297 else
297 if is_required? && value.blank?
298 if is_required? && value.blank?
298 errs << ::I18n.t('activerecord.errors.messages.blank')
299 errs << ::I18n.t('activerecord.errors.messages.blank')
299 end
300 end
300 errs += validate_field_value_format(value)
301 errs += validate_field_value_format(value)
301 end
302 end
302 errs
303 errs
303 end
304 end
304
305
305 # Returns true if value is a valid value for the custom field
306 # Returns true if value is a valid value for the custom field
306 def valid_field_value?(value)
307 def valid_field_value?(value)
307 validate_field_value(value).empty?
308 validate_field_value(value).empty?
308 end
309 end
309
310
310 def format_in?(*args)
311 def format_in?(*args)
311 args.include?(field_format)
312 args.include?(field_format)
312 end
313 end
313
314
314 protected
315 protected
315
316
316 # Returns the error message for the given value regarding its format
317 # Returns the error message for the given value regarding its format
317 def validate_field_value_format(value)
318 def validate_field_value_format(value)
318 errs = []
319 errs = []
319 if value.present?
320 if value.present?
320 errs << ::I18n.t('activerecord.errors.messages.invalid') unless regexp.blank? or value =~ Regexp.new(regexp)
321 errs << ::I18n.t('activerecord.errors.messages.invalid') unless regexp.blank? or value =~ Regexp.new(regexp)
321 errs << ::I18n.t('activerecord.errors.messages.too_short', :count => min_length) if min_length > 0 and value.length < min_length
322 errs << ::I18n.t('activerecord.errors.messages.too_short', :count => min_length) if min_length > 0 and value.length < min_length
322 errs << ::I18n.t('activerecord.errors.messages.too_long', :count => max_length) if max_length > 0 and value.length > max_length
323 errs << ::I18n.t('activerecord.errors.messages.too_long', :count => max_length) if max_length > 0 and value.length > max_length
323
324
324 # Format specific validations
325 # Format specific validations
325 case field_format
326 case field_format
326 when 'int'
327 when 'int'
327 errs << ::I18n.t('activerecord.errors.messages.not_a_number') unless value =~ /^[+-]?\d+$/
328 errs << ::I18n.t('activerecord.errors.messages.not_a_number') unless value =~ /^[+-]?\d+$/
328 when 'float'
329 when 'float'
329 begin; Kernel.Float(value); rescue; errs << ::I18n.t('activerecord.errors.messages.invalid') end
330 begin; Kernel.Float(value); rescue; errs << ::I18n.t('activerecord.errors.messages.invalid') end
330 when 'date'
331 when 'date'
331 errs << ::I18n.t('activerecord.errors.messages.not_a_date') unless value =~ /^\d{4}-\d{2}-\d{2}$/ && begin; value.to_date; rescue; false end
332 errs << ::I18n.t('activerecord.errors.messages.not_a_date') unless value =~ /^\d{4}-\d{2}-\d{2}$/ && begin; value.to_date; rescue; false end
332 when 'list'
333 when 'list'
333 errs << ::I18n.t('activerecord.errors.messages.inclusion') unless possible_values.include?(value)
334 errs << ::I18n.t('activerecord.errors.messages.inclusion') unless possible_values.include?(value)
334 end
335 end
335 end
336 end
336 errs
337 errs
337 end
338 end
339
340 # Removes multiple values for the custom field after setting the multiple attribute to false
341 # We kepp the value with the highest id for each customized object
342 def handle_multiplicity_change
343 if !new_record? && multiple_was && !multiple
344 ids = custom_values.
345 where("EXISTS(SELECT 1 FROM #{CustomValue.table_name} cve WHERE cve.custom_field_id = #{CustomValue.table_name}.custom_field_id" +
346 " AND cve.customized_type = #{CustomValue.table_name}.customized_type AND cve.customized_id = #{CustomValue.table_name}.customized_id" +
347 " AND cve.id > #{CustomValue.table_name}.id)").
348 pluck(:id)
349
350 if ids.any?
351 custom_values.where(:id => ids).delete_all
352 end
353 end
354 end
338 end
355 end
@@ -1,83 +1,83
1 <%= error_messages_for 'custom_field' %>
1 <%= error_messages_for 'custom_field' %>
2
2
3 <div class="box tabular">
3 <div class="box tabular">
4 <p><%= f.text_field :name, :required => true %></p>
4 <p><%= f.text_field :name, :required => true %></p>
5 <p><%= f.select :field_format, custom_field_formats_for_select(@custom_field), {}, :disabled => !@custom_field.new_record? %></p>
5 <p><%= f.select :field_format, custom_field_formats_for_select(@custom_field), {}, :disabled => !@custom_field.new_record? %></p>
6
6
7 <% if @custom_field.format_in? 'list', 'user', 'version' %>
7 <% if @custom_field.format_in? 'list', 'user', 'version' %>
8 <p><%= f.check_box :multiple, :disabled => @custom_field.multiple && !@custom_field.new_record? %></p>
8 <p><%= f.check_box :multiple %></p>
9 <% end %>
9 <% end %>
10
10
11 <% unless @custom_field.format_in? 'list', 'bool', 'date', 'user', 'version' %>
11 <% unless @custom_field.format_in? 'list', 'bool', 'date', 'user', 'version' %>
12 <p><label for="custom_field_min_length"><%=l(:label_min_max_length)%></label>
12 <p><label for="custom_field_min_length"><%=l(:label_min_max_length)%></label>
13 <%= f.text_field :min_length, :size => 5, :no_label => true %> -
13 <%= f.text_field :min_length, :size => 5, :no_label => true %> -
14 <%= f.text_field :max_length, :size => 5, :no_label => true %><br />(<%=l(:text_min_max_length_info)%>)</p>
14 <%= f.text_field :max_length, :size => 5, :no_label => true %><br />(<%=l(:text_min_max_length_info)%>)</p>
15 <p><%= f.text_field :regexp, :size => 50 %><br />(<%=l(:text_regexp_info)%>)</p>
15 <p><%= f.text_field :regexp, :size => 50 %><br />(<%=l(:text_regexp_info)%>)</p>
16 <% end %>
16 <% end %>
17
17
18 <% if @custom_field.format_in? 'list' %>
18 <% if @custom_field.format_in? 'list' %>
19 <p>
19 <p>
20 <%= f.text_area :possible_values, :value => @custom_field.possible_values.to_a.join("\n"), :rows => 15 %>
20 <%= f.text_area :possible_values, :value => @custom_field.possible_values.to_a.join("\n"), :rows => 15 %>
21 <em class="info"><%= l(:text_custom_field_possible_values_info) %></em>
21 <em class="info"><%= l(:text_custom_field_possible_values_info) %></em>
22 </p>
22 </p>
23 <% end %>
23 <% end %>
24
24
25 <% unless @custom_field.format_in? 'user', 'version' %>
25 <% unless @custom_field.format_in? 'user', 'version' %>
26 <p><%= @custom_field.field_format == 'bool' ? f.check_box(:default_value) : f.text_field(:default_value) %></p>
26 <p><%= @custom_field.field_format == 'bool' ? f.check_box(:default_value) : f.text_field(:default_value) %></p>
27 <% end %>
27 <% end %>
28
28
29 <%= call_hook(:view_custom_fields_form_upper_box, :custom_field => @custom_field, :form => f) %>
29 <%= call_hook(:view_custom_fields_form_upper_box, :custom_field => @custom_field, :form => f) %>
30 </div>
30 </div>
31
31
32 <div class="box tabular">
32 <div class="box tabular">
33 <% case @custom_field.class.name
33 <% case @custom_field.class.name
34 when "IssueCustomField" %>
34 when "IssueCustomField" %>
35
35
36 <fieldset><legend><%=l(:label_tracker_plural)%></legend>
36 <fieldset><legend><%=l(:label_tracker_plural)%></legend>
37 <% Tracker.sorted.all.each do |tracker| %>
37 <% Tracker.sorted.all.each do |tracker| %>
38 <%= check_box_tag "custom_field[tracker_ids][]",
38 <%= check_box_tag "custom_field[tracker_ids][]",
39 tracker.id,
39 tracker.id,
40 (@custom_field.trackers.include? tracker),
40 (@custom_field.trackers.include? tracker),
41 :id => "custom_field_tracker_ids_#{tracker.id}" %>
41 :id => "custom_field_tracker_ids_#{tracker.id}" %>
42 <label class="no-css" for="custom_field_tracker_ids_<%=tracker.id%>">
42 <label class="no-css" for="custom_field_tracker_ids_<%=tracker.id%>">
43 <%= h(tracker.name) %>
43 <%= h(tracker.name) %>
44 </label>
44 </label>
45 <% end %>
45 <% end %>
46 <%= hidden_field_tag "custom_field[tracker_ids][]", '' %>
46 <%= hidden_field_tag "custom_field[tracker_ids][]", '' %>
47 </fieldset>
47 </fieldset>
48 &nbsp;
48 &nbsp;
49 <p><%= f.check_box :is_required %></p>
49 <p><%= f.check_box :is_required %></p>
50 <p><%= f.check_box :is_for_all %></p>
50 <p><%= f.check_box :is_for_all %></p>
51 <p><%= f.check_box :is_filter %></p>
51 <p><%= f.check_box :is_filter %></p>
52 <p><%= f.check_box :searchable %></p>
52 <p><%= f.check_box :searchable %></p>
53
53
54 <% when "UserCustomField" %>
54 <% when "UserCustomField" %>
55 <p><%= f.check_box :is_required %></p>
55 <p><%= f.check_box :is_required %></p>
56 <p><%= f.check_box :visible %></p>
56 <p><%= f.check_box :visible %></p>
57 <p><%= f.check_box :editable %></p>
57 <p><%= f.check_box :editable %></p>
58 <p><%= f.check_box :is_filter %></p>
58 <p><%= f.check_box :is_filter %></p>
59
59
60 <% when "ProjectCustomField" %>
60 <% when "ProjectCustomField" %>
61 <p><%= f.check_box :is_required %></p>
61 <p><%= f.check_box :is_required %></p>
62 <p><%= f.check_box :visible %></p>
62 <p><%= f.check_box :visible %></p>
63 <p><%= f.check_box :searchable %></p>
63 <p><%= f.check_box :searchable %></p>
64 <p><%= f.check_box :is_filter %></p>
64 <p><%= f.check_box :is_filter %></p>
65
65
66 <% when "VersionCustomField" %>
66 <% when "VersionCustomField" %>
67 <p><%= f.check_box :is_required %></p>
67 <p><%= f.check_box :is_required %></p>
68 <p><%= f.check_box :is_filter %></p>
68 <p><%= f.check_box :is_filter %></p>
69
69
70 <% when "GroupCustomField" %>
70 <% when "GroupCustomField" %>
71 <p><%= f.check_box :is_required %></p>
71 <p><%= f.check_box :is_required %></p>
72 <p><%= f.check_box :is_filter %></p>
72 <p><%= f.check_box :is_filter %></p>
73
73
74 <% when "TimeEntryCustomField" %>
74 <% when "TimeEntryCustomField" %>
75 <p><%= f.check_box :is_required %></p>
75 <p><%= f.check_box :is_required %></p>
76 <p><%= f.check_box :is_filter %></p>
76 <p><%= f.check_box :is_filter %></p>
77
77
78 <% else %>
78 <% else %>
79 <p><%= f.check_box :is_required %></p>
79 <p><%= f.check_box :is_required %></p>
80
80
81 <% end %>
81 <% end %>
82 <%= call_hook(:"view_custom_fields_form_#{@custom_field.type.to_s.underscore}", :custom_field => @custom_field, :form => f) %>
82 <%= call_hook(:"view_custom_fields_form_#{@custom_field.type.to_s.underscore}", :custom_field => @custom_field, :form => f) %>
83 </div>
83 </div>
@@ -1,226 +1,244
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 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
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class CustomFieldTest < ActiveSupport::TestCase
20 class CustomFieldTest < ActiveSupport::TestCase
21 fixtures :custom_fields
21 fixtures :custom_fields
22
22
23 def test_create
23 def test_create
24 field = UserCustomField.new(:name => 'Money money money', :field_format => 'float')
24 field = UserCustomField.new(:name => 'Money money money', :field_format => 'float')
25 assert field.save
25 assert field.save
26 end
26 end
27
27
28 def test_before_validation
28 def test_before_validation
29 field = CustomField.new(:name => 'test_before_validation', :field_format => 'int')
29 field = CustomField.new(:name => 'test_before_validation', :field_format => 'int')
30 field.searchable = true
30 field.searchable = true
31 assert field.save
31 assert field.save
32 assert_equal false, field.searchable
32 assert_equal false, field.searchable
33 field.searchable = true
33 field.searchable = true
34 assert field.save
34 assert field.save
35 assert_equal false, field.searchable
35 assert_equal false, field.searchable
36 end
36 end
37
37
38 def test_regexp_validation
38 def test_regexp_validation
39 field = IssueCustomField.new(:name => 'regexp', :field_format => 'text', :regexp => '[a-z0-9')
39 field = IssueCustomField.new(:name => 'regexp', :field_format => 'text', :regexp => '[a-z0-9')
40 assert !field.save
40 assert !field.save
41 assert_include I18n.t('activerecord.errors.messages.invalid'),
41 assert_include I18n.t('activerecord.errors.messages.invalid'),
42 field.errors[:regexp]
42 field.errors[:regexp]
43 field.regexp = '[a-z0-9]'
43 field.regexp = '[a-z0-9]'
44 assert field.save
44 assert field.save
45 end
45 end
46
46
47 def test_default_value_should_be_validated
47 def test_default_value_should_be_validated
48 field = CustomField.new(:name => 'Test', :field_format => 'int')
48 field = CustomField.new(:name => 'Test', :field_format => 'int')
49 field.default_value = 'abc'
49 field.default_value = 'abc'
50 assert !field.valid?
50 assert !field.valid?
51 field.default_value = '6'
51 field.default_value = '6'
52 assert field.valid?
52 assert field.valid?
53 end
53 end
54
54
55 def test_default_value_should_not_be_validated_when_blank
55 def test_default_value_should_not_be_validated_when_blank
56 field = CustomField.new(:name => 'Test', :field_format => 'list', :possible_values => ['a', 'b'], :is_required => true, :default_value => '')
56 field = CustomField.new(:name => 'Test', :field_format => 'list', :possible_values => ['a', 'b'], :is_required => true, :default_value => '')
57 assert field.valid?
57 assert field.valid?
58 end
58 end
59
59
60 def test_should_not_change_field_format_of_existing_custom_field
60 def test_should_not_change_field_format_of_existing_custom_field
61 field = CustomField.find(1)
61 field = CustomField.find(1)
62 field.field_format = 'int'
62 field.field_format = 'int'
63 assert_equal 'list', field.field_format
63 assert_equal 'list', field.field_format
64 end
64 end
65
65
66 def test_possible_values_should_accept_an_array
66 def test_possible_values_should_accept_an_array
67 field = CustomField.new
67 field = CustomField.new
68 field.possible_values = ["One value", ""]
68 field.possible_values = ["One value", ""]
69 assert_equal ["One value"], field.possible_values
69 assert_equal ["One value"], field.possible_values
70 end
70 end
71
71
72 def test_possible_values_should_accept_a_string
72 def test_possible_values_should_accept_a_string
73 field = CustomField.new
73 field = CustomField.new
74 field.possible_values = "One value"
74 field.possible_values = "One value"
75 assert_equal ["One value"], field.possible_values
75 assert_equal ["One value"], field.possible_values
76 end
76 end
77
77
78 def test_possible_values_should_accept_a_multiline_string
78 def test_possible_values_should_accept_a_multiline_string
79 field = CustomField.new
79 field = CustomField.new
80 field.possible_values = "One value\nAnd another one \r\n \n"
80 field.possible_values = "One value\nAnd another one \r\n \n"
81 assert_equal ["One value", "And another one"], field.possible_values
81 assert_equal ["One value", "And another one"], field.possible_values
82 end
82 end
83
83
84 if "string".respond_to?(:encoding)
84 if "string".respond_to?(:encoding)
85 def test_possible_values_stored_as_binary_should_be_utf8_encoded
85 def test_possible_values_stored_as_binary_should_be_utf8_encoded
86 field = CustomField.find(11)
86 field = CustomField.find(11)
87 assert_kind_of Array, field.possible_values
87 assert_kind_of Array, field.possible_values
88 assert field.possible_values.size > 0
88 assert field.possible_values.size > 0
89 field.possible_values.each do |value|
89 field.possible_values.each do |value|
90 assert_equal "UTF-8", value.encoding.name
90 assert_equal "UTF-8", value.encoding.name
91 end
91 end
92 end
92 end
93 end
93 end
94
94
95 def test_destroy
95 def test_destroy
96 field = CustomField.find(1)
96 field = CustomField.find(1)
97 assert field.destroy
97 assert field.destroy
98 end
98 end
99
99
100 def test_new_subclass_instance_should_return_an_instance
100 def test_new_subclass_instance_should_return_an_instance
101 f = CustomField.new_subclass_instance('IssueCustomField')
101 f = CustomField.new_subclass_instance('IssueCustomField')
102 assert_kind_of IssueCustomField, f
102 assert_kind_of IssueCustomField, f
103 end
103 end
104
104
105 def test_new_subclass_instance_should_set_attributes
105 def test_new_subclass_instance_should_set_attributes
106 f = CustomField.new_subclass_instance('IssueCustomField', :name => 'Test')
106 f = CustomField.new_subclass_instance('IssueCustomField', :name => 'Test')
107 assert_kind_of IssueCustomField, f
107 assert_kind_of IssueCustomField, f
108 assert_equal 'Test', f.name
108 assert_equal 'Test', f.name
109 end
109 end
110
110
111 def test_new_subclass_instance_with_invalid_class_name_should_return_nil
111 def test_new_subclass_instance_with_invalid_class_name_should_return_nil
112 assert_nil CustomField.new_subclass_instance('WrongClassName')
112 assert_nil CustomField.new_subclass_instance('WrongClassName')
113 end
113 end
114
114
115 def test_new_subclass_instance_with_non_subclass_name_should_return_nil
115 def test_new_subclass_instance_with_non_subclass_name_should_return_nil
116 assert_nil CustomField.new_subclass_instance('Project')
116 assert_nil CustomField.new_subclass_instance('Project')
117 end
117 end
118
118
119 def test_string_field_validation_with_blank_value
119 def test_string_field_validation_with_blank_value
120 f = CustomField.new(:field_format => 'string')
120 f = CustomField.new(:field_format => 'string')
121
121
122 assert f.valid_field_value?(nil)
122 assert f.valid_field_value?(nil)
123 assert f.valid_field_value?('')
123 assert f.valid_field_value?('')
124
124
125 f.is_required = true
125 f.is_required = true
126 assert !f.valid_field_value?(nil)
126 assert !f.valid_field_value?(nil)
127 assert !f.valid_field_value?('')
127 assert !f.valid_field_value?('')
128 end
128 end
129
129
130 def test_string_field_validation_with_min_and_max_lengths
130 def test_string_field_validation_with_min_and_max_lengths
131 f = CustomField.new(:field_format => 'string', :min_length => 2, :max_length => 5)
131 f = CustomField.new(:field_format => 'string', :min_length => 2, :max_length => 5)
132
132
133 assert f.valid_field_value?(nil)
133 assert f.valid_field_value?(nil)
134 assert f.valid_field_value?('')
134 assert f.valid_field_value?('')
135 assert f.valid_field_value?('a' * 2)
135 assert f.valid_field_value?('a' * 2)
136 assert !f.valid_field_value?('a')
136 assert !f.valid_field_value?('a')
137 assert !f.valid_field_value?('a' * 6)
137 assert !f.valid_field_value?('a' * 6)
138 end
138 end
139
139
140 def test_string_field_validation_with_regexp
140 def test_string_field_validation_with_regexp
141 f = CustomField.new(:field_format => 'string', :regexp => '^[A-Z0-9]*$')
141 f = CustomField.new(:field_format => 'string', :regexp => '^[A-Z0-9]*$')
142
142
143 assert f.valid_field_value?(nil)
143 assert f.valid_field_value?(nil)
144 assert f.valid_field_value?('')
144 assert f.valid_field_value?('')
145 assert f.valid_field_value?('ABC')
145 assert f.valid_field_value?('ABC')
146 assert !f.valid_field_value?('abc')
146 assert !f.valid_field_value?('abc')
147 end
147 end
148
148
149 def test_date_field_validation
149 def test_date_field_validation
150 f = CustomField.new(:field_format => 'date')
150 f = CustomField.new(:field_format => 'date')
151
151
152 assert f.valid_field_value?(nil)
152 assert f.valid_field_value?(nil)
153 assert f.valid_field_value?('')
153 assert f.valid_field_value?('')
154 assert f.valid_field_value?('1975-07-14')
154 assert f.valid_field_value?('1975-07-14')
155 assert !f.valid_field_value?('1975-07-33')
155 assert !f.valid_field_value?('1975-07-33')
156 assert !f.valid_field_value?('abc')
156 assert !f.valid_field_value?('abc')
157 end
157 end
158
158
159 def test_list_field_validation
159 def test_list_field_validation
160 f = CustomField.new(:field_format => 'list', :possible_values => ['value1', 'value2'])
160 f = CustomField.new(:field_format => 'list', :possible_values => ['value1', 'value2'])
161
161
162 assert f.valid_field_value?(nil)
162 assert f.valid_field_value?(nil)
163 assert f.valid_field_value?('')
163 assert f.valid_field_value?('')
164 assert f.valid_field_value?('value2')
164 assert f.valid_field_value?('value2')
165 assert !f.valid_field_value?('abc')
165 assert !f.valid_field_value?('abc')
166 end
166 end
167
167
168 def test_int_field_validation
168 def test_int_field_validation
169 f = CustomField.new(:field_format => 'int')
169 f = CustomField.new(:field_format => 'int')
170
170
171 assert f.valid_field_value?(nil)
171 assert f.valid_field_value?(nil)
172 assert f.valid_field_value?('')
172 assert f.valid_field_value?('')
173 assert f.valid_field_value?('123')
173 assert f.valid_field_value?('123')
174 assert f.valid_field_value?('+123')
174 assert f.valid_field_value?('+123')
175 assert f.valid_field_value?('-123')
175 assert f.valid_field_value?('-123')
176 assert !f.valid_field_value?('6abc')
176 assert !f.valid_field_value?('6abc')
177 end
177 end
178
178
179 def test_float_field_validation
179 def test_float_field_validation
180 f = CustomField.new(:field_format => 'float')
180 f = CustomField.new(:field_format => 'float')
181
181
182 assert f.valid_field_value?(nil)
182 assert f.valid_field_value?(nil)
183 assert f.valid_field_value?('')
183 assert f.valid_field_value?('')
184 assert f.valid_field_value?('11.2')
184 assert f.valid_field_value?('11.2')
185 assert f.valid_field_value?('-6.250')
185 assert f.valid_field_value?('-6.250')
186 assert f.valid_field_value?('5')
186 assert f.valid_field_value?('5')
187 assert !f.valid_field_value?('6abc')
187 assert !f.valid_field_value?('6abc')
188 end
188 end
189
189
190 def test_multi_field_validation
190 def test_multi_field_validation
191 f = CustomField.new(:field_format => 'list', :multiple => 'true', :possible_values => ['value1', 'value2'])
191 f = CustomField.new(:field_format => 'list', :multiple => 'true', :possible_values => ['value1', 'value2'])
192
192
193 assert f.valid_field_value?(nil)
193 assert f.valid_field_value?(nil)
194 assert f.valid_field_value?('')
194 assert f.valid_field_value?('')
195 assert f.valid_field_value?([])
195 assert f.valid_field_value?([])
196 assert f.valid_field_value?([nil])
196 assert f.valid_field_value?([nil])
197 assert f.valid_field_value?([''])
197 assert f.valid_field_value?([''])
198
198
199 assert f.valid_field_value?('value2')
199 assert f.valid_field_value?('value2')
200 assert !f.valid_field_value?('abc')
200 assert !f.valid_field_value?('abc')
201
201
202 assert f.valid_field_value?(['value2'])
202 assert f.valid_field_value?(['value2'])
203 assert !f.valid_field_value?(['abc'])
203 assert !f.valid_field_value?(['abc'])
204
204
205 assert f.valid_field_value?(['', 'value2'])
205 assert f.valid_field_value?(['', 'value2'])
206 assert !f.valid_field_value?(['', 'abc'])
206 assert !f.valid_field_value?(['', 'abc'])
207
207
208 assert f.valid_field_value?(['value1', 'value2'])
208 assert f.valid_field_value?(['value1', 'value2'])
209 assert !f.valid_field_value?(['value1', 'abc'])
209 assert !f.valid_field_value?(['value1', 'abc'])
210 end
210 end
211
211
212 def test_changing_multiple_to_false_should_delete_multiple_values
213 field = ProjectCustomField.create!(:name => 'field', :field_format => 'list', :multiple => 'true', :possible_values => ['field1', 'field2'])
214 other = ProjectCustomField.create!(:name => 'other', :field_format => 'list', :multiple => 'true', :possible_values => ['other1', 'other2'])
215
216 item_with_multiple_values = Project.generate!(:custom_field_values => {field.id => ['field1', 'field2'], other.id => ['other1', 'other2']})
217 item_with_single_values = Project.generate!(:custom_field_values => {field.id => ['field1'], other.id => ['other2']})
218
219 assert_difference 'CustomValue.count', -1 do
220 field.multiple = false
221 field.save!
222 end
223
224 item_with_multiple_values = Project.find(item_with_multiple_values.id)
225 assert_kind_of String, item_with_multiple_values.custom_field_value(field)
226 assert_kind_of Array, item_with_multiple_values.custom_field_value(other)
227 assert_equal 2, item_with_multiple_values.custom_field_value(other).size
228 end
229
212 def test_value_class_should_return_the_class_used_for_fields_values
230 def test_value_class_should_return_the_class_used_for_fields_values
213 assert_equal User, CustomField.new(:field_format => 'user').value_class
231 assert_equal User, CustomField.new(:field_format => 'user').value_class
214 assert_equal Version, CustomField.new(:field_format => 'version').value_class
232 assert_equal Version, CustomField.new(:field_format => 'version').value_class
215 end
233 end
216
234
217 def test_value_class_should_return_nil_for_other_fields
235 def test_value_class_should_return_nil_for_other_fields
218 assert_nil CustomField.new(:field_format => 'text').value_class
236 assert_nil CustomField.new(:field_format => 'text').value_class
219 assert_nil CustomField.new.value_class
237 assert_nil CustomField.new.value_class
220 end
238 end
221
239
222 def test_value_from_keyword_for_list_custom_field
240 def test_value_from_keyword_for_list_custom_field
223 field = CustomField.find(1)
241 field = CustomField.find(1)
224 assert_equal 'PostgreSQL', field.value_from_keyword('postgresql', Issue.find(1))
242 assert_equal 'PostgreSQL', field.value_from_keyword('postgresql', Issue.find(1))
225 end
243 end
226 end
244 end
General Comments 0
You need to be logged in to leave comments. Login now