##// END OF EJS Templates
Use #read/#write_attribute instead of #super when overwriting default accessors (#16319)....
Jean-Philippe Lang -
r12693:d7729d6d4f3a
parent child
Show More
@@ -1,281 +1,282
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2014 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 store :format_store
26 26
27 27 validates_presence_of :name, :field_format
28 28 validates_uniqueness_of :name, :scope => :type
29 29 validates_length_of :name, :maximum => 30
30 30 validates_inclusion_of :field_format, :in => Proc.new { Redmine::FieldFormat.available_formats }
31 31 validate :validate_custom_field
32 32
33 33 before_validation :set_searchable
34 34 before_save do |field|
35 35 field.format.before_custom_field_save(field)
36 36 end
37 37 after_save :handle_multiplicity_change
38 38 after_save do |field|
39 39 if field.visible_changed? && field.visible
40 40 field.roles.clear
41 41 end
42 42 end
43 43
44 44 scope :sorted, lambda { order("#{table_name}.position ASC") }
45 45 scope :visible, lambda {|*args|
46 46 user = args.shift || User.current
47 47 if user.admin?
48 48 # nop
49 49 elsif user.memberships.any?
50 50 where("#{table_name}.visible = ? OR #{table_name}.id IN (SELECT DISTINCT cfr.custom_field_id FROM #{Member.table_name} m" +
51 51 " INNER JOIN #{MemberRole.table_name} mr ON mr.member_id = m.id" +
52 52 " INNER JOIN #{table_name_prefix}custom_fields_roles#{table_name_suffix} cfr ON cfr.role_id = mr.role_id" +
53 53 " WHERE m.user_id = ?)",
54 54 true, user.id)
55 55 else
56 56 where(:visible => true)
57 57 end
58 58 }
59 59
60 60 def visible_by?(project, user=User.current)
61 61 visible? || user.admin?
62 62 end
63 63
64 64 def format
65 65 @format ||= Redmine::FieldFormat.find(field_format)
66 66 end
67 67
68 68 def field_format=(arg)
69 69 # cannot change format of a saved custom field
70 70 if new_record?
71 71 @format = nil
72 72 super
73 73 end
74 74 end
75 75
76 76 def set_searchable
77 77 # make sure these fields are not searchable
78 78 self.searchable = false unless format.class.searchable_supported
79 79 # make sure only these fields can have multiple values
80 80 self.multiple = false unless format.class.multiple_supported
81 81 true
82 82 end
83 83
84 84 def validate_custom_field
85 85 format.validate_custom_field(self).each do |attribute, message|
86 86 errors.add attribute, message
87 87 end
88 88
89 89 if regexp.present?
90 90 begin
91 91 Regexp.new(regexp)
92 92 rescue
93 93 errors.add(:regexp, :invalid)
94 94 end
95 95 end
96 96
97 97 if default_value.present?
98 98 validate_field_value(default_value).each do |message|
99 99 errors.add :default_value, message
100 100 end
101 101 end
102 102 end
103 103
104 104 def possible_custom_value_options(custom_value)
105 105 format.possible_custom_value_options(custom_value)
106 106 end
107 107
108 108 def possible_values_options(object=nil)
109 109 if object.is_a?(Array)
110 110 object.map {|o| format.possible_values_options(self, o)}.reduce(:&) || []
111 111 else
112 112 format.possible_values_options(self, object) || []
113 113 end
114 114 end
115 115
116 116 def possible_values
117 values = super()
117 values = read_attribute(:possible_values)
118 118 if values.is_a?(Array)
119 119 values.each do |value|
120 120 value.force_encoding('UTF-8') if value.respond_to?(:force_encoding)
121 121 end
122 122 values
123 123 else
124 124 []
125 125 end
126 126 end
127 127
128 128 # Makes possible_values accept a multiline string
129 129 def possible_values=(arg)
130 130 if arg.is_a?(Array)
131 super(arg.compact.collect(&:strip).select {|v| !v.blank?})
131 values = arg.compact.collect(&:strip).select {|v| !v.blank?}
132 write_attribute(:possible_values, values)
132 133 else
133 134 self.possible_values = arg.to_s.split(/[\n\r]+/)
134 135 end
135 136 end
136 137
137 138 def cast_value(value)
138 139 format.cast_value(self, value)
139 140 end
140 141
141 142 def value_from_keyword(keyword, customized)
142 143 possible_values_options = possible_values_options(customized)
143 144 if possible_values_options.present?
144 145 keyword = keyword.to_s.downcase
145 146 if v = possible_values_options.detect {|text, id| text.downcase == keyword}
146 147 if v.is_a?(Array)
147 148 v.last
148 149 else
149 150 v
150 151 end
151 152 end
152 153 else
153 154 keyword
154 155 end
155 156 end
156 157
157 158 # Returns a ORDER BY clause that can used to sort customized
158 159 # objects by their value of the custom field.
159 160 # Returns nil if the custom field can not be used for sorting.
160 161 def order_statement
161 162 return nil if multiple?
162 163 format.order_statement(self)
163 164 end
164 165
165 166 # Returns a GROUP BY clause that can used to group by custom value
166 167 # Returns nil if the custom field can not be used for grouping.
167 168 def group_statement
168 169 return nil if multiple?
169 170 format.group_statement(self)
170 171 end
171 172
172 173 def join_for_order_statement
173 174 format.join_for_order_statement(self)
174 175 end
175 176
176 177 def visibility_by_project_condition(project_key=nil, user=User.current, id_column=nil)
177 178 if visible? || user.admin?
178 179 "1=1"
179 180 elsif user.anonymous?
180 181 "1=0"
181 182 else
182 183 project_key ||= "#{self.class.customized_class.table_name}.project_id"
183 184 id_column ||= id
184 185 "#{project_key} IN (SELECT DISTINCT m.project_id FROM #{Member.table_name} m" +
185 186 " INNER JOIN #{MemberRole.table_name} mr ON mr.member_id = m.id" +
186 187 " INNER JOIN #{table_name_prefix}custom_fields_roles#{table_name_suffix} cfr ON cfr.role_id = mr.role_id" +
187 188 " WHERE m.user_id = #{user.id} AND cfr.custom_field_id = #{id_column})"
188 189 end
189 190 end
190 191
191 192 def self.visibility_condition
192 193 if user.admin?
193 194 "1=1"
194 195 elsif user.anonymous?
195 196 "#{table_name}.visible"
196 197 else
197 198 "#{project_key} IN (SELECT DISTINCT m.project_id FROM #{Member.table_name} m" +
198 199 " INNER JOIN #{MemberRole.table_name} mr ON mr.member_id = m.id" +
199 200 " INNER JOIN #{table_name_prefix}custom_fields_roles#{table_name_suffix} cfr ON cfr.role_id = mr.role_id" +
200 201 " WHERE m.user_id = #{user.id} AND cfr.custom_field_id = #{id})"
201 202 end
202 203 end
203 204
204 205 def <=>(field)
205 206 position <=> field.position
206 207 end
207 208
208 209 # Returns the class that values represent
209 210 def value_class
210 211 format.target_class if format.respond_to?(:target_class)
211 212 end
212 213
213 214 def self.customized_class
214 215 self.name =~ /^(.+)CustomField$/
215 216 $1.constantize rescue nil
216 217 end
217 218
218 219 # to move in project_custom_field
219 220 def self.for_all
220 221 where(:is_for_all => true).order('position').all
221 222 end
222 223
223 224 def type_name
224 225 nil
225 226 end
226 227
227 228 # Returns the error messages for the given value
228 229 # or an empty array if value is a valid value for the custom field
229 230 def validate_custom_value(custom_value)
230 231 value = custom_value.value
231 232 errs = []
232 233 if value.is_a?(Array)
233 234 if !multiple?
234 235 errs << ::I18n.t('activerecord.errors.messages.invalid')
235 236 end
236 237 if is_required? && value.detect(&:present?).nil?
237 238 errs << ::I18n.t('activerecord.errors.messages.blank')
238 239 end
239 240 else
240 241 if is_required? && value.blank?
241 242 errs << ::I18n.t('activerecord.errors.messages.blank')
242 243 end
243 244 end
244 245 errs += format.validate_custom_value(custom_value)
245 246 errs
246 247 end
247 248
248 249 # Returns the error messages for the default custom field value
249 250 def validate_field_value(value)
250 251 validate_custom_value(CustomValue.new(:custom_field => self, :value => value))
251 252 end
252 253
253 254 # Returns true if value is a valid value for the custom field
254 255 def valid_field_value?(value)
255 256 validate_field_value(value).empty?
256 257 end
257 258
258 259 def format_in?(*args)
259 260 args.include?(field_format)
260 261 end
261 262
262 263 protected
263 264
264 265 # Removes multiple values for the custom field after setting the multiple attribute to false
265 266 # We kepp the value with the highest id for each customized object
266 267 def handle_multiplicity_change
267 268 if !new_record? && multiple_was && !multiple
268 269 ids = custom_values.
269 270 where("EXISTS(SELECT 1 FROM #{CustomValue.table_name} cve WHERE cve.custom_field_id = #{CustomValue.table_name}.custom_field_id" +
270 271 " AND cve.customized_type = #{CustomValue.table_name}.customized_type AND cve.customized_id = #{CustomValue.table_name}.customized_id" +
271 272 " AND cve.id > #{CustomValue.table_name}.id)").
272 273 pluck(:id)
273 274
274 275 if ids.any?
275 276 custom_values.where(:id => ids).delete_all
276 277 end
277 278 end
278 279 end
279 280 end
280 281
281 282 require_dependency 'redmine/field_format'
General Comments 0
You need to be logged in to leave comments. Login now