Skip to content

Commit

Permalink
Stop using cocina object when only version and druid needed
Browse files Browse the repository at this point in the history
  • Loading branch information
justinlittman committed Aug 17, 2023
1 parent 929c0f4 commit 21d760e
Show file tree
Hide file tree
Showing 16 changed files with 146 additions and 88 deletions.
12 changes: 6 additions & 6 deletions app/controllers/objects_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -84,20 +84,20 @@ def accession
EventFactory.create(druid: params[:id], event_type: 'accession_request', data: { workflow: })

# if this object is currently already in accessioning, we cannot start it again
if VersionService.in_accessioning?(@cocina_object)
if VersionService.in_accessioning?(druid: @cocina_object.externalIdentifier, version: @cocina_object.version)
EventFactory.create(druid: params[:id], event_type: 'accession_request_aborted', data: { workflow: })
return json_api_error(status: :conflict,
message: 'This object is already in accessioning, it can not be accessioned again until the workflow is complete')
end

updated_cocina_object = @cocina_object
# if this is an existing versionable object, open and close it without starting accessionWF
if VersionService.can_open?(@cocina_object)
updated_cocina_object = VersionService.open(@cocina_object, **version_open_params)
VersionService.close(updated_cocina_object, **version_close_params.merge(start_accession: false))
if VersionService.can_open?(druid: @cocina_object.externalIdentifier, version: @cocina_object.version)
updated_cocina_object = VersionService.open(cocina_object: @cocina_object, **version_open_params)
VersionService.close(druid: updated_cocina_object.externalIdentifier, version: updated_cocina_object.version, **version_close_params.merge(start_accession: false))
# if this is an existing accessioned object that is currently open, just close it without starting accessionWF
elsif VersionService.open?(@cocina_object)
VersionService.close(@cocina_object, **version_close_params.merge(start_accession: false))
elsif VersionService.open?(druid: @cocina_object.externalIdentifier, version: @cocina_object.version)
VersionService.close(druid: @cocina_object.externalIdentifier, version: @cocina_object.version, **version_close_params.merge(start_accession: false))
end

# initialize workflow
Expand Down
17 changes: 11 additions & 6 deletions app/controllers/versions_controller.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# frozen_string_literal: true

class VersionsController < ApplicationController
before_action :load_cocina_object, except: %i[index]
before_action :load_cocina_object, only: %i[create]
before_action :check_cocina_object_exists, only: %i[index]
before_action :load_version, only: %i[current close_current openable]

def index
object_versions = ObjectVersion.where(druid: params[:object_id])
Expand All @@ -22,7 +23,7 @@ def index
end

def create
updated_cocina_object = VersionService.open(@cocina_object, **create_params)
updated_cocina_object = VersionService.open(cocina_object: @cocina_object, **create_params)

add_headers(updated_cocina_object)
render json: Cocina::Models.without_metadata(updated_cocina_object)
Expand All @@ -33,18 +34,18 @@ def create
end

def current
render plain: @cocina_object.version
render plain: @version
end

def close_current
VersionService.close(@cocina_object, **close_params)
render plain: "version #{@cocina_object.version} closed"
VersionService.close(druid: params[:object_id], version: @version, **close_params)
render plain: "version #{@version} closed"
rescue VersionService::VersioningError => e
render build_error('Unable to close version', e)
end

def openable
render plain: VersionService.can_open?(@cocina_object).to_s
render plain: VersionService.can_open?(druid: params[:object_id], version: @version).to_s
rescue Preservation::Client::Error => e
render build_error('Unable to check if openable due to preservation client error', e, status: :internal_server_error)
end
Expand Down Expand Up @@ -94,4 +95,8 @@ def boolean_param(params_hash, key)
params_hash[key] = ActiveModel::Type::Boolean.new.cast(params_hash[key]) if params_hash.key?(key)
params_hash
end

def load_version
@version = CocinaObjectStore.version(params[:object_id])
end
end
4 changes: 2 additions & 2 deletions app/controllers/workspaces_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

# Handles API routes for managing the DOR workspace
class WorkspacesController < ApplicationController
before_action :load_cocina_object, only: [:reset]
before_action :check_cocina_object_exists, only: %i[create]

rescue_from(DruidTools::SameContentExistsError, DruidTools::DifferentContentExistsError) do |e|
Expand All @@ -25,7 +24,8 @@ def destroy
# Once an object has been transferred to preservation, reset the workspace by
# renaming the druid-tree to a versioned directory and removing the export directory
def reset
ResetWorkspaceJob.perform_later(druid: params[:object_id], version: @cocina_object.version)
version = CocinaObjectStore.version(params[:object_id])
ResetWorkspaceJob.perform_later(druid: params[:object_id], version:)
head :no_content
end

Expand Down
17 changes: 17 additions & 0 deletions app/services/cocina_object_store.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@ def self.find_collections_for(cocina_item, swallow_exceptions: false)
end
end

# Retrieves the version of a Cocina object from the datastore.
# @param [String] druid
# @return [Integer] version
# @raise [CocinaObjectNotFoundError] raised when the requested Cocina object is not found.
def self.version(druid)
new.version(druid)
end

# @return [Cocina::Models::DROWithMetadata,Cocina::Models::CollectionWithMetadata,Cocina::Models::AdminPolicyWithMetadata]
def find(druid)
ar_to_cocina_find(druid)
Expand Down Expand Up @@ -113,6 +121,15 @@ def ar_exists?(druid)
Dro.exists?(external_identifier: druid) || Collection.exists?(external_identifier: druid) || AdminPolicy.exists?(external_identifier: druid)
end

def version(druid)
ar_cocina_object = Dro.select(:version).find_by(external_identifier: druid) ||
AdminPolicy.select(:version).find_by(external_identifier: druid) ||
Collection.select(:version).find_by(external_identifier: druid)

ar_cocina_object&.version ||
raise(CocinaObjectNotFoundError.new("Couldn't find object with 'external_identifier'=#{druid}", druid))
end

def destroy(druid)
cocina_object = CocinaObjectStore.find(druid)

Expand Down
6 changes: 3 additions & 3 deletions app/services/constituent_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,18 @@ def add(constituent_druids:)
return errors if errors.any?

# Make sure the virtual object is open before making modifications
updated_virtual_object = if VersionService.open?(virtual_object)
updated_virtual_object = if VersionService.open?(druid: virtual_object.externalIdentifier, version: virtual_object.version)
virtual_object
else
VersionService.open(virtual_object,
VersionService.open(cocina_object: virtual_object,
description: VERSION_DESCRIPTION,
significance: VERSION_SIGNIFICANCE,
event_factory:)
end

updated_virtual_object = ResetContentMetadataService.reset(cocina_item: updated_virtual_object, constituent_druids:)

VersionService.close(updated_virtual_object,
VersionService.close(druid: updated_virtual_object.externalIdentifier, version: updated_virtual_object.version,
event_factory:)

UpdateObjectService.update(updated_virtual_object)
Expand Down
6 changes: 3 additions & 3 deletions app/services/embargo_release_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,17 @@ def release
return
end

unless VersionService.can_open?(cocina_object)
unless VersionService.can_open?(druid: cocina_object.externalIdentifier, version: cocina_object.version)
Rails.logger.warn("Skipping #{druid} - object is already open")
return
end
Rails.logger.info("Releasing embargo for #{druid}")

updated_cocina_object = VersionService.open(cocina_object, description: 'embargo released', significance: 'admin')
updated_cocina_object = VersionService.open(cocina_object:, description: 'embargo released', significance: 'admin')

updated_cocina_object = release_cocina_object(updated_cocina_object)

VersionService.close(updated_cocina_object)
VersionService.close(druid: updated_cocina_object.externalIdentifier, version: updated_cocina_object.version)

EventFactory.create(druid:, event_type: 'embargo_released', data: {})

Expand Down
79 changes: 42 additions & 37 deletions app/services/version_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,58 +10,66 @@ class VersioningError < StandardError; end
# @param [String] opening_user_name add opening username to the events datastream
# @param [Boolean] assume_accessioned If true, does not check whether object has been accessioned.
# @param [Class] event_factory (EventFactory) the factory for creating events
def self.open(cocina_object, description:, significance:, event_factory: EventFactory, opening_user_name: nil, assume_accessioned: false)
new(cocina_object, event_factory:).open(description:,
significance:,
opening_user_name:,
assume_accessioned:)
def self.open(cocina_object:, description:, significance:, event_factory: EventFactory, opening_user_name: nil, assume_accessioned: false)
new(druid: cocina_object.externalIdentifier, version: cocina_object.version).open(description:,
significance:,
opening_user_name:,
assume_accessioned:,
event_factory:,
cocina_object:)
end

# @param [Cocina::Models::DRO,Cocina::Models::Collection] cocina_object the item being acted upon
# @param [String] druid of the item
# @param [Integer] version of the item
# @param [Boolean] assume_accessioned If true, does not check whether object has been accessioned.
def self.can_open?(cocina_object, assume_accessioned: false)
new(cocina_object).can_open?(assume_accessioned:)
def self.can_open?(druid:, version:, assume_accessioned: false)
new(druid:, version:).can_open?(assume_accessioned:)
end

# @param [Cocina::Models::DRO,Cocina::Models::Collection] cocina_object the item being acted upon
def self.open?(cocina_object)
new(cocina_object).open_for_versioning?
# @param [String] druid of the item
# @param [Integer] version of the item
def self.open?(druid:, version:)
new(druid:, version:).open_for_versioning?
end

# @param [Cocina::Models::DRO,Cocina::Models::Collection] cocina_object the item being acted upon
# @param [String] druid of the item
# @param [Integer] version of the item
# @param [String] description describes the version change
# @param [Symbol] significance which part of the version tag to increment
# :major, :minor, :admin (see Dor::VersionTag#increment)
# @param [String] user_name add username to the events datastream
# @param [Boolean] start_accession (true) set to true if you want accessioning to start, false otherwise
# @param [Class] event_factory (EventFactory) the factory for creating events
def self.close(cocina_object, event_factory: EventFactory, description: nil, significance: nil, user_name: nil, start_accession: true)
new(cocina_object, event_factory:).close(description:,
significance:,
user_name:,
start_accession:)
def self.close(druid:, version:, event_factory: EventFactory, description: nil, significance: nil, user_name: nil, start_accession: true) # rubocop:disable Metrics/ParameterLists
new(druid:, version:).close(description:,
significance:,
user_name:,
start_accession:,
event_factory:)
end

def self.in_accessioning?(cocina_object)
new(cocina_object).accessioning?
def self.in_accessioning?(druid:, version:)
new(druid:, version:).accessioning?
end

# @param [Cocina::Models::DRO,Cocina::Models::Collection] cocina_object the item being acted upon
# @param [Class] event_factory (nil) the factory for creating events
def initialize(cocina_object, event_factory: nil)
@cocina_object = cocina_object
@event_factory = event_factory
# @param [String] druid of the item
# @param [Integer] version of the item
def initialize(druid:, version:)
@druid = druid
@version = version
end

# Increments the version number and initializes versioningWF for the object
# @param [Cocina::Models::DRO,Cocina::Models::Collection] cocina_object the item being acted upon
# @param [String] significance set significance (major/minor/patch) of version change
# @param [String] description set description of version change
# @param [String] opening_user_name add opening username to the events datastream
# @param [Boolean] assume_accessioned If true, does not check whether object has been accessioned.
# @param [Class] event_factory (EventFactory) the factory for creating events
# @return [Cocina::Models::DRO, Cocina::Models::AdminPolicy, Cocina::Models::Collection] updated cocina object
# @raise [VersionService::VersioningError] if the object hasn't been accessioned, or if a version is already opened
# @raise [Preservation::Client::Error] if bad response from preservation catalog.
def open(significance:, description:, opening_user_name:, assume_accessioned:)
def open(cocina_object:, significance:, description:, opening_user_name:, assume_accessioned:, event_factory:)
raise ArgumentError, 'description and significance are required to open a new version' if description.blank? || significance.blank?

ensure_openable!(assume_accessioned:)
Expand Down Expand Up @@ -98,22 +106,23 @@ def can_open?(assume_accessioned: false)
# @param [Symbol] :significance which part of the version tag to increment
# :major, :minor, :admin (see Dor::VersionTag#increment)
# @param [String] :user_name add username to the events datastream
# @param [Class] event_factory (EventFactory) the factory for creating events
# @param [Boolean] :start_accession set to true if you want accessioning to start (default), false otherwise
# @raise [VersionService::VersioningError] if the object hasn't been opened for versioning, or if accessionWF has
# already been instantiated or the current version is missing a tag or description
def close(description:, significance:, user_name:, start_accession: true)
def close(description:, significance:, user_name:, event_factory:, start_accession: true)
ObjectVersion.update_current_version(druid:, description:, significance: significance.to_sym) if description || significance

raise VersionService::VersioningError, "Trying to close version #{cocina_object.version} on #{druid} which is not opened for versioning" unless open_for_versioning?
raise VersionService::VersioningError, "Trying to close version #{cocina_object.version} on #{druid} which has active assemblyWF" if active_assembly_wf?
raise VersionService::VersioningError, "Trying to close version #{version} on #{druid} which is not opened for versioning" unless open_for_versioning?
raise VersionService::VersioningError, "Trying to close version #{version} on #{druid} which has active assemblyWF" if active_assembly_wf?
raise VersionService::VersioningError, "accessionWF already created for versioned object #{druid}" if accessioning?

# Default to creating accessionWF when calling close_version
workflow_client.close_version(druid:,
version: cocina_object.version.to_s,
version: version.to_s,
create_accession_wf: start_accession)

event_factory.create(druid:, event_type: 'version_close', data: { who: user_name, version: cocina_object.version.to_s })
event_factory.create(druid:, event_type: 'version_close', data: { who: user_name, version: version.to_s })
end

# Performs checks on whether a new version can be opened for an object
Expand Down Expand Up @@ -149,7 +158,7 @@ def retrieve_version_from_preservation
# Checks if current version has any incomplete wf steps and there is a versionWF
# @return [Boolean] true if object is open for versioning
def open_for_versioning?
return true if workflow_client.active_lifecycle(druid:, milestone_name: 'opened', version: cocina_object.version.to_s)
return true if workflow_client.active_lifecycle(druid:, milestone_name: 'opened', version: version.to_s)

false
end
Expand All @@ -159,12 +168,12 @@ def open_for_versioning?
# accessioning can take place until that is resolved.
# @return [Boolean] true if object is currently being accessioned or is failing an audit
def accessioning?
return true if workflow_client.active_lifecycle(druid:, milestone_name: 'submitted', version: cocina_object.version.to_s)
return true if workflow_client.active_lifecycle(druid:, milestone_name: 'submitted', version: version.to_s)

false
end

attr_reader :cocina_object, :event_factory
attr_reader :druid, :version

private

Expand All @@ -180,8 +189,4 @@ def active_assembly_wf?
def workflow_client
@workflow_client ||= WorkflowClientFactory.build
end

def druid
cocina_object.externalIdentifier
end
end
10 changes: 5 additions & 5 deletions bin/migrate-cocina
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,12 @@ end
def perform_version(cocina_object:, version_description:)
version_open_params = { significance: 'admin', description: version_description }
# if this is an existing versionable object, open and close it, which will start accessionWF
if VersionService.can_open?(cocina_object)
opened_cocina_object = VersionService.open(cocina_object, **version_open_params)
VersionService.close(opened_cocina_object)
if VersionService.can_open?(druid: cocina_object.externalIdentifier, version: cocina_object.version)
opened_cocina_object = VersionService.open(cocina_object:, **version_open_params)
VersionService.close(druid: opened_cocina_object.externalIdentifier, version: opened_cocina_object.version)
# if this is an existing accessioned object that is currently open, just close it
else
VersionService.close(cocina_object, **version_open_params)
VersionService.close(druid: cocina_object.externalIdentifier, version: cocina_object.version, **version_open_params)
end
end

Expand All @@ -80,7 +80,7 @@ def perform_migrate(migrator_class:, obj:, mode:)
migrator.migrate
updated_cocina_object = obj.to_cocina_with_metadata # This validates the cocina object
Cocina::ObjectValidator.validate(updated_cocina_object) # This validation is performed by UpdateObjectService.
raise 'Cannot version' if migrator.version? && !(VersionService.can_open?(updated_cocina_object) || VersionService.open?(updated_cocina_object))
raise 'Cannot version' if migrator.version? && !(VersionService.can_open?(druid: updated_cocina_object.externalIdentifier, version: updated_cocina_object.version) || VersionService.open?(druid: updated_cocina_object.externalIdentifier, version: updated_cocina_object.version))

if mode == :migrate
updated_cocina_object = UpdateObjectService.update(updated_cocina_object)
Expand Down
4 changes: 1 addition & 3 deletions spec/requests/authorization_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@
require 'rails_helper'

RSpec.describe 'Authorization' do
let(:cocina_object) { instance_double(Cocina::Models::DRO, version: 5) }

before do
allow(CocinaObjectStore).to receive(:find).and_return(cocina_object)
allow(CocinaObjectStore).to receive(:version).and_return(5)
allow(Honeybadger).to receive(:notify)
allow(Honeybadger).to receive(:context)
end
Expand Down
3 changes: 1 addition & 2 deletions spec/requests/reset_workspace_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@
require 'rails_helper'

RSpec.describe 'Reset workspace' do
let(:cocina_object) { instance_double(Cocina::Models::DRO, version: 2) }
let(:druid) { 'druid:bb222cc3333' }

before do
allow(CocinaObjectStore).to receive(:find).and_return(cocina_object)
allow(CocinaObjectStore).to receive(:version).and_return(2)
allow(ResetWorkspaceJob).to receive(:perform_later)
end

Expand Down

0 comments on commit 21d760e

Please sign in to comment.