|
| 1 | +# frozen_string_literal: true |
| 2 | + |
| 3 | +# Controller for editing the descriptive metadata of an object. |
| 4 | +class DescriptionsController < ApplicationController |
| 5 | + before_action :load_cocina_object |
| 6 | + |
| 7 | + FIELD_RESULT_KEY = { |
| 8 | + 'title' => :title, 'note' => :note, 'language' => :language, |
| 9 | + 'contributor' => :contributor, 'subject' => :subject, 'form' => :form, |
| 10 | + 'event' => :event, 'related_resource' => :relatedResource |
| 11 | + }.freeze |
| 12 | + |
| 13 | + FIELD_MODEL_CLASS = { |
| 14 | + 'title' => Cocina::Models::Title, |
| 15 | + 'note' => Cocina::Models::DescriptiveValue, |
| 16 | + 'language' => Cocina::Models::Language, |
| 17 | + 'contributor' => Cocina::Models::Contributor, |
| 18 | + 'subject' => Cocina::Models::DescriptiveValue, |
| 19 | + 'form' => Cocina::Models::DescriptiveValue, |
| 20 | + 'event' => Cocina::Models::Event, |
| 21 | + 'related_resource' => Cocina::Models::RelatedResource |
| 22 | + }.freeze |
| 23 | + |
| 24 | + def edit |
| 25 | + authorize! @cocina_object, with: ObjectPolicy, to: :edit_description? |
| 26 | + open_version_if_needed! |
| 27 | + rescue Sdr::Repository::Error |
| 28 | + # If pre-opening fails, save will attempt it again |
| 29 | + end |
| 30 | + |
| 31 | + # rubocop:disable Metrics/AbcSize |
| 32 | + def field_json |
| 33 | + authorize! @cocina_object, with: ObjectPolicy, to: :edit_description? |
| 34 | + |
| 35 | + result_key = FIELD_RESULT_KEY[params.require(:field_type)] |
| 36 | + return head :bad_request unless result_key |
| 37 | + |
| 38 | + built = DescriptionBuilder.new(existing_description: {}).build(description_params) |
| 39 | + item = built[result_key]&.first |
| 40 | + return head(:unprocessable_content) unless item |
| 41 | + |
| 42 | + render json: FIELD_MODEL_CLASS[params[:field_type]].new(item).to_h |
| 43 | + rescue ActionController::ParameterMissing, JSON::ParserError, |
| 44 | + Cocina::Models::ValidationError, Dry::Struct::Error |
| 45 | + head :unprocessable_content |
| 46 | + end |
| 47 | + # rubocop:enable Metrics/AbcSize |
| 48 | + |
| 49 | + def render_field |
| 50 | + authorize! @cocina_object, with: ObjectPolicy, to: :edit_description? |
| 51 | + |
| 52 | + data = JSON.parse(params.require(:json)).deep_symbolize_keys |
| 53 | + result = field_partial_config(params.require(:field_type), data) |
| 54 | + return head :bad_request unless result |
| 55 | + |
| 56 | + render partial: result[:partial], locals: result[:locals] |
| 57 | + rescue JSON::ParserError, Cocina::Models::ValidationError, Dry::Struct::Error |
| 58 | + head :unprocessable_content |
| 59 | + end |
| 60 | + |
| 61 | + def update |
| 62 | + authorize! @cocina_object, with: ObjectPolicy, to: :edit_description? |
| 63 | + |
| 64 | + new_description = build_description |
| 65 | + validate_result = CocinaSupport.validate(@cocina_object, description: new_description) |
| 66 | + |
| 67 | + if validate_result.failure? |
| 68 | + flash.now[:warning] = validate_result.failure |
| 69 | + render :edit, status: :unprocessable_content |
| 70 | + return |
| 71 | + end |
| 72 | + |
| 73 | + save_description!(new_description) |
| 74 | + rescue Sdr::Repository::Error => e |
| 75 | + flash.now[:warning] = e.message |
| 76 | + render :edit, status: :unprocessable_content |
| 77 | + end |
| 78 | + |
| 79 | + private |
| 80 | + |
| 81 | + def load_cocina_object |
| 82 | + @cocina_object = Sdr::Repository.find(druid: params.expect(:object_druid)) |
| 83 | + rescue Sdr::Repository::NotFoundResponse |
| 84 | + redirect_to root_path, flash: { warning: 'Object not found.' } |
| 85 | + end |
| 86 | + |
| 87 | + def open_version_if_needed! |
| 88 | + return if Sdr::VersionService.open?(druid: @cocina_object.externalIdentifier) |
| 89 | + |
| 90 | + Sdr::VersionService.open(druid: @cocina_object.externalIdentifier, |
| 91 | + description: 'Descriptive metadata edited via web form', |
| 92 | + opening_user_name: current_user.sunetid) |
| 93 | + rescue Dor::Services::Client::Error => e |
| 94 | + raise Sdr::Repository::Error, "Opening version failed: #{e.message}" |
| 95 | + end |
| 96 | + |
| 97 | + def close_version! |
| 98 | + druid = @cocina_object.externalIdentifier |
| 99 | + Sdr::VersionService.close(druid:) if Sdr::VersionService.closeable?(druid:) |
| 100 | + rescue Dor::Services::Client::Error => e |
| 101 | + raise Sdr::Repository::Error, "Closing version failed: #{e.message}" |
| 102 | + end |
| 103 | + |
| 104 | + def save_description!(new_description) |
| 105 | + open_version_if_needed! |
| 106 | + updated_object = @cocina_object.new(description: new_description) |
| 107 | + Sdr::Repository.update(cocina_object: updated_object, |
| 108 | + user_name: current_user.sunetid, |
| 109 | + description: 'Descriptive metadata edited via web form') |
| 110 | + close_version! |
| 111 | + redirect_to object_path(druid: @cocina_object.externalIdentifier), flash: { success: 'Description updated.' } |
| 112 | + end |
| 113 | + |
| 114 | + # rubocop:disable Metrics/CyclomaticComplexity |
| 115 | + def field_partial_config(field_type, data) |
| 116 | + case field_type |
| 117 | + when 'title' then { partial: 'descriptions/title_fields', |
| 118 | + locals: { title: Cocina::Models::Title.new(data) } } |
| 119 | + when 'note' then { partial: 'descriptions/note_fields', |
| 120 | + locals: { note: Cocina::Models::DescriptiveValue.new(data) } } |
| 121 | + when 'language' then { partial: 'descriptions/language_fields', |
| 122 | + locals: { language: Cocina::Models::Language.new(data) } } |
| 123 | + when 'contributor' then { partial: 'descriptions/contributor_fields', locals: { contributor: data } } |
| 124 | + when 'subject' then { partial: 'descriptions/subject_fields', locals: { subject: data } } |
| 125 | + when 'form' then { partial: 'descriptions/form_fields', locals: { form: data } } |
| 126 | + when 'event' then { partial: 'descriptions/event_fields', locals: { event: data } } |
| 127 | + when 'related_resource' then { partial: 'descriptions/related_resource_fields', |
| 128 | + locals: { related_resource: data } } |
| 129 | + end |
| 130 | + end |
| 131 | + # rubocop:enable Metrics/CyclomaticComplexity |
| 132 | + |
| 133 | + def build_description |
| 134 | + DescriptionBuilder.new(existing_description: @cocina_object.description.to_h) |
| 135 | + .build(description_params) |
| 136 | + end |
| 137 | + |
| 138 | + # rubocop:disable Rails/StrongParametersExpect |
| 139 | + def description_params |
| 140 | + params.require(:description).permit( |
| 141 | + title: [:value, :type, :_raw_json, :_original, { struct_parts: %i[value type] }], |
| 142 | + note: %i[value type _raw_json], |
| 143 | + language: %i[code value _raw_json], |
| 144 | + contributor: %i[_raw_json _original name_value life_dates type primary role_value], |
| 145 | + subject: [:_raw_json, :value, :type, :source_code, :_struct_original, { struct_parts: %i[value type] }], |
| 146 | + form: %i[_raw_json value type source_code], |
| 147 | + event: %i[_raw_json _original date_value date_start_value date_end_value type place_value publisher_value], |
| 148 | + related_resource: %i[_raw_json title_value type url], |
| 149 | + access: { physical_location: %i[value type], access_contact: %i[value type] } |
| 150 | + ).to_h.deep_symbolize_keys |
| 151 | + end |
| 152 | + # rubocop:enable Rails/StrongParametersExpect |
| 153 | +end |
0 commit comments