OSDN Git Service

Changed Enumerations to use a Single Table Inheritance
authorEric Davis <edavis@littlestreamsoftware.com>
Sat, 30 May 2009 23:30:36 +0000 (23:30 +0000)
committerEric Davis <edavis@littlestreamsoftware.com>
Sat, 30 May 2009 23:30:36 +0000 (23:30 +0000)
* Added migrations to change Enumerations to an STI relationship
* Added TimeEntryActivity model (STI)
* Added DocumentCategory model (STI)
* Added IssuePriority model (STI)
* Added Enumeration#get_subclasses to get a list of the subclasses of Enumeration
* Changed Enumeration to use the STI type field instead of the opt field
* Changed Enumeration#opt to return the old opt values but with a deprecation warning.
* Removed Enumeration::OPTIONS
* Removed the dynamic named_scopes in favor of specific named_scopes.  Kept for
  compatibility reasons.
* Added Enumeration#default so each subclass can easily find it's default record.
* Fixed Enumeration#default to use the STI scoping with a fake default scope for finding Enumeration's default.
* Added a 'all' named scope for getting all records in order by position.
* Added Deprecation warnings to the old named_scopes in Enumerations.
* Moved various methods off of Enumeration and onto the concrete classes
* Changed the EnumerationsController to use types
* Updated the Enumeration list template
* Added has_many relationships to the Enumeration STI classes.
* Fixes for tests.

  #3007

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2777 e93f8b46-1217-0410-a6f0-8f06a7374b81

32 files changed:
app/controllers/documents_controller.rb
app/controllers/enumerations_controller.rb
app/controllers/issues_controller.rb
app/controllers/reports_controller.rb
app/controllers/timelog_controller.rb
app/helpers/issues_helper.rb
app/helpers/timelog_helper.rb
app/models/document.rb
app/models/document_category.rb [new file with mode: 0644]
app/models/enumeration.rb
app/models/issue.rb
app/models/issue_priority.rb [new file with mode: 0644]
app/models/mail_handler.rb
app/models/query.rb
app/models/time_entry.rb
app/models/time_entry_activity.rb [new file with mode: 0644]
app/views/documents/_form.rhtml
app/views/enumerations/_form.rhtml
app/views/enumerations/list.rhtml
app/views/issues/bulk_edit.rhtml
app/views/my/blocks/_issuesassignedtome.rhtml
db/migrate/20090323224724_add_type_to_enumerations.rb [new file with mode: 0644]
db/migrate/20090401221305_update_enumerations_to_sti.rb [new file with mode: 0644]
test/fixtures/enumerations.yml
test/fixtures/issues.yml
test/functional/issues_controller_test.rb
test/integration/issues_test.rb
test/unit/document_category_test.rb [new file with mode: 0644]
test/unit/enumeration_test.rb
test/unit/issue_priority_test.rb [new file with mode: 0644]
test/unit/issue_test.rb
test/unit/time_entry_activity_test.rb [new file with mode: 0644]

index f5825e7..c2e0876 100644 (file)
@@ -53,7 +53,7 @@ class DocumentsController < ApplicationController
   end
   
   def edit
-    @categories = Enumeration.document_categories
+    @categories = DocumentCategory.all
     if request.post? and @document.update_attributes(params[:document])
       flash[:notice] = l(:notice_successful_update)
       redirect_to :action => 'show', :id => @document
index ac55c42..63965fa 100644 (file)
@@ -31,14 +31,19 @@ class EnumerationsController < ApplicationController
   end
 
   def new
-    @enumeration = Enumeration.new(:opt => params[:opt])
+    begin
+      @enumeration = params[:type].constantize.new
+    rescue NameError
+      @enumeration = Enumeration.new      
+    end
   end
 
   def create
     @enumeration = Enumeration.new(params[:enumeration])
+    @enumeration.type = params[:enumeration][:type]
     if @enumeration.save
       flash[:notice] = l(:notice_successful_create)
-      redirect_to :action => 'list', :opt => @enumeration.opt
+      redirect_to :action => 'list', :type => @enumeration.type
     else
       render :action => 'new'
     end
@@ -50,9 +55,10 @@ class EnumerationsController < ApplicationController
 
   def update
     @enumeration = Enumeration.find(params[:id])
+    @enumeration.type = params[:enumeration][:type] if params[:enumeration][:type]
     if @enumeration.update_attributes(params[:enumeration])
       flash[:notice] = l(:notice_successful_update)
-      redirect_to :action => 'list', :opt => @enumeration.opt
+      redirect_to :action => 'list', :type => @enumeration.type
     else
       render :action => 'edit'
     end
@@ -65,12 +71,12 @@ class EnumerationsController < ApplicationController
       @enumeration.destroy
       redirect_to :action => 'index'
     elsif params[:reassign_to_id]
-      if reassign_to = Enumeration.find_by_opt_and_id(@enumeration.opt, params[:reassign_to_id])
+      if reassign_to = Enumeration.find_by_type_and_id(@enumeration.type, params[:reassign_to_id])
         @enumeration.destroy(reassign_to)
         redirect_to :action => 'index'
       end
     end
-    @enumerations = Enumeration.values(@enumeration.opt) - [@enumeration]
+    @enumerations = Enumeration.find(:all, :conditions => ['type = (?)', @enumeration.type]) - [@enumeration]
   #rescue
   #  flash[:error] = 'Unable to delete enumeration'
   #  redirect_to :action => 'index'
index 7fcf56e..de51676 100644 (file)
@@ -113,7 +113,7 @@ class IssuesController < ApplicationController
     @changesets.reverse! if User.current.wants_comments_in_reverse_order?
     @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
     @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
-    @priorities = Enumeration.priorities
+    @priorities = IssuePriority.all
     @time_entry = TimeEntry.new
     respond_to do |format|
       format.html { render :template => 'issues/show.rhtml' }
@@ -163,7 +163,7 @@ class IssuesController < ApplicationController
         return
       end              
     end        
-    @priorities = Enumeration.priorities
+    @priorities = IssuePriority.all
     render :layout => !request.xhr?
   end
   
@@ -173,7 +173,7 @@ class IssuesController < ApplicationController
   
   def edit
     @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
-    @priorities = Enumeration.priorities
+    @priorities = IssuePriority.all
     @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
     @time_entry = TimeEntry.new
     
@@ -237,7 +237,7 @@ class IssuesController < ApplicationController
   def bulk_edit
     if request.post?
       status = params[:status_id].blank? ? nil : IssueStatus.find_by_id(params[:status_id])
-      priority = params[:priority_id].blank? ? nil : Enumeration.find_by_id(params[:priority_id])
+      priority = params[:priority_id].blank? ? nil : IssuePriority.find_by_id(params[:priority_id])
       assigned_to = (params[:assigned_to_id].blank? || params[:assigned_to_id] == 'none') ? nil : User.find_by_id(params[:assigned_to_id])
       category = (params[:category_id].blank? || params[:category_id] == 'none') ? nil : @project.issue_categories.find_by_id(params[:category_id])
       fixed_version = (params[:fixed_version_id].blank? || params[:fixed_version_id] == 'none') ? nil : @project.versions.find_by_id(params[:fixed_version_id])
@@ -415,7 +415,7 @@ class IssuesController < ApplicationController
       @assignables << @issue.assigned_to if @issue && @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
     end
     
-    @priorities = Enumeration.priorities.reverse
+    @priorities = IssuePriority.all.reverse
     @statuses = IssueStatus.find(:all, :order => 'position')
     @back = request.env['HTTP_REFERER']
     
index 92bf530..d192a19 100644 (file)
@@ -37,7 +37,7 @@ class ReportsController < ApplicationController
       render :template => "reports/issue_report_details"
     when "priority"
       @field = "priority_id"
-      @rows = Enumeration.priorities
+      @rows = IssuePriority.all
       @data = issues_by_priority
       @report_title = l(:field_priority)
       render :template => "reports/issue_report_details"   
@@ -68,7 +68,7 @@ class ReportsController < ApplicationController
     else
       @trackers = @project.trackers
       @versions = @project.versions.sort
-      @priorities = Enumeration.priorities
+      @priorities = IssuePriority.all
       @categories = @project.issue_categories
       @assignees = @project.members.collect { |m| m.user }
       @authors = @project.members.collect { |m| m.user }
@@ -130,7 +130,7 @@ private
                                                   p.id as priority_id,
                                                   count(i.id) as total 
                                                 from 
-                                                  #{Issue.table_name} i, #{IssueStatus.table_name} s, #{Enumeration.table_name} p
+                                                  #{Issue.table_name} i, #{IssueStatus.table_name} s, #{IssuePriority.table_name} p
                                                 where 
                                                   i.status_id=s.id 
                                                   and i.priority_id=p.id
index 60cc391..29ba499 100644 (file)
@@ -46,7 +46,7 @@ class TimelogController < ApplicationController
                                           :klass => Tracker,
                                           :label => :label_tracker},
                              'activity' => {:sql => "#{TimeEntry.table_name}.activity_id",
-                                           :klass => Enumeration,
+                                           :klass => TimeEntryActivity,
                                            :label => :label_activity},
                              'issue' => {:sql => "#{TimeEntry.table_name}.issue_id",
                                          :klass => Issue,
index a85a83a..73ac4be 100644 (file)
@@ -68,8 +68,8 @@ module IssuesHelper
         u = User.find_by_id(detail.value) and value = u.name if detail.value
         u = User.find_by_id(detail.old_value) and old_value = u.name if detail.old_value
       when 'priority_id'
-        e = Enumeration.find_by_id(detail.value) and value = e.name if detail.value
-        e = Enumeration.find_by_id(detail.old_value) and old_value = e.name if detail.old_value
+        e = IssuePriority.find_by_id(detail.value) and value = e.name if detail.value
+        e = IssuePriority.find_by_id(detail.old_value) and old_value = e.name if detail.old_value
       when 'category_id'
         c = IssueCategory.find_by_id(detail.value) and value = c.name if detail.value
         c = IssueCategory.find_by_id(detail.old_value) and old_value = c.name if detail.old_value
index d34694f..861c2ac 100644 (file)
@@ -27,7 +27,7 @@ module TimelogHelper
   end
   
   def activity_collection_for_select_options
-    activities = Enumeration.activities
+    activities = TimeEntryActivity.all
     collection = []
     collection << [ "--- #{l(:actionview_instancetag_blank_option)} ---", '' ] unless activities.detect(&:is_default)
     activities.each { |a| collection << [a.name, a.id] }
index f78c15e..a96c278 100644 (file)
@@ -17,7 +17,7 @@
 
 class Document < ActiveRecord::Base
   belongs_to :project
-  belongs_to :category, :class_name => "Enumeration", :foreign_key => "category_id"
+  belongs_to :category, :class_name => "DocumentCategory", :foreign_key => "category_id"
   acts_as_attachable :delete_permission => :manage_documents
 
   acts_as_searchable :columns => ['title', "#{table_name}.description"], :include => :project
@@ -31,7 +31,7 @@ class Document < ActiveRecord::Base
   
   def after_initialize
     if new_record?
-      self.category ||= Enumeration.document_categories.default
+      self.category ||= DocumentCategory.default
     end
   end
 end
diff --git a/app/models/document_category.rb b/app/models/document_category.rb
new file mode 100644 (file)
index 0000000..e04db7d
--- /dev/null
@@ -0,0 +1,36 @@
+# 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 DocumentCategory < Enumeration
+  has_many :documents, :foreign_key => 'category_id'
+
+  OptionName = :enumeration_doc_categories
+  # Backwards compatiblity.  Can be removed post-0.9
+  OptName = 'DCAT'
+
+  def option_name
+    OptionName
+  end
+
+  def objects_count
+    documents.count
+  end
+
+  def transfer_relations(to)
+    documents.update_all("category_id = #{to.id}")
+  end
+end
index d466940..380a4d3 100644 (file)
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
 class Enumeration < ActiveRecord::Base
-  acts_as_list :scope => 'opt = \'#{opt}\''
+  acts_as_list :scope => 'type = \'#{type}\''
 
   before_destroy :check_integrity
   
-  validates_presence_of :opt, :name
-  validates_uniqueness_of :name, :scope => [:opt]
+  validates_presence_of :name
+  validates_uniqueness_of :name, :scope => [:type]
   validates_length_of :name, :maximum => 30
-
-  # Single table inheritance would be an option
-  OPTIONS = {
-    "IPRI" => {:label => :enumeration_issue_priorities, :model => Issue, :foreign_key => :priority_id, :scope => :priorities},
-    "DCAT" => {:label => :enumeration_doc_categories, :model => Document, :foreign_key => :category_id, :scope => :document_categories},
-    "ACTI" => {:label => :enumeration_activities, :model => TimeEntry, :foreign_key => :activity_id, :scope => :activities}
-  }.freeze
   
-  # Creates a named scope for each type of value. The scope has a +default+ method
-  # that returns the default value, or nil if no value is set as default.
-  # Example:
-  #   Enumeration.priorities
-  #   Enumeration.priorities.default
-  OPTIONS.each do |k, v|
-    next unless v[:scope]
-    named_scope v[:scope], :conditions => { :opt => k }, :order => 'position' do
-      def default
-        find(:first, :conditions => { :is_default => true })
-      end
+  # Backwards compatiblity named_scopes.
+  # Can be removed post-0.9
+  named_scope :priorities, :conditions => { :type => "IssuePriority" }, :order => 'position' do
+    ActiveSupport::Deprecation.warn("Enumeration#priorities is deprecated, use the IssuePriority class. (#{Redmine::Info.issue(3007)})")
+    def default
+      find(:first, :conditions => { :is_default => true })
+    end
+  end
+
+  named_scope :document_categories, :conditions => { :type => "DocumentCategory" }, :order => 'position' do
+    ActiveSupport::Deprecation.warn("Enumeration#document_categories is deprecated, use the DocumentCategories class. (#{Redmine::Info.issue(3007)})")
+    def default
+      find(:first, :conditions => { :is_default => true })
+    end
+  end
+
+  named_scope :activities, :conditions => { :type => "TimeEntryActivity" }, :order => 'position' do
+    ActiveSupport::Deprecation.warn("Enumeration#activities is deprecated, use the TimeEntryActivity class. (#{Redmine::Info.issue(3007)})")
+    def default
+      find(:first, :conditions => { :is_default => true })
     end
   end
   
-  named_scope :values, lambda {|opt| { :conditions => { :opt => opt }, :order => 'position' } } do
+  named_scope :values, lambda {|type| { :conditions => { :type => type }, :order => 'position' } } do
     def default
       find(:first, :conditions => { :is_default => true })
     end
   end
 
+  named_scope :all, :order => 'position'
+
+  def self.default
+    # Creates a fake default scope so Enumeration.default will check
+    # it's type.  STI subclasses will automatically add their own
+    # types to the finder.
+    if self.descends_from_active_record?
+      find(:first, :conditions => { :is_default => true, :type => 'Enumeration' })
+    else
+      # STI classes are
+      find(:first, :conditions => { :is_default => true })
+    end
+  end
+  
+  # Overloaded on concrete classes
   def option_name
-    OPTIONS[self.opt][:label]
+    nil
+  end
+
+  # Backwards compatiblity.  Can be removed post-0.9
+  def opt
+    ActiveSupport::Deprecation.warn("Enumeration#opt is deprecated, use the STI classes now. (#{Redmine::Info.issue(3007)})")
+    return OptName
   end
 
   def before_save
     if is_default? && is_default_changed?
-      Enumeration.update_all("is_default = #{connection.quoted_false}", {:opt => opt})
+      Enumeration.update_all("is_default = #{connection.quoted_false}", {:type => type})
     end
   end
   
+  # Overloaded on concrete classes
   def objects_count
-    OPTIONS[self.opt][:model].count(:conditions => "#{OPTIONS[self.opt][:foreign_key]} = #{id}")
+    0
   end
   
   def in_use?
@@ -75,7 +99,7 @@ class Enumeration < ActiveRecord::Base
   # If a enumeration is specified, objects are reassigned
   def destroy(reassign_to = nil)
     if reassign_to && reassign_to.is_a?(Enumeration)
-      OPTIONS[self.opt][:model].update_all("#{OPTIONS[self.opt][:foreign_key]} = #{reassign_to.id}", "#{OPTIONS[self.opt][:foreign_key]} = #{id}")
+      self.transfer_relations(reassign_to)
     end
     destroy_without_reassign
   end
@@ -85,9 +109,23 @@ class Enumeration < ActiveRecord::Base
   end
   
   def to_s; name end
+
+  # Returns the Subclasses of Enumeration.  Each Subclass needs to be
+  # required in development mode.
+  #
+  # Note: subclasses is protected in ActiveRecord
+  def self.get_subclasses
+    @@subclasses[Enumeration]
+  end
   
 private
   def check_integrity
     raise "Can't delete enumeration" if self.in_use?
   end
+
 end
+
+# Force load the subclasses in development mode
+require_dependency 'time_entry_activity'
+require_dependency 'document_category'
+require_dependency 'issue_priority'
index da49577..861d898 100644 (file)
@@ -22,7 +22,7 @@ class Issue < ActiveRecord::Base
   belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
   belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id'
   belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id'
-  belongs_to :priority, :class_name => 'Enumeration', :foreign_key => 'priority_id'
+  belongs_to :priority, :class_name => 'IssuePriority', :foreign_key => 'priority_id'
   belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
 
   has_many :journals, :as => :journalized, :dependent => :destroy
@@ -67,7 +67,7 @@ class Issue < ActiveRecord::Base
     if new_record?
       # set default values for new records only
       self.status ||= IssueStatus.default
-      self.priority ||= Enumeration.priorities.default
+      self.priority ||= IssuePriority.default
     end
   end
   
diff --git a/app/models/issue_priority.rb b/app/models/issue_priority.rb
new file mode 100644 (file)
index 0000000..cea0f06
--- /dev/null
@@ -0,0 +1,36 @@
+# 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 IssuePriority < Enumeration
+  has_many :issues, :foreign_key => 'priority_id'
+
+  OptionName = :enumeration_issue_priorities
+  # Backwards compatiblity.  Can be removed post-0.9
+  OptName = 'IPRI'
+
+  def option_name
+    OptionName
+  end
+
+  def objects_count
+    issues.count
+  end
+
+  def transfer_relations(to)
+    issues.update_all("priority_id = #{to.id}")
+  end
+end
index 023e1d6..0276653 100644 (file)
@@ -91,7 +91,7 @@ class MailHandler < ActionMailer::Base
     project = target_project
     tracker = (get_keyword(:tracker) && project.trackers.find_by_name(get_keyword(:tracker))) || project.trackers.find(:first)
     category = (get_keyword(:category) && project.issue_categories.find_by_name(get_keyword(:category)))
-    priority = (get_keyword(:priority) && Enumeration.find_by_opt_and_name('IPRI', get_keyword(:priority)))
+    priority = (get_keyword(:priority) && IssuePriority.find_by_name(get_keyword(:priority)))
     status =  (get_keyword(:status) && IssueStatus.find_by_name(get_keyword(:status)))
 
     # check permission
index 355a410..4e5799f 100644 (file)
@@ -102,7 +102,7 @@ class Query < ActiveRecord::Base
     QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true),
     QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true),
     QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true),
-    QueryColumn.new(:priority, :sortable => "#{Enumeration.table_name}.position", :default_order => 'desc', :groupable => true),
+    QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true),
     QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
     QueryColumn.new(:author),
     QueryColumn.new(:assigned_to, :sortable => ["#{User.table_name}.lastname", "#{User.table_name}.firstname", "#{User.table_name}.id"], :groupable => true),
@@ -152,7 +152,7 @@ class Query < ActiveRecord::Base
     
     @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },       
                            "tracker_id" => { :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } },                                                                                                                
-                           "priority_id" => { :type => :list, :order => 3, :values => Enumeration.find(:all, :conditions => ['opt=?','IPRI'], :order => 'position').collect{|s| [s.name, s.id.to_s] } },
+                           "priority_id" => { :type => :list, :order => 3, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] } },
                            "subject" => { :type => :text, :order => 8 },  
                            "created_on" => { :type => :date_past, :order => 9 },                        
                            "updated_on" => { :type => :date_past, :order => 10 },
index 7d16549..e6cbdfe 100644 (file)
@@ -21,7 +21,7 @@ class TimeEntry < ActiveRecord::Base
   belongs_to :project
   belongs_to :issue
   belongs_to :user
-  belongs_to :activity, :class_name => 'Enumeration', :foreign_key => :activity_id
+  belongs_to :activity, :class_name => 'TimeEntryActivity', :foreign_key => 'activity_id'
   
   attr_protected :project_id, :user_id, :tyear, :tmonth, :tweek
 
@@ -37,7 +37,7 @@ class TimeEntry < ActiveRecord::Base
 
   def after_initialize
     if new_record? && self.activity.nil?
-      if default_activity = Enumeration.activities.default
+      if default_activity = TimeEntryActivity.default
         self.activity_id = default_activity.id
       end
     end
diff --git a/app/models/time_entry_activity.rb b/app/models/time_entry_activity.rb
new file mode 100644 (file)
index 0000000..8e35671
--- /dev/null
@@ -0,0 +1,36 @@
+# 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 TimeEntryActivity < Enumeration
+  has_many :time_entries, :foreign_key => 'activity_id'
+
+  OptionName = :enumeration_activities
+  # Backwards compatiblity.  Can be removed post-0.9
+  OptName = 'ACTI'
+  
+  def option_name
+    OptionName
+  end
+
+  def objects_count
+    time_entries.count
+  end
+
+  def transfer_relations(to)
+    time_entries.update_all("activity_id = #{to.id}")
+  end
+end
index 12a16f5..b118ebd 100644 (file)
@@ -2,7 +2,7 @@
 <div class="box">
 <!--[form:document]-->
 <p><label for="document_category_id"><%=l(:field_category)%></label>
-<%= select('document', 'category_id', Enumeration.document_categories.collect {|c| [c.name, c.id]}) %></p>
+<%= select('document', 'category_id', DocumentCategory.all.collect {|c| [c.name, c.id]}) %></p>
 
 <p><label for="document_title"><%=l(:field_title)%> <span class="required">*</span></label>
 <%= text_field 'document', 'title', :size => 60 %></p>
index 3f98f52..dac0c93 100644 (file)
@@ -1,7 +1,7 @@
 <%= error_messages_for 'enumeration' %>
 <div class="box">
 <!--[form:optvalue]-->
-<%= hidden_field 'enumeration', 'opt'  %>
+<%= hidden_field 'enumeration', 'type'  %>
 
 <p><label for="enumeration_name"><%=l(:field_name)%></label>
 <%= text_field 'enumeration', 'name'  %></p>
index 485dec7..8143ad0 100644 (file)
@@ -1,9 +1,9 @@
 <h2><%=l(:label_enumerations)%></h2>
 
-<% Enumeration::OPTIONS.each do |option, params| %>
-<h3><%= l(params[:label]) %></h3>
+<% Enumeration.get_subclasses.each do |klass| %>
+<h3><%= l(klass::OptionName) %></h3>
 
-<% enumerations = Enumeration.values(option) %>
+<% enumerations = klass.all %>
 <% if enumerations.any? %>
 <table class="list">
 <% enumerations.each do |enumeration| %>
@@ -20,7 +20,7 @@
 <% reset_cycle %>
 <% end %>
 
-<p><%= link_to l(:label_enumeration_new), { :action => 'new', :opt => option } %></p>
+<p><%= link_to l(:label_enumeration_new), { :action => 'new', :type => klass.name } %></p>
 <% end %>
 
 <% html_title(l(:label_enumerations)) -%>
index 2141168..4ea9ffd 100644 (file)
@@ -13,7 +13,7 @@
 <%= select_tag('status_id', "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(@available_statuses, :id, :name)) %></label>
 <% end %>
 <label><%= l(:field_priority) %>: 
-<%= select_tag('priority_id', "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(Enumeration.priorities, :id, :name)) %></label>
+<%= select_tag('priority_id', "<option value=\"\">#{l(:label_no_change_option)}</option>" + options_from_collection_for_select(IssuePriority.all, :id, :name)) %></label>
 <label><%= l(:field_category) %>: 
 <%= select_tag('category_id', content_tag('option', l(:label_no_change_option), :value => '') +
                                 content_tag('option', l(:label_none), :value => 'none') +
index 69814bb..0666a47 100644 (file)
@@ -4,7 +4,7 @@
                                 :conditions => {:assigned_to_id => User.current.id},
                                 :limit => 10, 
                                 :include => [ :status, :project, :tracker, :priority ], 
-                                :order => "#{Enumeration.table_name}.position DESC, #{Issue.table_name}.updated_on DESC") %>
+                                :order => "#{IssuePriority.table_name}.position DESC, #{Issue.table_name}.updated_on DESC") %>
 <%= render :partial => 'issues/list_simple', :locals => { :issues => assigned_issues } %>
 <% if assigned_issues.length > 0 %>
 <p class="small"><%= link_to l(:label_issue_view_all), :controller => 'issues',
diff --git a/db/migrate/20090323224724_add_type_to_enumerations.rb b/db/migrate/20090323224724_add_type_to_enumerations.rb
new file mode 100644 (file)
index 0000000..c2aef5e
--- /dev/null
@@ -0,0 +1,9 @@
+class AddTypeToEnumerations < ActiveRecord::Migration
+  def self.up
+    add_column :enumerations, :type, :string
+  end
+
+  def self.down
+    remove_column :enumerations, :type
+  end
+end
diff --git a/db/migrate/20090401221305_update_enumerations_to_sti.rb b/db/migrate/20090401221305_update_enumerations_to_sti.rb
new file mode 100644 (file)
index 0000000..50bd520
--- /dev/null
@@ -0,0 +1,11 @@
+class UpdateEnumerationsToSti < ActiveRecord::Migration
+  def self.up
+    Enumeration.update_all("type = 'IssuePriority'", "opt = 'IPRI'")
+    Enumeration.update_all("type = 'DocumentCategory'", "opt = 'DCAT'")
+    Enumeration.update_all("type = 'TimeEntryActivity'", "opt = 'ACTI'")
+  end
+
+  def self.down
+    # no-op
+  end
+end
index 22a581a..6e7983e 100644 (file)
@@ -3,46 +3,67 @@ enumerations_001:
   name: Uncategorized\r
   id: 1\r
   opt: DCAT\r
+  type: DocumentCategory\r
 enumerations_002: \r
   name: User documentation\r
   id: 2\r
   opt: DCAT\r
+  type: DocumentCategory\r
 enumerations_003: \r
   name: Technical documentation\r
   id: 3\r
   opt: DCAT\r
+  type: DocumentCategory\r
 enumerations_004: \r
   name: Low\r
   id: 4\r
   opt: IPRI\r
+  type: IssuePriority\r
 enumerations_005: \r
   name: Normal\r
   id: 5\r
   opt: IPRI\r
+  type: IssuePriority\r
   is_default: true\r
 enumerations_006: \r
   name: High\r
   id: 6\r
   opt: IPRI\r
+  type: IssuePriority\r
 enumerations_007: \r
   name: Urgent\r
   id: 7\r
   opt: IPRI\r
+  type: IssuePriority\r
 enumerations_008: \r
   name: Immediate\r
   id: 8\r
   opt: IPRI\r
+  type: IssuePriority\r
 enumerations_009: \r
   name: Design\r
   id: 9\r
   opt: ACTI\r
+  type: TimeEntryActivity\r
 enumerations_010: \r
   name: Development\r
   id: 10\r
   opt: ACTI\r
+  type: TimeEntryActivity\r
   is_default: true\r
 enumerations_011: \r
   name: QA\r
   id: 11\r
   opt: ACTI\r
-  
\ No newline at end of file
+  type: TimeEntryActivity\r
+enumerations_012:\r
+  name: Default Enumeration\r
+  id: 12\r
+  opt: ''\r
+  type: Enumeration\r
+  is_default: true\r
+enumerations_013:\r
+  name: Another Enumeration\r
+  id: 13\r
+  opt: ''\r
+  type: Enumeration\r
index 856f802..b66f2eb 100644 (file)
@@ -95,7 +95,7 @@ issues_007:
   created_on: <%= 10.days.ago.to_date.to_s(:db) %>\r
   project_id: 1\r
   updated_on: <%= 10.days.ago.to_date.to_s(:db) %>\r
-  priority_id: 3\r
+  priority_id: 5\r
   subject: Issue due today\r
   id: 7\r
   fixed_version_id: \r
@@ -112,7 +112,7 @@ issues_008:
   created_on: <%= 10.days.ago.to_date.to_s(:db) %>\r
   project_id: 1\r
   updated_on: <%= 10.days.ago.to_date.to_s(:db) %>\r
-  priority_id: 3\r
+  priority_id: 5\r
   subject: Closed issue\r
   id: 8\r
   fixed_version_id: \r
index 16c1a08..95e75b4 100644 (file)
@@ -715,7 +715,7 @@ class IssuesControllerTest < Test::Unit::TestCase
            :id => 1,
            :issue => { :status_id => 2, :assigned_to_id => 3 },
            :notes => 'Assigned to dlopper',
-           :time_entry => { :hours => '', :comments => '', :activity_id => Enumeration.activities.first }
+           :time_entry => { :hours => '', :comments => '', :activity_id => TimeEntryActivity.first }
     end
     assert_redirected_to :action => 'show', :id => '1'
     issue.reload
@@ -753,7 +753,7 @@ class IssuesControllerTest < Test::Unit::TestCase
       post :edit,
            :id => 1,
            :notes => '2.5 hours added',
-           :time_entry => { :hours => '2.5', :comments => '', :activity_id => Enumeration.activities.first }
+           :time_entry => { :hours => '2.5', :comments => '', :activity_id => TimeEntryActivity.first }
     end
     assert_redirected_to :action => 'show', :id => '1'
     
index 710fa5f..9a73252 100644 (file)
@@ -41,7 +41,7 @@ class IssuesTest < ActionController::IntegrationTest
     
     post 'projects/1/issues', :tracker_id => "1",
                                  :issue => { :start_date => "2006-12-26", 
-                                             :priority_id => "3", 
+                                             :priority_id => "4", 
                                              :subject => "new test issue", 
                                              :category_id => "", 
                                              :description => "new issue", 
diff --git a/test/unit/document_category_test.rb b/test/unit/document_category_test.rb
new file mode 100644 (file)
index 0000000..6fa93a3
--- /dev/null
@@ -0,0 +1,36 @@
+# redMine - project management software
+# Copyright (C) 2006-2008  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.
+
+require File.dirname(__FILE__) + '/../test_helper'
+
+class DocumentCategoryTest < Test::Unit::TestCase
+  fixtures :enumerations, :documents
+
+  def test_should_be_an_enumeration
+    assert DocumentCategory.ancestors.include?(Enumeration)
+  end
+  
+  def test_objects_count
+    assert_equal 1, DocumentCategory.find_by_name("Uncategorized").objects_count
+    assert_equal 0, DocumentCategory.find_by_name("User documentation").objects_count
+  end
+
+  def test_option_name
+    assert_equal :enumeration_doc_categories, DocumentCategory.new.option_name
+  end
+end
+
index c192cee..14ea5e2 100644 (file)
@@ -38,40 +38,42 @@ class EnumerationTest < Test::Unit::TestCase
   end
   
   def test_default
-    e = Enumeration.priorities.default
+    e = Enumeration.default
     assert e.is_a?(Enumeration)
     assert e.is_default?
-    assert_equal 'Normal', e.name
+    assert_equal 'Default Enumeration', e.name
   end
   
   def test_create
-    e = Enumeration.new(:opt => 'IPRI', :name => 'Very urgent', :is_default => false)
+    e = Enumeration.new(:name => 'Not default', :is_default => false)
+    e.type = 'Enumeration'
     assert e.save
-    assert_equal 'Normal', Enumeration.priorities.default.name
+    assert_equal 'Default Enumeration', Enumeration.default.name
   end
   
   def test_create_as_default
-    e = Enumeration.new(:opt => 'IPRI', :name => 'Very urgent', :is_default => true)
+    e = Enumeration.new(:name => 'Very urgent', :is_default => true)
+    e.type = 'Enumeration'
     assert e.save
-    assert_equal e, Enumeration.priorities.default
+    assert_equal e, Enumeration.default
   end
   
   def test_update_default
-    e = Enumeration.priorities.default
+    e = Enumeration.default
     e.update_attributes(:name => 'Changed', :is_default => true)
-    assert_equal e, Enumeration.priorities.default
+    assert_equal e, Enumeration.default
   end
   
   def test_update_default_to_non_default
-    e = Enumeration.priorities.default
+    e = Enumeration.default
     e.update_attributes(:name => 'Changed', :is_default => false)
-    assert_nil Enumeration.priorities.default
+    assert_nil Enumeration.default
   end
   
   def test_change_default
-    e = Enumeration.find_by_name('Urgent')
-    e.update_attributes(:name => 'Urgent', :is_default => true)
-    assert_equal e, Enumeration.priorities.default
+    e = Enumeration.find_by_name('Default Enumeration')
+    e.update_attributes(:name => 'Changed Enumeration', :is_default => true)
+    assert_equal e, Enumeration.default
   end
   
   def test_destroy_with_reassign
diff --git a/test/unit/issue_priority_test.rb b/test/unit/issue_priority_test.rb
new file mode 100644 (file)
index 0000000..e2da1e8
--- /dev/null
@@ -0,0 +1,38 @@
+# redMine - project management software
+# Copyright (C) 2006-2008  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.
+
+require File.dirname(__FILE__) + '/../test_helper'
+
+class IssuePriorityTest < Test::Unit::TestCase
+  fixtures :enumerations, :issues
+
+  def test_should_be_an_enumeration
+    assert IssuePriority.ancestors.include?(Enumeration)
+  end
+  
+  def test_objects_count
+    # low priority
+    assert_equal 5, IssuePriority.find(4).objects_count
+    # urgent
+    assert_equal 0, IssuePriority.find(7).objects_count
+  end
+
+  def test_option_name
+    assert_equal :enumeration_issue_priorities, IssuePriority.new.option_name
+  end
+end
+
index d836f2b..b2eee22 100644 (file)
@@ -27,14 +27,14 @@ class IssueTest < Test::Unit::TestCase
            :time_entries
 
   def test_create
-    issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => Enumeration.priorities.first, :subject => 'test_create', :description => 'IssueTest#test_create', :estimated_hours => '1:30')
+    issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'test_create', :description => 'IssueTest#test_create', :estimated_hours => '1:30')
     assert issue.save
     issue.reload
     assert_equal 1.5, issue.estimated_hours
   end
   
   def test_create_minimal
-    issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => Enumeration.priorities.first, :subject => 'test_create')
+    issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'test_create')
     assert issue.save
     assert issue.description.nil?
   end
@@ -123,7 +123,7 @@ class IssueTest < Test::Unit::TestCase
   end
   
   def test_category_based_assignment
-    issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => Enumeration.priorities.first, :subject => 'Assignment test', :description => 'Assignment test', :category_id => 1)
+    issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'Assignment test', :description => 'Assignment test', :category_id => 1)
     assert_equal IssueCategory.find(1).assigned_to, issue.assigned_to
   end
   
@@ -139,7 +139,7 @@ class IssueTest < Test::Unit::TestCase
   
   def test_should_close_duplicates
     # Create 3 issues
-    issue1 = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :priority => Enumeration.priorities.first, :subject => 'Duplicates test', :description => 'Duplicates test')
+    issue1 = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'Duplicates test', :description => 'Duplicates test')
     assert issue1.save
     issue2 = issue1.clone
     assert issue2.save
@@ -166,7 +166,7 @@ class IssueTest < Test::Unit::TestCase
   
   def test_should_not_close_duplicated_issue
     # Create 3 issues
-    issue1 = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :priority => Enumeration.priorities.first, :subject => 'Duplicates test', :description => 'Duplicates test')
+    issue1 = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'Duplicates test', :description => 'Duplicates test')
     assert issue1.save
     issue2 = issue1.clone
     assert issue2.save
@@ -248,7 +248,7 @@ class IssueTest < Test::Unit::TestCase
   
   def test_create_should_send_email_notification
     ActionMailer::Base.deliveries.clear
-    issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => Enumeration.priorities.first, :subject => 'test_create', :estimated_hours => '1:30')
+    issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'test_create', :estimated_hours => '1:30')
 
     assert issue.save
     assert_equal 1, ActionMailer::Base.deliveries.size
diff --git a/test/unit/time_entry_activity_test.rb b/test/unit/time_entry_activity_test.rb
new file mode 100644 (file)
index 0000000..f99c8ab
--- /dev/null
@@ -0,0 +1,36 @@
+# redMine - project management software
+# Copyright (C) 2006-2008  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.
+
+require File.dirname(__FILE__) + '/../test_helper'
+
+class TimeEntryActivityTest < Test::Unit::TestCase
+  fixtures :enumerations, :time_entries
+
+  def test_should_be_an_enumeration
+    assert TimeEntryActivity.ancestors.include?(Enumeration)
+  end
+  
+  def test_objects_count
+    assert_equal 3, TimeEntryActivity.find_by_name("Design").objects_count
+    assert_equal 1, TimeEntryActivity.find_by_name("Development").objects_count
+  end
+
+  def test_option_name
+    assert_equal :enumeration_activities, TimeEntryActivity.new.option_name
+  end
+end
+