diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 774d004244e83..1ac565a885a6a 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -106,7 +106,17 @@ In case you have modifications in your application's webpack configuration, adap You can read more about this change on PR [\#12238](https://github.com/decidim/decidim/pull/12238). -### 3.4. [[TITLE OF THE ACTION]] +### 3.4. Allow removal of orphan categories + +A bug was identified that prevented the deletion of categories lacking associated resources. This action is a one-time task that must be performed directly in the production database. + +```console +bin/rails decidim:upgrade:fix_orphan_categorizations +``` + +You can read more about this change on PR [\#12143](https://github.com/decidim/decidim/pull/12143). + +### 3.5. [[TITLE OF THE ACTION]] You can read more about this change on PR [\#XXXX](https://github.com/decidim/decidim/pull/XXXX). diff --git a/decidim-admin/app/commands/decidim/admin/destroy_category.rb b/decidim-admin/app/commands/decidim/admin/destroy_category.rb index 39da3d11996a7..778178e3dbb27 100644 --- a/decidim-admin/app/commands/decidim/admin/destroy_category.rb +++ b/decidim-admin/app/commands/decidim/admin/destroy_category.rb @@ -5,10 +5,10 @@ module Admin # A command with all the business logic to destroy a category in the # system. class DestroyCategory < Decidim::Commands::DestroyResource - private + protected def invalid? - resource.nil? || resource.subcategories.any? + resource.nil? || resource.subcategories.any? || !resource.unused? end end end diff --git a/decidim-admin/app/views/decidim/admin/categories/index.html.erb b/decidim-admin/app/views/decidim/admin/categories/index.html.erb index f996cd740bb7c..5a17b62433f2d 100644 --- a/decidim-admin/app/views/decidim/admin/categories/index.html.erb +++ b/decidim-admin/app/views/decidim/admin/categories/index.html.erb @@ -48,7 +48,11 @@ <% end %> <% if allowed_to? :destroy, :category, category: subcategory %> - <%= icon_link_to "delete-bin-line", category_path(current_participatory_space, subcategory), t("actions.destroy", scope: "decidim.admin"), class: "action-icon--remove", method: :delete, data: { confirm: t("actions.confirm_destroy", scope: "decidim.admin") } %> + <% if subcategory.unused? %> + <%= icon_link_to "delete-bin-line", category_path(current_participatory_space, subcategory), t("actions.destroy", scope: "decidim.admin"), class: "action-icon--remove", method: :delete, data: { confirm: t("actions.confirm_destroy", scope: "decidim.admin") } %> + <% else %> + <%= icon "delete-bin-line", class: "action-icon action-icon--disabled", role: "img", "aria-hidden": true %> + <% end %> <% end %> diff --git a/decidim-admin/lib/decidim/admin/test/commands/destroy_category_examples.rb b/decidim-admin/lib/decidim/admin/test/commands/destroy_category_examples.rb index 9a013cae8afdd..e2143a324fb1b 100644 --- a/decidim-admin/lib/decidim/admin/test/commands/destroy_category_examples.rb +++ b/decidim-admin/lib/decidim/admin/test/commands/destroy_category_examples.rb @@ -19,6 +19,15 @@ module Admin end end + context "when the category is being used by a resource" do + let(:component) { create(:dummy_component, participatory_space:) } + let!(:resource) { create(:dummy_resource, component:, category:) } + + it "broadcasts invalid" do + expect { command.call }.to broadcast(:invalid) + end + end + context "when the category is not empty" do let!(:subcategory) { create :subcategory, parent: category } diff --git a/decidim-core/lib/decidim/core/test/shared_examples/has_category.rb b/decidim-core/lib/decidim/core/test/shared_examples/has_category.rb index e86c5618c0ddf..8c257a5056e35 100644 --- a/decidim-core/lib/decidim/core/test/shared_examples/has_category.rb +++ b/decidim-core/lib/decidim/core/test/shared_examples/has_category.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true shared_examples_for "has category" do + let(:participatory_space) { subject.participatory_space } + context "when the category is from another organization" do before do subject.category = create(:category) @@ -8,4 +10,29 @@ it { is_expected.not_to be_valid } end + + context "when the category is from the same organization" do + before do + subject.category = create(:category, participatory_space:) + end + + it { is_expected.to be_valid } + end + + context "when the resource is being deleted" do + before do + subject.category = create(:category, participatory_space:) + subject.save! + end + + it "persists the categorization" do + expect(subject.categorization).to be_persisted + end + + it "deletes the categorization" do + expect(Decidim::Categorization.count).to eq(1) + expect { subject.destroy }.to change(Decidim::Categorization, :count).by(-1) + expect(Decidim::Categorization.count).to eq(0) + end + end end diff --git a/decidim-core/lib/decidim/has_category.rb b/decidim-core/lib/decidim/has_category.rb index f779436d1eaf4..6fb7f190809c9 100644 --- a/decidim-core/lib/decidim/has_category.rb +++ b/decidim-core/lib/decidim/has_category.rb @@ -8,7 +8,7 @@ module HasCategory extend ActiveSupport::Concern included do - has_one :categorization, as: :categorizable + has_one :categorization, as: :categorizable, class_name: "Decidim::Categorization", dependent: :destroy has_one :category, through: :categorization scope :with_category, lambda { |category| diff --git a/decidim-core/lib/tasks/upgrade/decidim_fix_categorization.rake b/decidim-core/lib/tasks/upgrade/decidim_fix_categorization.rake new file mode 100644 index 0000000000000..9885d94056f81 --- /dev/null +++ b/decidim-core/lib/tasks/upgrade/decidim_fix_categorization.rake @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +namespace :decidim do + namespace :upgrade do + desc "Remove orphan categorizations" + task fix_orphan_categorizations: :environment do + logger = Logger.new($stdout) + logger.info("Removing orphan categorizations...") + + Decidim::Categorization.find_each do |categorization| + categorization.destroy if categorization.categorizable.nil? + end + end + end +end