Class: Query
- Inherits:
-
ActiveRecord::Base
- Object
- ActiveRecord::Base
- Query
- Defined in:
- app/models/query.rb
Overview
Direct Known Subclasses
Defined Under Namespace
Classes: StatementInvalid
Constant Summary collapse
- VISIBILITY_PRIVATE =
0
- VISIBILITY_ROLES =
1
- VISIBILITY_PUBLIC =
2
Class Method Summary collapse
- .add_available_column(column) ⇒ Object
-
.build_from_params(params, attributes = {}) ⇒ Object
Builds a new query from the given params and attributes.
-
.operators_labels ⇒ Object
Returns a hash of localized labels for all filter operators.
Instance Method Summary collapse
-
#add_available_filter(field, options) ⇒ Object
Adds an available filter.
- #add_filter(field, operator, values = nil) ⇒ Object
- #add_filter_error(field, message) ⇒ Object
-
#add_filters(fields, operators, values) ⇒ Object
Add multiple filters using
add_filter
. - #add_short_filter(field, expression) ⇒ Object
- #all_projects ⇒ Object
- #all_projects_values ⇒ Object
- #available_block_columns ⇒ Object
-
#available_filters ⇒ Object
Return a hash of available filters.
-
#available_filters_as_json ⇒ Object
Returns a representation of the available filters for JSON serialization.
- #available_inline_columns ⇒ Object
- #available_totalable_columns ⇒ Object
- #block_columns ⇒ Object
-
#build_from_params(params) ⇒ Object
Builds the query from the given params.
- #column_names=(names) ⇒ Object
- #columns ⇒ Object
- #default_columns_names ⇒ Object
-
#delete_available_filter(field) ⇒ Object
Removes an available filter.
- #editable_by?(user) ⇒ Boolean
- #group_by_column ⇒ Object
-
#group_by_sort_order ⇒ Object
Returns the SQL sort order that should be prepended for grouping.
- #group_by_statement ⇒ Object
-
#groupable_columns ⇒ Object
Returns an array of columns that can be used to group the results.
-
#grouped? ⇒ Boolean
Returns true if the query is a grouped query.
- #has_column?(column) ⇒ Boolean
- #has_custom_field_column? ⇒ Boolean
- #has_default_columns? ⇒ Boolean
- #has_filter?(field) ⇒ Boolean
-
#initialize(attributes = nil, *args) ⇒ Query
constructor
A new instance of Query.
- #inline_columns ⇒ Object
-
#issue_custom_fields ⇒ Object
Returns a scope of issue custom fields that are available as columns or filters.
- #label_for(field) ⇒ Object
- #operator_for(field) ⇒ Object
- #project_statement ⇒ Object
- #queried_table_name ⇒ Object
- #sort_criteria ⇒ Object
- #sort_criteria=(arg) ⇒ Object
- #sort_criteria_key(arg) ⇒ Object
- #sort_criteria_order(arg) ⇒ Object
- #sort_criteria_order_for(key) ⇒ Object
-
#sortable_columns ⇒ Object
Returns a Hash of columns and the key for sorting.
- #statement ⇒ Object
-
#total_by_group_for(column) ⇒ Object
Returns a hash of the sum of the given column for each group, or nil if the query is not grouped.
-
#total_for(column) ⇒ Object
Returns the sum of values for the given column.
- #totalable_columns ⇒ Object
- #totalable_names ⇒ Object
- #totalable_names=(names) ⇒ Object
- #totals {|totals| ... } ⇒ Object
- #totals_by_group {|totals| ... } ⇒ Object
- #trackers ⇒ Object
- #type_for(field) ⇒ Object
- #validate_query_filters ⇒ Object
- #value_for(field, index = 0) ⇒ Object
- #values_for(field) ⇒ Object
Constructor Details
#initialize(attributes = nil, *args) ⇒ Query
Removed at 4.0.0
Returns a new instance of Query
236 237 238 239 |
# File 'app/models/query.rb', line 236 def initialize(attributes=nil, *args) super attributes @is_for_all = project.nil? end |
Class Method Details
.add_available_column(column) ⇒ Object
437 438 439 |
# File 'app/models/query.rb', line 437 def self.add_available_column(column) self.available_columns << (column) if column.is_a?(QueryColumn) end |
.build_from_params(params, attributes = {}) ⇒ Object
Builds a new query from the given params and attributes
258 259 260 |
# File 'app/models/query.rb', line 258 def self.build_from_params(params, attributes={}) new(attributes).build_from_params(params) end |
.operators_labels ⇒ Object
Returns a hash of localized labels for all filter operators
308 309 310 |
# File 'app/models/query.rb', line 308 def self.operators_labels operators.inject({}) {|h, operator| h[operator.first] = l(*operator.last); h} end |
Instance Method Details
#add_available_filter(field, options) ⇒ Object
Adds an available filter
359 360 361 362 363 |
# File 'app/models/query.rb', line 359 def add_available_filter(field, ) @available_filters ||= ActiveSupport::OrderedHash.new @available_filters[field] = @available_filters end |
#add_filter(field, operator, values = nil) ⇒ Object
383 384 385 386 387 388 389 390 391 |
# File 'app/models/query.rb', line 383 def add_filter(field, operator, values=nil) # values must be an array return unless values.nil? || values.is_a?(Array) # check if field is defined as an available filter if available_filters.has_key? field = available_filters[field] filters[field] = {:operator => operator, :values => (values || [''])} end end |
#add_filter_error(field, message) ⇒ Object
290 291 292 293 |
# File 'app/models/query.rb', line 290 def add_filter_error(field, ) m = label_for(field) + " " + l(, :scope => 'activerecord.errors.messages') errors.add(:base, m) end |
#add_filters(fields, operators, values) ⇒ Object
Add multiple filters using add_filter
404 405 406 407 408 409 410 |
# File 'app/models/query.rb', line 404 def add_filters(fields, operators, values) if fields.is_a?(Array) && operators.is_a?(Hash) && (values.nil? || values.is_a?(Hash)) fields.each do |field| add_filter(field, operators[field], values && values[field]) end end end |
#add_short_filter(field, expression) ⇒ Object
393 394 395 396 397 398 399 400 401 |
# File 'app/models/query.rb', line 393 def add_short_filter(field, expression) return unless expression && available_filters.has_key?(field) field_type = available_filters[field][:type] operators_by_filter_type[field_type].sort.reverse.detect do |operator| next unless expression =~ /^#{Regexp.escape(operator)}(.*)$/ values = $1 add_filter field, operator, values.present? ? values.split('|') : [''] end || add_filter(field, '=', expression.split('|')) end |
#all_projects ⇒ Object
328 329 330 |
# File 'app/models/query.rb', line 328 def all_projects @all_projects ||= Project.visible.to_a end |
#all_projects_values ⇒ Object
332 333 334 335 336 337 338 339 340 341 |
# File 'app/models/query.rb', line 332 def all_projects_values return @all_projects_values if @all_projects_values values = [] Project.project_tree(all_projects) do |p, level| prefix = (level > 0 ? ('--' * level + ' ') : '') values << ["#{prefix}#{p.name}", p.id.to_s] end @all_projects_values = values end |
#available_block_columns ⇒ Object
474 475 476 |
# File 'app/models/query.rb', line 474 def available_block_columns available_columns.reject(&:inline?) end |
#available_filters ⇒ Object
Return a hash of available filters
373 374 375 376 377 378 379 380 381 |
# File 'app/models/query.rb', line 373 def available_filters unless @available_filters initialize_available_filters @available_filters.each do |field, | [:name] ||= l([:label] || "field_#{field}".gsub(/_id$/, '')) end end @available_filters end |
#available_filters_as_json ⇒ Object
Returns a representation of the available filters for JSON serialization
313 314 315 316 317 318 319 320 321 322 323 324 325 326 |
# File 'app/models/query.rb', line 313 def available_filters_as_json json = {} available_filters.each do |field, | = .slice(:type, :name, :values) if [:values] && values_for(field) missing = Array(values_for(field)).select(&:present?) - [:values].map(&:last) if missing.any? && respond_to?(method = "find_#{field}_filter_values") [:values] += send(method, missing) end end json[field] = .stringify_keys end json end |
#available_inline_columns ⇒ Object
470 471 472 |
# File 'app/models/query.rb', line 470 def available_inline_columns available_columns.select(&:inline?) end |
#available_totalable_columns ⇒ Object
478 479 480 |
# File 'app/models/query.rb', line 478 def available_totalable_columns available_columns.select(&:totalable) end |
#block_columns ⇒ Object
466 467 468 |
# File 'app/models/query.rb', line 466 def block_columns columns.reject(&:inline?) end |
#build_from_params(params) ⇒ Object
Builds the query from the given params
242 243 244 245 246 247 248 249 250 251 252 253 254 255 |
# File 'app/models/query.rb', line 242 def build_from_params(params) if params[:fields] || params[:f] self.filters = {} add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v]) else available_filters.keys.each do |field| add_short_filter(field, params[field]) if params[field] end end self.group_by = params[:group_by] || (params[:query] && params[:query][:group_by]) self.column_names = params[:c] || (params[:query] && params[:query][:column_names]) self.totalable_names = params[:t] || (params[:query] && params[:query][:totalable_names]) self end |
#column_names=(names) ⇒ Object
486 487 488 489 490 491 492 493 494 495 496 |
# File 'app/models/query.rb', line 486 def column_names=(names) if names names = names.select {|n| n.is_a?(Symbol) || !n.blank? } names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym } # Set column_names to nil if default columns if names == default_columns_names names = nil end end write_attribute(:column_names, names) end |
#columns ⇒ Object
454 455 456 457 458 459 460 |
# File 'app/models/query.rb', line 454 def columns # preserve the column_names order cols = (has_default_columns? ? default_columns_names : column_names).collect do |name| available_columns.find { |col| col.name == name } end.compact available_columns.select(&:frozen?) | cols end |
#default_columns_names ⇒ Object
482 483 484 |
# File 'app/models/query.rb', line 482 def default_columns_names [] end |
#delete_available_filter(field) ⇒ Object
Removes an available filter
366 367 368 369 370 |
# File 'app/models/query.rb', line 366 def delete_available_filter(field) if @available_filters @available_filters.delete(field) end end |
#editable_by?(user) ⇒ Boolean
295 296 297 298 299 300 301 |
# File 'app/models/query.rb', line 295 def editable_by?(user) return false unless user # Admin can edit them all and regular users can edit their private queries return true if user.admin? || (is_private? && self.user_id == user.id) # Members can not edit public queries that are for all project (only admin is allowed to) is_public? && !@is_for_all && user.allowed_to?(:manage_public_queries, project) end |
#group_by_column ⇒ Object
566 567 568 |
# File 'app/models/query.rb', line 566 def group_by_column groupable_columns.detect {|c| c.groupable && c.name.to_s == group_by} end |
#group_by_sort_order ⇒ Object
Returns the SQL sort order that should be prepended for grouping
554 555 556 557 558 559 |
# File 'app/models/query.rb', line 554 def group_by_sort_order if column = group_by_column order = (sort_criteria_order_for(column.name) || column.default_order || 'asc').try(:upcase) Array(column.sortable).map {|s| "#{s} #{order}"} end end |
#group_by_statement ⇒ Object
570 571 572 |
# File 'app/models/query.rb', line 570 def group_by_statement group_by_column.try(:groupable) end |
#groupable_columns ⇒ Object
Returns an array of columns that can be used to group the results
442 443 444 |
# File 'app/models/query.rb', line 442 def groupable_columns available_columns.select {|c| c.groupable} end |
#grouped? ⇒ Boolean
Returns true if the query is a grouped query
562 563 564 |
# File 'app/models/query.rb', line 562 def grouped? !group_by_column.nil? end |
#has_column?(column) ⇒ Boolean
498 499 500 |
# File 'app/models/query.rb', line 498 def has_column?(column) column_names && column_names.include?(column.is_a?(QueryColumn) ? column.name : column) end |
#has_custom_field_column? ⇒ Boolean
502 503 504 |
# File 'app/models/query.rb', line 502 def has_custom_field_column? columns.any? {|column| column.is_a? QueryCustomFieldColumn} end |
#has_default_columns? ⇒ Boolean
506 507 508 |
# File 'app/models/query.rb', line 506 def has_default_columns? column_names.nil? || column_names.empty? end |
#has_filter?(field) ⇒ Boolean
412 413 414 |
# File 'app/models/query.rb', line 412 def has_filter?(field) filters and filters[field] end |
#inline_columns ⇒ Object
462 463 464 |
# File 'app/models/query.rb', line 462 def inline_columns columns.select(&:inline?) end |
#issue_custom_fields ⇒ Object
Returns a scope of issue custom fields that are available as columns or filters
344 345 346 347 348 349 350 |
# File 'app/models/query.rb', line 344 def issue_custom_fields if project project.rolled_up_custom_fields else IssueCustomField.all end end |
#label_for(field) ⇒ Object
432 433 434 435 |
# File 'app/models/query.rb', line 432 def label_for(field) label = available_filters[field][:name] if available_filters.has_key?(field) label ||= queried_class.human_attribute_name(field, :default => field) end |
#operator_for(field) ⇒ Object
420 421 422 |
# File 'app/models/query.rb', line 420 def operator_for(field) has_filter?(field) ? filters[field][:operator] : nil end |
#project_statement ⇒ Object
574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 |
# File 'app/models/query.rb', line 574 def project_statement project_clauses = [] if project && !project.descendants.active.empty? if has_filter?("subproject_id") case operator_for("subproject_id") when '=' # include the selected subprojects ids = [project.id] + values_for("subproject_id").each(&:to_i) project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',') when '!*' # main project only project_clauses << "#{Project.table_name}.id = %d" % project.id else # all subprojects project_clauses << "#{Project.table_name}.lft >= #{project.lft} AND #{Project.table_name}.rgt <= #{project.rgt}" end elsif Setting.display_subprojects_issues? project_clauses << "#{Project.table_name}.lft >= #{project.lft} AND #{Project.table_name}.rgt <= #{project.rgt}" else project_clauses << "#{Project.table_name}.id = %d" % project.id end elsif project project_clauses << "#{Project.table_name}.id = %d" % project.id end project_clauses.any? ? project_clauses.join(' AND ') : nil end |
#queried_table_name ⇒ Object
232 233 234 |
# File 'app/models/query.rb', line 232 def queried_table_name @queried_table_name ||= self.class.queried_class.table_name end |
#sort_criteria ⇒ Object
537 538 539 |
# File 'app/models/query.rb', line 537 def sort_criteria read_attribute(:sort_criteria) || [] end |
#sort_criteria=(arg) ⇒ Object
526 527 528 529 530 531 532 533 534 535 |
# File 'app/models/query.rb', line 526 def sort_criteria=(arg) c = [] if arg.is_a?(Hash) arg = arg.keys.sort.collect {|k| arg[k]} end if arg c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, (o == 'desc' || o == false) ? 'desc' : 'asc']} end write_attribute(:sort_criteria, c) end |
#sort_criteria_key(arg) ⇒ Object
541 542 543 |
# File 'app/models/query.rb', line 541 def sort_criteria_key(arg) sort_criteria && sort_criteria[arg] && sort_criteria[arg].first end |
#sort_criteria_order(arg) ⇒ Object
545 546 547 |
# File 'app/models/query.rb', line 545 def sort_criteria_order(arg) sort_criteria && sort_criteria[arg] && sort_criteria[arg].last end |
#sort_criteria_order_for(key) ⇒ Object
Removed at 3.4.0
549 550 551 |
# File 'app/models/query.rb', line 549 def sort_criteria_order_for(key) sort_criteria.detect {|k, order| key.to_s == k}.try(:last) end |
#sortable_columns ⇒ Object
Returns a Hash of columns and the key for sorting
447 448 449 450 451 452 |
# File 'app/models/query.rb', line 447 def sortable_columns available_columns.inject({}) {|h, column| h[column.name.to_s] = column.sortable h } end |
#statement ⇒ Object
601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 |
# File 'app/models/query.rb', line 601 def statement # filters clauses filters_clauses = [] filters.each_key do |field| next if field == "subproject_id" v = values_for(field).clone next unless v and !v.empty? operator = operator_for(field) # "me" value substitution if %w(assigned_to_id author_id user_id watcher_id).include?(field) if v.delete("me") if User.current.logged? v.push(User.current.id.to_s) v += User.current.group_ids.map(&:to_s) if field == 'assigned_to_id' else v.push("0") end end end if field == 'project_id' if v.delete('mine') v += User.current.memberships.map(&:project_id).map(&:to_s) end end if field =~ /cf_(\d+)$/ # custom field filters_clauses << sql_for_custom_field(field, operator, v, $1) elsif respond_to?("sql_for_#{field}_field") # specific statement filters_clauses << send("sql_for_#{field}_field", field, operator, v) else # regular field filters_clauses << '(' + sql_for_field(field, operator, v, queried_table_name, field) + ')' end end if filters and valid? if (c = group_by_column) && c.is_a?(QueryCustomFieldColumn) # Excludes results for which the grouped custom field is not visible filters_clauses << c.custom_field.visibility_by_project_condition end filters_clauses << project_statement filters_clauses.reject!(&:blank?) filters_clauses.any? ? filters_clauses.join(' AND ') : nil end |
#total_by_group_for(column) ⇒ Object
Returns a hash of the sum of the given column for each group, or nil if the query is not grouped
658 659 660 661 662 |
# File 'app/models/query.rb', line 658 def total_by_group_for(column) grouped_query do |scope| total_with_scope(column, scope) end end |
#total_for(column) ⇒ Object
Returns the sum of values for the given column
652 653 654 |
# File 'app/models/query.rb', line 652 def total_for(column) total_with_scope(column, base_scope) end |
#totalable_columns ⇒ Object
510 511 512 513 |
# File 'app/models/query.rb', line 510 def totalable_columns names = totalable_names available_totalable_columns.select {|column| names.include?(column.name)} end |
#totalable_names ⇒ Object
522 523 524 |
# File 'app/models/query.rb', line 522 def totalable_names [:totalable_names] || Setting.issue_list_default_totals.map(&:to_sym) || [] end |
#totalable_names=(names) ⇒ Object
515 516 517 518 519 520 |
# File 'app/models/query.rb', line 515 def totalable_names=(names) if names names = names.select(&:present?).map {|n| n.is_a?(Symbol) ? n : n.to_sym} end [:totalable_names] = names end |
#totals {|totals| ... } ⇒ Object
664 665 666 667 668 |
# File 'app/models/query.rb', line 664 def totals totals = totalable_columns.map {|column| [column, total_for(column)]} yield totals if block_given? totals end |
#totals_by_group {|totals| ... } ⇒ Object
670 671 672 673 674 |
# File 'app/models/query.rb', line 670 def totals_by_group totals = totalable_columns.map {|column| [column, total_by_group_for(column)]} yield totals if block_given? totals end |
#trackers ⇒ Object
303 304 305 |
# File 'app/models/query.rb', line 303 def trackers @trackers ||= (project.nil? ? Tracker.all : project.rolled_up_trackers).visible.sorted end |
#type_for(field) ⇒ Object
416 417 418 |
# File 'app/models/query.rb', line 416 def type_for(field) available_filters[field][:type] if available_filters.has_key?(field) end |
#validate_query_filters ⇒ Object
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 |
# File 'app/models/query.rb', line 262 def validate_query_filters filters.each_key do |field| if values_for(field) case type_for(field) when :integer add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/\A[+-]?\d+(,[+-]?\d+)*\z/) } when :float add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/\A[+-]?\d+(\.\d*)?\z/) } when :date, :date_past case operator_for(field) when "=", ">=", "<=", "><" add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && (!v.match(/\A\d{4}-\d{2}-\d{2}(T\d{2}((:)?\d{2}){0,2}(Z|\d{2}:?\d{2})?)?\z/) || parse_date(v).nil?) } when ">t-", "<t-", "t-", ">t+", "<t+", "t+", "><t+", "><t-" add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^\d+$/) } end end end add_filter_error(field, :blank) unless # filter requires one or more values (values_for(field) and !values_for(field).first.blank?) or # filter doesn't require any value ["o", "c", "!*", "*", "t", "ld", "w", "lw", "l2w", "m", "lm", "y", "*o", "!o"].include? operator_for(field) end if filters end |
#value_for(field, index = 0) ⇒ Object
428 429 430 |
# File 'app/models/query.rb', line 428 def value_for(field, index=0) (values_for(field) || [])[index] end |
#values_for(field) ⇒ Object
424 425 426 |
# File 'app/models/query.rb', line 424 def values_for(field) has_filter?(field) ? filters[field][:values] : nil end |