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