Skip to content
This repository has been archived by the owner on Nov 9, 2021. It is now read-only.

Commit

Permalink
Mapper for Hyrax metadata. Story #12.
Browse files Browse the repository at this point in the history
* The mapper can handle any fields that are defined in
`Hyrax::CoreMetadata` or `Hyrax::BasicMetadata`.

* If you are importing from a CSV file, the CSV headers should exactly
match the property names.  For an example, see:
spec/fixtures/hyrax/example.csv

* For fields with multiple values, there is a default delimiter that
will be used to separate the values.  You can set your own delimiter if
you need to.

* I removed the code that automatically requires files from the
spec/support directory because there are some support files that define
the `Hyrax` module and other hyrax-y classes.  We don't want to load
them for all tests because we don't want to accidentally have a
dependency on Hyrax.  Only load them in the exact tests where you need
them.

* Out of scope: Transforming data.  I didn't attempt to convert any
values to `RDF::URI` or `Date` objects.  All values are just imported as
`String`s.  See:
http://samvera.github.io/metadata_application_profile.html
  • Loading branch information
val99erie authored and bess committed Jan 3, 2019
1 parent 80d6fa7 commit f04d5a5
Show file tree
Hide file tree
Showing 12 changed files with 322 additions and 4 deletions.
23 changes: 23 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
1 change: 1 addition & 0 deletions lib/darlingtonia.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
84 changes: 84 additions & 0 deletions lib/darlingtonia/hyrax_basic_metadata_mapper.rb
Original file line number Diff line number Diff line change
@@ -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<Symbol>] 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
82 changes: 82 additions & 0 deletions spec/darlingtonia/hyrax_basic_metadata_mapper_spec.rb
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions spec/darlingtonia/record_importer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions spec/fixtures/hyrax/example.csv
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions spec/integration/import_csv_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
77 changes: 77 additions & 0 deletions spec/integration/import_hyrax_csv.rb
Original file line number Diff line number Diff line change
@@ -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
2 changes: 0 additions & 2 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 30 additions & 0 deletions spec/support/hyrax/basic_metadata.rb
Original file line number Diff line number Diff line change
@@ -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
15 changes: 15 additions & 0 deletions spec/support/hyrax/core_metadata.rb
Original file line number Diff line number Diff line change
@@ -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
7 changes: 5 additions & 2 deletions spec/support/shared_contexts/with_work_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit f04d5a5

Please sign in to comment.