Skip to content

Commit

Permalink
i16 Expand AssetDestroyer to also destroy AssetResources (#861)
Browse files Browse the repository at this point in the history
* expand AssetDestroyer to also delete AssetResources

* don't set validation status on deleted Asset

Prevents the following error when deleting an AssetResource via the
Valkyrie Transactions:

`SolrDocument#get_members: undefined method 'members' for AssetResource`

Setting the validation status required looking up the Asset's members
recursively. But since we're in the middle of deleting things:

1. Expected data was missing, and more importantly,
2. We don't care about setting the Asset's validation status because
   it's being deleted

* use transaction to thoroughly delete members

The Transaction ensures the members will be deleted thoroughly. For
example, using the transaction means all the related Sipity records will
be destroyed, which is what we want

* rescue and log error if AssetResource is not found

* use dedicated logger instead of puts

* ensure path to logger exists

This should fix specs that are failing in CI
  • Loading branch information
bkiahstroud authored Apr 23, 2024
1 parent 92d73b2 commit 567d7b7
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 10 deletions.
42 changes: 34 additions & 8 deletions app/services/ams/asset_destroyer.rb
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
module AMS
class AssetDestroyer
attr_accessor :asset_ids, :user_email
attr_accessor :asset_ids, :user_email, :logger

def initialize(asset_ids: [], user_email: nil)
@asset_ids = Array(asset_ids)
@user_email = user_email
@logger = setup_logger
end

def destroy(asset_ids)
puts "Initiating destruction sequence for #{asset_ids.count} Assets..."
logger.info "Initiating destruction sequence for #{asset_ids.count} Assets..."
Array(asset_ids).each do |asset_id|
destroy_asset_by_id asset_id
end
end

def eradicate_asset_tombstones(asset_ids)
puts "Initiating eradication sequence for #{asset_ids.count} Asset Tombstones..."
logger.info "Initiating eradication sequence for #{asset_ids.count} Asset Tombstones..."
Array(asset_ids).each do |asset_id|
begin
Asset.find asset_id
Expand All @@ -24,22 +25,29 @@ def eradicate_asset_tombstones(asset_ids)
# May as well try the Sipity::Entity too
delete_sipity_entity_by_id(asset_id)
else
puts "Lookup of Asset with ID '#{asset_id}' did not return a Tombstone. Skipping..."
logger.warn "Lookup of Asset with ID '#{asset_id}' did not return a Tombstone. Skipping..."
end
end
end

private

def destroy_asset_by_id(asset_id)
# Order is important! When looking up a record, if it is found in Fedora but not Postgres,
# the record in Fedora is copied over to Postgres.
destroy_in_fedora(asset_id)
destroy_in_postgres(asset_id)
end

def destroy_in_fedora(asset_id)
asset = Asset.find asset_id

# Get IDs required to delete associated Tombstones and Sipity::Entities
all_member_ids = [ asset.id ] + asset.all_members.map(&:id)

# Use ActorStack to destroy front-end Asset and Associated Objects
actor.destroy(actor_env(asset))
puts "Asset '#{asset_id}' destroyed."
logger.debug "Asset '#{asset_id}' destroyed."

# Also delete the Tombstone in Fedora and Sipity::Entity
all_member_ids.each do |id|
Expand All @@ -50,6 +58,17 @@ def destroy_asset_by_id(asset_id)
error_rescue(e, "Asset", asset_id)
end

def destroy_in_postgres(asset_id)
asset_resource = AssetResource.find(asset_id)

Hyrax::Transactions::Container['work_resource.destroy']
.with_step_args('work_resource.delete' => { user: user },
'work_resource.delete_all_file_sets' => { user: user })
.call(asset_resource).value!
rescue Valkyrie::Persistence::ObjectNotFoundError
puts "No AssetResource found with ID #{asset_id}"
end

def actor
@actor ||= Hyrax::CurationConcern.actor
end
Expand Down Expand Up @@ -78,7 +97,7 @@ def map_asset_members(asset)

def eradicate_tombstone_by_id(id)
ActiveFedora::Base.eradicate id
puts "Tombstone '#{id}' destroyed."
logger.debug "Tombstone '#{id}' destroyed."
rescue => e
error_rescue(e, "Tombstone", id)
end
Expand All @@ -88,15 +107,22 @@ def delete_sipity_entity_by_id(id)
raise "Returned multiple Sipity::Entities for ID '#{id}" if entity.length > 1
entity.first.destroy

puts "Sipity::Entity '#{id}' destroyed."
logger.debug "Sipity::Entity '#{id}' destroyed."
rescue => e
error_rescue(e, "Sipity::Entity", id)
end

def error_rescue(error, object_type, id)
msg = error.class.to_s
msg += ": #{error.message}" unless error.message.empty?
puts "Error destroying '#{object_type}' for '#{id}'. #{msg}"
logger.error "Error destroying '#{object_type}' for '#{id}'. #{msg}"
end

def setup_logger
logger_path = Rails.root.join('tmp', 'imports', 'asset_destroyer.log')
FileUtils.mkdir_p(logger_path.dirname)

Logger.new(logger_path)
end
end
end
6 changes: 4 additions & 2 deletions app/services/listeners/cascade_delete_listener.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ def on_object_deleted(event)
resource = event.to_h.fetch(:object) { Hyrax.query_service.find_by(id: event[:object_id]) }
return unless resource.is_a?(AssetResource) || resource.is_a?(PhysicalInstantiationResource) || resource.is_a?(DigitalInstantiationResource)
resource.members.each do |member|
Hyrax.index_adapter.delete(resource: member)
Hyrax.persister.delete(resource: member)
Hyrax::Transactions::Container['work_resource.destroy']
.with_step_args('work_resource.delete' => { user: event[:user] },
'work_resource.delete_all_file_sets' => { user: event[:user] })
.call(member).value!
end
end
end
Expand Down
3 changes: 3 additions & 0 deletions app/services/listeners/validate_aapb_listener.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ def on_object_metadata_updated(event)
end

def on_object_deleted(event)
# Not concerned with validation status if the Asset itself is being deleted
return if event[:object].is_a?(AssetResource)

on_object_membership_updated(event)
end

Expand Down

0 comments on commit 567d7b7

Please sign in to comment.