Changed Enumerations to use a Single Table Inheritance
* 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
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
+67
-29
@@ -16,53 +16,77 @@
|
||||
# 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
|
||||
end
|
||||
end
|
||||
|
||||
named_scope :values, lambda {|opt| { :conditions => { :opt => opt }, :order => 'position' } } do
|
||||
# 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 {|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'
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
+2
-2
@@ -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 },
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user