Skip to content

Commit cb087bf

Browse files
committed
Cleanup "Other" document type (category) when not linked to any documents
https://community.openproject.org/work_packages/68711
1 parent 9601032 commit cb087bf

File tree

2 files changed

+191
-0
lines changed

2 files changed

+191
-0
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# frozen_string_literal: true
2+
3+
class CleanupOtherDocumentType < ActiveRecord::Migration[8.0]
4+
def change
5+
reversible do |dir|
6+
dir.up do
7+
cleanup_other_document_type_if_orphaned
8+
end
9+
10+
# No-op for down migration
11+
end
12+
end
13+
14+
private
15+
16+
def cleanup_other_document_type_if_orphaned
17+
say_with_time "Cleaning up orphaned 'Other' document type" do
18+
names = ["Other"]
19+
localised_name = localised_other_name
20+
names << localised_name if localised_name.present?
21+
placeholders = names.map { "?" }.join(", ")
22+
23+
sql = <<~SQL.squish
24+
DELETE FROM document_types
25+
WHERE name IN (#{placeholders})
26+
AND NOT EXISTS (
27+
SELECT 1
28+
FROM documents
29+
WHERE documents.type_id = document_types.id
30+
)
31+
SQL
32+
33+
execute OpenProject::SqlSanitization.sanitize(sql, *names)
34+
end
35+
end
36+
37+
def localised_other_name
38+
return if Setting.default_language == "en"
39+
40+
I18n.t!("seeds.common.document_categories.item_2.name", locale: Setting.default_language)
41+
rescue I18n::MissingTranslationData
42+
nil
43+
end
44+
end
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
# frozen_string_literal: true
2+
3+
#-- copyright
4+
# OpenProject is an open source project management software.
5+
# Copyright (C) the OpenProject GmbH
6+
#
7+
# This program is free software; you can redistribute it and/or
8+
# modify it under the terms of the GNU General Public License version 3.
9+
#
10+
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
11+
# Copyright (C) 2006-2013 Jean-Philippe Lang
12+
# Copyright (C) 2010-2013 the ChiliProject Team
13+
#
14+
# This program is free software; you can redistribute it and/or
15+
# modify it under the terms of the GNU General Public License
16+
# as published by the Free Software Foundation; either version 2
17+
# of the License, or (at your option) any later version.
18+
#
19+
# This program is distributed in the hope that it will be useful,
20+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
21+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22+
# GNU General Public License for more details.
23+
#
24+
# You should have received a copy of the GNU General Public License
25+
# along with this program; if not, write to the Free Software
26+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
27+
#
28+
# See COPYRIGHT and LICENSE files for more details.
29+
#++
30+
31+
require "spec_helper"
32+
require_module_spec_helper
33+
require Rails.root.join("modules/documents/db/migrate/20251031071403_cleanup_other_document_type.rb")
34+
35+
RSpec.describe CleanupOtherDocumentType, type: :model do
36+
before do
37+
I18n.backend.store_translations(:de, {
38+
seeds: {
39+
common: {
40+
document_categories: {
41+
item_2: {
42+
name: "Andere"
43+
}
44+
}
45+
}
46+
}
47+
})
48+
end
49+
50+
after do
51+
I18n.backend.reload! # Clean up mock translations
52+
end
53+
54+
describe "up migration" do
55+
context "when 'Other' document type has no associated documents" do
56+
let!(:other_type) { create(:document_type, name: "Other") }
57+
let!(:kept_type) { create(:document_type, name: "Report") }
58+
59+
it "deletes the orphaned 'Other' type" do
60+
expect { ActiveRecord::Migration.suppress_messages { described_class.migrate(:up) } }
61+
.to change(DocumentType, :count).by(-1)
62+
63+
expect(DocumentType.find_by(name: "Other")).to be_nil
64+
expect(DocumentType.find_by(name: "Report")).to be_present
65+
end
66+
end
67+
68+
context "when 'Other' document type has associated documents" do
69+
let!(:other_type) { create(:document_type, name: "Other") }
70+
let!(:document) { create(:document, type: other_type) }
71+
72+
it "does not delete the 'Other' type" do
73+
expect { ActiveRecord::Migration.suppress_messages { described_class.migrate(:up) } }
74+
.not_to change(DocumentType, :count)
75+
76+
expect(DocumentType.find_by(name: "Other")).to be_present
77+
end
78+
end
79+
80+
context "when no 'Other' document type exists" do
81+
let!(:report_type) { create(:document_type, name: "Report") }
82+
83+
it "does not affect other types" do
84+
expect { ActiveRecord::Migration.suppress_messages { described_class.migrate(:up) } }
85+
.not_to change(DocumentType, :count)
86+
87+
expect(DocumentType.find_by(name: "Report")).to be_present
88+
end
89+
end
90+
91+
context "with default language set to German", with_settings: { default_language: "de" } do
92+
context "when localized 'Andere' type has no associated documents" do
93+
let!(:andere_type) { create(:document_type, name: "Andere") }
94+
let!(:kept_type) { create(:document_type, name: "Bericht") }
95+
96+
it "deletes the orphaned 'Andere' type" do
97+
expect { ActiveRecord::Migration.suppress_messages { described_class.migrate(:up) } }
98+
.to change(DocumentType, :count).by(-1)
99+
100+
expect(DocumentType.find_by(name: "Andere")).to be_nil
101+
expect(DocumentType.find_by(name: "Bericht")).to be_present
102+
end
103+
end
104+
105+
context "when localized 'Andere' type has associated documents" do
106+
let!(:andere_type) { create(:document_type, name: "Andere") }
107+
let!(:document) { create(:document, type: andere_type) }
108+
109+
it "does not delete the 'Andere' type" do
110+
expect { ActiveRecord::Migration.suppress_messages { described_class.migrate(:up) } }
111+
.not_to change(DocumentType, :count)
112+
113+
expect(DocumentType.find_by(name: "Andere")).to be_present
114+
end
115+
end
116+
117+
context "when both English and German 'Other' types exist without documents" do
118+
let!(:other_type) { create(:document_type, name: "Other") }
119+
let!(:andere_type) { create(:document_type, name: "Andere") }
120+
121+
it "deletes both orphaned types" do
122+
expect { ActiveRecord::Migration.suppress_messages { described_class.migrate(:up) } }
123+
.to change(DocumentType, :count).by(-2)
124+
125+
expect(DocumentType.find_by(name: "Other")).to be_nil
126+
expect(DocumentType.find_by(name: "Andere")).to be_nil
127+
end
128+
end
129+
end
130+
131+
context "with multiple document types including orphaned 'Other'" do
132+
let!(:other_type) { create(:document_type, name: "Other") }
133+
let!(:note_type) { create(:document_type, name: "Note") }
134+
let!(:report_type) { create(:document_type, name: "Report") }
135+
let!(:document) { create(:document, type: note_type) }
136+
137+
it "only deletes orphaned 'Other' type" do
138+
expect { ActiveRecord::Migration.suppress_messages { described_class.migrate(:up) } }
139+
.to change(DocumentType, :count).by(-1)
140+
141+
expect(DocumentType.find_by(name: "Other")).to be_nil
142+
expect(DocumentType.find_by(name: "Note")).to be_present
143+
expect(DocumentType.find_by(name: "Report")).to be_present
144+
end
145+
end
146+
end
147+
end

0 commit comments

Comments
 (0)