##// END OF EJS Templates
Moved CUSTOM_FIELDS_TABS out of the model....
Jean-Philippe Lang -
r11847:b19b90234529
parent child
Show More
@@ -1,149 +1,170
1 1 # encoding: utf-8
2 2 #
3 3 # Redmine - project management software
4 4 # Copyright (C) 2006-2013 Jean-Philippe Lang
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; either version 2
9 9 # of the License, or (at your option) any later version.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 19
20 20 module CustomFieldsHelper
21 21
22 CUSTOM_FIELDS_TABS = [
23 {:name => 'IssueCustomField', :partial => 'custom_fields/index',
24 :label => :label_issue_plural},
25 {:name => 'TimeEntryCustomField', :partial => 'custom_fields/index',
26 :label => :label_spent_time},
27 {:name => 'ProjectCustomField', :partial => 'custom_fields/index',
28 :label => :label_project_plural},
29 {:name => 'VersionCustomField', :partial => 'custom_fields/index',
30 :label => :label_version_plural},
31 {:name => 'UserCustomField', :partial => 'custom_fields/index',
32 :label => :label_user_plural},
33 {:name => 'GroupCustomField', :partial => 'custom_fields/index',
34 :label => :label_group_plural},
35 {:name => 'TimeEntryActivityCustomField', :partial => 'custom_fields/index',
36 :label => TimeEntryActivity::OptionName},
37 {:name => 'IssuePriorityCustomField', :partial => 'custom_fields/index',
38 :label => IssuePriority::OptionName},
39 {:name => 'DocumentCategoryCustomField', :partial => 'custom_fields/index',
40 :label => DocumentCategory::OptionName}
41 ]
42
22 43 def custom_fields_tabs
23 CustomField::CUSTOM_FIELDS_TABS
44 CUSTOM_FIELDS_TABS
24 45 end
25 46
26 47 # Return custom field html tag corresponding to its format
27 48 def custom_field_tag(name, custom_value)
28 49 custom_field = custom_value.custom_field
29 50 field_name = "#{name}[custom_field_values][#{custom_field.id}]"
30 51 field_name << "[]" if custom_field.multiple?
31 52 field_id = "#{name}_custom_field_values_#{custom_field.id}"
32 53
33 54 tag_options = {:id => field_id, :class => "#{custom_field.field_format}_cf"}
34 55
35 56 field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format)
36 57 case field_format.try(:edit_as)
37 58 when "date"
38 59 text_field_tag(field_name, custom_value.value, tag_options.merge(:size => 10)) +
39 60 calendar_for(field_id)
40 61 when "text"
41 62 text_area_tag(field_name, custom_value.value, tag_options.merge(:rows => 3))
42 63 when "bool"
43 64 hidden_field_tag(field_name, '0') + check_box_tag(field_name, '1', custom_value.true?, tag_options)
44 65 when "list"
45 66 blank_option = ''.html_safe
46 67 unless custom_field.multiple?
47 68 if custom_field.is_required?
48 69 unless custom_field.default_value.present?
49 70 blank_option = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---", :value => '')
50 71 end
51 72 else
52 73 blank_option = content_tag('option')
53 74 end
54 75 end
55 76 s = select_tag(field_name, blank_option + options_for_select(custom_field.possible_values_options(custom_value.customized), custom_value.value),
56 77 tag_options.merge(:multiple => custom_field.multiple?))
57 78 if custom_field.multiple?
58 79 s << hidden_field_tag(field_name, '')
59 80 end
60 81 s
61 82 else
62 83 text_field_tag(field_name, custom_value.value, tag_options)
63 84 end
64 85 end
65 86
66 87 # Return custom field label tag
67 88 def custom_field_label_tag(name, custom_value, options={})
68 89 required = options[:required] || custom_value.custom_field.is_required?
69 90
70 91 content_tag "label", h(custom_value.custom_field.name) +
71 92 (required ? " <span class=\"required\">*</span>".html_safe : ""),
72 93 :for => "#{name}_custom_field_values_#{custom_value.custom_field.id}"
73 94 end
74 95
75 96 # Return custom field tag with its label tag
76 97 def custom_field_tag_with_label(name, custom_value, options={})
77 98 custom_field_label_tag(name, custom_value, options) + custom_field_tag(name, custom_value)
78 99 end
79 100
80 101 def custom_field_tag_for_bulk_edit(name, custom_field, projects=nil, value='')
81 102 field_name = "#{name}[custom_field_values][#{custom_field.id}]"
82 103 field_name << "[]" if custom_field.multiple?
83 104 field_id = "#{name}_custom_field_values_#{custom_field.id}"
84 105
85 106 tag_options = {:id => field_id, :class => "#{custom_field.field_format}_cf"}
86 107
87 108 field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format)
88 109 case field_format.try(:edit_as)
89 110 when "date"
90 111 text_field_tag(field_name, value, tag_options.merge(:size => 10)) +
91 112 calendar_for(field_id)
92 113 when "text"
93 114 text_area_tag(field_name, value, tag_options.merge(:rows => 3))
94 115 when "bool"
95 116 select_tag(field_name, options_for_select([[l(:label_no_change_option), ''],
96 117 [l(:general_text_yes), '1'],
97 118 [l(:general_text_no), '0']], value), tag_options)
98 119 when "list"
99 120 options = []
100 121 options << [l(:label_no_change_option), ''] unless custom_field.multiple?
101 122 options << [l(:label_none), '__none__'] unless custom_field.is_required?
102 123 options += custom_field.possible_values_options(projects)
103 124 select_tag(field_name, options_for_select(options, value), tag_options.merge(:multiple => custom_field.multiple?))
104 125 else
105 126 text_field_tag(field_name, value, tag_options)
106 127 end
107 128 end
108 129
109 130 # Return a string used to display a custom value
110 131 def show_value(custom_value)
111 132 return "" unless custom_value
112 133 format_value(custom_value.value, custom_value.custom_field.field_format)
113 134 end
114 135
115 136 # Return a string used to display a custom value
116 137 def format_value(value, field_format)
117 138 if value.is_a?(Array)
118 139 value.collect {|v| format_value(v, field_format)}.compact.sort.join(', ')
119 140 else
120 141 Redmine::CustomFieldFormat.format_value(value, field_format)
121 142 end
122 143 end
123 144
124 145 # Return an array of custom field formats which can be used in select_tag
125 146 def custom_field_formats_for_select(custom_field)
126 147 Redmine::CustomFieldFormat.as_select(custom_field.class.customized_class.name)
127 148 end
128 149
129 150 # Renders the custom_values in api views
130 151 def render_api_custom_values(custom_values, api)
131 152 api.array :custom_fields do
132 153 custom_values.each do |custom_value|
133 154 attrs = {:id => custom_value.custom_field_id, :name => custom_value.custom_field.name}
134 155 attrs.merge!(:multiple => true) if custom_value.custom_field.multiple?
135 156 api.custom_field attrs do
136 157 if custom_value.value.is_a?(Array)
137 158 api.array :value do
138 159 custom_value.value.each do |value|
139 160 api.value value unless value.blank?
140 161 end
141 162 end
142 163 else
143 164 api.value custom_value.value
144 165 end
145 166 end
146 167 end
147 168 end unless custom_values.empty?
148 169 end
149 170 end
@@ -1,409 +1,386
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2013 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 class CustomField < ActiveRecord::Base
19 19 include Redmine::SubclassFactory
20 20
21 21 has_many :custom_values, :dependent => :delete_all
22 22 has_and_belongs_to_many :roles, :join_table => "#{table_name_prefix}custom_fields_roles#{table_name_suffix}", :foreign_key => "custom_field_id"
23 23 acts_as_list :scope => 'type = \'#{self.class}\''
24 24 serialize :possible_values
25 25
26 26 validates_presence_of :name, :field_format
27 27 validates_uniqueness_of :name, :scope => :type
28 28 validates_length_of :name, :maximum => 30
29 29 validates_inclusion_of :field_format, :in => Redmine::CustomFieldFormat.available_formats
30 30 validate :validate_custom_field
31 31
32 32 before_validation :set_searchable
33 33 after_save :handle_multiplicity_change
34 34 after_save do |field|
35 35 if field.visible_changed? && field.visible
36 36 field.roles.clear
37 37 end
38 38 end
39 39
40 40 scope :sorted, lambda { order("#{table_name}.position ASC") }
41 41 scope :visible, lambda {|*args|
42 42 user = args.shift || User.current
43 43 if user.admin?
44 44 # nop
45 45 elsif user.memberships.any?
46 46 where("#{table_name}.visible = ? OR #{table_name}.id IN (SELECT DISTINCT cfr.custom_field_id FROM #{Member.table_name} m" +
47 47 " INNER JOIN #{MemberRole.table_name} mr ON mr.member_id = m.id" +
48 48 " INNER JOIN #{table_name_prefix}custom_fields_roles#{table_name_suffix} cfr ON cfr.role_id = mr.role_id" +
49 49 " WHERE m.user_id = ?)",
50 50 true, user.id)
51 51 else
52 52 where(:visible => true)
53 53 end
54 54 }
55 55
56 CUSTOM_FIELDS_TABS = [
57 {:name => 'IssueCustomField', :partial => 'custom_fields/index',
58 :label => :label_issue_plural},
59 {:name => 'TimeEntryCustomField', :partial => 'custom_fields/index',
60 :label => :label_spent_time},
61 {:name => 'ProjectCustomField', :partial => 'custom_fields/index',
62 :label => :label_project_plural},
63 {:name => 'VersionCustomField', :partial => 'custom_fields/index',
64 :label => :label_version_plural},
65 {:name => 'UserCustomField', :partial => 'custom_fields/index',
66 :label => :label_user_plural},
67 {:name => 'GroupCustomField', :partial => 'custom_fields/index',
68 :label => :label_group_plural},
69 {:name => 'TimeEntryActivityCustomField', :partial => 'custom_fields/index',
70 :label => TimeEntryActivity::OptionName},
71 {:name => 'IssuePriorityCustomField', :partial => 'custom_fields/index',
72 :label => IssuePriority::OptionName},
73 {:name => 'DocumentCategoryCustomField', :partial => 'custom_fields/index',
74 :label => DocumentCategory::OptionName}
75 ]
76
77 CUSTOM_FIELDS_NAMES = CUSTOM_FIELDS_TABS.collect{|v| v[:name]}
78
79 56 def visible_by?(project, user=User.current)
80 57 visible? || user.admin?
81 58 end
82 59
83 60 def field_format=(arg)
84 61 # cannot change format of a saved custom field
85 62 super if new_record?
86 63 end
87 64
88 65 def set_searchable
89 66 # make sure these fields are not searchable
90 67 self.searchable = false if %w(int float date bool).include?(field_format)
91 68 # make sure only these fields can have multiple values
92 69 self.multiple = false unless %w(list user version).include?(field_format)
93 70 true
94 71 end
95 72
96 73 def validate_custom_field
97 74 if self.field_format == "list"
98 75 errors.add(:possible_values, :blank) if self.possible_values.nil? || self.possible_values.empty?
99 76 errors.add(:possible_values, :invalid) unless self.possible_values.is_a? Array
100 77 end
101 78
102 79 if regexp.present?
103 80 begin
104 81 Regexp.new(regexp)
105 82 rescue
106 83 errors.add(:regexp, :invalid)
107 84 end
108 85 end
109 86
110 87 if default_value.present? && !valid_field_value?(default_value)
111 88 errors.add(:default_value, :invalid)
112 89 end
113 90 end
114 91
115 92 def possible_values_options(obj=nil)
116 93 case field_format
117 94 when 'user', 'version'
118 95 if obj.respond_to?(:project) && obj.project
119 96 case field_format
120 97 when 'user'
121 98 obj.project.users.sort.collect {|u| [u.to_s, u.id.to_s]}
122 99 when 'version'
123 100 obj.project.shared_versions.sort.collect {|u| [u.to_s, u.id.to_s]}
124 101 end
125 102 elsif obj.is_a?(Array)
126 103 obj.collect {|o| possible_values_options(o)}.reduce(:&)
127 104 else
128 105 []
129 106 end
130 107 when 'bool'
131 108 [[l(:general_text_Yes), '1'], [l(:general_text_No), '0']]
132 109 else
133 110 possible_values || []
134 111 end
135 112 end
136 113
137 114 def possible_values(obj=nil)
138 115 case field_format
139 116 when 'user', 'version'
140 117 possible_values_options(obj).collect(&:last)
141 118 when 'bool'
142 119 ['1', '0']
143 120 else
144 121 values = super()
145 122 if values.is_a?(Array)
146 123 values.each do |value|
147 124 value.force_encoding('UTF-8') if value.respond_to?(:force_encoding)
148 125 end
149 126 end
150 127 values || []
151 128 end
152 129 end
153 130
154 131 # Makes possible_values accept a multiline string
155 132 def possible_values=(arg)
156 133 if arg.is_a?(Array)
157 134 super(arg.compact.collect(&:strip).select {|v| !v.blank?})
158 135 else
159 136 self.possible_values = arg.to_s.split(/[\n\r]+/)
160 137 end
161 138 end
162 139
163 140 def cast_value(value)
164 141 casted = nil
165 142 unless value.blank?
166 143 case field_format
167 144 when 'string', 'text', 'list'
168 145 casted = value
169 146 when 'date'
170 147 casted = begin; value.to_date; rescue; nil end
171 148 when 'bool'
172 149 casted = (value == '1' ? true : false)
173 150 when 'int'
174 151 casted = value.to_i
175 152 when 'float'
176 153 casted = value.to_f
177 154 when 'user', 'version'
178 155 casted = (value.blank? ? nil : field_format.classify.constantize.find_by_id(value.to_i))
179 156 end
180 157 end
181 158 casted
182 159 end
183 160
184 161 def value_from_keyword(keyword, customized)
185 162 possible_values_options = possible_values_options(customized)
186 163 if possible_values_options.present?
187 164 keyword = keyword.to_s.downcase
188 165 if v = possible_values_options.detect {|text, id| text.downcase == keyword}
189 166 if v.is_a?(Array)
190 167 v.last
191 168 else
192 169 v
193 170 end
194 171 end
195 172 else
196 173 keyword
197 174 end
198 175 end
199 176
200 177 # Returns a ORDER BY clause that can used to sort customized
201 178 # objects by their value of the custom field.
202 179 # Returns nil if the custom field can not be used for sorting.
203 180 def order_statement
204 181 return nil if multiple?
205 182 case field_format
206 183 when 'string', 'text', 'list', 'date', 'bool'
207 184 # COALESCE is here to make sure that blank and NULL values are sorted equally
208 185 "COALESCE(#{join_alias}.value, '')"
209 186 when 'int', 'float'
210 187 # Make the database cast values into numeric
211 188 # Postgresql will raise an error if a value can not be casted!
212 189 # CustomValue validations should ensure that it doesn't occur
213 190 "CAST(CASE #{join_alias}.value WHEN '' THEN '0' ELSE #{join_alias}.value END AS decimal(30,3))"
214 191 when 'user', 'version'
215 192 value_class.fields_for_order_statement(value_join_alias)
216 193 else
217 194 nil
218 195 end
219 196 end
220 197
221 198 # Returns a GROUP BY clause that can used to group by custom value
222 199 # Returns nil if the custom field can not be used for grouping.
223 200 def group_statement
224 201 return nil if multiple?
225 202 case field_format
226 203 when 'list', 'date', 'bool', 'int'
227 204 order_statement
228 205 when 'user', 'version'
229 206 "COALESCE(#{join_alias}.value, '')"
230 207 else
231 208 nil
232 209 end
233 210 end
234 211
235 212 def join_for_order_statement
236 213 case field_format
237 214 when 'user', 'version'
238 215 "LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" +
239 216 " ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" +
240 217 " AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" +
241 218 " AND #{join_alias}.custom_field_id = #{id}" +
242 219 " AND (#{visibility_by_project_condition})" +
243 220 " AND #{join_alias}.value <> ''" +
244 221 " AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" +
245 222 " WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" +
246 223 " AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" +
247 224 " AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)" +
248 225 " LEFT OUTER JOIN #{value_class.table_name} #{value_join_alias}" +
249 226 " ON CAST(CASE #{join_alias}.value WHEN '' THEN '0' ELSE #{join_alias}.value END AS decimal(30,0)) = #{value_join_alias}.id"
250 227 when 'int', 'float'
251 228 "LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" +
252 229 " ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" +
253 230 " AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" +
254 231 " AND #{join_alias}.custom_field_id = #{id}" +
255 232 " AND (#{visibility_by_project_condition})" +
256 233 " AND #{join_alias}.value <> ''" +
257 234 " AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" +
258 235 " WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" +
259 236 " AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" +
260 237 " AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)"
261 238 when 'string', 'text', 'list', 'date', 'bool'
262 239 "LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" +
263 240 " ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" +
264 241 " AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" +
265 242 " AND #{join_alias}.custom_field_id = #{id}" +
266 243 " AND (#{visibility_by_project_condition})" +
267 244 " AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" +
268 245 " WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" +
269 246 " AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" +
270 247 " AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)"
271 248 else
272 249 nil
273 250 end
274 251 end
275 252
276 253 def join_alias
277 254 "cf_#{id}"
278 255 end
279 256
280 257 def value_join_alias
281 258 join_alias + "_" + field_format
282 259 end
283 260
284 261 def visibility_by_project_condition(project_key=nil, user=User.current)
285 262 if visible? || user.admin?
286 263 "1=1"
287 264 elsif user.anonymous?
288 265 "1=0"
289 266 else
290 267 project_key ||= "#{self.class.customized_class.table_name}.project_id"
291 268 "#{project_key} IN (SELECT DISTINCT m.project_id FROM #{Member.table_name} m" +
292 269 " INNER JOIN #{MemberRole.table_name} mr ON mr.member_id = m.id" +
293 270 " INNER JOIN #{table_name_prefix}custom_fields_roles#{table_name_suffix} cfr ON cfr.role_id = mr.role_id" +
294 271 " WHERE m.user_id = #{user.id} AND cfr.custom_field_id = #{id})"
295 272 end
296 273 end
297 274
298 275 def self.visibility_condition
299 276 if user.admin?
300 277 "1=1"
301 278 elsif user.anonymous?
302 279 "#{table_name}.visible"
303 280 else
304 281 "#{project_key} IN (SELECT DISTINCT m.project_id FROM #{Member.table_name} m" +
305 282 " INNER JOIN #{MemberRole.table_name} mr ON mr.member_id = m.id" +
306 283 " INNER JOIN #{table_name_prefix}custom_fields_roles#{table_name_suffix} cfr ON cfr.role_id = mr.role_id" +
307 284 " WHERE m.user_id = #{user.id} AND cfr.custom_field_id = #{id})"
308 285 end
309 286 end
310 287
311 288 def <=>(field)
312 289 position <=> field.position
313 290 end
314 291
315 292 # Returns the class that values represent
316 293 def value_class
317 294 case field_format
318 295 when 'user', 'version'
319 296 field_format.classify.constantize
320 297 else
321 298 nil
322 299 end
323 300 end
324 301
325 302 def self.customized_class
326 303 self.name =~ /^(.+)CustomField$/
327 304 begin; $1.constantize; rescue nil; end
328 305 end
329 306
330 307 # to move in project_custom_field
331 308 def self.for_all
332 309 where(:is_for_all => true).order('position').all
333 310 end
334 311
335 312 def type_name
336 313 nil
337 314 end
338 315
339 316 # Returns the error messages for the given value
340 317 # or an empty array if value is a valid value for the custom field
341 318 def validate_field_value(value)
342 319 errs = []
343 320 if value.is_a?(Array)
344 321 if !multiple?
345 322 errs << ::I18n.t('activerecord.errors.messages.invalid')
346 323 end
347 324 if is_required? && value.detect(&:present?).nil?
348 325 errs << ::I18n.t('activerecord.errors.messages.blank')
349 326 end
350 327 value.each {|v| errs += validate_field_value_format(v)}
351 328 else
352 329 if is_required? && value.blank?
353 330 errs << ::I18n.t('activerecord.errors.messages.blank')
354 331 end
355 332 errs += validate_field_value_format(value)
356 333 end
357 334 errs
358 335 end
359 336
360 337 # Returns true if value is a valid value for the custom field
361 338 def valid_field_value?(value)
362 339 validate_field_value(value).empty?
363 340 end
364 341
365 342 def format_in?(*args)
366 343 args.include?(field_format)
367 344 end
368 345
369 346 protected
370 347
371 348 # Returns the error message for the given value regarding its format
372 349 def validate_field_value_format(value)
373 350 errs = []
374 351 if value.present?
375 352 errs << ::I18n.t('activerecord.errors.messages.invalid') unless regexp.blank? or value =~ Regexp.new(regexp)
376 353 errs << ::I18n.t('activerecord.errors.messages.too_short', :count => min_length) if min_length > 0 and value.length < min_length
377 354 errs << ::I18n.t('activerecord.errors.messages.too_long', :count => max_length) if max_length > 0 and value.length > max_length
378 355
379 356 # Format specific validations
380 357 case field_format
381 358 when 'int'
382 359 errs << ::I18n.t('activerecord.errors.messages.not_a_number') unless value =~ /^[+-]?\d+$/
383 360 when 'float'
384 361 begin; Kernel.Float(value); rescue; errs << ::I18n.t('activerecord.errors.messages.invalid') end
385 362 when 'date'
386 363 errs << ::I18n.t('activerecord.errors.messages.not_a_date') unless value =~ /^\d{4}-\d{2}-\d{2}$/ && begin; value.to_date; rescue; false end
387 364 when 'list'
388 365 errs << ::I18n.t('activerecord.errors.messages.inclusion') unless possible_values.include?(value)
389 366 end
390 367 end
391 368 errs
392 369 end
393 370
394 371 # Removes multiple values for the custom field after setting the multiple attribute to false
395 372 # We kepp the value with the highest id for each customized object
396 373 def handle_multiplicity_change
397 374 if !new_record? && multiple_was && !multiple
398 375 ids = custom_values.
399 376 where("EXISTS(SELECT 1 FROM #{CustomValue.table_name} cve WHERE cve.custom_field_id = #{CustomValue.table_name}.custom_field_id" +
400 377 " AND cve.customized_type = #{CustomValue.table_name}.customized_type AND cve.customized_id = #{CustomValue.table_name}.customized_id" +
401 378 " AND cve.id > #{CustomValue.table_name}.id)").
402 379 pluck(:id)
403 380
404 381 if ids.any?
405 382 custom_values.where(:id => ids).delete_all
406 383 end
407 384 end
408 385 end
409 386 end
General Comments 0
You need to be logged in to leave comments. Login now