From 9e05eabba4bd56312c3cca41e3679bcbed63feb7 Mon Sep 17 00:00:00 2001 From: Justin Littman Date: Tue, 16 Aug 2022 12:37:15 -0400 Subject: [PATCH] Adds deposit date into cocina description. closes #2368 --- .../cocina_generator/description/edtf.rb | 56 ++++++++++++ .../description/events_generator.rb | 89 +++++++++++++++++++ .../cocina_generator/description/generator.rb | 76 ++-------------- .../cocina_generator/description/w3cdtf.rb | 15 ++++ db/structure.sql | 2 + spec/factories/work_versions.rb | 13 +++ .../description/generator_spec.rb | 77 ++++++++++++---- 7 files changed, 242 insertions(+), 86 deletions(-) create mode 100644 app/services/cocina_generator/description/edtf.rb create mode 100644 app/services/cocina_generator/description/events_generator.rb create mode 100644 app/services/cocina_generator/description/w3cdtf.rb diff --git a/app/services/cocina_generator/description/edtf.rb b/app/services/cocina_generator/description/edtf.rb new file mode 100644 index 000000000..bdd2d0964 --- /dev/null +++ b/app/services/cocina_generator/description/edtf.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +module CocinaGenerator + module Description + # Helper methods for EDTF + class Edtf + def self.props(date) + new(date).props + end + + def initialize(date) + @date = date + end + + def props + { + encoding: { code: 'edtf' } + }.merge(date.is_a?(EDTF::Interval) ? interval_props : date_props) + end + + private + + attr_reader :date + + def interval_props + { + structuredValue: interval_structured_values + }.tap do |props| + if date.from&.uncertain? || date.to&.uncertain? + props[:qualifier] = 'approximate' + props[:structuredValue].each { |struct_date_val| struct_date_val.delete(:qualifier) } + end + end.compact + end + + def interval_structured_values + [].tap do |structured_values| + structured_values << date_props_for(date.from, type: 'start') if date.from + structured_values << date_props_for(date.to, type: 'end') if date.to + end + end + + def date_props + date_props_for(date) + end + + def date_props_for(props_date, type: nil) + { + qualifier: props_date.uncertain? ? 'approximate' : nil, + value: props_date.edtf.chomp('?'), + type: type + }.compact + end + end + end +end diff --git a/app/services/cocina_generator/description/events_generator.rb b/app/services/cocina_generator/description/events_generator.rb new file mode 100644 index 000000000..f7dd0f119 --- /dev/null +++ b/app/services/cocina_generator/description/events_generator.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +module CocinaGenerator + module Description + # This generates Events for a work + class EventsGenerator + def self.generate(work_version:) + new(work_version: work_version).generate + end + + def initialize(work_version:) + @work_version = work_version + end + + def generate + events = deposit_events + Array(created_date_event) + events += publisher_events.presence || Array(published_date_event) + events + end + + private + + attr_reader :work_version + + def publisher_events + @publisher_events ||= ContributorsGenerator.events_from_publisher_contributors(work_version: work_version, + pub_date: published_date_event) + end + + def published_date_event + edtf_event_for(date: work_version.published_edtf, event_type: 'publication', date_type: 'publication') + end + + def created_date_event + edtf_event_for(date: work_version.created_edtf, event_type: 'creation', date_type: 'creation') + end + + def deposit_events + return [] if deposit_versions.blank? + + Array(deposit_publication_event) + deposit_modification_events + end + + def deposit_publication_event + w3cdtf_event_for(work_version: deposit_publication_version, event_type: 'deposit', date_type: 'publication') + end + + def deposit_modification_events + deposit_modification_versions.map do |deposit_version| + w3cdtf_event_for(work_version: deposit_version, event_type: 'deposit', date_type: 'modification') + end + end + + def deposit_versions + # Treating this version as a deposit version + @deposit_versions ||= work_version.work.work_versions.filter do |check_work_version| + check_work_version.deposited? || check_work_version == work_version + end + end + + def deposit_publication_version + deposit_versions.first + end + + def deposit_modification_versions + deposit_versions.slice(1..-1) + end + + def edtf_event_for(date:, event_type:, date_type:) + return unless date + + Cocina::Models::Event.new( + type: event_type, + date: [Edtf.props(date).merge(type: date_type)] + ) + end + + def w3cdtf_event_for(work_version:, event_type:, date_type:) + date = work_version.published_at || work_version.updated_at + return unless date + + Cocina::Models::Event.new( + type: event_type, + date: [W3cdtf.props(date).merge(type: date_type)] + ) + end + end + end +end diff --git a/app/services/cocina_generator/description/generator.rb b/app/services/cocina_generator/description/generator.rb index 7d1fb3346..e0178dd56 100644 --- a/app/services/cocina_generator/description/generator.rb +++ b/app/services/cocina_generator/description/generator.rb @@ -19,7 +19,7 @@ def generate contributor: ContributorsGenerator.generate(work_version: work_version).presence, subject: keywords.presence, note: [abstract, citation].compact.presence, - event: generate_events.presence, + event: EventsGenerator.generate(work_version: work_version).presence, relatedResource: related_resources.presence, form: TypesGenerator.generate(work_version: work_version).presence, access: access, @@ -43,15 +43,6 @@ def title ] end - def generate_events - pub_events = ContributorsGenerator.events_from_publisher_contributors(work_version: work_version, - pub_date: published_date) - return [created_date] + pub_events if pub_events.present? && created_date - return pub_events if pub_events.present? # and no created_date - - [created_date, published_date].compact # no pub_events - end - def related_resources RelatedLinksGenerator.generate(object: work_version) + related_works end @@ -91,57 +82,6 @@ def citation ) end - def created_date - event_for(work_version.created_edtf, 'creation') - end - - def published_date - event_for(work_version.published_edtf, 'publication') - end - - def event_for(date, type) - return unless date - - date_props = { - encoding: { code: 'edtf' }, - type: type - }.merge(date.is_a?(EDTF::Interval) ? interval_props_for(date) : date_props_for(date)) - - Cocina::Models::Event.new( - type: type, - date: [date_props] - ) - end - - # rubocop:disable Metrics/AbcSize - # rubocop:disable Metrics/CyclomaticComplexity - - def interval_props_for(date) - structured_values = [] - structured_values << date_props_for(date.from, type: 'start') if date.from - structured_values << date_props_for(date.to, type: 'end') if date.to - result_props = { - structuredValue: structured_values - } - - if date.from&.uncertain? || date.to&.uncertain? - result_props[:qualifier] = 'approximate' - result_props[:structuredValue].each { |struct_date_val| struct_date_val.delete(:qualifier) } - end - - result_props.compact - end - # rubocop:enable Metrics/AbcSize - # rubocop:enable Metrics/CyclomaticComplexity - - def date_props_for(date, type: nil) - { - type: type, - qualifier: date.uncertain? ? 'approximate' : nil, - value: date.edtf.chomp('?') - }.compact - end - def access args = { accessContact: access_contacts @@ -173,18 +113,11 @@ def related_works end end - # rubocop:disable Metrics/MethodLength - def admin_metadata Cocina::Models::DescriptiveAdminMetadata.new( event: [ Cocina::Models::Event.new(type: 'creation', - date: [ - { - value: work_version.work.created_at.strftime('%Y-%m-%d'), - encoding: { code: 'w3cdtf' } - } - ]) + date: [W3cdtf.props(admin_metadata_creation_date)]) ], note: [ Cocina::Models::DescriptiveValue.new( @@ -194,7 +127,10 @@ def admin_metadata ] ) end - # rubocop:enable Metrics/MethodLength + + def admin_metadata_creation_date + work_version.work.work_versions.first&.published_at || work_version.work.created_at + end end end end diff --git a/app/services/cocina_generator/description/w3cdtf.rb b/app/services/cocina_generator/description/w3cdtf.rb new file mode 100644 index 000000000..d5f0a68af --- /dev/null +++ b/app/services/cocina_generator/description/w3cdtf.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module CocinaGenerator + module Description + # Helper methods for W3CDTF + class W3cdtf + def self.props(date) + { + value: date.strftime('%Y-%m-%d'), + encoding: { code: 'w3cdtf' } + } + end + end + end +end diff --git a/db/structure.sql b/db/structure.sql index d1d26a84b..92dd88711 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -21,6 +21,8 @@ CREATE TYPE public.work_access AS ENUM ( SET default_tablespace = ''; +SET default_table_access_method = heap; + -- -- Name: abstract_contributors; Type: TABLE; Schema: public; Owner: - -- diff --git a/spec/factories/work_versions.rb b/spec/factories/work_versions.rb index 0a2a1232a..225811693 100644 --- a/spec/factories/work_versions.rb +++ b/spec/factories/work_versions.rb @@ -13,6 +13,7 @@ access { 'world' } state { 'first_draft' } description { 'initial version' } + published_at { Time.zone.parse('2019-01-01') } work factory :valid_work_version do @@ -159,4 +160,16 @@ license { 'none' } state { 'reserving_purl' } end + + trait :with_work do + transient do + collection { nil } + owner { association(:user) } + end + work { association :work, collection: collection, owner: owner, work_versions: [instance] } + + after(:create) do |work_version, _evaluator| + work_version.work.update(head: work_version) + end + end end diff --git a/spec/services/cocina_generator/description/generator_spec.rb b/spec/services/cocina_generator/description/generator_spec.rb index cee21366d..2714cd23b 100644 --- a/spec/services/cocina_generator/description/generator_spec.rb +++ b/spec/services/cocina_generator/description/generator_spec.rb @@ -7,7 +7,7 @@ let(:contributor) { build(:org_contributor) } let(:work_version) do - build(:work_version, :with_creation_date_range, :published, :with_keywords, + build(:work_version, :with_work, :with_creation_date_range, :published, :with_keywords, :with_some_untitled_related_links, :with_related_works, :with_contact_emails, contributors: [contributor], @@ -125,7 +125,7 @@ type: 'creation', date: [ { - value: '2007-02-10', + value: '2019-01-01', encoding: { code: 'w3cdtf' } @@ -147,11 +147,24 @@ uri: 'http://id.worldcat.org/fast/' } end + let(:deposit_publication_event) do + { + type: 'deposit', + date: [ + { + type: 'publication', + value: '2019-01-01', + encoding: { code: 'w3cdtf' } + } + ] + } + end it 'creates description cocina model' do expect(model).to eq( Cocina::Models::RequestDescription.new( event: [ + deposit_publication_event, { date: [ { @@ -249,13 +262,14 @@ let(:contributor2) { build(:person_contributor, role: 'Author') } let(:contributor3) { build(:org_contributor, role: 'Conference') } let(:work_version) do - build(:work_version, :with_contact_emails, + build(:work_version, :with_work, :with_contact_emails, contributors: [contributor1, contributor2, contributor3], title: 'Test title') end it 'creates forms as well as contributors in description cocina model' do expect(model).to eq(Cocina::Models::RequestDescription.new( + event: [deposit_publication_event], note: [ { type: 'abstract', value: 'test abstract' }, { type: 'preferred citation', value: 'test citation' } @@ -323,7 +337,7 @@ context 'when publisher and publication date are entered by user' do let(:contributor) { build(:org_contributor, role: 'Publisher') } let(:work_version) do - build(:work_version, :published, :with_contact_emails, + build(:work_version, :with_work, :published, :with_contact_emails, contributors: [contributor], title: 'Test title') end @@ -336,6 +350,7 @@ ], title: [{ value: 'Test title' }], event: [ + deposit_publication_event, { type: 'publication', contributor: [ @@ -375,7 +390,7 @@ context 'when publisher entered by user, no publication date' do let(:contributor) { build(:org_contributor, role: 'Publisher') } let(:work_version) do - build(:work_version, :with_contact_emails, + build(:work_version, :with_work, :with_contact_emails, contributors: [contributor], title: 'Test title') end @@ -387,6 +402,7 @@ ], title: [{ value: 'Test title' }], event: [ + deposit_publication_event, { type: 'publication', contributor: [ @@ -418,7 +434,7 @@ let(:person_author) { build(:person_author, role: 'Author') } let(:pub_contrib) { build(:org_contributor, role: 'Publisher') } let(:work_version) do - build(:work_version, :published, :with_contact_emails, + build(:work_version, :with_work, :published, :with_contact_emails, authors: [person_author], contributors: [pub_contrib], title: 'Test title') end @@ -452,6 +468,7 @@ } ], event: [ + deposit_publication_event, { type: 'publication', contributor: [ @@ -486,12 +503,13 @@ context 'when publication date of year only' do let(:work_version) do - build(:work_version, :published_with_year_only) + build(:work_version, :with_work, :published_with_year_only) end it 'creates event of type publication with year only date' do expect(model[:event]).to eq( [ + Cocina::Models::Event.new(deposit_publication_event).to_h, Cocina::Models::Event.new({ type: 'publication', date: [ @@ -509,12 +527,13 @@ context 'when publication date of year and month only' do let(:work_version) do - build(:work_version, :published_with_year_month_only) + build(:work_version, :with_work, :published_with_year_month_only) end it 'creates event of type publication with year and month only date' do expect(model[:event]).to eq( [ + Cocina::Models::Event.new(deposit_publication_event).to_h, Cocina::Models::Event.new({ type: 'publication', date: [ @@ -532,12 +551,13 @@ context 'when creation date of year only' do let(:work_version) do - build(:work_version, :with_creation_date_year_only) + build(:work_version, :with_work, :with_creation_date_year_only) end it 'creates event of type creation with year only date' do expect(model[:event]).to eq( [ + Cocina::Models::Event.new(deposit_publication_event).to_h, Cocina::Models::Event.new({ type: 'creation', date: [ @@ -555,12 +575,13 @@ context 'when creation date of year and month only' do let(:work_version) do - build(:work_version, :with_creation_date_year_month_only) + build(:work_version, :with_work, :with_creation_date_year_month_only) end it 'creates event of type creation with year and month only date' do expect(model[:event]).to eq( [ + Cocina::Models::Event.new(deposit_publication_event).to_h, Cocina::Models::Event.new({ type: 'creation', date: [ @@ -578,12 +599,13 @@ context 'when approximate creation date' do let(:work_version) do - build(:work_version, :with_approximate_creation_date) + build(:work_version, :with_work, :with_approximate_creation_date) end it 'creates event of type creation with approximate date' do expect(model[:event]).to eq( [ + Cocina::Models::Event.new(deposit_publication_event).to_h, Cocina::Models::Event.new({ type: 'creation', date: [ @@ -602,12 +624,13 @@ context 'when approximate creation date of year only' do let(:work_version) do - build(:work_version, :with_approximate_creation_date_year_only) + build(:work_version, :with_work, :with_approximate_creation_date_year_only) end it 'creates event of type creation with approximate year only date' do expect(model[:event]).to eq( [ + Cocina::Models::Event.new(deposit_publication_event).to_h, Cocina::Models::Event.new({ type: 'creation', date: [ @@ -626,12 +649,13 @@ context 'when approximate creation date of year and month only date' do let(:work_version) do - build(:work_version, :with_approximate_creation_date_year_month_only) + build(:work_version, :with_work, :with_approximate_creation_date_year_month_only) end it 'creates event of type creation with approximate year and month only date' do expect(model[:event]).to eq( [ + Cocina::Models::Event.new(deposit_publication_event).to_h, Cocina::Models::Event.new({ type: 'creation', date: [ @@ -650,12 +674,13 @@ context 'when approximate creation date range' do let(:work_version) do - build(:work_version, :with_approximate_creation_date_range) + build(:work_version, :with_work, :with_approximate_creation_date_range) end it 'creates event of type creation with approximate date' do expect(model[:event]).to eq( [ + Cocina::Models::Event.new(deposit_publication_event).to_h, Cocina::Models::Event.new({ type: 'creation', date: [ @@ -678,7 +703,7 @@ context 'with blank abstract and citation' do let(:work_version) do - build(:work_version, abstract: '', citation: '') + build(:work_version, :with_work, abstract: '', citation: '') end it 'does not add to model' do @@ -690,11 +715,31 @@ let(:keyword) { build(:keyword, uri: '', cocina_type: '') } let(:work_version) do - build(:work_version, keywords: [keyword]) + build(:work_version, :with_work, keywords: [keyword]) end it 'does not add URI to model' do expect(model[:subject]).to eq [Cocina::Models::DescriptiveValue.new({ value: 'MyKeyword', type: 'topic' }).to_h] end end + + context 'when work version does not have published_at' do + let(:work_version) do + build(:work_version, :with_work, published_at: nil) + end + + it 'uses work created_at for adminMetadata creation event' do + expect(model[:adminMetadata][:event]).to eq( + [ + Cocina::Models::Event.new(type: 'creation', + date: [ + { + value: '2007-02-10', + encoding: { code: 'w3cdtf' } + } + ]).to_h + ] + ) + end + end end