summaryrefslogtreecommitdiffstats
path: root/app/models/query.rb
diff options
context:
space:
mode:
authorJean-Philippe Lang <jp_lang@yahoo.fr>2006-12-16 13:37:32 +0000
committerJean-Philippe Lang <jp_lang@yahoo.fr>2006-12-16 13:37:32 +0000
commit2b0142580f9c5e0b9dc54d1e338e355b235bb844 (patch)
treee5128fc007811c52a15391ee773eaae3fdbecdae /app/models/query.rb
parent236c735d08c097cfe1a7e5f5c52a9dd6711250aa (diff)
downloadredmine-2b0142580f9c5e0b9dc54d1e338e355b235bb844.tar.gz
redmine-2b0142580f9c5e0b9dc54d1e338e355b235bb844.zip
"queries" branch merged
git-svn-id: http://redmine.rubyforge.org/svn/trunk@95 e93f8b46-1217-0410-a6f0-8f06a7374b81
Diffstat (limited to 'app/models/query.rb')
-rw-r--r--app/models/query.rb166
1 files changed, 166 insertions, 0 deletions
diff --git a/app/models/query.rb b/app/models/query.rb
new file mode 100644
index 000000000..4ac34bd6d
--- /dev/null
+++ b/app/models/query.rb
@@ -0,0 +1,166 @@
+# redMine - project management software
+# Copyright (C) 2006 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+class Query < ActiveRecord::Base
+ belongs_to :project
+ belongs_to :user
+ serialize :filters
+
+ attr_protected :project, :user
+
+ validates_presence_of :name, :on => :save
+
+ @@operators = { "=" => :label_equals,
+ "!" => :label_not_equals,
+ "o" => :label_open_issues,
+ "c" => :label_closed_issues,
+ "!*" => :label_none,
+ "*" => :label_all,
+ "<t+" => :label_in_less_than,
+ ">t+" => :label_in_more_than,
+ "t+" => :label_in,
+ "t" => :label_today,
+ ">t-" => :label_less_than_ago,
+ "<t-" => :label_more_than_ago,
+ "t-" => :label_ago,
+ "~" => :label_contains,
+ "!~" => :label_not_contains }
+
+ cattr_reader :operators
+
+ @@operators_by_filter_type = { :list => [ "=", "!" ],
+ :list_status => [ "o", "=", "!", "c", "*" ],
+ :list_optional => [ "=", "!", "!*", "*" ],
+ :date => [ "<t+", ">t+", "t+", "t", ">t-", "<t-", "t-" ],
+ :date_past => [ ">t-", "<t-", "t-", "t" ],
+ :text => [ "~", "!~" ] }
+
+ cattr_reader :operators_by_filter_type
+
+ def initialize(attributes = nil)
+ super attributes
+ self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
+ self.is_public = true
+ end
+
+ def validate
+ filters.each_key do |field|
+ errors.add field.gsub(/\_id$/, ""), :activerecord_error_blank unless
+ # filter requires one or more values
+ (values_for(field) and !values_for(field).first.empty?) or
+ # filter doesn't require any value
+ ["o", "c", "!*", "*", "t"].include? operator_for(field)
+ end if filters
+ end
+
+ def available_filters
+ return @available_filters if @available_filters
+ @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all).collect{|s| [s.name, s.id.to_s] } },
+ "tracker_id" => { :type => :list, :order => 2, :values => Tracker.find(:all).collect{|s| [s.name, s.id.to_s] } },
+ "priority_id" => { :type => :list, :order => 3, :values => Enumeration.find(:all, :conditions => ['opt=?','IPRI']).collect{|s| [s.name, s.id.to_s] } },
+ "subject" => { :type => :text, :order => 7 },
+ "created_on" => { :type => :date_past, :order => 8 },
+ "updated_on" => { :type => :date_past, :order => 9 },
+ "start_date" => { :type => :date, :order => 10 },
+ "due_date" => { :type => :date, :order => 11 } }
+ unless project.nil?
+ # project specific filters
+ @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => @project.users.collect{|s| [s.name, s.id.to_s] } }
+ @available_filters["author_id"] = { :type => :list, :order => 5, :values => @project.users.collect{|s| [s.name, s.id.to_s] } }
+ @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } }
+ # remove category filter if no category defined
+ @available_filters.delete "category_id" if @available_filters["category_id"][:values].empty?
+ end
+ @available_filters
+ end
+
+ def add_filter(field, operator, values)
+ # values must be an array
+ return unless values and values.is_a? Array # and !values.first.empty?
+ # check if field is defined as an available filter
+ if available_filters.has_key? field
+ filter_options = available_filters[field]
+ # check if operator is allowed for that filter
+ #if @@operators_by_filter_type[filter_options[:type]].include? operator
+ # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]})
+ # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator
+ #end
+ filters[field] = {:operator => operator, :values => values }
+ end
+ end
+
+ def add_short_filter(field, expression)
+ return unless expression
+ parms = expression.scan(/^(o|c|\!|\*)?(.*)$/).first
+ add_filter field, (parms[0] || "="), [parms[1] || ""]
+ end
+
+ def has_filter?(field)
+ filters and filters[field]
+ end
+
+ def operator_for(field)
+ has_filter?(field) ? filters[field][:operator] : nil
+ end
+
+ def values_for(field)
+ has_filter?(field) ? filters[field][:values] : nil
+ end
+
+ def statement
+ sql = "1=1"
+ sql << " AND issues.project_id=%d" % project.id if project
+ filters.each_key do |field|
+ v = values_for field
+ next unless v and !v.empty?
+ sql = sql + " AND " unless sql.empty?
+ case operator_for field
+ when "="
+ sql = sql + "issues.#{field} IN (" + v.each(&:to_i).join(",") + ")"
+ when "!"
+ sql = sql + "issues.#{field} NOT IN (" + v.each(&:to_i).join(",") + ")"
+ when "!*"
+ sql = sql + "issues.#{field} IS NULL"
+ when "*"
+ sql = sql + "issues.#{field} IS NOT NULL"
+ when "o"
+ sql = sql + "issue_statuses.is_closed=#{connection.quoted_false}" if field == "status_id"
+ when "c"
+ sql = sql + "issue_statuses.is_closed=#{connection.quoted_true}" if field == "status_id"
+ when ">t-"
+ sql = sql + "issues.#{field} >= '%s'" % connection.quoted_date(Date.today - v.first.to_i)
+ when "<t-"
+ sql = sql + "issues.#{field} <= '" + (Date.today - v.first.to_i).strftime("%Y-%m-%d") + "'"
+ when "t-"
+ sql = sql + "issues.#{field} = '" + (Date.today - v.first.to_i).strftime("%Y-%m-%d") + "'"
+ when ">t+"
+ sql = sql + "issues.#{field} >= '" + (Date.today + v.first.to_i).strftime("%Y-%m-%d") + "'"
+ when "<t+"
+ sql = sql + "issues.#{field} <= '" + (Date.today + v.first.to_i).strftime("%Y-%m-%d") + "'"
+ when "t+"
+ sql = sql + "issues.#{field} = '" + (Date.today + v.first.to_i).strftime("%Y-%m-%d") + "'"
+ when "t"
+ sql = sql + "issues.#{field} = '%s'" % connection.quoted_date(Date.today)
+ when "~"
+ sql = sql + "issues.#{field} LIKE '%#{connection.quote_string(v.first)}%'"
+ when "!~"
+ sql = sql + "issues.#{field} NOT LIKE '%#{connection.quote_string(v.first)}%'"
+ end
+ end if filters and valid?
+ sql
+ end
+end