##// END OF EJS Templates
Optimize retrieval of user's projects members....
Jean-Philippe Lang -
r3493:5225fb70f533
parent child
Show More
@@ -1,571 +1,573
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2008 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 QueryColumn
19 19 attr_accessor :name, :sortable, :groupable, :default_order
20 20 include Redmine::I18n
21 21
22 22 def initialize(name, options={})
23 23 self.name = name
24 24 self.sortable = options[:sortable]
25 25 self.groupable = options[:groupable] || false
26 26 if groupable == true
27 27 self.groupable = name.to_s
28 28 end
29 29 self.default_order = options[:default_order]
30 30 end
31 31
32 32 def caption
33 33 l("field_#{name}")
34 34 end
35 35
36 36 # Returns true if the column is sortable, otherwise false
37 37 def sortable?
38 38 !sortable.nil?
39 39 end
40 40
41 41 def value(issue)
42 42 issue.send name
43 43 end
44 44 end
45 45
46 46 class QueryCustomFieldColumn < QueryColumn
47 47
48 48 def initialize(custom_field)
49 49 self.name = "cf_#{custom_field.id}".to_sym
50 50 self.sortable = custom_field.order_statement || false
51 51 if %w(list date bool int).include?(custom_field.field_format)
52 52 self.groupable = custom_field.order_statement
53 53 end
54 54 self.groupable ||= false
55 55 @cf = custom_field
56 56 end
57 57
58 58 def caption
59 59 @cf.name
60 60 end
61 61
62 62 def custom_field
63 63 @cf
64 64 end
65 65
66 66 def value(issue)
67 67 cv = issue.custom_values.detect {|v| v.custom_field_id == @cf.id}
68 68 cv && @cf.cast_value(cv.value)
69 69 end
70 70 end
71 71
72 72 class Query < ActiveRecord::Base
73 73 class StatementInvalid < ::ActiveRecord::StatementInvalid
74 74 end
75 75
76 76 belongs_to :project
77 77 belongs_to :user
78 78 serialize :filters
79 79 serialize :column_names
80 80 serialize :sort_criteria, Array
81 81
82 82 attr_protected :project_id, :user_id
83 83
84 84 validates_presence_of :name, :on => :save
85 85 validates_length_of :name, :maximum => 255
86 86
87 87 @@operators = { "=" => :label_equals,
88 88 "!" => :label_not_equals,
89 89 "o" => :label_open_issues,
90 90 "c" => :label_closed_issues,
91 91 "!*" => :label_none,
92 92 "*" => :label_all,
93 93 ">=" => :label_greater_or_equal,
94 94 "<=" => :label_less_or_equal,
95 95 "<t+" => :label_in_less_than,
96 96 ">t+" => :label_in_more_than,
97 97 "t+" => :label_in,
98 98 "t" => :label_today,
99 99 "w" => :label_this_week,
100 100 ">t-" => :label_less_than_ago,
101 101 "<t-" => :label_more_than_ago,
102 102 "t-" => :label_ago,
103 103 "~" => :label_contains,
104 104 "!~" => :label_not_contains }
105 105
106 106 cattr_reader :operators
107 107
108 108 @@operators_by_filter_type = { :list => [ "=", "!" ],
109 109 :list_status => [ "o", "=", "!", "c", "*" ],
110 110 :list_optional => [ "=", "!", "!*", "*" ],
111 111 :list_subprojects => [ "*", "!*", "=" ],
112 112 :date => [ "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-" ],
113 113 :date_past => [ ">t-", "<t-", "t-", "t", "w" ],
114 114 :string => [ "=", "~", "!", "!~" ],
115 115 :text => [ "~", "!~" ],
116 116 :integer => [ "=", ">=", "<=", "!*", "*" ] }
117 117
118 118 cattr_reader :operators_by_filter_type
119 119
120 120 @@available_columns = [
121 121 QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true),
122 122 QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true),
123 123 QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true),
124 124 QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true),
125 125 QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
126 126 QueryColumn.new(:author),
127 127 QueryColumn.new(:assigned_to, :sortable => ["#{User.table_name}.lastname", "#{User.table_name}.firstname", "#{User.table_name}.id"], :groupable => true),
128 128 QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
129 129 QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true),
130 130 QueryColumn.new(:fixed_version, :sortable => ["#{Version.table_name}.effective_date", "#{Version.table_name}.name"], :default_order => 'desc', :groupable => true),
131 131 QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
132 132 QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
133 133 QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
134 134 QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true),
135 135 QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
136 136 ]
137 137 cattr_reader :available_columns
138 138
139 139 def initialize(attributes = nil)
140 140 super attributes
141 141 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
142 142 end
143 143
144 144 def after_initialize
145 145 # Store the fact that project is nil (used in #editable_by?)
146 146 @is_for_all = project.nil?
147 147 end
148 148
149 149 def validate
150 150 filters.each_key do |field|
151 151 errors.add label_for(field), :blank unless
152 152 # filter requires one or more values
153 153 (values_for(field) and !values_for(field).first.blank?) or
154 154 # filter doesn't require any value
155 155 ["o", "c", "!*", "*", "t", "w"].include? operator_for(field)
156 156 end if filters
157 157 end
158 158
159 159 def editable_by?(user)
160 160 return false unless user
161 161 # Admin can edit them all and regular users can edit their private queries
162 162 return true if user.admin? || (!is_public && self.user_id == user.id)
163 163 # Members can not edit public queries that are for all project (only admin is allowed to)
164 164 is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project)
165 165 end
166 166
167 167 def available_filters
168 168 return @available_filters if @available_filters
169 169
170 170 trackers = project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers
171 171
172 172 @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
173 173 "tracker_id" => { :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } },
174 174 "priority_id" => { :type => :list, :order => 3, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] } },
175 175 "subject" => { :type => :text, :order => 8 },
176 176 "created_on" => { :type => :date_past, :order => 9 },
177 177 "updated_on" => { :type => :date_past, :order => 10 },
178 178 "start_date" => { :type => :date, :order => 11 },
179 179 "due_date" => { :type => :date, :order => 12 },
180 180 "estimated_hours" => { :type => :integer, :order => 13 },
181 181 "done_ratio" => { :type => :integer, :order => 14 }}
182 182
183 183 user_values = []
184 184 user_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
185 185 if project
186 186 user_values += project.users.sort.collect{|s| [s.name, s.id.to_s] }
187 187 else
188 # members of the user's projects
189 # OPTIMIZE: Is selecting from users per project (N+1)
190 user_values += User.current.projects.collect(&:users).flatten.uniq.sort.collect{|s| [s.name, s.id.to_s] }
188 project_ids = User.current.projects.collect(&:id)
189 if project_ids.any?
190 # members of the user's projects
191 user_values += User.active.find(:all, :conditions => ["#{User.table_name}.id IN (SELECT DISTINCT user_id FROM members WHERE project_id IN (?))", project_ids]).sort.collect{|s| [s.name, s.id.to_s] }
192 end
191 193 end
192 194 @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values } unless user_values.empty?
193 195 @available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values } unless user_values.empty?
194 196
195 197 if User.current.logged?
196 198 @available_filters["watcher_id"] = { :type => :list, :order => 15, :values => [["<< #{l(:label_me)} >>", "me"]] }
197 199 end
198 200
199 201 if project
200 202 # project specific filters
201 203 unless @project.issue_categories.empty?
202 204 @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } }
203 205 end
204 206 unless @project.shared_versions.empty?
205 207 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
206 208 end
207 209 unless @project.descendants.active.empty?
208 210 @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => @project.descendants.visible.collect{|s| [s.name, s.id.to_s] } }
209 211 end
210 212 add_custom_fields_filters(@project.all_issue_custom_fields)
211 213 else
212 214 # global filters for cross project issue list
213 215 system_shared_versions = Version.visible.find_all_by_sharing('system')
214 216 unless system_shared_versions.empty?
215 217 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => system_shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
216 218 end
217 219 add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true}))
218 220 end
219 221 @available_filters
220 222 end
221 223
222 224 def add_filter(field, operator, values)
223 225 # values must be an array
224 226 return unless values and values.is_a? Array # and !values.first.empty?
225 227 # check if field is defined as an available filter
226 228 if available_filters.has_key? field
227 229 filter_options = available_filters[field]
228 230 # check if operator is allowed for that filter
229 231 #if @@operators_by_filter_type[filter_options[:type]].include? operator
230 232 # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]})
231 233 # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator
232 234 #end
233 235 filters[field] = {:operator => operator, :values => values }
234 236 end
235 237 end
236 238
237 239 def add_short_filter(field, expression)
238 240 return unless expression
239 241 parms = expression.scan(/^(o|c|!\*|!|\*)?(.*)$/).first
240 242 add_filter field, (parms[0] || "="), [parms[1] || ""]
241 243 end
242 244
243 245 def has_filter?(field)
244 246 filters and filters[field]
245 247 end
246 248
247 249 def operator_for(field)
248 250 has_filter?(field) ? filters[field][:operator] : nil
249 251 end
250 252
251 253 def values_for(field)
252 254 has_filter?(field) ? filters[field][:values] : nil
253 255 end
254 256
255 257 def label_for(field)
256 258 label = available_filters[field][:name] if available_filters.has_key?(field)
257 259 label ||= field.gsub(/\_id$/, "")
258 260 end
259 261
260 262 def available_columns
261 263 return @available_columns if @available_columns
262 264 @available_columns = Query.available_columns
263 265 @available_columns += (project ?
264 266 project.all_issue_custom_fields :
265 267 IssueCustomField.find(:all)
266 268 ).collect {|cf| QueryCustomFieldColumn.new(cf) }
267 269 end
268 270
269 271 # Returns an array of columns that can be used to group the results
270 272 def groupable_columns
271 273 available_columns.select {|c| c.groupable}
272 274 end
273 275
274 276 # Returns a Hash of columns and the key for sorting
275 277 def sortable_columns
276 278 {'id' => "#{Issue.table_name}.id"}.merge(available_columns.inject({}) {|h, column|
277 279 h[column.name.to_s] = column.sortable
278 280 h
279 281 })
280 282 end
281 283
282 284 def columns
283 285 if has_default_columns?
284 286 available_columns.select do |c|
285 287 # Adds the project column by default for cross-project lists
286 288 Setting.issue_list_default_columns.include?(c.name.to_s) || (c.name == :project && project.nil?)
287 289 end
288 290 else
289 291 # preserve the column_names order
290 292 column_names.collect {|name| available_columns.find {|col| col.name == name}}.compact
291 293 end
292 294 end
293 295
294 296 def column_names=(names)
295 297 if names
296 298 names = names.select {|n| n.is_a?(Symbol) || !n.blank? }
297 299 names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym }
298 300 # Set column_names to nil if default columns
299 301 if names.map(&:to_s) == Setting.issue_list_default_columns
300 302 names = nil
301 303 end
302 304 end
303 305 write_attribute(:column_names, names)
304 306 end
305 307
306 308 def has_column?(column)
307 309 column_names && column_names.include?(column.name)
308 310 end
309 311
310 312 def has_default_columns?
311 313 column_names.nil? || column_names.empty?
312 314 end
313 315
314 316 def sort_criteria=(arg)
315 317 c = []
316 318 if arg.is_a?(Hash)
317 319 arg = arg.keys.sort.collect {|k| arg[k]}
318 320 end
319 321 c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, o == 'desc' ? o : 'asc']}
320 322 write_attribute(:sort_criteria, c)
321 323 end
322 324
323 325 def sort_criteria
324 326 read_attribute(:sort_criteria) || []
325 327 end
326 328
327 329 def sort_criteria_key(arg)
328 330 sort_criteria && sort_criteria[arg] && sort_criteria[arg].first
329 331 end
330 332
331 333 def sort_criteria_order(arg)
332 334 sort_criteria && sort_criteria[arg] && sort_criteria[arg].last
333 335 end
334 336
335 337 # Returns the SQL sort order that should be prepended for grouping
336 338 def group_by_sort_order
337 339 if grouped? && (column = group_by_column)
338 340 column.sortable.is_a?(Array) ?
339 341 column.sortable.collect {|s| "#{s} #{column.default_order}"}.join(',') :
340 342 "#{column.sortable} #{column.default_order}"
341 343 end
342 344 end
343 345
344 346 # Returns true if the query is a grouped query
345 347 def grouped?
346 348 !group_by.blank?
347 349 end
348 350
349 351 def group_by_column
350 352 groupable_columns.detect {|c| c.name.to_s == group_by}
351 353 end
352 354
353 355 def group_by_statement
354 356 group_by_column.groupable
355 357 end
356 358
357 359 def project_statement
358 360 project_clauses = []
359 361 if project && !@project.descendants.active.empty?
360 362 ids = [project.id]
361 363 if has_filter?("subproject_id")
362 364 case operator_for("subproject_id")
363 365 when '='
364 366 # include the selected subprojects
365 367 ids += values_for("subproject_id").each(&:to_i)
366 368 when '!*'
367 369 # main project only
368 370 else
369 371 # all subprojects
370 372 ids += project.descendants.collect(&:id)
371 373 end
372 374 elsif Setting.display_subprojects_issues?
373 375 ids += project.descendants.collect(&:id)
374 376 end
375 377 project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
376 378 elsif project
377 379 project_clauses << "#{Project.table_name}.id = %d" % project.id
378 380 end
379 381 project_clauses << Project.allowed_to_condition(User.current, :view_issues)
380 382 project_clauses.join(' AND ')
381 383 end
382 384
383 385 def statement
384 386 # filters clauses
385 387 filters_clauses = []
386 388 filters.each_key do |field|
387 389 next if field == "subproject_id"
388 390 v = values_for(field).clone
389 391 next unless v and !v.empty?
390 392 operator = operator_for(field)
391 393
392 394 # "me" value subsitution
393 395 if %w(assigned_to_id author_id watcher_id).include?(field)
394 396 v.push(User.current.logged? ? User.current.id.to_s : "0") if v.delete("me")
395 397 end
396 398
397 399 sql = ''
398 400 if field =~ /^cf_(\d+)$/
399 401 # custom field
400 402 db_table = CustomValue.table_name
401 403 db_field = 'value'
402 404 is_custom_filter = true
403 405 sql << "#{Issue.table_name}.id IN (SELECT #{Issue.table_name}.id FROM #{Issue.table_name} LEFT OUTER JOIN #{db_table} ON #{db_table}.customized_type='Issue' AND #{db_table}.customized_id=#{Issue.table_name}.id AND #{db_table}.custom_field_id=#{$1} WHERE "
404 406 sql << sql_for_field(field, operator, v, db_table, db_field, true) + ')'
405 407 elsif field == 'watcher_id'
406 408 db_table = Watcher.table_name
407 409 db_field = 'user_id'
408 410 sql << "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND "
409 411 sql << sql_for_field(field, '=', v, db_table, db_field) + ')'
410 412 else
411 413 # regular field
412 414 db_table = Issue.table_name
413 415 db_field = field
414 416 sql << '(' + sql_for_field(field, operator, v, db_table, db_field) + ')'
415 417 end
416 418 filters_clauses << sql
417 419
418 420 end if filters and valid?
419 421
420 422 (filters_clauses << project_statement).join(' AND ')
421 423 end
422 424
423 425 # Returns the issue count
424 426 def issue_count
425 427 Issue.count(:include => [:status, :project], :conditions => statement)
426 428 rescue ::ActiveRecord::StatementInvalid => e
427 429 raise StatementInvalid.new(e.message)
428 430 end
429 431
430 432 # Returns the issue count by group or nil if query is not grouped
431 433 def issue_count_by_group
432 434 r = nil
433 435 if grouped?
434 436 begin
435 437 # Rails will raise an (unexpected) RecordNotFound if there's only a nil group value
436 438 r = Issue.count(:group => group_by_statement, :include => [:status, :project], :conditions => statement)
437 439 rescue ActiveRecord::RecordNotFound
438 440 r = {nil => issue_count}
439 441 end
440 442 c = group_by_column
441 443 if c.is_a?(QueryCustomFieldColumn)
442 444 r = r.keys.inject({}) {|h, k| h[c.custom_field.cast_value(k)] = r[k]; h}
443 445 end
444 446 end
445 447 r
446 448 rescue ::ActiveRecord::StatementInvalid => e
447 449 raise StatementInvalid.new(e.message)
448 450 end
449 451
450 452 # Returns the issues
451 453 # Valid options are :order, :offset, :limit, :include, :conditions
452 454 def issues(options={})
453 455 order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',')
454 456 order_option = nil if order_option.blank?
455 457
456 458 Issue.find :all, :include => ([:status, :project] + (options[:include] || [])).uniq,
457 459 :conditions => Query.merge_conditions(statement, options[:conditions]),
458 460 :order => order_option,
459 461 :limit => options[:limit],
460 462 :offset => options[:offset]
461 463 rescue ::ActiveRecord::StatementInvalid => e
462 464 raise StatementInvalid.new(e.message)
463 465 end
464 466
465 467 # Returns the journals
466 468 # Valid options are :order, :offset, :limit
467 469 def journals(options={})
468 470 Journal.find :all, :include => [:details, :user, {:issue => [:project, :author, :tracker, :status]}],
469 471 :conditions => statement,
470 472 :order => options[:order],
471 473 :limit => options[:limit],
472 474 :offset => options[:offset]
473 475 rescue ::ActiveRecord::StatementInvalid => e
474 476 raise StatementInvalid.new(e.message)
475 477 end
476 478
477 479 # Returns the versions
478 480 # Valid options are :conditions
479 481 def versions(options={})
480 482 Version.find :all, :include => :project,
481 483 :conditions => Query.merge_conditions(project_statement, options[:conditions])
482 484 rescue ::ActiveRecord::StatementInvalid => e
483 485 raise StatementInvalid.new(e.message)
484 486 end
485 487
486 488 private
487 489
488 490 # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+
489 491 def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false)
490 492 sql = ''
491 493 case operator
492 494 when "="
493 495 sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
494 496 when "!"
495 497 sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
496 498 when "!*"
497 499 sql = "#{db_table}.#{db_field} IS NULL"
498 500 sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter
499 501 when "*"
500 502 sql = "#{db_table}.#{db_field} IS NOT NULL"
501 503 sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
502 504 when ">="
503 505 sql = "#{db_table}.#{db_field} >= #{value.first.to_i}"
504 506 when "<="
505 507 sql = "#{db_table}.#{db_field} <= #{value.first.to_i}"
506 508 when "o"
507 509 sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
508 510 when "c"
509 511 sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id"
510 512 when ">t-"
511 513 sql = date_range_clause(db_table, db_field, - value.first.to_i, 0)
512 514 when "<t-"
513 515 sql = date_range_clause(db_table, db_field, nil, - value.first.to_i)
514 516 when "t-"
515 517 sql = date_range_clause(db_table, db_field, - value.first.to_i, - value.first.to_i)
516 518 when ">t+"
517 519 sql = date_range_clause(db_table, db_field, value.first.to_i, nil)
518 520 when "<t+"
519 521 sql = date_range_clause(db_table, db_field, 0, value.first.to_i)
520 522 when "t+"
521 523 sql = date_range_clause(db_table, db_field, value.first.to_i, value.first.to_i)
522 524 when "t"
523 525 sql = date_range_clause(db_table, db_field, 0, 0)
524 526 when "w"
525 527 from = l(:general_first_day_of_week) == '7' ?
526 528 # week starts on sunday
527 529 ((Date.today.cwday == 7) ? Time.now.at_beginning_of_day : Time.now.at_beginning_of_week - 1.day) :
528 530 # week starts on monday (Rails default)
529 531 Time.now.at_beginning_of_week
530 532 sql = "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(from), connection.quoted_date(from + 7.days)]
531 533 when "~"
532 534 sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
533 535 when "!~"
534 536 sql = "LOWER(#{db_table}.#{db_field}) NOT LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
535 537 end
536 538
537 539 return sql
538 540 end
539 541
540 542 def add_custom_fields_filters(custom_fields)
541 543 @available_filters ||= {}
542 544
543 545 custom_fields.select(&:is_filter?).each do |field|
544 546 case field.field_format
545 547 when "text"
546 548 options = { :type => :text, :order => 20 }
547 549 when "list"
548 550 options = { :type => :list_optional, :values => field.possible_values, :order => 20}
549 551 when "date"
550 552 options = { :type => :date, :order => 20 }
551 553 when "bool"
552 554 options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
553 555 else
554 556 options = { :type => :string, :order => 20 }
555 557 end
556 558 @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name })
557 559 end
558 560 end
559 561
560 562 # Returns a SQL clause for a date or datetime field.
561 563 def date_range_clause(table, field, from, to)
562 564 s = []
563 565 if from
564 566 s << ("#{table}.#{field} > '%s'" % [connection.quoted_date((Date.yesterday + from).to_time.end_of_day)])
565 567 end
566 568 if to
567 569 s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date((Date.today + to).to_time.end_of_day)])
568 570 end
569 571 s.join(' AND ')
570 572 end
571 573 end
General Comments 0
You need to be logged in to leave comments. Login now