Index: test/unit/issue_subcategory_test.rb
===================================================================
--- test/unit/issue_subcategory_test.rb (revision 0)
+++ test/unit/issue_subcategory_test.rb (revision 0)
@@ -0,0 +1,24 @@
+require 'test_helper'
+
+class IssueCategorySubcategoryTest < ActiveSupport::TestCase
+ fixtures :issue_subcategories, :issues
+
+ def setup
+ @subcategory = IssueSubcategory.find(1)
+ end
+
+ def test_destroy
+ issue = @subcategory.issues.first
+ @subcategory.destroy
+ # Make sure the category was nullified on the issue
+ assert_nil issue.reload.subcategory
+ end
+
+ def test_destroy_with_reassign
+ issue = @subcategory.issues.first
+ reassign_to = IssueSubcategory.find(2)
+ @subcategory.destroy(reassign_to)
+ # Make sure the issue was reassigned
+ assert_equal reassign_to, issue.reload.subcategory
+ end
+end
Index: test/fixtures/issue_subcategories.yml
===================================================================
--- test/fixtures/issue_subcategories.yml (revision 0)
+++ test/fixtures/issue_subcategories.yml (revision 0)
@@ -0,0 +1,9 @@
+# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
+
+one:
+ issue_category_id: 1
+ name: Toner
+
+two:
+ issue_category_id: 1
+ name: Paper
Index: app/helpers/issue_categories_helper.rb
===================================================================
--- app/helpers/issue_categories_helper.rb (revision 2399)
+++ app/helpers/issue_categories_helper.rb (working copy)
@@ -16,4 +16,9 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module IssueCategoriesHelper
+ def add_issue_subcategory_link(name)
+ link_to_function name do |page|
+ page.insert_html :bottom, :issue_subcategories, :partial => "subcategories", :object => IssueSubcategory.new
+ end
+ end
end
Index: app/helpers/issues_helper.rb
===================================================================
--- app/helpers/issues_helper.rb (revision 2399)
+++ app/helpers/issues_helper.rb (working copy)
@@ -80,6 +80,9 @@
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
+ when 'subcategory_id'
+ c = IssueSubcategory.find_by_id(detail.value) and value = c.name if detail.value
+ c = IssueSubcategory.find_by_id(detail.old_value) and old_value = c.name if detail.old_value
when 'fixed_version_id'
v = Version.find_by_id(detail.value) and value = v.name if detail.value
v = Version.find_by_id(detail.old_value) and old_value = v.name if detail.old_value
@@ -150,6 +153,7 @@
l(:field_subject),
l(:field_assigned_to),
l(:field_category),
+ l(:field_sub_category),
l(:field_fixed_version),
l(:field_author),
l(:field_start_date),
@@ -176,6 +180,7 @@
issue.subject,
issue.assigned_to,
issue.category,
+ issue.subcategory,
issue.fixed_version,
issue.author.name,
format_date(issue.start_date),
Index: app/models/issue_category.rb
===================================================================
--- app/models/issue_category.rb (revision 2399)
+++ app/models/issue_category.rb (working copy)
@@ -18,11 +18,15 @@
class IssueCategory < ActiveRecord::Base
belongs_to :project
belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id'
+ has_many :subcategories, :class_name => 'IssueSubcategory', :foreign_key => 'category_id', :dependent => :nullify
has_many :issues, :foreign_key => 'category_id', :dependent => :nullify
validates_presence_of :name
validates_uniqueness_of :name, :scope => [:project_id]
validates_length_of :name, :maximum => 30
+
+ # after something changes, we need to save the changes!
+ after_update :save_sub_categories
alias :destroy_without_reassign :destroy
@@ -34,10 +38,40 @@
end
destroy_without_reassign
end
-
+
+ # we get passed a hash of key => value pairs that describe
+ # the new subcategories. we need to set their ID to this
+ # objects ID
+ def new_issue_subcategories=(subcategory_elements)
+ subcategory_elements.each do |subcat|
+ subcat[:category_id] = :id
+ subcategories.build(subcat)
+ end
+ end
+
+ # update or delete existing address lines
+ def existing_issue_subcategories=(subcategory_elements)
+ subcategories.reject(&:new_record?).each do |line|
+ attributes = subcategory_elements[line.id.to_s]
+ if attributes
+ line.attributes = attributes
+ else
+ subcategories.delete(line)
+ end
+ end
+ end
+
def <=>(category)
name <=> category.name
end
def to_s; name end
+
+ protected
+ #called after a change (add/update/delete) to save to the database
+ def save_sub_categories
+ subcategories.each do |subcategory|
+ subcategory.save(false)
+ end
+ end
end
Index: app/models/issue.rb
===================================================================
--- app/models/issue.rb (revision 2399)
+++ app/models/issue.rb (working copy)
@@ -24,6 +24,7 @@
belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id'
belongs_to :priority, :class_name => 'Enumeration', :foreign_key => 'priority_id'
belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
+ belongs_to :subcategory, :class_name => 'IssueSubcategory', :foreign_key => 'subcategory_id'
has_many :journals, :as => :journalized, :dependent => :destroy
has_many :time_entries, :dependent => :delete_all
Index: app/models/issue_subcategory.rb
===================================================================
--- app/models/issue_subcategory.rb (revision 0)
+++ app/models/issue_subcategory.rb (revision 0)
@@ -0,0 +1,12 @@
+class IssueSubcategory < ActiveRecord::Base
+ belongs_to :issue_category, :foreign_key => 'category_id'
+ validates_presence_of :name
+ validates_uniqueness_of :name, :scope => [:category_id]
+ validates_length_of :name, :maximum => 30
+
+ def <=>(subcategory)
+ name <=> subcategory.name
+ end
+
+ def to_s; name end
+end
Index: app/controllers/issues_controller.rb
===================================================================
--- app/controllers/issues_controller.rb (revision 2399)
+++ app/controllers/issues_controller.rb (working copy)
@@ -21,7 +21,7 @@
before_filter :find_issue, :only => [:show, :edit, :reply]
before_filter :find_issues, :only => [:bulk_edit, :move, :destroy]
before_filter :find_project, :only => [:new, :update_form, :preview]
- before_filter :authorize, :except => [:index, :changes, :gantt, :calendar, :preview, :update_form, :context_menu]
+ before_filter :authorize, :except => [:index, :changes, :gantt, :calendar, :preview, :update_form, :context_menu, :update_issue_subcategories]
before_filter :find_optional_project, :only => [:index, :changes, :gantt, :calendar]
accept_key_auth :index, :changes
@@ -43,6 +43,14 @@
helper :timelog
include Redmine::Export::PDF
+ def update_issue_subcategories
+ results = IssueSubcategory.find(:all, :conditions=>{"category_id"=> params[:category_id]})
+ render :update do |page|
+ page.replace_html 'issue_subcategory', :partial => 'subcategories', :object => results
+ end
+
+ end
+
def index
retrieve_query
sort_init 'id', 'desc'
Index: app/views/issue_categories/_subcategories.rhtml
===================================================================
--- app/views/issue_categories/_subcategories.rhtml (revision 0)
+++ app/views/issue_categories/_subcategories.rhtml (revision 0)
@@ -0,0 +1,10 @@
+
+<% new_or_existing = subcategories.new_record? ? 'new' : 'existing' %>
+<% prefix = "category[#{new_or_existing}_issue_subcategories][]" %>
+<% fields_for prefix, subcategories do |subcategory_form| -%>
+
+<%= subcategory_form.text_field :name %>
+<%= link_to_function l(:button_delete) , "$(this).up('.issue_subcategory').remove()" %>
+
<%= f.text_field :name, :size => 30, :required => true %>
<%= f.select :assigned_to_id, @project.users.collect{|u| [u.name, u.id]}, :include_blank => true %>
++<%= add_issue_subcategory_link l(:label_issue_subcategory_new) %> +
<%= f.select :tracker_id, @project.trackers.collect {|t| [t.name, t.id]}, :required => true %>
<%= observe_field :issue_tracker_id, :url => { :action => :new }, - :update => :content, - :with => "Form.serialize('issue-form')" %> +:update => :content, +:with => "Form.serialize('issue-form')" %><%= f.select :priority_id, (@priorities.collect {|p| [p.name, p.id]}), :required => true %>
<%= f.select :assigned_to_id, (@issue.assignable_users.collect {|m| [m.name, m.id]}), :include_blank => true %>
<% unless @project.issue_categories.empty? %> -<%= f.select :category_id, (@project.issue_categories.collect {|c| [c.name, c.id]}), :include_blank => true %> -<%= prompt_to_remote(l(:label_issue_category_new), - l(:label_issue_category_new), 'category[name]', - {:controller => 'projects', :action => 'add_issue_category', :id => @project}, - :class => 'small', :tabindex => 199) if authorize_for('projects', 'add_issue_category') %>
++ <%= f.select(:category_id, (@project.issue_categories.collect {|c| [c.name, c.id]}), {:include_blank => true}, {:onchange => "#{remote_function(:url => {:action => "update_issue_subcategories"}, + :with => "'category_id='+value")}"} )%> + +<% unless @issue.category.nil? %> +<%= f.collection_select(:subcategory_id, @issue.category.subcategories, :id, :name,:include_blank=>true) %> <% end %> -<%= content_tag('p', f.select(:fixed_version_id, - (@project.versions.sort.collect {|v| [v.name, v.id]}), - { :include_blank => true })) unless @project.versions.empty? %> + + <%= prompt_to_remote(l(:label_issue_category_new), +l(:label_issue_category_new), 'category[name]', +{:controller => 'projects', :action => 'add_issue_category', :id => @project}, +:class => 'small', :tabindex => 199) if authorize_for('projects', 'add_issue_category') %> +
+<% end %> +<%= content_tag('p', f.select(:fixed_version_id, +(@project.versions.sort.collect {|v| [v.name, v.id]}), +{ :include_blank => true })) unless @project.versions.empty? %>