From c8ce64717b0790f472a818c44674b0595515c738 Mon Sep 17 00:00:00 2001 From: Justin Coyne Date: Wed, 23 Mar 2022 14:09:25 -0500 Subject: [PATCH] Create local models so we can avoid cocina edits --- .../contents/file_component.html.erb | 4 +- app/components/contents/file_component.rb | 15 +- app/components/contents/resource_component.rb | 7 +- .../contents/structural_component.html.erb | 4 +- .../contents/structural_component.rb | 8 +- app/components/contents_component.html.erb | 4 +- app/components/contents_component.rb | 4 +- .../document_title_component.html.erb | 2 +- .../show/admin_policy_component.html.erb | 2 +- app/components/show/admin_policy_component.rb | 2 +- .../show/agreement_component.html.erb | 2 +- .../apo/default_object_rights_component.rb | 4 +- app/components/show/apo/overview_component.rb | 4 +- app/components/show/apo/roles_component.rb | 6 +- .../show/collection_component.html.erb | 2 +- app/components/show/collection_component.rb | 2 +- app/components/show/item/details_component.rb | 2 +- .../show/item/overview_component.rb | 2 +- app/components/show/item_component.html.erb | 2 +- app/components/workflow_process_row.rb | 10 +- app/controllers/apo_controller.rb | 4 +- app/controllers/application_controller.rb | 23 +-- app/controllers/catalog_controller.rb | 9 +- app/controllers/catkeys_controller.rb | 14 +- app/controllers/collections_controller.rb | 27 +-- app/controllers/content_types_controller.rb | 22 +-- app/controllers/datastreams_controller.rb | 2 +- app/controllers/embargos_controller.rb | 16 +- app/controllers/files_controller.rb | 31 ++-- app/controllers/items_controller.rb | 83 +++++---- app/controllers/report_controller.rb | 4 +- app/controllers/versions_controller.rb | 16 +- app/controllers/workflows_controller.rb | 2 +- app/forms/application_change_set.rb | 6 +- app/forms/catkey_form.rb | 16 +- app/helpers/items_helper.rb | 6 +- app/jobs/set_catkeys_and_barcodes_job.rb | 16 +- app/jobs/set_collection_job.rb | 2 +- app/jobs/set_governing_apo_job.rb | 2 +- app/jobs/set_rights_job.rb | 4 +- app/jobs/set_source_ids_csv_job.rb | 20 +-- app/models/ability.rb | 44 ++--- app/models/access_template.rb | 24 +++ app/models/admin_policy.rb | 36 ++++ app/models/admin_policy_change_set.rb | 3 + app/models/application_model.rb | 31 ++++ app/models/collection.rb | 39 +++++ app/models/collection_change_set.rb | 32 +--- app/models/embargo.rb | 18 ++ app/models/file_set.rb | 21 +++ app/models/item.rb | 78 +++++++++ app/models/item_change_set.rb | 104 ++++------- app/models/managed_file.rb | 60 +++++++ app/models/repository.rb | 32 ++++ app/presenters/argo_show_presenter.rb | 6 +- app/presenters/workflow_presenter.rb | 8 +- ...t_persister.rb => collection_persister.rb} | 19 +- app/services/item_change_set_persister.rb | 131 -------------- app/services/item_persister.rb | 164 ++++++++++++++++++ .../license_and_rights_statements_setter.rb | 22 +-- app/services/state_service.rb | 6 +- app/views/catalog/_history_default.html.erb | 6 +- app/views/collections/new.html.erb | 6 +- .../content_types/_content_type.html.erb | 2 +- app/views/datastreams/edit.html.erb | 6 +- app/views/datastreams/show.html.erb | 4 +- app/views/files/index.html.erb | 4 +- app/views/items/_collection_ui.html.erb | 6 +- .../items/_set_governing_apo_ui.html.erb | 4 +- app/views/items/_source_id_ui.html.erb | 4 +- app/views/versions/_close_ui.html.erb | 2 +- app/views/versions/_open_ui.html.erb | 2 +- app/views/workflows/_show.html.erb | 2 +- db/seeds.rb | 2 +- .../contents/file_component_spec.rb | 18 +- .../contents/resource_component_spec.rb | 41 +---- spec/components/contents_component_spec.rb | 6 +- .../document_title_component_spec.rb | 2 +- .../default_object_rights_component_spec.rb | 26 +-- .../show/apo/details_component_spec.rb | 2 +- .../show/apo/overview_component_spec.rb | 3 +- .../show/apo/roles_component_spec.rb | 26 +-- .../show/collection/details_component_spec.rb | 2 +- .../collection/overview_component_spec.rb | 28 +-- .../show/item/access_rights_component_spec.rb | 26 +-- .../show/item/details_component_spec.rb | 4 +- .../show/item/overview_component_spec.rb | 27 +-- spec/components/workflow_process_row_spec.rb | 16 +- spec/factories/apos.rb | 31 +++- spec/factories/collections.rb | 32 +++- spec/factories/embargoes.rb | 15 ++ spec/factories/file_set.rb | 24 +++ spec/factories/items.rb | 36 +++- spec/factories/managed_files.rb | 36 ++++ spec/features/apo_spec.rb | 2 +- spec/features/create_collection_spec.rb | 4 +- spec/features/edit_item_tags_spec.rb | 2 +- spec/features/item_catkey_change_spec.rb | 2 +- spec/features/item_manage_release_spec.rb | 2 +- spec/features/item_source_id_change_spec.rb | 2 +- spec/features/item_view_spec.rb | 4 +- .../features/open_and_close_a_version_spec.rb | 2 +- spec/features/set_governing_apo_spec.rb | 6 +- .../jobs/set_catkeys_and_barcodes_job_spec.rb | 26 ++- spec/jobs/set_collection_job_spec.rb | 15 +- spec/jobs/set_source_ids_csv_job_spec.rb | 19 +- spec/models/ability_spec.rb | 53 ++---- spec/models/collection_change_set_spec.rb | 23 +-- spec/models/collection_spec.rb | 52 ++++++ spec/models/item_change_set_spec.rb | 44 +---- spec/models/item_spec.rb | 50 ++++++ spec/presenters/workflow_presenter_spec.rb | 4 +- spec/requests/apo_count_spec.rb | 8 +- spec/requests/collection_count_spec.rb | 6 +- spec/requests/edit_datastream_spec.rb | 18 +- spec/requests/edit_use_statement_spec.rb | 79 +-------- spec/requests/files_spec.rb | 17 +- spec/requests/set_catkey_spec.rb | 122 ++----------- spec/requests/set_governing_apo_spec.rb | 2 +- spec/requests/view_cocina_model_spec.rb | 2 +- ...r_spec.rb => collection_persister_spec.rb} | 18 +- ...rsister_spec.rb => item_persister_spec.rb} | 41 +++-- ...cense_and_rights_statements_setter_spec.rb | 73 ++------ spec/views/collections/new.html.erb_spec.rb | 2 +- .../_content_type.html.erb_spec.rb | 9 +- spec/views/datastreams/show.html.erb_spec.rb | 6 +- spec/views/files/index.html.erb_spec.rb | 4 +- .../items/_collection_ui.html.erb_spec.rb | 2 +- .../_set_governing_apo_ui.html.erb_spec.rb | 11 +- .../items/_source_id_ui.html.erb_spec.rb | 6 +- .../views/versions/_close_ui.html.erb_spec.rb | 2 +- spec/views/versions/_open_ui.html.erb_spec.rb | 2 +- spec/views/workflows/_show.html.erb_spec.rb | 3 +- 133 files changed, 1313 insertions(+), 1174 deletions(-) create mode 100644 app/models/access_template.rb create mode 100644 app/models/admin_policy.rb create mode 100644 app/models/application_model.rb create mode 100644 app/models/collection.rb create mode 100644 app/models/embargo.rb create mode 100644 app/models/file_set.rb create mode 100644 app/models/item.rb create mode 100644 app/models/managed_file.rb create mode 100644 app/models/repository.rb rename app/services/{collection_change_set_persister.rb => collection_persister.rb} (74%) delete mode 100644 app/services/item_change_set_persister.rb create mode 100644 app/services/item_persister.rb create mode 100644 spec/factories/embargoes.rb create mode 100644 spec/factories/file_set.rb create mode 100644 spec/factories/managed_files.rb create mode 100644 spec/models/collection_spec.rb create mode 100644 spec/models/item_spec.rb rename spec/services/{collection_change_set_persister_spec.rb => collection_persister_spec.rb} (89%) rename spec/services/{item_change_set_persister_spec.rb => item_persister_spec.rb} (91%) diff --git a/app/components/contents/file_component.html.erb b/app/components/contents/file_component.html.erb index 6971ec8afe..75424953f9 100644 --- a/app/components/contents/file_component.html.erb +++ b/app/components/contents/file_component.html.erb @@ -6,7 +6,7 @@ <%= filename %> <% end %> - <%= hasMimeType %> + <%= mime_type %> <%= number_to_human_size(size) %> <% if image? %> <%= height %> @@ -15,7 +15,7 @@ <%= role %> <%= tag :span, class: 'bi-check' if publish %> <%= tag :span, class: 'bi-check' if shelve %> - <%= tag :span, class: 'bi-check' if sdrPreserve %> + <%= tag :span, class: 'bi-check' if preserve %> <%= view_access %> <%= download_access %> diff --git a/app/components/contents/file_component.rb b/app/components/contents/file_component.rb index 2f211767a4..4cbd432597 100644 --- a/app/components/contents/file_component.rb +++ b/app/components/contents/file_component.rb @@ -19,15 +19,14 @@ def image? @image end - delegate :access, :administrative, :filename, :hasMimeType, :size, :externalIdentifier, :use, :presentation, to: :file - delegate :publish, :shelve, :sdrPreserve, to: :administrative + delegate :filename, :mime_type, :size, :use, :publish, :shelve, :preserve, to: :file def view_access - access.view.capitalize + file.view_access.capitalize end def download_access - access.download.capitalize + file.download_access.capitalize end def role @@ -41,15 +40,11 @@ def link_attrs end def height - return '' if presentation&.height.blank? - - "#{presentation.height} px" + "#{file.height} px" if file.height end def width - return '' if presentation&.width.blank? - - "#{presentation.width} px" + "#{file.width} px" if file.width end end end diff --git a/app/components/contents/resource_component.rb b/app/components/contents/resource_component.rb index d9b7f66bfb..3b2b8f882b 100644 --- a/app/components/contents/resource_component.rb +++ b/app/components/contents/resource_component.rb @@ -2,6 +2,7 @@ module Contents class ResourceComponent < ViewComponent::Base + # @param [FileSet] resource def initialize(resource:, resource_counter:, object_id:, viewable:) @resource = resource @resource_counter = resource_counter @@ -23,10 +24,6 @@ def type resource.type.delete_prefix('https://cocina.sul.stanford.edu/models/resources/') end - delegate :label, to: :resource - - def files - resource.structural.contains - end + delegate :label, :files, to: :resource end end diff --git a/app/components/contents/structural_component.html.erb b/app/components/contents/structural_component.html.erb index 0aca3a327c..343a01f1c3 100644 --- a/app/components/contents/structural_component.html.erb +++ b/app/components/contents/structural_component.html.erb @@ -1,4 +1,4 @@ diff --git a/app/components/contents/structural_component.rb b/app/components/contents/structural_component.rb index c4d80aff68..dcef80d131 100644 --- a/app/components/contents/structural_component.rb +++ b/app/components/contents/structural_component.rb @@ -2,13 +2,13 @@ module Contents class StructuralComponent < ViewComponent::Base - def initialize(structural:, object_id:, viewable:) - @structural = structural + def initialize(item:, viewable:) + @item = item @viewable = viewable - @object_id = object_id + @object_id = item.id end - attr_reader :structural, :object_id + attr_reader :item, :object_id def viewable? @viewable diff --git a/app/components/contents_component.html.erb b/app/components/contents_component.html.erb index e60c2c4935..a9c678e683 100644 --- a/app/components/contents_component.html.erb +++ b/app/components/contents_component.html.erb @@ -1,7 +1,7 @@

@@ -28,7 +28,7 @@ <% end %>
- <%= render Contents::StructuralComponent.new(structural: @cocina.structural, object_id: @cocina.externalIdentifier, viewable: can?(:view_content, @cocina)) %> + <%= render Contents::StructuralComponent.new(item: @item, viewable: can?(:view_content, @item)) %>
diff --git a/app/components/contents_component.rb b/app/components/contents_component.rb index e4de1414ac..731fae3d79 100644 --- a/app/components/contents_component.rb +++ b/app/components/contents_component.rb @@ -2,13 +2,13 @@ class ContentsComponent < ApplicationComponent def initialize(presenter:) + @item = presenter.item @document = presenter.document - @cocina = presenter.cocina @state_service = presenter.state_service end def render? - @cocina.respond_to?(:structural) + @item.is_a? Item end delegate :allows_modification?, to: :@state_service diff --git a/app/components/document_title_component.html.erb b/app/components/document_title_component.html.erb index 012d2c31b9..1903a3b326 100644 --- a/app/components/document_title_component.html.erb +++ b/app/components/document_title_component.html.erb @@ -1,5 +1,5 @@
- <%= render OpenCloseComponent.new(id: @document.id) if helpers.can?(:manage_item, @presenter.cocina) %> + <%= render OpenCloseComponent.new(id: @document.id) if helpers.can?(:manage_item, @presenter.item) %> <%= content_tag @as, class: @classes do %> <%= title -%> diff --git a/app/components/show/admin_policy_component.html.erb b/app/components/show/admin_policy_component.html.erb index 6cf57b8d39..ac5e2e3a90 100644 --- a/app/components/show/admin_policy_component.html.erb +++ b/app/components/show/admin_policy_component.html.erb @@ -4,7 +4,7 @@
<%= render Show::ExternalLinksComponent.new(document: document) %> <%= render Show::ControlsComponent.new(presenter: presenter, - manager: helpers.can?(:manage_item, cocina)) %> + manager: helpers.can?(:manage_item, item)) %>
diff --git a/app/components/show/admin_policy_component.rb b/app/components/show/admin_policy_component.rb index 2c360c2a4c..c764c723e1 100644 --- a/app/components/show/admin_policy_component.rb +++ b/app/components/show/admin_policy_component.rb @@ -8,6 +8,6 @@ def initialize(presenter:) attr_reader :presenter - delegate :document, :cocina, :view_token, to: :presenter + delegate :document, :item, :view_token, to: :presenter end end diff --git a/app/components/show/agreement_component.html.erb b/app/components/show/agreement_component.html.erb index ffbb73dccf..e1d3c60c57 100644 --- a/app/components/show/agreement_component.html.erb +++ b/app/components/show/agreement_component.html.erb @@ -4,7 +4,7 @@
<%= render Show::ExternalLinksComponent.new(document: document) %> <%= render Show::ControlsComponent.new(presenter: presenter, - manager: helpers.can?(:manage_item, presenter.cocina)) %> + manager: helpers.can?(:manage_item, presenter.item)) %>
diff --git a/app/components/show/apo/default_object_rights_component.rb b/app/components/show/apo/default_object_rights_component.rb index 9eeb5205d3..c0f9a3c941 100644 --- a/app/components/show/apo/default_object_rights_component.rb +++ b/app/components/show/apo/default_object_rights_component.rb @@ -6,7 +6,7 @@ class DefaultObjectRightsComponent < ApplicationComponent # @param [ArgoShowPresenter] presenter def initialize(presenter:) @presenter = presenter - @default_access = @presenter.cocina.administrative.accessTemplate + @default_access = @presenter.item.access_template end def access_rights @@ -22,7 +22,7 @@ def license end def use_and_reproduction - @default_access.useAndReproductionStatement || 'None' + @default_access.use_statement || 'None' end end end diff --git a/app/components/show/apo/overview_component.rb b/app/components/show/apo/overview_component.rb index 59a1c65424..3b21e88c45 100644 --- a/app/components/show/apo/overview_component.rb +++ b/app/components/show/apo/overview_component.rb @@ -7,11 +7,11 @@ class OverviewComponent < ApplicationComponent def initialize(presenter:) @presenter = presenter @solr_document = presenter.document - @registration_workflow = presenter.cocina.administrative.registrationWorkflow + @registration_workflows = presenter.item.registration_workflows end def registration_workflow - @registration_workflow.present? ? @registration_workflow.join(', ') : 'None' + @registration_workflows.present? ? @registration_workflows.join(', ') : 'None' end delegate :id, :status, to: :@solr_document diff --git a/app/components/show/apo/roles_component.rb b/app/components/show/apo/roles_component.rb index 66256756f5..a2f8cd7a1d 100644 --- a/app/components/show/apo/roles_component.rb +++ b/app/components/show/apo/roles_component.rb @@ -15,17 +15,17 @@ def permissions private def model - @presenter.cocina + @presenter.item end def manage_permissions - manage_role = model.administrative.roles&.find { |role| role.name == 'dor-apo-manager' } + manage_role = model.roles&.find { |role| role.name == 'dor-apo-manager' } managers = manage_role ? manage_role.members.map { |member| "#{member.type}:#{member.identifier}" } : [] build_permissions(managers, 'manage') end def view_permissions - view_role = model.administrative.roles&.find { |role| role.name == 'dor-apo-viewer' } + view_role = model.roles&.find { |role| role.name == 'dor-apo-viewer' } viewers = view_role ? view_role.members.map { |member| "#{member.type}:#{member.identifier}" } : [] build_permissions(viewers, 'view') end diff --git a/app/components/show/collection_component.html.erb b/app/components/show/collection_component.html.erb index 1468d75f5f..f2f10a111e 100644 --- a/app/components/show/collection_component.html.erb +++ b/app/components/show/collection_component.html.erb @@ -4,7 +4,7 @@
<%= render Show::ExternalLinksComponent.new(document: document) %> <%= render Show::ControlsComponent.new(presenter: presenter, - manager: helpers.can?(:manage_item, cocina)) %> + manager: helpers.can?(:manage_item, item)) %>
diff --git a/app/components/show/collection_component.rb b/app/components/show/collection_component.rb index c0a00b01c6..3ff882ca79 100644 --- a/app/components/show/collection_component.rb +++ b/app/components/show/collection_component.rb @@ -8,6 +8,6 @@ def initialize(presenter:) attr_reader :presenter - delegate :document, :cocina, :view_token, to: :presenter + delegate :document, :item, :view_token, to: :presenter end end diff --git a/app/components/show/item/details_component.rb b/app/components/show/item/details_component.rb index 300d2f7600..14318e42b6 100644 --- a/app/components/show/item/details_component.rb +++ b/app/components/show/item/details_component.rb @@ -10,7 +10,7 @@ def initialize(presenter:) end def render? - !@presenter.cocina.is_a? NilModel + @presenter.item end delegate :object_type, :created_date, :preservation_size, to: :@solr_document diff --git a/app/components/show/item/overview_component.rb b/app/components/show/item/overview_component.rb index 181797f9c0..07c7622e6c 100644 --- a/app/components/show/item/overview_component.rb +++ b/app/components/show/item/overview_component.rb @@ -10,7 +10,7 @@ def initialize(presenter:) end def render? - !@presenter.cocina.is_a? NilModel + @presenter.item end delegate :id, :status, to: :@solr_document diff --git a/app/components/show/item_component.html.erb b/app/components/show/item_component.html.erb index c5a5e19992..1e5af368e8 100644 --- a/app/components/show/item_component.html.erb +++ b/app/components/show/item_component.html.erb @@ -7,7 +7,7 @@
<%= render Show::ExternalLinksComponent.new(document: document) %> <%= render Show::ControlsComponent.new(presenter: presenter, - manager: helpers.can?(:manage_item, presenter.cocina)) %> + manager: helpers.can?(:manage_item, presenter.item)) %>
diff --git a/app/components/workflow_process_row.rb b/app/components/workflow_process_row.rb index f965e5b235..406544b275 100644 --- a/app/components/workflow_process_row.rb +++ b/app/components/workflow_process_row.rb @@ -4,11 +4,11 @@ class WorkflowProcessRow < ApplicationComponent # @param [Dor::Workflow::Response::Process] process_status the model for the WorkflowProcess # @param [Integer] index the row index - # @param [Cocina::Models::DRO,Cocina::Models::Collection] cocina_object the repository object that the workflow is about - def initialize(process:, index:, cocina_object:) + # @param [Item,Collection] item the repository object that the workflow is about + def initialize(process:, index:, item:) @process = process @index = index - @cocina_object = cocina_object + @item = item end delegate :druid, :workflow_name, :repository, :name, :status, :datetime, @@ -25,8 +25,8 @@ def error? end def show_reset_button? - error? && can?(:manage_item, cocina_object) + error? && can?(:manage_item, item) end - attr_reader :process, :index, :cocina_object + attr_reader :process, :index, :item end diff --git a/app/controllers/apo_controller.rb b/app/controllers/apo_controller.rb index d1e9d4c3e3..80a39633bc 100644 --- a/app/controllers/apo_controller.rb +++ b/app/controllers/apo_controller.rb @@ -17,14 +17,14 @@ def edit end def new - authorize! :create, Cocina::Models::AdminPolicy + authorize! :create, AdminPolicy @form = ApoForm.new(nil, search_service: search_service) render layout: 'one_column' end def create - authorize! :create, Cocina::Models::AdminPolicy + authorize! :create, AdminPolicy change_set = AdminPolicyChangeSet.new unless change_set.validate(params[:apo_form].merge(registered_by: current_user.login)) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 15426d0ba1..8516459c59 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -10,19 +10,10 @@ class ApplicationController < ActionController::Base layout :determine_layout - # Currently we know that not all objects are Cocina compliant, this ensures that we can at least - # receive some object and so, at least administrators can be authorized to operate on it. - # See: https://argo.stanford.edu/catalog?f[data_quality_ssim][]=Cocina+conversion+failed - # @return [Cocina::Models::DRO,NilModel] - def maybe_load_cocina(druid) - object_client = Dor::Services::Client.object(druid) - object_client.find - rescue Dor::Services::Client::UnexpectedResponse - NilModel.new(druid) - end + delegate :maybe_load_cocina, to: :Repository - def allows_modification?(cocina_object) - state_service = StateService.new(cocina_object) + def allows_modification?(item) + state_service = StateService.new(item) state_service.allows_modification? end @@ -47,13 +38,11 @@ def default_html_head protected - def enforce_versioning - return redirect_to solr_document_path(@cocina.externalIdentifier), flash: { error: 'Unable to retrieve the cocina model' } if @cocina.is_a? NilModel - + def enforce_versioning(item = @item) # if this object has been submitted and doesn't have an open version, they cannot change it. - return true if allows_modification?(@cocina) + return true if allows_modification?(item) - redirect_to solr_document_path(@cocina.externalIdentifier), flash: { error: 'Object cannot be modified in its current state.' } + redirect_to solr_document_path(item.id), flash: { error: 'Object cannot be modified in its current state.' } false end end diff --git a/app/controllers/catalog_controller.rb b/app/controllers/catalog_controller.rb index f7cc919314..51b58ed83f 100644 --- a/app/controllers/catalog_controller.rb +++ b/app/controllers/catalog_controller.rb @@ -207,10 +207,13 @@ def show params[:id] = Druid.new(params[:id]).with_namespace _deprecated_response, @document = search_service.fetch(params[:id]) - @cocina = maybe_load_cocina(params[:id]) - flash[:alert] = 'Warning: this object cannot currently be represented in the Cocina model.' if @cocina.instance_of?(NilModel) + begin + @item = Repository.find(params[:id]) + rescue Repository::NotCocina + flash[:alert] = 'Warning: this object cannot currently be represented in the Cocina model.' + end - authorize! :view_metadata, @cocina + authorize! :view_metadata, @item @workflows = WorkflowService.workflows_for(druid: params[:id]) diff --git a/app/controllers/catkeys_controller.rb b/app/controllers/catkeys_controller.rb index dd79a5fe14..e7ce424947 100644 --- a/app/controllers/catkeys_controller.rb +++ b/app/controllers/catkeys_controller.rb @@ -11,7 +11,7 @@ class CatkeysController < ApplicationController end def edit - @change_set = CatkeyForm.new(@cocina) + @change_set = CatkeyForm.new(@item) respond_to do |format| format.html { render layout: !request.xhr? } end @@ -20,20 +20,20 @@ def edit def update return unless enforce_versioning - form = CatkeyForm.new(@cocina) + form = CatkeyForm.new(@item) form.validate(catkey: update_params[:catkey].strip) form.save - Argo::Indexer.reindex_druid_remotely(@cocina.externalIdentifier) + Argo::Indexer.reindex_druid_remotely(@item.id) - msg = "Catkey for #{@cocina.externalIdentifier} has been updated!" - redirect_to solr_document_path(@cocina.externalIdentifier), notice: msg + msg = "Catkey for #{@item.id} has been updated!" + redirect_to solr_document_path(@item.id), notice: msg end private def load_and_authorize_resource - @cocina = maybe_load_cocina(params[:item_id]) - authorize! :manage_item, @cocina + @item = Repository.find(params[:item_id]) + authorize! :manage_item, @item end def update_params diff --git a/app/controllers/collections_controller.rb b/app/controllers/collections_controller.rb index e146f9f815..4bfd0a209b 100644 --- a/app/controllers/collections_controller.rb +++ b/app/controllers/collections_controller.rb @@ -5,8 +5,8 @@ class CollectionsController < ApplicationController include Blacklight::FacetsHelperBehavior # for facet_configuration_for_field def new - @cocina = maybe_load_cocina(params[:apo_id]) - authorize! :manage_item, @cocina + @item = Repository.find(params[:apo_id]) + authorize! :manage_item, @item respond_to do |format| format.html { render layout: !request.xhr? } @@ -14,8 +14,8 @@ def new end def create - cocina = maybe_load_cocina(params[:apo_id]) - authorize! :manage_item, cocina + cocina_admin_policy = Repository.find(params[:apo_id]) + authorize! :manage_item, cocina_admin_policy form = CollectionForm.new return render 'new' unless form.validate(params.merge(apo_druid: params[:apo_id])) @@ -23,34 +23,35 @@ def create form.save collection_druid = form.model.externalIdentifier - cocina_admin_policy = object_client.find - collections = Array(cocina_admin_policy.administrative.collectionsForRegistration).dup + collections = Array(cocina_admin_policy.collections_for_registration).dup # The following two steps mimic the behavior of `Dor::AdministrativeMetadataDS#add_default_collection` (from the now de-coupled dor-services gem) # 1. If collection is already listed, remove it temporarily collections.delete(collection_druid) # 2. Move the collection DRUID to the front of the list of registration collections collections.unshift(collection_druid) - updated_cocina_admin_policy = cocina_admin_policy.new( - administrative: cocina_admin_policy.administrative.new( + + updated_cocina_admin_policy = cocina_admin_policy.model.new( + administrative: cocina_admin_policy.model.administrative.new( collectionsForRegistration: collections ) ) object_client.update(params: updated_cocina_admin_policy) + Argo::Indexer.reindex_druid_remotely(params[:apo_id]) redirect_to solr_document_path(params[:apo_id]), notice: "Created collection #{collection_druid}" end # save the form def update - @cocina = maybe_load_cocina(params[:id]) - authorize! :manage_item, @cocina - return unless enforce_versioning + @collection = Repository.find(params[:id]) + authorize! :manage_item, @collection + return unless enforce_versioning(@collection) - change_set = CollectionChangeSet.new(@cocina) + change_set = CollectionChangeSet.new(@collection) attributes = params.require(:collection).permit(:copyright, :use_statement, :license) change_set.validate(**attributes) change_set.save - Argo::Indexer.reindex_druid_remotely(@cocina.externalIdentifier) + Argo::Indexer.reindex_druid_remotely(@collection.id) redirect_to solr_document_path(params[:id]), status: :see_other end diff --git a/app/controllers/content_types_controller.rb b/app/controllers/content_types_controller.rb index dcda95174d..54ad144e7a 100644 --- a/app/controllers/content_types_controller.rb +++ b/app/controllers/content_types_controller.rb @@ -11,15 +11,15 @@ def show # set the content type in the content metadata def update - authorize! :manage_item, @cocina + authorize! :manage_item, @item # if this object has been submitted and doesnt have an open version, they cannot change it. return unless enforce_versioning return render_error('Invalid new content type.') unless valid_content_type? - object_client.update(params: @cocina.new(cocina_update_attributes)) - Argo::Indexer.reindex_druid_remotely(@cocina.externalIdentifier) + object_client.update(params: @item.model.new(cocina_update_attributes)) + Argo::Indexer.reindex_druid_remotely(@item.id) redirect_to solr_document_path(params[:item_id]), notice: 'Content type updated!' end @@ -36,15 +36,15 @@ def cocina_update_attributes attributes[:structural] = if resource_types_should_change? structural_with_resource_type_changes else - @cocina.structural.new(hasMemberOrders: member_orders) + @item.model.structural.new(hasMemberOrders: member_orders) end end end def old_resource_type - return '' if [Cocina::Models::ObjectType.collection, Cocina::Models::ObjectType.admin_policy].include? @cocina.type + return '' unless @item.is_a? Item - Constants::RESOURCE_TYPES.key(@cocina.structural.contains&.first&.type) || '' + Constants::RESOURCE_TYPES.key(@item.file_sets&.first&.type) || '' end def new_content_type @@ -64,9 +64,9 @@ def member_orders end def structural_with_resource_type_changes - @cocina.structural.new( + @item.model.structural.new( hasMemberOrders: member_orders, - contains: Array(@cocina.structural&.contains).map do |resource| + contains: Array(@item.model.structural&.contains).map do |resource| next resource unless resource.type == Constants::RESOURCE_TYPES[params[:old_resource_type]] resource.new(type: Constants::RESOURCE_TYPES[params[:new_resource_type]]) @@ -76,8 +76,8 @@ def structural_with_resource_type_changes def resource_types_should_change? params[:new_resource_type].present? && - @cocina.structural.contains.map(&:type) - .any? { |resource_type| resource_type == Constants::RESOURCE_TYPES[params[:old_resource_type]] } + @item.model.structural.contains.map(&:type) + .any? { |resource_type| resource_type == Constants::RESOURCE_TYPES[params[:old_resource_type]] } end def valid_content_type? @@ -91,7 +91,7 @@ def object_client def load_resource raise 'missing druid' if params[:item_id].blank? - @cocina = object_client.find + @item = Repository.find(params[:item_id]) @old_resource_type = old_resource_type end end diff --git a/app/controllers/datastreams_controller.rb b/app/controllers/datastreams_controller.rb index 704756942c..9603b05d68 100644 --- a/app/controllers/datastreams_controller.rb +++ b/app/controllers/datastreams_controller.rb @@ -77,7 +77,7 @@ def store_xml(druid:, datastream:, content:) def show_aspect druid = Druid.new(params[:item_id]).with_namespace @response, @document = search_service.fetch druid # this does the authorization - @cocina = maybe_load_cocina(druid) + @item = Repository.find(druid) @object_client = Dor::Services::Client.object(druid) end end diff --git a/app/controllers/embargos_controller.rb b/app/controllers/embargos_controller.rb index 234610188f..7f0d9b9f92 100644 --- a/app/controllers/embargos_controller.rb +++ b/app/controllers/embargos_controller.rb @@ -4,7 +4,7 @@ class EmbargosController < ApplicationController before_action :load_and_authorize_resource def new - @change_set = change_set_class.new(@cocina) + @change_set = change_set_class.new(@item) respond_to do |format| format.html { render layout: !request.xhr? } @@ -12,7 +12,7 @@ def new end def edit - @change_set = change_set_class.new(@cocina) + @change_set = change_set_class.new(@item) respond_to do |format| format.html { render layout: !request.xhr? } @@ -23,17 +23,17 @@ def update begin update_params[:embargo_release_date].to_date rescue Date::Error - return redirect_to solr_document_path(@cocina.externalIdentifier), + return redirect_to solr_document_path(@item.id), flash: { error: 'Invalid date' } end - change_set = change_set_class.new(@cocina) + change_set = change_set_class.new(@item) change_set.validate(update_params) change_set.save - Argo::Indexer.reindex_druid_remotely(@cocina.externalIdentifier) + Argo::Indexer.reindex_druid_remotely(@item.id) respond_to do |format| - format.any { redirect_to solr_document_path(@cocina.externalIdentifier), notice: 'Embargo was successfully updated' } + format.any { redirect_to solr_document_path(@item.id), notice: 'Embargo was successfully updated' } end end @@ -44,9 +44,9 @@ def change_set_class end def load_and_authorize_resource - @cocina = maybe_load_cocina(params[:item_id]) + @item = Repository.find(params[:item_id]) - authorize! :manage_item, @cocina + authorize! :manage_item, @item end def update_params diff --git a/app/controllers/files_controller.rb b/app/controllers/files_controller.rb index 51619440b6..7b39c7dcb3 100644 --- a/app/controllers/files_controller.rb +++ b/app/controllers/files_controller.rb @@ -12,7 +12,7 @@ def index raise ArgumentError, 'Missing file parameter' if filename.blank? @has_been_accessioned = WorkflowClientFactory.build.lifecycle(druid: params[:item_id], milestone_name: 'accessioned') - files = Array(@cocina_model.structural&.contains).map { |fs| fs.structural.contains }.flatten + files = @item.file_sets.map(&:files).flatten @file = files.find { |file| file.filename == params[:id] } if @has_been_accessioned @@ -34,7 +34,7 @@ def index end def preserved - authorize! :view_content, @cocina_model + authorize! :view_content, @item # Set headers on the response before writing to the response stream send_file_headers!( @@ -45,7 +45,7 @@ def preserved response.headers['Last-Modified'] = Time.now.utc.rfc2822 # HTTP requires GMT date/time Preservation::Client.objects.content( - druid: @cocina_model.externalIdentifier, + druid: @item.id, filepath: filename, version: params[:version], on_data: proc { |data, _count| response.stream.write data } @@ -57,7 +57,7 @@ def preserved render status: :not_found, plain: "Preserved file not found: #{e}" rescue Preservation::Client::Error => e - message = "Preservation client error getting content of #{filename} for #{@cocina_model.externalIdentifier} (version #{params[:version]}): #{e}" + message = "Preservation client error getting content of #{filename} for #{@item.id} (version #{params[:version]}): #{e}" logger.error(message) Honeybadger.notify(message) render status: :internal_server_error, plain: message @@ -66,19 +66,19 @@ def preserved end def download - authorize! :view_content, @cocina_model + authorize! :view_content, @item - response.headers['Content-Disposition'] = "attachment; filename=#{Druid.new(@cocina_model).without_namespace}.zip" + response.headers['Content-Disposition'] = "attachment; filename=#{Druid.new(@item.id).without_namespace}.zip" zip_tricks_stream do |zip| - preserved_files(@cocina_model).each do |filename| + preserved_files(@item).each do |filename| zip.write_deflated_file(filename) do |sink| - Preservation::Client.objects.content(druid: @cocina_model.externalIdentifier, + Preservation::Client.objects.content(druid: @item.id, filepath: filename, - version: @cocina_model.version, + version: @item.version, on_data: proc { |data, _count| sink.write data }) rescue StandardError => e sink.close - message = "Could not zip #{filename} (#{@cocina_model.externalIdentifier}) for download: #{e}" + message = "Could not zip #{filename} (#{@item.id}) for download: #{e}" logger.error(message) Honeybadger.notify(message) render status: :internal_server_error, plain: message @@ -89,12 +89,9 @@ def download private - def preserved_files(cocina_model) - resources = Array(cocina_model.structural&.contains) - resources.flat_map do |resource| - resource.structural.contains.select do |file| - file.administrative.sdrPreserve - end.map(&:filename) + def preserved_files(item) + item.file_sets.flat_map do |file_set| + file_set.files.select(&:preserve).map(&:filename) end end @@ -103,6 +100,6 @@ def filename end def load_resource - @cocina_model = Dor::Services::Client.object(params[:item_id]).find + @item = Repository.find(params[:item_id]) end end diff --git a/app/controllers/items_controller.rb b/app/controllers/items_controller.rb index 59220ec7a5..5ebae22cb6 100644 --- a/app/controllers/items_controller.rb +++ b/app/controllers/items_controller.rb @@ -2,7 +2,7 @@ # rubocop:disable Metrics/ClassLength class ItemsController < ApplicationController - before_action :load_cocina + before_action :load_resource before_action :authorize_manage!, only: %i[ add_collection remove_collection purge_object @@ -35,8 +35,8 @@ class ItemsController < ApplicationController end end - rescue_from Cocina::Models::ValidationError do |exception| - message = "Error building Cocina: #{exception.message.truncate(200)}" + rescue_from Cocina::Models::ValidationError, Repository::NotCocina do |exception| + message = exception.is_a?(Repository::NotCocina) ? exception.message : "Error building Cocina: #{exception.message.truncate(200)}" Honeybadger.notify(exception) logger.error(message) if turbo_frame_request? @@ -49,8 +49,8 @@ class ItemsController < ApplicationController def add_collection response_message = if params[:collection].present? - new_collections = Array(@cocina.structural&.isMemberOf) + [params[:collection]] - change_set = ItemChangeSet.new(@cocina) + new_collections = @item.collection_ids + [params[:collection]] + change_set = ItemChangeSet.new(@item) change_set.validate(collection_ids: new_collections) change_set.save reindex @@ -59,33 +59,33 @@ def add_collection 'No collection selected' end - object_client = Dor::Services::Client.object(@cocina.externalIdentifier) + object_client = Dor::Services::Client.object(@item.id) @collection_list = object_client.collections render partial: 'collection_ui', locals: { response_message: response_message } end def remove_collection - new_collections = Array(@cocina.structural&.isMemberOf) - [params[:collection]] - change_set = ItemChangeSet.new(@cocina) + new_collections = @item.collection_ids - [params[:collection]] + change_set = ItemChangeSet.new(@item) change_set.validate(collection_ids: new_collections) change_set.save reindex - object_client = Dor::Services::Client.object(@cocina.externalIdentifier) + object_client = Dor::Services::Client.object(@item.id) @collection_list = object_client.collections render partial: 'collection_ui', locals: { response_message: 'Collection successfully removed' } end def show - authorize! :view_metadata, @cocina + authorize! :view_metadata, @item respond_to do |format| - format.json { render json: CocinaHashPresenter.new(cocina_object: @cocina).render } + format.json { render json: CocinaHashPresenter.new(cocina_object: @item.model).render } end end def source_id - change_set = ItemChangeSet.new(@cocina) + change_set = ItemChangeSet.new(@item) change_set.validate(source_id: params[:new_id]) change_set.save reindex @@ -104,17 +104,17 @@ def purge_object end def refresh_metadata - authorize! :manage_desc_metadata, @cocina + authorize! :manage_desc_metadata, @item - catkey = @cocina.identification&.catalogLinks&.find { |link| link.catalog == 'symphony' }&.catalogRecordId + catkey = @item.catkey if catkey.blank? render status: :bad_request, plain: 'object must have catkey to refresh descMetadata' return end - Dor::Services::Client.object(@cocina.externalIdentifier).refresh_metadata + Dor::Services::Client.object(@item.id).refresh_metadata - redirect_to solr_document_path(params[:id]), notice: "Metadata for #{@cocina.externalIdentifier} successfully refreshed from catkey: #{catkey}" + redirect_to solr_document_path(params[:id]), notice: "Metadata for #{@item.id} successfully refreshed from catkey: #{catkey}" rescue Dor::Services::Client::UnexpectedResponse => e user_begin = 'An error occurred while attempting to refresh metadata' user_end = 'Please try again or contact the #dlss-infrastructure Slack channel for assistance.' @@ -124,7 +124,7 @@ def refresh_metadata # set the object's access to its admin policy's accessTemplate def apply_apo_defaults - Dor::Services::Client.object(@cocina.externalIdentifier).apply_admin_policy_defaults + Dor::Services::Client.object(@item.id).apply_admin_policy_defaults reindex redirect_to solr_document_path(params[:id]), notice: 'APO defaults applied!' rescue Dor::Services::Client::UnexpectedResponse => e @@ -135,7 +135,7 @@ def apply_apo_defaults def set_governing_apo return redirect_to solr_document_path(params[:id]), flash: { error: "Can't set governing APO on an invalid model" } if @cocina.is_a? NilModel - authorize! :manage_governing_apo, @cocina, params[:new_apo_id] + authorize! :manage_governing_apo, @item, params[:new_apo_id] change_set = build_change_set change_set.validate(admin_policy_id: params[:new_apo_id]) @@ -146,7 +146,7 @@ def set_governing_apo end def collection_ui - object_client = Dor::Services::Client.object(@cocina.externalIdentifier) + object_client = Dor::Services::Client.object(@item.id) @collection_list = object_client.collections respond_to do |format| format.html { render layout: !request.xhr? } @@ -155,12 +155,12 @@ def collection_ui # Draw form for barcode def edit_barcode - @change_set = ItemChangeSet.new(@cocina) + @change_set = ItemChangeSet.new(@item) end def show_barcode - change_set = ItemChangeSet.new(@cocina) - state_service = StateService.new(@cocina) + change_set = ItemChangeSet.new(@item) + state_service = StateService.new(@item) render Show::BarcodeComponent.new(change_set: change_set, state_service: state_service) end @@ -171,7 +171,7 @@ def edit_copyright def show_copyright change_set = build_change_set - state_service = StateService.new(@cocina) + state_service = StateService.new(@item) render Show::CopyrightComponent.new(change_set: change_set, state_service: state_service) end @@ -182,7 +182,7 @@ def edit_use_statement def show_use_statement change_set = build_change_set - state_service = StateService.new(@cocina) + state_service = StateService.new(@item) render Show::UseStatementComponent.new(change_set: change_set, state_service: state_service) end @@ -193,13 +193,13 @@ def edit_license def show_license change_set = build_change_set - state_service = StateService.new(@cocina) + state_service = StateService.new(@item) render Show::LicenseComponent.new(change_set: change_set, state_service: state_service) end # save the form def update - change_set = ItemChangeSet.new(@cocina) + change_set = ItemChangeSet.new(@item) if change_set.validate(**item_params) change_set.save # may raise Dor::Services::Client::BadRequestError reindex @@ -213,7 +213,7 @@ def update def edit_rights @change_set = build_change_set - if @cocina.collection? + if @item.is_a?(Collection) render partial: 'edit_collection_rights' else render partial: 'edit_dro_rights' @@ -222,12 +222,12 @@ def edit_rights def show_rights @change_set = build_change_set - state_service = StateService.new(@cocina) - if @cocina.collection? - change_set = CollectionChangeSet.new(@cocina) + state_service = StateService.new(@item) + if @item.is_a? Collection + change_set = CollectionChangeSet.new(@item) render Show::Collection::AccessRightsComponent.new(change_set: change_set, state_service: state_service) else - change_set = ItemChangeSet.new(@cocina) + change_set = ItemChangeSet.new(@item) render Show::Item::AccessRightsComponent.new(change_set: change_set, state_service: state_service) end end @@ -252,25 +252,32 @@ def item_params :view_access, :download_access, :access_location, :controlled_digital_lending) end - def load_cocina - raise 'missing druid' unless params[:id] - - @cocina = maybe_load_cocina(params[:id]) + def load_resource + @item = Repository.find(params[:id]) end def reindex - Argo::Indexer.reindex_druid_remotely(@cocina.externalIdentifier) + Argo::Indexer.reindex_druid_remotely(@item.id) end # --- # Permissions def authorize_manage! - authorize! :manage_item, @cocina + authorize! :manage_item, @item end def build_change_set - @cocina.collection? ? CollectionChangeSet.new(@cocina) : ItemChangeSet.new(@cocina) + change_set_class.new(@item) + end + + def change_set_class + case @item + when Item + ItemChangeSet + when Collection + CollectionChangeSet + end end end # rubocop:enable Metrics/ClassLength diff --git a/app/controllers/report_controller.rb b/app/controllers/report_controller.rb index d683f51b41..a482e7c85e 100644 --- a/app/controllers/report_controller.rb +++ b/app/controllers/report_controller.rb @@ -49,8 +49,8 @@ def reset ids = Report.new(params, current_user: current_user).druids ids.each do |druid| druid = Druid.new(druid).with_namespace - cocina = Dor::Services::Client.object(druid).find - next unless current_ability.can_update_workflow?('waiting', cocina) + item = Repository.find(druid) + next unless current_ability.can_update_workflow?('waiting', item) WorkflowClientFactory.build.update_status( druid: druid, diff --git a/app/controllers/versions_controller.rb b/app/controllers/versions_controller.rb index 6c369688ae..adccbbbf50 100644 --- a/app/controllers/versions_controller.rb +++ b/app/controllers/versions_controller.rb @@ -30,13 +30,13 @@ def close_ui end def open - VersionService.open(identifier: @cocina_object.externalIdentifier, + VersionService.open(identifier: @item.id, significance: params[:significance], description: params[:description], opening_user_name: current_user.to_s) - msg = "#{@cocina_object.externalIdentifier} is open for modification!" + msg = "#{@item.id} is open for modification!" redirect_to solr_document_path(params[:item_id]), notice: msg - Argo::Indexer.reindex_druid_remotely(@cocina_object.externalIdentifier) + Argo::Indexer.reindex_druid_remotely(@item.id) rescue StandardError => e raise e unless e.to_s == 'Object net yet accessioned' @@ -48,14 +48,14 @@ def open # values, update those fields on the version metadata datastream def close VersionService.close( - identifier: @cocina_object.externalIdentifier, + identifier: @item.id, description: params[:description], significance: params[:significance], user_name: current_user.to_s ) - msg = "Version #{@cocina_object.version} of #{@cocina_object.externalIdentifier} has been closed!" + msg = "Version #{@item.version} of #{@item.id} has been closed!" redirect_to solr_document_path(params[:item_id]), notice: msg - Argo::Indexer.reindex_druid_remotely(@cocina_object.externalIdentifier) + Argo::Indexer.reindex_druid_remotely(@item.id) end private @@ -78,7 +78,7 @@ def which_significance_changed(current_tag, previous_tag) end def load_and_authorize_resource - @cocina_object = maybe_load_cocina(params[:item_id]) - authorize! :manage_item, @cocina_object + @item = Repository.find(params[:item_id]) + authorize! :manage_item, @item end end diff --git a/app/controllers/workflows_controller.rb b/app/controllers/workflows_controller.rb index 2edd43a575..c552e08003 100644 --- a/app/controllers/workflows_controller.rb +++ b/app/controllers/workflows_controller.rb @@ -104,7 +104,7 @@ def build_show_presenter(workflow) workflow_steps: workflow_processes(params[:id])) WorkflowPresenter.new(view: view_context, workflow_status: status, - cocina_object: maybe_load_cocina(params[:item_id])) + item: Repository.find(params[:item_id])) end def workflow_processes(workflow_name) diff --git a/app/forms/application_change_set.rb b/app/forms/application_change_set.rb index 9a9cf310c1..fd0782890e 100644 --- a/app/forms/application_change_set.rb +++ b/app/forms/application_change_set.rb @@ -3,9 +3,9 @@ # A superclass for the change sets that handles forms backed by Cocina objects class ApplicationChangeSet < Reform::Form # needed for generating the update route - def to_param - model.externalIdentifier - end + # def to_param + # model.externalIdentifier + # end def persisted? model.present? diff --git a/app/forms/catkey_form.rb b/app/forms/catkey_form.rb index 8e353b3b65..a4459459db 100644 --- a/app/forms/catkey_form.rb +++ b/app/forms/catkey_form.rb @@ -5,22 +5,12 @@ class CatkeyForm < ApplicationChangeSet # When the object is initialized, copy the properties from the cocina model to the form: def setup_properties!(_options) - self.catkey = Catkey.symphony_links(model).join(', ') + self.catkey = model.catkeys.join(', ') end # @raises [Dor::Services::Client::BadRequestError] when the server doesn't accept the request # @raises [Cocina::Models::ValidationError] when given invalid Cocina values or structures - def save_model - return unless changed?(:catkey) - - updated = model - identification_props = updated.identification.new(catalogLinks: Catkey.serialize(model, catkey.split(/\s*,\s*/))) - updated = updated.new(identification: identification_props) - - object_client.update(params: updated) - end - - def object_client - Dor::Services::Client.object(model.externalIdentifier) + def sync + model.catkeys = catkey.split(/\s*,\s*/) if changed?(:catkey) end end diff --git a/app/helpers/items_helper.rb b/app/helpers/items_helper.rb index 0c560a3105..84121a77ec 100644 --- a/app/helpers/items_helper.rb +++ b/app/helpers/items_helper.rb @@ -10,9 +10,9 @@ def document_presenter(document) super.tap do |presenter| # rubocop:disable Rails/HelperInstanceVariable presenter.view_token = @verified_token_with_expiration if presenter.respond_to? :view_token - if presenter.respond_to? :cocina - presenter.cocina = @cocina - presenter.state_service = StateService.new(@cocina) + if presenter.respond_to? :item + presenter.item = @item + presenter.state_service = StateService.new(@item) end # rubocop:enable Rails/HelperInstanceVariable end diff --git a/app/jobs/set_catkeys_and_barcodes_job.rb b/app/jobs/set_catkeys_and_barcodes_job.rb index 6afadc8b07..9b59401d47 100644 --- a/app/jobs/set_catkeys_and_barcodes_job.rb +++ b/app/jobs/set_catkeys_and_barcodes_job.rb @@ -21,11 +21,11 @@ def perform(bulk_action_id, params) with_bulk_action_log do |log| update_druid_count(count: update_druids.count) update_druids.each_with_index do |current_druid, i| - cocina_object = Dor::Services::Client.object(current_druid).find + item = Repository.find(current_druid) args = {} args[:catkeys] = catkeys[i] if catkeys args[:barcode] = barcodes[i] if barcodes - change_set = ItemChangeSet.new(cocina_object) + change_set = ItemChangeSet.new(item) change_set.validate(args) update_catkey_and_barcode(change_set, log) if change_set.changed? end @@ -43,11 +43,11 @@ def params_from(params) private def update_catkey_and_barcode(change_set, log) - cocina_object = change_set.model - log.puts("#{Time.current} Beginning SetCatkeysAndBarcodesJob for #{cocina_object.externalIdentifier}") + item = change_set.model + log.puts("#{Time.current} Beginning SetCatkeysAndBarcodesJob for #{item.id}") - unless ability.can?(:manage_item, cocina_object) - log.puts("#{Time.current} Not authorized for #{cocina_object.externalIdentifier}") + unless ability.can?(:manage_item, item) + log.puts("#{Time.current} Not authorized for #{item.id}") bulk_action.increment(:druid_count_fail).save return end @@ -55,8 +55,8 @@ def update_catkey_and_barcode(change_set, log) log_update(change_set, log) begin - state_service = StateService.new(cocina_object) - open_new_version(cocina_object.externalIdentifier, cocina_object.version, version_message(change_set)) unless state_service.allows_modification? + state_service = StateService.new(item) + open_new_version(item.id, item.version, version_message(change_set)) unless state_service.allows_modification? change_set.save bulk_action.increment(:druid_count_success).save diff --git a/app/jobs/set_collection_job.rb b/app/jobs/set_collection_job.rb index 25fd7e04d1..87528cf1fb 100644 --- a/app/jobs/set_collection_job.rb +++ b/app/jobs/set_collection_job.rb @@ -23,7 +23,7 @@ def perform(bulk_action_id, params) cocina_object = cocina_object.new(version: new_version.to_i) end - change_set = ItemChangeSet.new(cocina_object) + change_set = ItemChangeSet.new(Item.new(cocina_object)) change_set.validate(collection_ids: new_collection_ids) change_set.save diff --git a/app/jobs/set_governing_apo_job.rb b/app/jobs/set_governing_apo_job.rb index a676cd2796..e0bcdad02a 100644 --- a/app/jobs/set_governing_apo_job.rb +++ b/app/jobs/set_governing_apo_job.rb @@ -20,7 +20,7 @@ def perform(bulk_action_id, params) state_service = StateService.new(cocina_item) open_new_version(cocina_item.externalIdentifier, cocina_item.version, 'Set new governing APO') unless state_service.allows_modification? - change_set = ItemChangeSet.new(cocina_item) + change_set = ItemChangeSet.new(Item.new(cocina_item)) change_set.validate(admin_policy_id: new_apo_id) change_set.save success.call('Governing APO updated') diff --git a/app/jobs/set_rights_job.rb b/app/jobs/set_rights_job.rb index 8b5c3a1368..53ae988c07 100644 --- a/app/jobs/set_rights_job.rb +++ b/app/jobs/set_rights_job.rb @@ -23,9 +23,9 @@ def perform(bulk_action_id, params) # Collection only allows setting view access to dark or world view_access = access_params[:view_access] == 'dark' ? 'dark' : 'world' access_params = { view_access: view_access } - CollectionChangeSet.new(cocina_object) + CollectionChangeSet.new(Collection.new(cocina_object)) else - ItemChangeSet.new(cocina_object) + ItemChangeSet.new(Item.new(cocina_object)) end change_set.validate(**access_params) diff --git a/app/jobs/set_source_ids_csv_job.rb b/app/jobs/set_source_ids_csv_job.rb index e76b31ebe8..bd47e15ceb 100644 --- a/app/jobs/set_source_ids_csv_job.rb +++ b/app/jobs/set_source_ids_csv_job.rb @@ -28,22 +28,22 @@ def perform(bulk_action_id, params) private def update_one(current_druid, source_id, log) - cocina_object = Dor::Services::Client.object(current_druid).find + item = Repository.find(current_druid) - unless ability.can?(:manage_item, cocina_object) - log.puts("#{Time.current} Not authorized for #{cocina_object.externalIdentifier}") + unless ability.can?(:manage_item, item) + log.puts("#{Time.current} Not authorized for #{item.id}") bulk_action.increment(:druid_count_fail).save return end - state_service = StateService.new(cocina_object) + state_service = StateService.new(item) unless state_service.allows_modification? - new_version = open_new_version(cocina_object.externalIdentifier, cocina_object.version, version_message(source_id)) - cocina_object = cocina_object.new(version: new_version.to_i) + new_version = open_new_version(item.id, item.version, version_message(source_id)) + item.version = new_version.to_i end - change_set_class = cocina_object.collection? ? CollectionChangeSet : ItemChangeSet - change_set = change_set_class.new(cocina_object) + change_set_class = item.is_a?(Collection) ? CollectionChangeSet : ItemChangeSet + change_set = change_set_class.new(item) if change_set.validate(source_id: source_id) update_source_ids(change_set, log) if change_set.changed? else @@ -63,8 +63,8 @@ def params_from(params) end def update_source_ids(change_set, log) - cocina_object = change_set.model - log.puts("#{Time.current} Beginning set source_id for #{cocina_object.externalIdentifier}") + item = change_set.model + log.puts("#{Time.current} Beginning set source_id for #{item.id}") log_update(change_set, log) diff --git a/app/models/ability.rb b/app/models/ability.rb index 20558c7c45..cb291d0f63 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -24,52 +24,52 @@ def grant_permissions if current_user.manager? can %i[manage_item manage_desc_metadata manage_governing_apo view_content view_metadata], - [NilModel, Cocina::Models::DRO, Cocina::Models::Collection] - can :create, Cocina::Models::AdminPolicy + [NilModel, Item, Collection] + can :create, AdminPolicy end - can %i[view_metadata view_content], [Cocina::Models::DRO, Cocina::Models::Collection, Cocina::Models::AdminPolicy] if current_user.viewer? + can %i[view_metadata view_content], [Item, Collection, AdminPolicy] if current_user.viewer? - can :manage_item, Cocina::Models::AdminPolicy do |cocina_object| - can_manage_items? current_user.roles(cocina_object.externalIdentifier) + can :manage_item, AdminPolicy do |cocina_object| + can_manage_items? current_user.roles(cocina_object.id) end - can :manage_item, [Cocina::Models::Collection, Cocina::Models::DRO] do |cocina_object| - can_manage_items? current_user.roles(cocina_object.administrative.hasAdminPolicy) + can :manage_item, [Collection, Item] do |item| + can_manage_items? current_user.roles(item.admin_policy_id) end - can :manage_desc_metadata, Cocina::Models::AdminPolicy do |cocina_admin_policy| - can_edit_desc_metadata? current_user.roles(cocina_admin_policy.externalIdentifier) + can :manage_desc_metadata, AdminPolicy do |admin_policy| + can_edit_desc_metadata? current_user.roles(admin_policy.id) end - can :manage_desc_metadata, [Cocina::Models::Collection, Cocina::Models::DRO] do |cocina_object| - can_edit_desc_metadata? current_user.roles(cocina_object.administrative.hasAdminPolicy) + can :manage_desc_metadata, [Collection, Item] do |item| + can_edit_desc_metadata? current_user.roles(item.admin_policy_id) end - can :manage_governing_apo, [Cocina::Models::Collection, Cocina::Models::DRO] do |cocina_object, new_apo_id| + can :manage_governing_apo, [Collection, Item] do |item, new_apo_id| # user must have management privileges on both the target APO and the APO currently governing the item - can_manage_items?(current_user.roles(new_apo_id)) && can?(:manage_item, cocina_object) + can_manage_items?(current_user.roles(new_apo_id)) && can?(:manage_item, item) end - can :view_content, Cocina::Models::DRO do |cocina_item| - can_view? current_user.roles(cocina_item.administrative.hasAdminPolicy) + can :view_content, Item do |item| + can_view? current_user.roles(item.admin_policy_id) end - can :view_metadata, [Cocina::Models::Collection, Cocina::Models::DRO] do |cocina_object| - can_view? current_user.roles(cocina_object.administrative.hasAdminPolicy) + can :view_metadata, [Collection, Item] do |item| + can_view? current_user.roles(item.admin_policy_id) end - can :view_metadata, Cocina::Models::AdminPolicy do |cocina_admin_policy| - can_view?(current_user.roles(cocina_admin_policy.externalIdentifier)) || - can_view?(current_user.roles(cocina_admin_policy.administrative.hasAdminPolicy)) + can :view_metadata, AdminPolicy do |admin_policy| + can_view?(current_user.roles(admin_policy.id)) || + can_view?(current_user.roles(admin_policy.admin_policy_id)) end end # Returns true if they have been granted permission to update all workflows or # The status is currently "waiting" and they can manage that item - def can_update_workflow?(status, cocina_object) + def can_update_workflow?(status, item) can?(:update, :workflow) || - (status == 'waiting' && can?(:manage_item, cocina_object)) + (status == 'waiting' && can?(:manage_item, item)) end private diff --git a/app/models/access_template.rb b/app/models/access_template.rb new file mode 100644 index 0000000000..1f31db0031 --- /dev/null +++ b/app/models/access_template.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +class AccessTemplate < ApplicationModel + define_attribute_methods :license, :copyright, :use_statement, :view, :download + + attribute :license + attribute :copyright + attribute :use_statement + attribute :view + attribute :download + + def initialize(cocina_model = Cocina::Models::AdminPolicyAccessTemplate.new) + super + end + + # When the object is initialized, copy the properties from the cocina model to the entity: + def setup_properties! + self.license = model.license + self.copyright = model.copyright + self.use_statement = model.useAndReproductionStatement + self.view = model.view + self.download = model.download + end +end diff --git a/app/models/admin_policy.rb b/app/models/admin_policy.rb new file mode 100644 index 0000000000..5855e0860e --- /dev/null +++ b/app/models/admin_policy.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +class AdminPolicy < ApplicationModel + define_attribute_methods :id, :version, :label, :admin_policy_id, + :registration_workflows, :collections_for_registration, :access_template, :roles + + attribute :id + attribute :version + attribute :label + attribute :admin_policy_id + attribute :registration_workflows + attribute :collections_for_registration + attribute :access_template + attribute :roles + + # When the object is initialized, copy the properties from the cocina model to the entity: + def setup_properties! + self.id = model.externalIdentifier + self.version = model.version + self.label = model.label + self.admin_policy_id = model.administrative.hasAdminPolicy + self.registration_workflows = model.administrative.registrationWorkflow + self.collections_for_registration = model.administrative.collectionsForRegistration + self.access_template = AccessTemplate.new(model.administrative.accessTemplate) + self.roles = model.administrative.roles + end + + def save + raise 'not implemented' + # @model = AdminPolicyChangeSetPersister.update(model, self) + end + + def self.model_name + ::ActiveModel::Name.new(nil, nil, 'AdminPolicy') + end +end diff --git a/app/models/admin_policy_change_set.rb b/app/models/admin_policy_change_set.rb index bd9463d14c..95e1a85f34 100644 --- a/app/models/admin_policy_change_set.rb +++ b/app/models/admin_policy_change_set.rb @@ -65,6 +65,9 @@ def collection_radio @changes[:collection_radio] end + # @param [ActionController::Parameters] a list of collections expressed as a hash + # @example + # change_set.collections_for_registration = {"0"=>{"id"=>"druid:yj747bb6128"}} def collections_for_registration=(val) @changes[:collections_for_registration] = val end diff --git a/app/models/application_model.rb b/app/models/application_model.rb new file mode 100644 index 0000000000..baeebe4dde --- /dev/null +++ b/app/models/application_model.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +# @abstract +class ApplicationModel + include ActiveModel::Dirty + include ActiveModel::API + + def self.attribute(name) + define_method name do + instance_variable_get(:"@#{name}") + end + + define_method :"#{name}=" do |val| + send(:"#{name}_will_change!") unless val == instance_variable_get(:"@#{name}") + instance_variable_set(:"@#{name}", val) + end + end + + def initialize(cocina = nil) + @model = cocina + setup_properties! + clear_changes_information + end + + # The original cocina data + attr_reader :model + + def persisted? + id.present? + end +end diff --git a/app/models/collection.rb b/app/models/collection.rb new file mode 100644 index 0000000000..c0030eda38 --- /dev/null +++ b/app/models/collection.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +class Collection < ApplicationModel + define_attribute_methods :id, :version, :admin_policy_id, :catkeys, :copyright, + :license, :source_id, :use_statement, :view_access + + attribute :id + attribute :version + attribute :admin_policy_id + attribute :catkeys + attribute :copyright + attribute :license + attribute :source_id + attribute :use_statement + attribute :view_access + + # When the object is initialized, copy the properties from the cocina model to the form: + def setup_properties! + self.id = model.externalIdentifier + self.version = model.version + self.admin_policy_id = model.administrative.hasAdminPolicy + + self.catkeys = Catkey.symphony_links(model) if model.identification + self.copyright = model.access.copyright + self.use_statement = model.access.useAndReproductionStatement + self.license = model.access.license + self.source_id = model.identification&.sourceId + + self.view_access = model.access.view + end + + def save + @model = CollectionPersister.update(model, self) + end + + def self.model_name + ::ActiveModel::Name.new(nil, nil, 'Collection') + end +end diff --git a/app/models/collection_change_set.rb b/app/models/collection_change_set.rb index 5facb132ed..658aae0f11 100644 --- a/app/models/collection_change_set.rb +++ b/app/models/collection_change_set.rb @@ -2,33 +2,15 @@ # Represents a set of changes to a collection class CollectionChangeSet < ApplicationChangeSet - property :admin_policy_id, virtual: true - property :catkeys, virtual: true - property :copyright, virtual: true - property :license, virtual: true - property :use_statement, virtual: true - property :source_id, virtual: true - property :view_access, virtual: true + property :admin_policy_id + property :catkeys + property :copyright + property :license + property :use_statement + property :source_id + property :view_access def self.model_name ::ActiveModel::Name.new(nil, nil, 'Collection') end - - def id - model.externalIdentifier - end - - # When the object is initialized, copy the properties from the cocina model to the form: - def setup_properties!(_options) - self.catkeys = Catkey.symphony_links(model) if model.identification - self.copyright = model.access.copyright - self.use_statement = model.access.useAndReproductionStatement - self.license = model.access.license - self.source_id = model.identification&.sourceId - self.view_access = model.access.view - end - - def save_model - CollectionChangeSetPersister.update(model, self) - end end diff --git a/app/models/embargo.rb b/app/models/embargo.rb new file mode 100644 index 0000000000..c7fead0531 --- /dev/null +++ b/app/models/embargo.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class Embargo < ApplicationModel + define_attribute_methods :release_date, :view_access, :download_access, :access_location + + attribute :release_date + attribute :view_access + attribute :download_access + attribute :access_location + + # When the object is initialized, copy the properties from the cocina model to the entity: + def setup_properties! + self.release_date = model.releaseDate + self.view_access = model.view + self.download_access = model.download + self.access_location = model.location + end +end diff --git a/app/models/file_set.rb b/app/models/file_set.rb new file mode 100644 index 0000000000..a94e88ab30 --- /dev/null +++ b/app/models/file_set.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class FileSet < ApplicationModel + define_attribute_methods :files, :type, :label + + attribute :files + attribute :type + attribute :label + + # When the object is initialized, copy the properties from the cocina model to the entity: + def setup_properties! + self.type = model.type + self.label = model.label + self.files = model.structural.contains.map { |cocina| ManagedFile.new(cocina) } + end + + # has the collection or any of its members changed? + def changed? + super || files.any?(&:changed?) + end +end diff --git a/app/models/item.rb b/app/models/item.rb new file mode 100644 index 0000000000..c1bbdc4e3d --- /dev/null +++ b/app/models/item.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +class Item < ApplicationModel + define_attribute_methods :id, :version, :type, :admin_policy_id, :catkeys, + :collection_ids, :copyright, :embargo, :license, + :source_id, :use_statement, :barcode, + :view_access, :download_access, :access_location, :controlled_digital_lending, + :file_sets, :members, :release_tags + + attribute :id + attribute :version + attribute :type + attribute :admin_policy_id + attribute :catkeys + attribute :collection_ids + attribute :copyright + attribute :embargo + attribute :license + attribute :source_id + attribute :use_statement + attribute :barcode + attribute :view_access + attribute :download_access + attribute :access_location + attribute :controlled_digital_lending + attribute :file_sets + attribute :members + attribute :release_tags + + # When the object is initialized, copy the properties from the cocina model to the entity: + def setup_properties! + self.id = model.externalIdentifier + self.version = model.version + self.type = model.type + self.admin_policy_id = model.administrative.hasAdminPolicy + + self.catkeys = Catkey.symphony_links(model) + self.barcode = model.identification.barcode + self.source_id = model.identification.sourceId + + setup_acccess_properties! + + self.collection_ids = Array(model.structural&.isMemberOf) + self.file_sets = model.structural.contains.map { |cocina| FileSet.new(cocina) } + self.members = Array(model.structural.hasMemberOrders&.first&.members) + self.release_tags = model.administrative.releaseTags + end + + def setup_acccess_properties! + self.copyright = model.access.copyright + self.use_statement = model.access.useAndReproductionStatement + self.license = model.access.license + + self.view_access = model.access.view + self.download_access = model.access.download + self.access_location = model.access.location + self.controlled_digital_lending = model.access.controlledDigitalLending + self.embargo = Embargo.new(model.access.embargo) if model.access.embargo + end + + def save + @model = ItemPersister.update(model, self) + end + + # This checks to see if the embargo or any of the properties of the embargo changed + def embargo_changed? + super || embargo&.changed? + end + + # has the collection or any of its members changed? + def file_sets_changed? + super || file_sets.any?(&:changed?) + end + + def self.model_name + ::ActiveModel::Name.new(nil, nil, 'Item') + end +end diff --git a/app/models/item_change_set.rb b/app/models/item_change_set.rb index fc479b15d2..80f9b0727d 100644 --- a/app/models/item_change_set.rb +++ b/app/models/item_change_set.rb @@ -3,22 +3,24 @@ require 'reform/form/coercion' # Represents a set of changes to an item. -class ItemChangeSet < ApplicationChangeSet # rubocop:disable Metrics/ClassLength +class ItemChangeSet < ApplicationChangeSet feature Coercion # Casts properties to a specific type - property :admin_policy_id, virtual: true - property :catkeys, virtual: true - property :collection_ids, virtual: true - property :copyright, virtual: true + + property :admin_policy_id + property :catkeys + property :collection_ids + property :copyright property :embargo_release_date, virtual: true property :embargo_access, virtual: true - property :license, virtual: true - property :source_id, virtual: true - property :use_statement, virtual: true - property :barcode, virtual: true - property :view_access, virtual: true - property :download_access, virtual: true - property :access_location, virtual: true - property :controlled_digital_lending, virtual: true, type: Dry::Types['params.nil'] | Dry::Types['params.bool'] + property :license + property :source_id + property :use_statement + property :barcode + property :view_access + property :download_access + property :access_location + property :controlled_digital_lending, type: Dry::Types['params.nil'] | Dry::Types['params.bool'] + property :file_sets validates :source_id, presence: true, if: -> { changed?(:source_id) } validates :embargo_access, inclusion: { @@ -45,71 +47,47 @@ def self.model_name ::ActiveModel::Name.new(nil, nil, 'Item') end - def id - model.externalIdentifier - end - # When the object is initialized, copy the properties from the cocina model to the form: def setup_properties!(_options) - if model.identification - self.catkeys = Catkey.symphony_links(model) - self.barcode = model.identification.barcode - self.source_id = model.identification.sourceId - end - - self.copyright = model.access.copyright - self.use_statement = model.access.useAndReproductionStatement - self.license = model.access.license - self.view_access = model.access.view - self.download_access = model.access.download - self.access_location = model.access.location - self.controlled_digital_lending = model.access.controlledDigitalLending - - setup_embargo_properties! if model.access.embargo - end - - def setup_embargo_properties! - embargo = model.access.embargo - self.embargo_release_date = embargo.releaseDate.to_date.to_fs(:default) - self.embargo_access = if embargo.view == 'location-based' - "loc:#{embargo.location}" - elsif embargo.download == 'none' && embargo.view.in?(%w[stanford world]) - "#{embargo.view}-nd" + super + embargo = model.embargo + return unless embargo + + self.embargo_release_date = embargo.release_date + self.embargo_access = if embargo.view_access == 'location-based' + "loc:#{embargo.access_location}" + elsif embargo.download_access == 'none' && embargo.view_access.in?(%w[stanford world]) + "#{embargo.view_access}-nd" else - embargo.view + embargo.view_access end end - # @param structural [Cocina::Models::DRO] the DRO metadata to modify - # @return [Cocina::Models::DRO] a copy of the the Cocina model, with the new structural overlaid - def update_files(updated) + # Copies access onto the files + def update_files # Convert to hash so we can mutate it - structure_hash = updated.structural.to_h - Array(structure_hash[:contains]).each do |fileset| - fileset[:structural][:contains].each do |file| + file_sets.each do |file_set| + file_set.files.each do |managed_file| case view_access when 'dark' - # Ensure files attached to dark objects are neither published nor shelved - file[:access].merge!(view: 'dark', download: 'none', controlledDigitalLending: false, location: nil) - file[:administrative].merge!(publish: false) - file[:administrative].merge!(shelve: false) + managed_file.dark! when 'citation-only' - file[:access].merge!(view: 'dark', download: 'none', controlledDigitalLending: false, location: nil) + managed_file.citation_only! else - file[:access].merge!(view: view_access, - download: download_access, - controlledDigitalLending: controlled_digital_lending, - location: access_location) + managed_file.view_access = view_access + managed_file.download_access = download_access + managed_file.controlled_digital_lending = controlled_digital_lending + managed_file.access_location = access_location end end end - updated.new(structural: structure_hash) end def sync - super self.download_access = 'none' if clear_download? # This must be before clearing location self.access_location = nil if clear_location? + update_files if rights_changed? + super # call super last, so all the changs are copied to the Item end def clear_download? @@ -120,14 +98,6 @@ def clear_location? (changed?(:view_access) || changed?(:download_access)) && view_access != 'location-based' && download_access != 'location-based' end - # @raises [Dor::Services::Client::BadRequestError] when the server doesn't accept the request - # @raises [Cocina::Models::ValidationError] when given invalid Cocina values or structures - def save_model - updated = model - updated = update_files(updated) if rights_changed? # This would ideally live in #sync, but reform doesn't support immutable models. - ItemChangeSetPersister.update(updated, self) - end - def rights_changed? changed?(:view_access) || changed?(:download_access) || changed?(:location) || changed?(:controlled_digital_lending) end diff --git a/app/models/managed_file.rb b/app/models/managed_file.rb new file mode 100644 index 0000000000..be50acf232 --- /dev/null +++ b/app/models/managed_file.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +class ManagedFile < ApplicationModel + define_attribute_methods :view_access, :download_access, :access_location, + :controlled_digital_lending, :publish, :shelve, :preserve, + :filename, :mime_type, :size, :use, :height, :width + + attribute :view_access + attribute :download_access + attribute :access_location + attribute :controlled_digital_lending + attribute :publish + attribute :shelve + attribute :preserve + attribute :filename + attribute :mime_type + attribute :size + attribute :use + attribute :height + attribute :width + + # When the object is initialized, copy the properties from the cocina model to the entity: + def setup_properties! + self.filename = model.filename + self.mime_type = model.hasMimeType + self.size = model.size + self.use = model.use + + self.view_access = model.access.view + self.download_access = model.access.download + self.access_location = model.access.location + self.controlled_digital_lending = model.access.controlledDigitalLending + + self.publish = model.administrative.publish + self.shelve = model.administrative.shelve + self.preserve = model.administrative.sdrPreserve + + self.height = model.presentation&.height + self.width = model.presentation&.width + end + + def administrative_changed? + publish_changed? || shelve_changed? || preserve_changed? + end + + # Assigns the correct access and ensures publsh and shelve are false + def dark! + citation_only! + self.publish = false + self.shelve = false + end + + # Assigns the correct access so the object shows on PURL, but doesn't reveal any files + def citation_only! + self.view_access = 'dark' + self.download_access = 'none' + self.controlled_digital_lending = false + self.access_location = nil + end +end diff --git a/app/models/repository.rb b/app/models/repository.rb new file mode 100644 index 0000000000..d1cf6a5084 --- /dev/null +++ b/app/models/repository.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +class Repository + class NotCocina < StandardError; end + + def self.find(id) + raise ArgumentError, 'Missing identifier' unless id + + cocina = maybe_load_cocina(id) + raise NotCocina, 'Unable to retrieve the cocina model' if cocina.is_a? NilModel + + klass = if cocina.collection? + Collection + elsif cocina.admin_policy? + AdminPolicy + else + Item + end + @item = klass.new(cocina) + end + + # Currently we know that not all objects are Cocina compliant, this ensures that we can at least + # receive some object and so, at least administrators can be authorized to operate on it. + # See: https://argo.stanford.edu/catalog?f[data_quality_ssim][]=Cocina+conversion+failed + # @return [Cocina::Models::DRO,NilModel] + def self.maybe_load_cocina(druid) + object_client = Dor::Services::Client.object(druid) + object_client.find + rescue Dor::Services::Client::UnexpectedResponse + NilModel.new(druid) + end +end diff --git a/app/presenters/argo_show_presenter.rb b/app/presenters/argo_show_presenter.rb index 12aca4a204..7bb15cc602 100644 --- a/app/presenters/argo_show_presenter.rb +++ b/app/presenters/argo_show_presenter.rb @@ -10,14 +10,14 @@ def heading end def change_set - cocina.collection? ? CollectionChangeSet.new(cocina) : ItemChangeSet.new(cocina) + item.is_a?(Collection) ? CollectionChangeSet.new(item) : ItemChangeSet.new(item) end def id - cocina.externalIdentifier + item.externalIdentifier end delegate :allows_modification?, to: :state_service - attr_accessor :cocina, :view_token, :state_service + attr_accessor :item, :view_token, :state_service end diff --git a/app/presenters/workflow_presenter.rb b/app/presenters/workflow_presenter.rb index 5cff580836..1c93835bc0 100644 --- a/app/presenters/workflow_presenter.rb +++ b/app/presenters/workflow_presenter.rb @@ -3,11 +3,11 @@ class WorkflowPresenter # @param [Object] view the rails view context # @param [WorkflowStatus] workflow_status - # @param [Cocina::Models::DRO,Cocina::Models::Collection] cocina_object the repository object that the workflow is about - def initialize(view:, workflow_status:, cocina_object:) + # @param [Item,Collection] item the repository object that the workflow is about + def initialize(view:, workflow_status:, item:) @view = view @workflow_status = workflow_status - @cocina_object = cocina_object + @item = item end delegate :druid, :workflow_name, to: :workflow_status @@ -17,7 +17,7 @@ def processes workflow_status.process_statuses end - attr_reader :cocina_object + attr_reader :item private diff --git a/app/services/collection_change_set_persister.rb b/app/services/collection_persister.rb similarity index 74% rename from app/services/collection_change_set_persister.rb rename to app/services/collection_persister.rb index f235f9701a..1665e03d59 100644 --- a/app/services/collection_change_set_persister.rb +++ b/app/services/collection_persister.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true # Writes updates to Cocina collections -class CollectionChangeSetPersister +class CollectionPersister # @param [Cocina::Models::Collection] model the orignal state of the collection - # @param [CollectionChangeSet] change_set the values to update. + # @param [Collection] change_set the values to update. # @return [Cocina::Models::Collection] the model with updates applied def self.update(model, change_set) new(model, change_set).update @@ -34,10 +34,13 @@ def update attr_reader :model, :change_set - delegate :admin_policy_id, :catkeys, *ACCESS_FIELDS.keys, :changed?, to: :change_set + delegate :admin_policy_id, :catkeys, *ACCESS_FIELDS.keys, + :source_id_changed?, :catkeys_changed?, :admin_policy_id_changed?, + :copyright_changed?, :license_changed?, :use_statement_changed?, + :view_access_changed?, to: :change_set def access_changed? - ACCESS_FIELDS.keys.any? { |field| changed?(field) } + ACCESS_FIELDS.keys.any? { |field| public_send("#{field}_changed?") } end def updated_access(updated) @@ -49,21 +52,21 @@ def updated_access(updated) def updated_access_properties {}.tap do |access_properties| ACCESS_FIELDS.each do |field, cocina_field| - access_properties[cocina_field] = public_send(field).presence if changed?(field) + access_properties[cocina_field] = public_send(field).presence if public_send("#{field}_changed?") end end end def update_identification(updated) - return updated unless changed?(:source_id) || changed?(:catkeys) + return updated unless source_id_changed? || catkeys_changed? identification_props = updated.identification&.to_h || {} - identification_props[:catalogLinks] = Catkey.serialize(model, catkeys) if changed?(:catkeys) + identification_props[:catalogLinks] = Catkey.serialize(model, catkeys) if catkeys_changed? updated.new(identification: identification_props.compact.presence) end def updated_administrative(updated) - return updated unless changed?(:admin_policy_id) + return updated unless admin_policy_id_changed? updated_administrative = updated.administrative.new(hasAdminPolicy: admin_policy_id) updated.new(administrative: updated_administrative) diff --git a/app/services/item_change_set_persister.rb b/app/services/item_change_set_persister.rb deleted file mode 100644 index effefb578b..0000000000 --- a/app/services/item_change_set_persister.rb +++ /dev/null @@ -1,131 +0,0 @@ -# frozen_string_literal: true - -# Writes updates Cocina Models -class ItemChangeSetPersister - # @param [Cocina::Models::DRO] model the orignal state of the model - # @param [ItemChangeSet] change_set the values to update. - # @return [Cocina::Models::DRO] the model with updates applied - def self.update(model, change_set) - new(model, change_set).update - end - - def initialize(model, change_set) - @model = model - @change_set = change_set - end - - # @raises [Dor::Services::Client::BadRequestError] when the server doesn't accept the request - def update - updated_model = update_identification(model) - .then { |updated| updated_administrative(updated) } - .then { |updated| updated_access(updated) } - .then { |updated| update_structural(updated) } - object_client.update(params: updated_model) - end - - private - - # The map between the change set fields and the Cocina field names - ACCESS_FIELDS = { - copyright: :copyright, - license: :license, - use_statement: :useAndReproductionStatement, - view_access: :view, - download_access: :download, - access_location: :location, - controlled_digital_lending: :controlledDigitalLending - }.freeze - - attr_reader :model, :change_set - - delegate :admin_policy_id, :barcode, :catkeys, :source_id, :collection_ids, - :embargo_release_date, :embargo_access, - *ACCESS_FIELDS.keys, - :changed?, to: :change_set - - def object_client - Dor::Services::Client.object(model.externalIdentifier) - end - - def updated_administrative(updated) - return updated unless changed?(:admin_policy_id) - - updated_administrative = updated.administrative.new(hasAdminPolicy: admin_policy_id) - updated.new(administrative: updated_administrative) - end - - def update_structural(updated) - return updated unless changed?(:collection_ids) - - updated_structural = if collection_ids - updated.structural.new(isMemberOf: collection_ids) - else - updated.structural.to_h.without(:isMemberOf) # clear collection membership - end - updated.new(structural: updated_structural) - end - - def access_changed? - embargo_changed? || ACCESS_FIELDS.keys.any? { |field| changed?(field) } - end - - # This is a subset of access_changed? that ignores copyright, license, use_statement, and embargo. - def object_permissions_changed? - %i[view_access download_access access_location controlled_digital_lending].any? { |field| changed?(field) } - end - - def identification_changed? - changed?(:source_id) || changed?(:catkeys) || changed?(:barcode) - end - - def embargo_changed? - %i[embargo_release_date embargo_access].any? { |field| changed?(field) } - end - - def updated_access(updated) - return updated unless access_changed? - - props = updated_access_properties.merge(updated_embargo(updated.access)) - updated.new(access: updated.access.new(props)) - end - - def updated_access_properties - {}.tap do |access_properties| - ACCESS_FIELDS.filter { |field, _cocina_field| changed?(field) }.each do |field, cocina_field| - val = public_send(field) - access_properties[cocina_field] = val.is_a?(String) ? val.presence : val # allow boolean false - end - end - end - - def updated_embargo(existing_access) - @updated_embargo ||= begin - embargo_class = existing_access.embargo || Cocina::Models::Embargo - embargo_props.present? ? { embargo: embargo_class.new(embargo_props) } : {} - end - end - - def embargo_props - @embargo_props ||= begin - new_embargo_props = {} - new_embargo_props[:releaseDate] = embargo_release_date if changed?(:embargo_release_date) - - new_embargo_props.merge!(embargo_rights) if changed?(:embargo_access) - new_embargo_props - end - end - - def embargo_rights - CocinaDroAccess.from_form_value(embargo_access).value_or(nil) if embargo_access.present? - end - - def update_identification(updated) - return updated unless identification_changed? - - identification_props = updated.identification.to_h - identification_props[:sourceId] = source_id if changed?(:source_id) - identification_props[:barcode] = barcode.presence if changed?(:barcode) - identification_props[:catalogLinks] = Catkey.serialize(model, catkeys) if changed?(:catkeys) - updated.new(identification: identification_props.presence) - end -end diff --git a/app/services/item_persister.rb b/app/services/item_persister.rb new file mode 100644 index 0000000000..d338330cec --- /dev/null +++ b/app/services/item_persister.rb @@ -0,0 +1,164 @@ +# frozen_string_literal: true + +# Writes updates Cocina Models +class ItemPersister # rubocop:disable Metrics/ClassLength + # @param [Cocina::Models::DRO] model the orignal state of the model + # @param [Item] change_set the values to update. + # @return [Cocina::Models::DRO] the model with updates applied + def self.update(model, change_set) + new(model, change_set).update + end + + def initialize(model, change_set) + @model = model + @change_set = change_set + end + + # @raises [Dor::Services::Client::BadRequestError] when the server doesn't accept the request + def update + updated_model = update_type(model) + .then { |updated| update_structural(updated) } + .then { |updated| update_identification(updated) } + .then { |updated| updated_administrative(updated) } + .then { |updated| updated_access(updated) } + object_client.update(params: updated_model) + end + + private + + # The map between the change set fields and the Cocina field names + ACCESS_FIELDS = { + copyright: :copyright, + license: :license, + use_statement: :useAndReproductionStatement, + view_access: :view, + download_access: :download, + access_location: :location, + controlled_digital_lending: :controlledDigitalLending + }.freeze + + attr_reader :model, :change_set + + delegate :admin_policy_id, :barcode, :catkey, :collection_ids, + :embargo, :source_id, + *ACCESS_FIELDS.keys, + :collection_ids_changed?, :source_id_changed?, + :catkey_changed?, :barcode_changed?, :admin_policy_id_changed?, + :copyright_changed?, :license_changed?, :use_statement_changed?, + :embargo_changed?, :file_sets_changed?, :members_changed?, + :file_sets, :members, + :view_access_changed?, :download_access_changed?, + :access_location_changed?, :controlled_digital_lending_changed?, + :type, :type_changed?, to: :change_set + + def object_client + Dor::Services::Client.object(model.externalIdentifier) + end + + def update_type(updated) + return updated unless type_changed? + + updated.new(type: type) + end + + def updated_administrative(updated) + return updated unless admin_policy_id_changed? + + updated_administrative = updated.administrative.new(hasAdminPolicy: admin_policy_id) + updated.new(administrative: updated_administrative) + end + + def update_structural(updated) + return updated unless structural_changed? + + updated_structural = updated.structural.new(isMemberOf: Array(collection_ids)) + updated_structural = update_file_sets(updated_structural) if file_sets_changed? + if members_changed? + orders = updated_structural.hasMemberOrders || [] + first_order = orders.first || Cocina::Models::Sequence + new_order = first_order.new(members: members) + updated_structural = updated_structural.new(hasMemberOrders: [new_order]) + end + + updated.new(structural: updated_structural) + end + + def update_file_sets(updated_structural) + contains = file_sets.map do |file_set| + new_files = file_set.files.map do |file| + update_file(file) + end + new_struct = file_set.model.structural.new(contains: new_files) + file_set.model.new(structural: new_struct) + end + updated_structural.new(contains: contains) + end + + def update_file(file) + new_access = file.model.access.new(view: file.view_access, download: file.download_access, location: file.access_location, + controlledDigitalLending: file.controlled_digital_lending) + new_file = file.model.new(access: new_access) + new_file = new_file.new(filename: file.filename) if file.filename_changed? + + if file.administrative_changed? + admin = file.model.administrative + admin = admin.new(publish: file.publish) if file.publish_changed? + admin = admin.new(shelve: file.shelve) if file.shelve_changed? + admin = admin.new(sdrPreserve: file.preserve) if file.preserve_changed? + new_file = new_file.new(administrative: admin) + end + new_file + end + + def access_changed? + embargo_changed? || ACCESS_FIELDS.keys.any? { |field| public_send("#{field}_changed?".to_sym) } + end + + def identification_changed? + source_id_changed? || catkey_changed? || barcode_changed? + end + + def structural_changed? + collection_ids_changed? || file_sets_changed? || members_changed? + end + + # rubocop:disable Metrics/PerceivedComplexity + # rubocop:disable Metrics/CyclomaticComplexity + def updated_access(updated) + return updated unless access_changed? + + access_properties = {} + access_properties[:copyright] = copyright.presence if copyright_changed? + access_properties[:license] = license.presence if license_changed? + access_properties[:useAndReproductionStatement] = use_statement if use_statement_changed? + access_properties[:embargo] = embargo_props if embargo_changed? + access_properties[:view] = view_access if view_access_changed? + access_properties[:download] = download_access if download_access_changed? + access_properties[:location] = access_location if access_location_changed? + access_properties[:controlledDigitalLending] = controlled_digital_lending if controlled_digital_lending_changed? + updated.new(access: updated.access.new(access_properties)) + end + # rubocop:enable Metrics/PerceivedComplexity + # rubocop:enable Metrics/CyclomaticComplexity + + def embargo_props + embargo = change_set.embargo + updated_model = embargo.model + updated_model = updated_model.new(releaseDate: embargo.release_date) if embargo.release_date_changed? + updated_model = updated_model.new(view: embargo.view_access) if embargo.view_access_changed? + updated_model = updated_model.new(download: embargo.download_access) if embargo.download_access_changed? + updated_model = updated_model.new(accessLocation: embargo.accessLocation) if embargo.access_location_changed? + + updated_model + end + + def update_identification(updated) + return updated unless identification_changed? + + identification_props = updated.identification&.to_h || {} + identification_props[:sourceId] = source_id if source_id_changed? + identification_props[:barcode] = barcode.presence if barcode_changed? + identification_props[:catalogLinks] = Catkey.serialize(model, catkey) if catkey_changed? + updated.new(identification: identification_props.presence) + end +end diff --git a/app/services/license_and_rights_statements_setter.rb b/app/services/license_and_rights_statements_setter.rb index 826bcff920..7252e4fe69 100644 --- a/app/services/license_and_rights_statements_setter.rb +++ b/app/services/license_and_rights_statements_setter.rb @@ -40,8 +40,8 @@ def initialize(ability:, druid:, copyright: nil, license: nil, use_statement: ni # ability lacks manage_item permission, or when an underlying exception is raised # @return [Cocina::Models::Collection,Cocina::Models::DRO,nil] the cocina object with updates applied or `nil` if no changes def set - raise "#{druid} cannot be changed by #{ability.current_user}" unless ability.can?(:manage_item, cocina_object) - raise "#{druid} is not an item or collection (#{cocina_object.type})" unless cocina_object.dro? || cocina_object.collection? + raise "#{druid} cannot be changed by #{ability.current_user}" unless ability.can?(:manage_item, item) + raise "#{druid} is not an item or collection (#{item.class})" unless item.is_a?(Item) || item.is_a?(Collection) return unless change_set.changed? open_new_version! unless state_service.allows_modification? @@ -56,18 +56,18 @@ def set def open_new_version! raise "unable to open new version for #{druid}" unless openable? - VersionService.open(identifier: cocina_object.externalIdentifier, + VersionService.open(identifier: item.id, significance: 'minor', description: new_version_message, opening_user_name: ability.current_user.to_s) end def openable? - DorObjectWorkflowStatus.new(druid, version: cocina_object.version).can_open_version? + DorObjectWorkflowStatus.new(druid, version: item.version).can_open_version? end def state_service - StateService.new(cocina_object) + StateService.new(item) end def change_set @@ -75,22 +75,22 @@ def change_set args[:license] = license unless license.nil? args[:copyright] = copyright unless copyright.nil? args[:use_statement] = use_statement unless use_statement.nil? - change_set_class.new(cocina_object).tap do |change_set| + change_set_class.new(item).tap do |change_set| change_set.validate(args) end end def change_set_class - case cocina_object - when Cocina::Models::DRO + case item + when Item ItemChangeSet - when Cocina::Models::Collection + when Collection CollectionChangeSet end end - def cocina_object - Dor::Services::Client.object(druid).find + def item + @item = Repository.find(druid) end def new_version_message diff --git a/app/services/state_service.rb b/app/services/state_service.rb index 7604b67f7f..6950b82cde 100644 --- a/app/services/state_service.rb +++ b/app/services/state_service.rb @@ -9,9 +9,9 @@ class StateService STATES = Types::Symbol.enum(:unlock, :lock, :lock_inactive, :unlock_inactive) UNLOCKED_STATES = [STATES[:unlock], STATES[:unlock_inactive]].freeze - def initialize(cocina) - @druid = cocina.externalIdentifier - @version = cocina.version + def initialize(item) + @druid = item.id + @version = item.version end def allows_modification? diff --git a/app/views/catalog/_history_default.html.erb b/app/views/catalog/_history_default.html.erb index 5f3ce34e8e..1151598489 100644 --- a/app/views/catalog/_history_default.html.erb +++ b/app/views/catalog/_history_default.html.erb @@ -27,13 +27,13 @@ - <% if @cocina.administrative.respond_to? :releaseTags %> - <% if @cocina.administrative.releaseTags.present? %> + <% if @item.respond_to?(:release_tags) %> + <% if @item.release_tags.present? %>
Releases
- <%= render 'show_releases', release_tags: @cocina.administrative.releaseTags %> + <%= render 'show_releases', release_tags: @item.release_tags %>
diff --git a/app/views/collections/new.html.erb b/app/views/collections/new.html.erb index d47169cb1d..9628e532e8 100644 --- a/app/views/collections/new.html.erb +++ b/app/views/collections/new.html.erb @@ -2,8 +2,8 @@ <% component.header { 'Create Collection' } %> <% component.body do %>
-

APO — <%= @cocina.label %>

- <%= form_tag apo_collections_path(@cocina.externalIdentifier) do %> +

APO — <%= @item.label %>

+ <%= form_tag apo_collections_path(@item.id) do %>