diff --git a/.rubocop.yml b/.rubocop.yml index 885edd0..b7b82b6 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -9,12 +9,35 @@ Lint/HandleExceptions: - 'spec/**/*' - 'lib/darlingtonia/spec/**/*' +Metrics/AbcSize: + Exclude: + - 'spec/support/hyrax/basic_metadata.rb' + Metrics/BlockLength: Exclude: - 'spec/**/*' - 'lib/darlingtonia/spec/**/*' +Metrics/LineLength: + Exclude: + - 'lib/darlingtonia/hyrax_basic_metadata_mapper.rb' + +Metrics/MethodLength: + Exclude: + - 'spec/support/hyrax/basic_metadata.rb' + - 'lib/darlingtonia/hyrax_basic_metadata_mapper.rb' + RSpec/DescribeClass: Exclude: - 'spec/integration/**/*' - 'spec/*_spec.rb' + +RSpec/ExampleLength: + Exclude: + - 'spec/darlingtonia/hyrax_basic_metadata_mapper_spec.rb' + - 'spec/integration/import_hyrax_csv.rb' + +RSpec/MultipleExpectations: + Exclude: + - 'spec/darlingtonia/hyrax_basic_metadata_mapper_spec.rb' + - 'spec/integration/import_hyrax_csv.rb' diff --git a/lib/darlingtonia.rb b/lib/darlingtonia.rb index 96f3097..af6c179 100644 --- a/lib/darlingtonia.rb +++ b/lib/darlingtonia.rb @@ -54,6 +54,7 @@ def initialize require 'darlingtonia/version' require 'darlingtonia/metadata_mapper' require 'darlingtonia/hash_mapper' + require 'darlingtonia/hyrax_basic_metadata_mapper' require 'darlingtonia/importer' require 'darlingtonia/record_importer' diff --git a/lib/darlingtonia/hyrax_basic_metadata_mapper.rb b/lib/darlingtonia/hyrax_basic_metadata_mapper.rb new file mode 100644 index 0000000..fb06d86 --- /dev/null +++ b/lib/darlingtonia/hyrax_basic_metadata_mapper.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +module Darlingtonia + ## + # A mapper for Hyrax metadata. + # + # Maps from hash accessor syntax (`['title']`) to method call dot syntax (`.title`). + # + # The fields provided by this mapper are the same as the properties defined in `Hyrax::CoreMetadata` and `Hyrax::BasicMetadata`. + # + # @note This mapper allows you to set values for all the Hyrax fields, but depending on how you create the records, some of the values might get clobbered. For example, if you use Hyrax's actor stack to create records, it might overwrite fields like `date_modified` or `depositor`. + # + # @see HashMapper Parent class for more info and examples. + class HyraxBasicMetadataMapper < HashMapper + ## + # @return [Enumerable] The fields the mapper can process. + def fields + core_fields + basic_fields + end + + # Properties defined with `multiple: false` in + # Hyrax should return a single value instead of + # an Array of values. + def depositor + metadata['depositor'] + end + + def date_uploaded + metadata['date_uploaded'] + end + + def date_modified + metadata['date_modified'] + end + + def label + metadata['label'] + end + + def relative_path + metadata['relative_path'] + end + + def import_url + metadata['import_url'] + end + + ## + # @return [String] The delimiter that will be used to split a metadata field into separate values. + # @example + # mapper = HyraxBasicMetadataMapper.new + # mapper.metadata = { 'language' => 'English|~|French|~|Japanese' } + # mapper.language = ['English', 'French', 'Japanese'] + # + def delimiter + @delimiter ||= '|~|' + end + attr_writer :delimiter + + ## + # @see MetadataMapper#map_field + def map_field(name) + Array(metadata[name.to_s]&.split(delimiter)) + end + + protected + + # Properties defined in Hyrax::CoreMetadata + def core_fields + [:depositor, :title, :date_uploaded, :date_modified] + end + + # Properties defined in Hyrax::BasicMetadata + def basic_fields + [:label, :relative_path, :import_url, + :resource_type, :creator, :contributor, + :description, :keyword, :license, + :rights_statement, :publisher, :date_created, + :subject, :language, :identifier, + :based_near, :related_url, + :bibliographic_citation, :source] + end + end +end diff --git a/spec/darlingtonia/hyrax_basic_metadata_mapper_spec.rb b/spec/darlingtonia/hyrax_basic_metadata_mapper_spec.rb new file mode 100644 index 0000000..49436ee --- /dev/null +++ b/spec/darlingtonia/hyrax_basic_metadata_mapper_spec.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Darlingtonia::HyraxBasicMetadataMapper do + let(:mapper) { described_class.new } + + # Properties defined in Hyrax::CoreMetadata + let(:core_fields) do + [:depositor, :title, :date_uploaded, :date_modified] + end + + # Properties defined in Hyrax::BasicMetadata + let(:basic_fields) do + [:label, :relative_path, :import_url, + :resource_type, :creator, :contributor, + :description, :keyword, :license, + :rights_statement, :publisher, :date_created, + :subject, :language, :identifier, :based_near, + :related_url, :bibliographic_citation, :source] + end + + it_behaves_like 'a Darlingtonia::Mapper' do + let(:metadata) do + { title: ['A Title for a Record'], + my_custom_field: ['This gets ignored'] } + end + let(:expected_fields) { core_fields + basic_fields } + end + + context 'with metadata, but some missing fields' do + before { mapper.metadata = metadata } + let(:metadata) do + { 'depositor' => 'someone@example.org', + 'title' => 'A Title', + 'language' => 'English' } + end + + it 'provides methods for the fields, even fields that aren\'t included in the metadata' do + expect(metadata).to include('title') + expect(mapper).to respond_to(:title) + + expect(metadata).not_to include('label') + expect(mapper).to respond_to(:label) + end + + it 'returns single values for single-value fields' do + expect(mapper.depositor).to eq 'someone@example.org' + expect(mapper.date_uploaded).to eq nil + expect(mapper.date_modified).to eq nil + expect(mapper.label).to eq nil + expect(mapper.relative_path).to eq nil + expect(mapper.import_url).to eq nil + end + + it 'returns array values for multi-value fields' do + expect(mapper.title).to eq ['A Title'] + expect(mapper.language).to eq ['English'] + expect(mapper.keyword).to eq [] + expect(mapper.subject).to eq [] + end + end + + context 'fields with multiple values' do + before { mapper.metadata = metadata } + let(:metadata) do + { 'title' => 'A Title', + 'language' => 'English|~|French|~|Japanese' } + end + + it 'splits the values using the delimiter' do + expect(mapper.title).to eq ['A Title'] + expect(mapper.language).to eq ['English', 'French', 'Japanese'] + expect(mapper.keyword).to eq [] + end + + it 'can set a different delimiter' do + expect(mapper.delimiter).to eq '|~|' + mapper.delimiter = 'ಠ_ಠ' + expect(mapper.delimiter).to eq 'ಠ_ಠ' + end + end +end diff --git a/spec/darlingtonia/record_importer_spec.rb b/spec/darlingtonia/record_importer_spec.rb index f4897a5..4211051 100644 --- a/spec/darlingtonia/record_importer_spec.rb +++ b/spec/darlingtonia/record_importer_spec.rb @@ -17,6 +17,7 @@ end context 'with a registered work type' do + load File.expand_path("../../support/shared_contexts/with_work_type.rb", __FILE__) include_context 'with a work type' it 'creates a work for record' do diff --git a/spec/fixtures/hyrax/example.csv b/spec/fixtures/hyrax/example.csv new file mode 100644 index 0000000..cdc7237 --- /dev/null +++ b/spec/fixtures/hyrax/example.csv @@ -0,0 +1,3 @@ +title,depositor,date_uploaded,date_modified,label,relative_path,import_url,resource_type,creator,contributor,description,keyword,license,rights_statement,publisher,date_created,subject,language,identifier,based_near,related_url,bibliographic_citation,source +Work 1 Title,user@example.com,2018-12-21,2018-01-01,Work 1 Label,tmp/files,https://example.com,Work 1 Type,Work 1 creator,Work 1 contrib,Desc 1,Key 1,Lic 1,RS 1,Pub 1,2018-06-06,Subj 1,English|~|Japanese,Ident 1,Based 1,https://example.com/related,Bib 1,Source 1 +Work 2 Title,,1970-12-21,,Work 2 Label,,,Work 2 Type,,,Desc 2,,,,Pub 2,,Subj 2 diff --git a/spec/integration/import_csv_spec.rb b/spec/integration/import_csv_spec.rb index 56ecd78..d969cc9 100644 --- a/spec/integration/import_csv_spec.rb +++ b/spec/integration/import_csv_spec.rb @@ -7,6 +7,7 @@ let(:parser) { Darlingtonia::CsvParser.new(file: file) } let(:file) { File.open('spec/fixtures/example.csv') } + load File.expand_path("../../support/shared_contexts/with_work_type.rb", __FILE__) include_context 'with a work type' it 'creates a record for each CSV line' do diff --git a/spec/integration/import_hyrax_csv.rb b/spec/integration/import_hyrax_csv.rb new file mode 100644 index 0000000..fcfb6bd --- /dev/null +++ b/spec/integration/import_hyrax_csv.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe 'importing a CSV with Hyrax defaults', :clean do + subject(:importer) { Darlingtonia::Importer.new(parser: parser) } + let(:parser) { Darlingtonia::CsvParser.new(file: csv_file) } + + let(:csv_file) { File.open('spec/fixtures/hyrax/example.csv') } + after { csv_file.close } + + before do + # Force it to use the Hyrax mapper instead of the default mapper + allow(Darlingtonia::HashMapper).to receive(:new).and_return(Darlingtonia::HyraxBasicMetadataMapper.new) + end + + load File.expand_path("../../support/shared_contexts/with_work_type.rb", __FILE__) + include_context 'with a work type' + + it 'creates the record(s)' do + expect { importer.import }.to change { Work.count }.to 2 + + works = Work.all + work1 = works.find { |w| w.title == ['Work 1 Title'] } + work2 = works.find { |w| w.title == ['Work 2 Title'] } + + # First Record + expect(work1.depositor).to eq 'user@example.com' + expect(work1.date_uploaded).to eq '2018-12-21' + expect(work1.date_modified).to eq '2018-01-01' + expect(work1.label).to eq 'Work 1 Label' + expect(work1.relative_path).to eq 'tmp/files' + expect(work1.import_url).to eq 'https://example.com' + expect(work1.resource_type).to eq ['Work 1 Type'] + expect(work1.creator).to eq ['Work 1 creator'] + expect(work1.contributor).to eq ['Work 1 contrib'] + expect(work1.description).to eq ['Desc 1'] + expect(work1.keyword).to eq ['Key 1'] + expect(work1.license).to eq ['Lic 1'] + expect(work1.rights_statement).to eq ['RS 1'] + expect(work1.publisher).to eq ['Pub 1'] + expect(work1.date_created).to eq ['2018-06-06'] + expect(work1.subject).to eq ['Subj 1'] + + # An example with 2 values + expect(work1.language).to contain_exactly('English', 'Japanese') + + expect(work1.identifier).to eq ['Ident 1'] + expect(work1.based_near).to eq ['Based 1'] + expect(work1.related_url).to eq ['https://example.com/related'] + expect(work1.bibliographic_citation).to eq ['Bib 1'] + expect(work1.source).to eq ['Source 1'] + + # Second Record + expect(work2.depositor).to be_nil + expect(work2.date_uploaded).to eq '1970-12-21' + expect(work2.date_modified).to be_nil + expect(work2.label).to eq 'Work 2 Label' + expect(work2.relative_path).to be_nil + expect(work2.import_url).to be_nil + expect(work2.resource_type).to eq ['Work 2 Type'] + expect(work2.creator).to eq [] + expect(work2.contributor).to eq [] + expect(work2.description).to eq ['Desc 2'] + expect(work2.keyword).to eq [] + expect(work2.license).to eq [] + expect(work2.rights_statement).to eq [] + expect(work2.publisher).to eq ['Pub 2'] + expect(work2.date_created).to eq [] + expect(work2.subject).to eq ['Subj 2'] + expect(work2.language).to eq [] + expect(work2.identifier).to eq [] + expect(work2.based_near).to eq [] + expect(work2.related_url).to eq [] + expect(work2.bibliographic_citation).to eq [] + expect(work2.source).to eq [] + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index fc48a27..51b765e 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -9,8 +9,6 @@ require 'darlingtonia' require 'darlingtonia/spec' -Dir['./spec/support/**/*.rb'].each { |f| require f } - RSpec.configure do |config| config.filter_run focus: true config.run_all_when_everything_filtered = true diff --git a/spec/support/hyrax/basic_metadata.rb b/spec/support/hyrax/basic_metadata.rb new file mode 100644 index 0000000..0f4c51d --- /dev/null +++ b/spec/support/hyrax/basic_metadata.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module Hyrax + module BasicMetadata + def self.included(work) + work.property :label, predicate: ActiveFedora::RDF::Fcrepo::Model.downloadFilename, multiple: false + work.property :relative_path, predicate: ::RDF::URI.new('http://scholarsphere.psu.edu/ns#relativePath'), multiple: false + work.property :import_url, predicate: ::RDF::URI.new('http://scholarsphere.psu.edu/ns#importUrl'), multiple: false + work.property :resource_type, predicate: ::RDF::Vocab::DC.type + work.property :creator, predicate: ::RDF::Vocab::DC11.creator + work.property :contributor, predicate: ::RDF::Vocab::DC11.contributor + work.property :description, predicate: ::RDF::Vocab::DC11.description + work.property :keyword, predicate: ::RDF::Vocab::DC11.relation + work.property :license, predicate: ::RDF::Vocab::DC.rights + work.property :rights_statement, predicate: ::RDF::Vocab::EDM.rights + work.property :publisher, predicate: ::RDF::Vocab::DC11.publisher + work.property :date_created, predicate: ::RDF::Vocab::DC.created + work.property :subject, predicate: ::RDF::Vocab::DC11.subject + work.property :language, predicate: ::RDF::Vocab::DC11.language + work.property :identifier, predicate: ::RDF::Vocab::DC.identifier + + # Note: based_near is defined differently here than in Hyrax. + work.property :based_near, predicate: ::RDF::Vocab::FOAF.based_near + + work.property :related_url, predicate: ::RDF::RDFS.seeAlso + work.property :bibliographic_citation, predicate: ::RDF::Vocab::DC.bibliographicCitation + work.property :source, predicate: ::RDF::Vocab::DC.source + end + end +end diff --git a/spec/support/hyrax/core_metadata.rb b/spec/support/hyrax/core_metadata.rb new file mode 100644 index 0000000..982b59d --- /dev/null +++ b/spec/support/hyrax/core_metadata.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Hyrax + module CoreMetadata + def self.included(work) + work.property :depositor, predicate: ::RDF::URI.new('http://id.loc.gov/vocabulary/relators/dpt'), multiple: false + + work.property :title, predicate: ::RDF::Vocab::DC.title + + work.property :date_uploaded, predicate: ::RDF::Vocab::DC.dateSubmitted, multiple: false + + work.property :date_modified, predicate: ::RDF::Vocab::DC.modified, multiple: false + end + end +end diff --git a/spec/support/shared_contexts/with_work_type.rb b/spec/support/shared_contexts/with_work_type.rb index aff04ef..82c3a97 100644 --- a/spec/support/shared_contexts/with_work_type.rb +++ b/spec/support/shared_contexts/with_work_type.rb @@ -3,9 +3,12 @@ shared_context 'with a work type' do # A work type must be defined for the default `RecordImporter` to save objects before do + load './spec/support/hyrax/core_metadata.rb' + load './spec/support/hyrax/basic_metadata.rb' + class Work < ActiveFedora::Base - property :title, predicate: ::RDF::URI('http://example.com/title') - property :description, predicate: ::RDF::URI('http://example.com/description') + include ::Hyrax::CoreMetadata + include ::Hyrax::BasicMetadata end module Hyrax