Skip to content
10 changes: 10 additions & 0 deletions app/models/call_for_evidence_response_form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ class CallForEvidenceResponseForm < ApplicationRecord

after_destroy :destroy_call_for_evidence_response_form_data_if_required

def attachable
call_for_evidence_participation&.call_for_evidence
end

# AssetData calls #deleted? on each attachments item but response forms have no soft-delete state
# (unlike Attachment/Image) so there's only one correct answer: false
def deleted?
false
end

private

def destroy_call_for_evidence_response_form_data_if_required
Expand Down
23 changes: 15 additions & 8 deletions app/models/call_for_evidence_response_form_data.rb
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
class CallForEvidenceResponseFormData < ApplicationRecord
include AssetData
mount_uploader :file, ResponseDocumentUploader, mount_on: :carrierwave_file

has_one :call_for_evidence_response_form

has_many :assets,
as: :assetable,
inverse_of: :assetable

validates :file, presence: true

def auth_bypass_ids
[call_for_evidence_response_form.call_for_evidence_participation.call_for_evidence.auth_bypass_id].compact
end

def all_asset_variants_uploaded?
asset_variants = assets.map(&:variant).map(&:to_sym)
required_variants = [Asset.variants[:original].to_sym]
Expand All @@ -29,4 +22,18 @@ def filename
def assets_match_updated_image_filename
assets.all? { |asset| asset.filename.include?(filename) } if filename
end

def attachable
call_for_evidence_response_form&.call_for_evidence_participation&.call_for_evidence || Edition.new
end

# A response document is never shared across editions
# so it can never have been "replaced"
def replaced?
false
end

def attachments
[call_for_evidence_response_form || Attachment::Null.new]
end
end
10 changes: 10 additions & 0 deletions app/models/consultation_response_form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ class ConsultationResponseForm < ApplicationRecord

after_destroy :destroy_consultation_response_form_data_if_required

def attachable
consultation_participation&.consultation
end

# AssetData calls #deleted? on each attachments item but response forms have no soft-delete state
# (unlike Attachment/Image) so there's only one correct answer: false
def deleted?
false
end

private

def destroy_consultation_response_form_data_if_required
Expand Down
22 changes: 15 additions & 7 deletions app/models/consultation_response_form_data.rb
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
class ConsultationResponseFormData < ApplicationRecord
include AssetData
mount_uploader :file, ResponseDocumentUploader, mount_on: :carrierwave_file

has_one :consultation_response_form
has_many :assets,
as: :assetable,
inverse_of: :assetable

validates :file, presence: true

def auth_bypass_ids
[consultation_response_form.consultation_participation.consultation.auth_bypass_id].compact
end

def all_asset_variants_uploaded?
asset_variants = assets.map(&:variant).map(&:to_sym)
required_variants = [Asset.variants[:original].to_sym]
Expand All @@ -28,4 +22,18 @@ def filename
def assets_match_updated_image_filename
assets.all? { |asset| asset.filename.include?(filename) } if filename
end

def attachable
consultation_response_form&.consultation_participation&.consultation || Edition.new
end

# A response document is never shared across editions
# so it can never have been "replaced"
def replaced?
false
end

def attachments
[consultation_response_form || Attachment::Null.new]
end
end
14 changes: 14 additions & 0 deletions app/services/service_listeners/attachment_asset_publisher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,20 @@ def self.call(attachable)
Image.includes(:image_data).unscoped.where(edition: attachable.attachables).find_each do |image|
PublishAttachmentAssetJob.perform_async(image.image_data.id, "ImageData")
end

response_form_data = response_form_data_for(attachable)
if response_form_data
PublishAttachmentAssetJob.perform_async(response_form_data.id, response_form_data.class.to_s)
end
end
end

def self.response_form_data_for(edition)
case edition
when Consultation
edition.consultation_participation&.consultation_response_form&.consultation_response_form_data
when CallForEvidence
edition.call_for_evidence_participation&.call_for_evidence_response_form&.call_for_evidence_response_form_data
end
end
end
Expand Down
3 changes: 3 additions & 0 deletions app/services/service_listeners/attachment_updater.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ def self.call(attachable: nil, attachment_data: nil)
Image.includes(:image_data).unscoped.where(edition: attachable.attachables).find_each do |image|
update_attachment_data! image.image_data
end

response_form_data = AttachmentAssetPublisher.response_form_data_for(attachable)
update_attachment_data! response_form_data if response_form_data
end
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ def self.call(attachable)

DeleteAttachmentAssetJob.perform_async(image_data.id, "ImageData") if image_data&.needs_discarding?
end

response_form_data = AttachmentAssetPublisher.response_form_data_for(attachable)
DeleteAttachmentAssetJob.perform_async(response_form_data.id, response_form_data.class.to_s) if response_form_data&.needs_discarding?
Comment thread
eYinka marked this conversation as resolved.
end
end
end
Expand Down
9 changes: 6 additions & 3 deletions app/sidekiq/asset_manager_attachment_metadata_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@ class AssetManagerAttachmentMetadataJob < JobBase
sidekiq_options queue: "asset_manager"

def perform(assetable_id, assetable_type = "AttachmentData")
asset_data = assetable_type.constantize.find(assetable_id)
klass = assetable_type.constantize
asset_data = klass.find(assetable_id)

return if asset_data.blank?

return unless asset_data.all_asset_variants_uploaded?

AssetManager::AttachmentUpdater.call(asset_data)

assetable_type.constantize.where(replaced_by: asset_data).find_each do |replaced_attachment_data|
# Only replace assets whose model actually supports asset replacement (e.g. AttachmentData, ImageData).
Comment thread
eYinka marked this conversation as resolved.
return unless klass.include?(Replaceable)

klass.where(replaced_by: asset_data).find_each do |replaced_attachment_data|
AssetManager::AttachmentUpdater.replace(replaced_attachment_data)
rescue AssetManager::ServiceHelper::AssetNotFound => e
logger.warn("AssetManagerAttachmentMetadataJob: #{e}")
Expand Down
2 changes: 1 addition & 1 deletion app/uploaders/response_document_uploader.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
class ResponseDocumentUploader < WhitehallUploader
storage Storage::PreviewableStorage
storage Storage::AttachmentStorage
def extension_allowlist
%w[pdf csv rtf doc docx xls xlsx odt ods]
end
Expand Down
17 changes: 0 additions & 17 deletions app/uploaders/storage/previewable_storage.rb

This file was deleted.

9 changes: 3 additions & 6 deletions test/attachment_test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,8 @@ def create_attachment(attachment_data:, edition:)
end
end

# Defining an explicit list, since models like Consultation response form data
# also includes AssetData but can't run generically like Attachment/Image.
Comment thread
eYinka marked this conversation as resolved.
def for_each_asset_data_type(&block)
asset_data_types = ApplicationRecord
.descendants
.select { |klass| klass.include?(AssetData) }
.map(&:name)

asset_data_types.each(&block)
%w[AttachmentData ImageData].each(&block)

@eYinka eYinka Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AssetData is auto-discovered by this shared test helper
(for_each_asset_data_type) and it previously scanned by module inclusion. Since
Consultation/CallForEvidence response form models now include AssetData too, that scan was changed to an explicit %w[AttachmentData ImageData] list to avoid sweeping response form data into shared tests built for a generic edition. Hopefully this is okay?

end
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ class Admin::CallsForEvidenceControllerTest < ActionController::TestCase

setup do
login_as :writer
CallForEvidenceResponseForm.any_instance.stubs(:call_for_evidence_participation).returns(stub(call_for_evidence: stub(auth_bypass_id: "auth bypass id")))
CallForEvidenceResponseForm.any_instance.stubs(:call_for_evidence_participation).returns(stub(call_for_evidence: stub(auth_bypass_id: "auth bypass id", id: 1)))
end

should_be_an_admin_controller
Expand Down
2 changes: 1 addition & 1 deletion test/functional/admin/consultations_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ class Admin::ConsultationsControllerTest < ActionController::TestCase

setup do
login_as :writer
ConsultationResponseForm.any_instance.stubs(:consultation_participation).returns(stub(consultation: stub(auth_bypass_id: "auth bypass id")))
ConsultationResponseForm.any_instance.stubs(:consultation_participation).returns(stub(consultation: stub(auth_bypass_id: "auth bypass id", id: 1)))
end

should_be_an_admin_controller
Expand Down
Loading