darlingtonia 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a2fc4e8abdff2180e2ef7ff1303661e806c4c304
4
+ data.tar.gz: 7d0f74053c870263e63ff9b84dc72f77c3f923eb
5
+ SHA512:
6
+ metadata.gz: 752c341d5200f4bdb1c9a59a3f0188a31522558d20ea6e0d2c7f01eec36e4581aea4e6b6ffbc73c6fbc3dbce81db17e0af80a1c29ddf54bc7b0d453567f36351
7
+ data.tar.gz: 2ebc1641f150f1e34316a4a60f6fbb16d8fd6cf13a3a5a624f9b0d92707817662df32cdf8ff20512c4efb4ce9924f6c16fde9cef9f674174c6ab3f6483109771
@@ -0,0 +1,2 @@
1
+ tmp
2
+ Gemfile.lock
@@ -0,0 +1,13 @@
1
+ inherit_gem:
2
+ bixby: bixby_default.yml
3
+
4
+ AllCops:
5
+ TargetRubyVersion: 2.3
6
+
7
+ Metrics/BlockLength:
8
+ Exclude:
9
+ - 'spec/**/*'
10
+
11
+ RSpec/DescribeClass:
12
+ Exclude:
13
+ - 'spec/integration/**/*'
@@ -0,0 +1,14 @@
1
+ language: ruby
2
+ sudo: false
3
+ cache: bundler
4
+ dist: trusty
5
+
6
+ rvm:
7
+ - 2.3.4
8
+ - 2.4
9
+ - 2.5
10
+ script:
11
+ - bundle exec rake
12
+ matrix:
13
+ allow_failures:
14
+ - rvm: 2.5
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
6
+
7
+ gemspec
8
+
9
+ unless ENV['CI']
10
+ gem 'guard'
11
+ gem 'pry'
12
+ end
@@ -0,0 +1,15 @@
1
+ Darlingtonia
2
+ ============
3
+
4
+ Object import for Hyrax.
5
+
6
+ Development
7
+ -----------
8
+
9
+ ```sh
10
+ git clone https://github.com/curationexperts/darlingtonia
11
+ cd darlingtonia
12
+
13
+ bundle install
14
+ bundle exec rspec
15
+ ```
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require 'bundler/setup'
5
+ rescue LoadError
6
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
7
+ end
8
+
9
+ require 'fcrepo_wrapper'
10
+ require 'solr_wrapper'
11
+ require 'active_fedora/rake_support'
12
+
13
+ require 'rspec/core/rake_task'
14
+ require 'rubocop/rake_task'
15
+
16
+ Bundler::GemHelper.install_tasks
17
+
18
+ desc 'Run style checker'
19
+ RuboCop::RakeTask.new(:rubocop) do |task|
20
+ task.fail_on_error = true
21
+ end
22
+
23
+ desc 'Run specs'
24
+ RSpec::Core::RakeTask.new(:spec)
25
+
26
+ desc 'Run specs with Fedora & Solr servers'
27
+ task :spec_with_server do
28
+ with_test_server { Rake::Task['spec'].invoke }
29
+ end
30
+
31
+ desc 'Check style and run specs'
32
+ task ci: %w[rubocop spec_with_server]
33
+
34
+ task default: :ci
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.push File.expand_path('../lib', __FILE__)
4
+ require 'darlingtonia/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = 'darlingtonia'
8
+ gem.version = Darlingtonia::VERSION
9
+ gem.platform = Gem::Platform::RUBY
10
+ gem.authors = ['Tom Johnson', 'Data Curation Experts']
11
+ gem.email = 'tom@curationexperts.com'
12
+ gem.summary = 'Hyrax importers.'
13
+ gem.license = 'Apache-2.0'
14
+ gem.files = %w[AUTHORS CHANGELOG.md README.md LICENSE] +
15
+ Dir.glob('lib/**/*.rb')
16
+ gem.require_paths = %w[lib]
17
+ gem.has_rdoc = false
18
+
19
+ gem.required_ruby_version = '>= 2.3.4'
20
+
21
+ gem.add_dependency 'active-fedora', '>= 11.0', '<= 12.99'
22
+
23
+ gem.add_development_dependency 'yard', '~> 0.9'
24
+ gem.add_development_dependency 'bixby', '~> 0.3'
25
+ gem.add_development_dependency 'hyrax-spec', '~> 0.2'
26
+ gem.add_development_dependency 'rspec', '~> 3.6'
27
+ gem.add_development_dependency 'coveralls', '~> 0.8'
28
+ gem.add_development_dependency 'solr_wrapper', '~> 0.3'
29
+ gem.add_development_dependency 'fcrepo_wrapper', '~> 0.9'
30
+
31
+ gem.files = `git ls-files`.split("\n")
32
+ gem.test_files = `git ls-files -- {spec}/*`.split("\n")
33
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_fedora'
4
+
5
+ ##
6
+ # Bulk object import for Hyrax
7
+ module Darlingtonia
8
+ require 'darlingtonia/version'
9
+ require 'darlingtonia/hash_mapper'
10
+
11
+ require 'darlingtonia/importer'
12
+ require 'darlingtonia/record_importer'
13
+
14
+ require 'darlingtonia/input_record'
15
+
16
+ require 'darlingtonia/parser'
17
+ require 'darlingtonia/parsers/csv_parser'
18
+
19
+ require 'darlingtonia/validator'
20
+ require 'darlingtonia/validators/csv_format_validator'
21
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Darlingtonia
4
+ ##
5
+ # A Validator that always gives an error named `:everytime`.
6
+ #
7
+ # @example
8
+ # validator = AlwaysInvalidValidator.new
9
+ # validator.validate(:anything, :at, :all) # => [Error<#...>]
10
+ class AlwaysInvalidValidator < Validator
11
+ ##
12
+ # @return [Array<Validator::Error>]
13
+ def validate(*)
14
+ [Error.new(self, :everytime)]
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Darlingtonia
4
+ ##
5
+ # A generic metadata mapper for input records
6
+ #
7
+ # Maps from hash accessor syntax (`['title']`) to method call dot syntax (`.title`)
8
+ class HashMapper
9
+ ##
10
+ # @!attribute [r] meadata
11
+ # @return [Hash<String, String>]
12
+ attr_reader :metadata
13
+
14
+ ##
15
+ # @param meta [#to_h]
16
+ # @return [Hash<String, String>]
17
+ def metadata=(meta)
18
+ @metadata = meta.to_h
19
+ end
20
+
21
+ ##
22
+ # @param name [Symbol]
23
+ #
24
+ # @return [Boolean]
25
+ def field?(name)
26
+ fields.include?(name)
27
+ end
28
+
29
+ ##
30
+ # @return [Enumerable<Symbol>] The fields the mapper can process
31
+ def fields
32
+ return [] if metadata.nil?
33
+ metadata.keys.map(&:to_sym)
34
+ end
35
+
36
+ def method_missing(method_name, *args, &block)
37
+ return Array(metadata[method_name.to_s]) if fields.include?(method_name)
38
+ super
39
+ end
40
+
41
+ def respond_to_missing?(method_name, include_private = false)
42
+ field?(method_name) || super
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Darlingtonia
4
+ class Importer
5
+ extend Forwardable
6
+
7
+ ##
8
+ # @!attribute [rw] parser
9
+ # @return [Parser]
10
+ # @!attribute [rw] record_importer
11
+ # @return [RecordImporter]
12
+ attr_accessor :parser, :record_importer
13
+
14
+ ##
15
+ # @!method records()
16
+ # @see Parser#records
17
+ def_delegator :parser, :records, :records
18
+
19
+ ##
20
+ # @param parser [Parser]
21
+ def initialize(parser:, record_importer: RecordImporter.new)
22
+ self.parser = parser
23
+ self.record_importer = record_importer
24
+ end
25
+
26
+ ##
27
+ # @return [void]
28
+ def import
29
+ records.each { |record| record_importer.import(record: record) }
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Darlingtonia
4
+ class InputRecord
5
+ ##
6
+ # @!attribute [rw] mapper
7
+ # @return [#map_fields]
8
+ attr_accessor :mapper
9
+
10
+ ##
11
+ # @param metadata [Object]
12
+ # @param mapper [#map_fields]
13
+ def initialize(mapper: HashMapper.new)
14
+ self.mapper = mapper
15
+ end
16
+
17
+ class << self
18
+ def from(metadata:, mapper: HashMapper.new)
19
+ mapper.metadata = metadata
20
+ new(mapper: mapper)
21
+ end
22
+ end
23
+
24
+ ##
25
+ # @return [Hash<Symbol, Object>]
26
+ def attributes
27
+ mapper.fields.each_with_object({}) do |field, attrs|
28
+ attrs[field] = public_send(field)
29
+ end
30
+ end
31
+
32
+ ##
33
+ # Respond to methods matching mapper fields
34
+ def method_missing(method_name, *args, &block)
35
+ return super unless mapper.field?(method_name)
36
+ mapper.public_send(method_name)
37
+ end
38
+
39
+ ##
40
+ # @see #method_missing
41
+ def respond_to_missing?(method_name, include_private = false)
42
+ mapper.field?(method_name) || super
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Darlingtonia
4
+ ##
5
+ # A generic parser
6
+ #
7
+ # @example
8
+ # file = File.open('path/to/import/manifest.csv')
9
+ #
10
+ # Parser.for(file: file).records
11
+ #
12
+ class Parser
13
+ @subclasses = [] # @private
14
+
15
+ ##
16
+ # @!attribute [rw] file
17
+ # @return [File]
18
+ # @!attribute [rw] validators
19
+ # @return [Array<Validator>]
20
+ # @!attribute [r] errors
21
+ # @return [Array]
22
+ attr_accessor :file, :validators
23
+ attr_reader :errors
24
+
25
+ ##
26
+ # @param file [File]
27
+ def initialize(file:, **_opts)
28
+ self.file = file
29
+ @errors = []
30
+ @validators ||= []
31
+
32
+ yield self if block_given?
33
+ end
34
+
35
+ class << self
36
+ ##
37
+ # @param file [Object]
38
+ #
39
+ # @return [Darlingtonia::Parser] a parser instance appropriate for
40
+ # the arguments
41
+ #
42
+ # @raise [NoParserError]
43
+ def for(file:)
44
+ klass =
45
+ @subclasses.find { |k| k.match?(file: file) } ||
46
+ raise(NoParserError)
47
+
48
+ klass.new(file: file)
49
+ end
50
+
51
+ ##
52
+ # @abstract
53
+ # @return [Boolean]
54
+ def match?(**_opts); end
55
+
56
+ private
57
+
58
+ ##
59
+ # @private Register a new class when inherited
60
+ def inherited(subclass)
61
+ @subclasses << subclass
62
+ super
63
+ end
64
+ end
65
+
66
+ ##
67
+ # @abstract
68
+ #
69
+ # @yield [record] gives each record in the file to the block
70
+ # @yieldparam record [ImportRecord]
71
+ #
72
+ # @return [Enumerable<ImportRecord>]
73
+ def records
74
+ raise NotImplementedError
75
+ end
76
+
77
+ ##
78
+ # @return [Boolean] true if the
79
+ def valid?
80
+ errors.empty?
81
+ end
82
+
83
+ ##
84
+ # @return [Boolean]
85
+ def validate
86
+ validators.each_with_object(errors) do |validator, errs|
87
+ errs.concat(validator.validate(parser: self))
88
+ end
89
+
90
+ valid?
91
+ end
92
+
93
+ ##
94
+ # @return [true]
95
+ # @raise [ValidationError] if the file to parse is invalid
96
+ def validate!
97
+ validate || raise(ValidationError)
98
+ end
99
+
100
+ class NoParserError < TypeError; end
101
+ class ValidationError < RuntimeError; end
102
+ end
103
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'csv'
4
+
5
+ module Darlingtonia
6
+ ##
7
+ # A parser for CSV files
8
+ class CsvParser < Parser
9
+ EXTENSION = '.csv'
10
+
11
+ class << self
12
+ ##
13
+ # Matches all '.csv' filenames.
14
+ def match?(file:, **_opts)
15
+ File.extname(file) == EXTENSION
16
+ rescue TypeError
17
+ false
18
+ end
19
+ end
20
+
21
+ ##
22
+ # Gives a record for each line in the .csv
23
+ #
24
+ # @see Parser#records
25
+ def records
26
+ return enum_for(:records) unless block_given?
27
+
28
+ file.rewind
29
+
30
+ CSV.parse(file.read, headers: true).each do |row|
31
+ yield InputRecord.from(metadata: row)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Darlingtonia
4
+ class RecordImporter
5
+ ##
6
+ # @param record [ImportRecord]
7
+ #
8
+ # @return [void]
9
+ def import(record:)
10
+ import_type.create(record.attributes)
11
+ end
12
+
13
+ def import_type
14
+ raise 'No curation_concern found for import' unless
15
+ defined?(Hyrax) && Hyrax&.config&.curation_concerns&.any?
16
+
17
+ Hyrax.config.curation_concerns.first
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Darlingtonia
4
+ ##
5
+ # @abstract A null validator; always returns an empty error collection
6
+ class Validator
7
+ Error = Struct.new(:validator, :name, :description, :lineno)
8
+
9
+ ##
10
+ # @param parser [Parser]
11
+ # @param error_stream [#add]
12
+ #
13
+ # @return [Enumerator<Error>] a collection of errors found in validation
14
+ #
15
+ # rubocop:disable Lint/UnusedMethodArgument
16
+ def validate(parser:, **)
17
+ []
18
+ end
19
+ # rubocop:enable Lint/UnusedMethodArgument
20
+ end
21
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Darlingtonia
4
+ ##
5
+ # A validator for correctly formatted CSV
6
+ class CsvFormatValidator < Validator
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Darlingtonia
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Darlingtonia::CsvFormatValidator do
6
+ it_behaves_like 'a Darlingtonia::Validator'
7
+ end
@@ -0,0 +1,48 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require 'spec_helper'
5
+
6
+ describe Darlingtonia::CsvParser do
7
+ subject(:parser) { described_class.new(file: file) }
8
+ let(:file) { Tempfile.new(['fake', '.csv']) }
9
+
10
+ shared_context 'with content' do
11
+ let(:csv_content) do
12
+ <<-EOS
13
+ title,description,date
14
+ The Moomins and the Great Flood,"The Moomins and the Great Flood (Swedish: Småtrollen och den stora översvämningen, literally The Little Trolls and the Great Flood) is a book written by Finnish author Tove Jansson in 1945, during the end of World War II. It was the first book to star the Moomins, but is often seen as a prelude to the main Moomin books, as most of the main characters are introduced in the next book.",1945
15
+ Comet in Moominland,"Comet in Moominland is the second in Tove Jansson's series of Moomin books. Published in 1946, it marks the first appearance of several main characters, like Snufkin and the Snork Maiden.",1946
16
+ EOS
17
+ end
18
+
19
+ let(:record_count) { 2 }
20
+
21
+ before do
22
+ file.write(csv_content)
23
+ file.rewind
24
+ end
25
+ end
26
+
27
+ it_behaves_like 'a Darlingtonia::Parser' do
28
+ include_context 'with content'
29
+ end
30
+
31
+ it 'matches .csv files' do
32
+ expect(Darlingtonia::Parser.for(file: file)).to be_a described_class
33
+ end
34
+
35
+ describe '#records' do
36
+ include_context 'with content'
37
+
38
+ it 'has the correct titles' do
39
+ expect(parser.records.map(&:title))
40
+ .to contain_exactly(['The Moomins and the Great Flood'],
41
+ ['Comet in Moominland'])
42
+ end
43
+
44
+ it 'has correct other fields' do
45
+ expect(parser.records.map(&:date)).to contain_exactly(['1945'], ['1946'])
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe Darlingtonia::HashMapper do
4
+ it_behaves_like 'a Darlingtonia::Mapper' do
5
+ let(:expected_fields) { metadata.keys.map(&:to_sym) }
6
+ let(:metadata) { { 'a_field' => 'a', 'b_field' => 'b', 'c_field' => 'c' } }
7
+ end
8
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Darlingtonia::Importer do
6
+ subject(:importer) { described_class.new(parser: parser) }
7
+ let(:parser) { FakeParser.new(file: input) }
8
+ let(:input) { [{ 'title' => '1' }, { 'title' => '2' }, { 'title' => '3' }] }
9
+
10
+ let(:fake_record_importer) do
11
+ Class.new do
12
+ def import(record:)
13
+ records << record.attributes
14
+ end
15
+
16
+ def records
17
+ @records ||= []
18
+ end
19
+ end
20
+ end
21
+
22
+ describe '#records' do
23
+ it 'reflects the parsed records' do
24
+ expect(importer.records.map(&:attributes))
25
+ .to contain_exactly(*parser.records.map(&:attributes))
26
+ end
27
+ end
28
+
29
+ describe '#import' do
30
+ let(:record_importer) { fake_record_importer.new }
31
+
32
+ before { importer.record_importer = record_importer }
33
+
34
+ it 'sends records to the record importer' do
35
+ expect { importer.import }
36
+ .to change { record_importer.records }
37
+ .from(be_empty)
38
+ .to a_collection_containing_exactly(*importer.records.map(&:attributes))
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe Darlingtonia::InputRecord do
4
+ subject(:record) { described_class.from(metadata: metadata) }
5
+
6
+ let(:metadata) do
7
+ { 'title' => 'Comet in Moominland',
8
+ 'description' => 'A book about moomins.' }
9
+ end
10
+
11
+ it 'has metadata and a mapper' do
12
+ is_expected
13
+ .to have_attributes(mapper: an_instance_of(Darlingtonia::HashMapper))
14
+ end
15
+
16
+ describe '#attributes' do
17
+ it 'handles basic text fields' do
18
+ expect(record.attributes).to include(:title, :description)
19
+ end
20
+ end
21
+
22
+ describe 'mapped fields' do
23
+ it 'has methods for metadata fields' do
24
+ expect(record.title).to contain_exactly metadata['title']
25
+ end
26
+
27
+ it 'has methods for additional mapped metadata fields' do
28
+ expect(record.description).to contain_exactly metadata['description']
29
+ end
30
+
31
+ it 'knows it responds to methods for metadata fields' do
32
+ expect(record).to respond_to :title
33
+ end
34
+
35
+ it 'knows it responds to methods for additional metadata fields' do
36
+ expect(record).to respond_to :description
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Darlingtonia::Parser do
6
+ subject(:parser) { described_class.new(file: file) }
7
+ let(:file) { :fake_file }
8
+
9
+ it_behaves_like 'a Darlingtonia::Parser'
10
+
11
+ describe '.for' do
12
+ it 'raises an error' do
13
+ expect { described_class.for(file: file) }.to raise_error TypeError
14
+ end
15
+
16
+ context 'with a matching parser subclass' do
17
+ before(:context) do
18
+ ##
19
+ # An importer that matches all types
20
+ class FakeParser < described_class
21
+ class << self
22
+ def match?(**_opts)
23
+ true
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ after(:context) { Object.send(:remove_const, :FakeParser) }
30
+
31
+ it 'returns an importer instance' do
32
+ expect(described_class.for(file: file)).to be_a FakeParser
33
+ end
34
+ end
35
+ end
36
+
37
+ describe '#records' do
38
+ it 'raises NotImplementedError' do
39
+ expect { parser.records }.to raise_error NotImplementedError
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Darlingtonia::Validator do
6
+ it_behaves_like 'a Darlingtonia::Validator'
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Darlingtonia::VERSION do
6
+ it { is_expected.to be_a String }
7
+ end
@@ -0,0 +1,4 @@
1
+ title,description
2
+ Fake Item,A description of a fake item.
3
+ Fake Item with Quoted Description,"Lorem ipsum dolor sit amet, cu mel habeo antiopam, id pri mucius oporteat. No graeco accumsan deterruisset est. Vix te sonet doctus perpetua, mei at odio eius nostrum. Ex postea quidam menandri duo. Rebum ullum cu mel.\nAperiam malorum indoctum ad nec.\nIn duo nonumes accusata, detraxit adipisci philosophia quo id. Ei usu volutpat vituperatoribus. Ut veniam dolorem qui. Ei legendos erroribus usu. Mentitum moderatius ei est, mei eius magna alterum no. Legere vivendum per ad, vim ei putent facilis."
4
+ Fake Item with Unicode Description,"Лорем ипсум долор сит амет, ех мовет толлит модератиус ест. Еу яуидам сенсерит цонсецтетуер про, при иисяуе ерудити цоррумпит ат. Ех усу нусяуам пхаедрум темпорибус, ест ат омнесяуе инструцтиор.\nЯуо ех мелиоре инсоленс праесент, иудицо тантас еурипидис хис ут. Аццусам урбанитас инструцтиор ан еам. Но хас вениам дицунт дебитис, нец ут суас аццусам перицула, нец риденс аетерно виртуте не.\nАт про еним вереар, ут солум юсто меи."
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe 'importing a csv batch' do
6
+ subject(:importer) { Darlingtonia::Importer.new(parser: parser) }
7
+ let(:parser) { Darlingtonia::CsvParser.new(file: file) }
8
+ let(:file) { File.open('spec/fixtures/example.csv') }
9
+
10
+ # A work type must be defined for the default `RecordImporter` to save objects
11
+ before do
12
+ class Work < ActiveFedora::Base
13
+ property :title, predicate: ::RDF::URI('http://example.com/title')
14
+ property :description, predicate: ::RDF::URI('http://example.com/description')
15
+ end
16
+
17
+ module Hyrax
18
+ def self.config
19
+ Config.new
20
+ end
21
+
22
+ class Config
23
+ def curation_concerns
24
+ [Work]
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ after do
31
+ Object.send(:remove_const, :Hyrax)
32
+ Object.send(:remove_const, :Work)
33
+ end
34
+
35
+ it 'creates a record for each CSV line' do
36
+ expect { importer.import }.to change { Work.count }.to 3
37
+ end
38
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pry' unless ENV['CI']
4
+ ENV['environment'] ||= 'test'
5
+
6
+ require 'bundler/setup'
7
+ require 'darlingtonia'
8
+
9
+ Dir['./spec/support/**/*.rb'].each { |f| require f }
10
+
11
+ RSpec.configure do |config|
12
+ config.filter_run focus: true
13
+ config.run_all_when_everything_filtered = true
14
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ class FakeParser < Darlingtonia::Parser
4
+ METADATA = [{ 'title' => '1' }, { 'title' => '2' }, { 'title' => '3' }].freeze
5
+
6
+ def initialize(file: METADATA)
7
+ super
8
+ end
9
+
10
+ def records
11
+ return enum_for(:records) unless block_given?
12
+
13
+ file.each { |hsh| yield Darlingtonia::InputRecord.from(metadata: hsh) }
14
+ end
15
+ end
16
+
17
+ require 'support/shared_examples/a_parser'
18
+
19
+ # rubocop:disable RSpec/FilePath
20
+ describe FakeParser do
21
+ it_behaves_like 'a Darlingtonia::Parser' do
22
+ subject(:parser) { described_class.new }
23
+ let(:record_count) { 3 }
24
+ end
25
+ end
26
+ # rubocop:enable RSpec/FilePath
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ shared_examples 'a Darlingtonia::Mapper' do
4
+ subject(:mapper) { described_class.new }
5
+
6
+ before { mapper.metadata = metadata }
7
+
8
+ describe '#metadata' do
9
+ it 'can be set' do
10
+ expect { mapper.metadata = nil }
11
+ .to change { mapper.metadata }
12
+ end
13
+ end
14
+
15
+ describe '#field?' do
16
+ it 'does not have bogus fields' do
17
+ expect(mapper.field?(:NOT_A_REAL_FIELD)).to be_falsey
18
+ end
19
+
20
+ it 'has fields that are expected' do
21
+ if defined?(expected_fields)
22
+ expected_fields.each do |field|
23
+ expect(mapper.field?(field)).to be_truthy
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ describe '#fields' do
30
+ it { expect(mapper.fields).to contain_exactly(*expected_fields) }
31
+ end
32
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'darlingtonia/always_invalid_validator'
4
+
5
+ shared_examples 'a Darlingtonia::Parser' do
6
+ describe '#file' do
7
+ it 'is an accessor' do
8
+ expect { parser.file = :a_new_file }
9
+ .to change { parser.file }
10
+ .to(:a_new_file)
11
+ end
12
+ end
13
+
14
+ describe '#records' do
15
+ it 'yields records' do
16
+ unless described_class == Darlingtonia::Parser
17
+ expect { |b| parser.records(&b) }
18
+ .to yield_control.exactly(record_count).times
19
+ end
20
+ end
21
+ end
22
+
23
+ describe '#valid?' do
24
+ it 'is valid' do
25
+ expect(parser).to be_valid
26
+ end
27
+
28
+ context 'when not valid' do
29
+ before do
30
+ parser.validators = [Darlingtonia::AlwaysInvalidValidator.new]
31
+ end
32
+
33
+ it 'is invalid' do
34
+ expect { parser.validate }
35
+ .to change { parser.valid? }
36
+ .to be_falsey
37
+ end
38
+ end
39
+ end
40
+
41
+ describe '#validate' do
42
+ it 'is true when valid' do
43
+ expect(parser.validate).to be_truthy
44
+ end
45
+
46
+ context 'when not valid' do
47
+ before do
48
+ parser.validators = [Darlingtonia::AlwaysInvalidValidator.new]
49
+ end
50
+
51
+ it 'is invalid' do
52
+ expect(parser.validate).to be_falsey
53
+ end
54
+ end
55
+ end
56
+
57
+ describe '#validate!' do
58
+ it 'is true when valid' do
59
+ expect(parser.validate).to be_truthy
60
+ end
61
+
62
+ context 'when not valid' do
63
+ before do
64
+ parser.validators = [Darlingtonia::AlwaysInvalidValidator.new]
65
+ end
66
+
67
+ it 'raises a ValidationError' do
68
+ expect { parser.validate! }
69
+ .to raise_error Darlingtonia::Parser::ValidationError
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ shared_examples 'a Darlingtonia::Validator' do
4
+ subject(:validator) { described_class.new }
5
+
6
+ define :be_a_validator_error do # |expected|
7
+ match { false } # { |actual| some_condition }
8
+ end
9
+
10
+ describe '#validate' do
11
+ context 'without a parser' do
12
+ it 'raises ArgumentError' do
13
+ expect { validator.validate }.to raise_error ArgumentError
14
+ end
15
+ end
16
+
17
+ it 'gives an empty error collection for a valid parser' do
18
+ expect(validator.validate(parser: valid_parser)).to be_empty if
19
+ defined?(valid_parser)
20
+ end
21
+
22
+ context 'for an invalid parser' do
23
+ it 'gives an non-empty error collection' do
24
+ expect(validator.validate(parser: invalid_parser)).not_to be_empty if
25
+ defined?(invalid_parser)
26
+ end
27
+
28
+ it 'gives usable errors' do
29
+ pending 'we need to clarify the error type and usage'
30
+
31
+ validator.validate(parser: invalid_parser).each do |error|
32
+ expect(error).to be_a_validator_error
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
metadata ADDED
@@ -0,0 +1,195 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: darlingtonia
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Tom Johnson
8
+ - Data Curation Experts
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2018-01-11 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: active-fedora
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '11.0'
21
+ - - "<="
22
+ - !ruby/object:Gem::Version
23
+ version: '12.99'
24
+ type: :runtime
25
+ prerelease: false
26
+ version_requirements: !ruby/object:Gem::Requirement
27
+ requirements:
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ version: '11.0'
31
+ - - "<="
32
+ - !ruby/object:Gem::Version
33
+ version: '12.99'
34
+ - !ruby/object:Gem::Dependency
35
+ name: yard
36
+ requirement: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.9'
41
+ type: :development
42
+ prerelease: false
43
+ version_requirements: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.9'
48
+ - !ruby/object:Gem::Dependency
49
+ name: bixby
50
+ requirement: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.3'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.3'
62
+ - !ruby/object:Gem::Dependency
63
+ name: hyrax-spec
64
+ requirement: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.2'
69
+ type: :development
70
+ prerelease: false
71
+ version_requirements: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.2'
76
+ - !ruby/object:Gem::Dependency
77
+ name: rspec
78
+ requirement: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.6'
83
+ type: :development
84
+ prerelease: false
85
+ version_requirements: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.6'
90
+ - !ruby/object:Gem::Dependency
91
+ name: coveralls
92
+ requirement: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.8'
97
+ type: :development
98
+ prerelease: false
99
+ version_requirements: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.8'
104
+ - !ruby/object:Gem::Dependency
105
+ name: solr_wrapper
106
+ requirement: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.3'
111
+ type: :development
112
+ prerelease: false
113
+ version_requirements: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '0.3'
118
+ - !ruby/object:Gem::Dependency
119
+ name: fcrepo_wrapper
120
+ requirement: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '0.9'
125
+ type: :development
126
+ prerelease: false
127
+ version_requirements: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '0.9'
132
+ description:
133
+ email: tom@curationexperts.com
134
+ executables: []
135
+ extensions: []
136
+ extra_rdoc_files: []
137
+ files:
138
+ - ".gitignore"
139
+ - ".rubocop.yml"
140
+ - ".travis.yml"
141
+ - Gemfile
142
+ - README.md
143
+ - Rakefile
144
+ - darlingtonia.gemspec
145
+ - lib/darlingtonia.rb
146
+ - lib/darlingtonia/always_invalid_validator.rb
147
+ - lib/darlingtonia/hash_mapper.rb
148
+ - lib/darlingtonia/importer.rb
149
+ - lib/darlingtonia/input_record.rb
150
+ - lib/darlingtonia/parser.rb
151
+ - lib/darlingtonia/parsers/csv_parser.rb
152
+ - lib/darlingtonia/record_importer.rb
153
+ - lib/darlingtonia/validator.rb
154
+ - lib/darlingtonia/validators/csv_format_validator.rb
155
+ - lib/darlingtonia/version.rb
156
+ - spec/darlingtonia/csv_format_validator_spec.rb
157
+ - spec/darlingtonia/csv_parser_spec.rb
158
+ - spec/darlingtonia/hash_mapper_spec.rb
159
+ - spec/darlingtonia/importer_spec.rb
160
+ - spec/darlingtonia/input_record_spec.rb
161
+ - spec/darlingtonia/parser_spec.rb
162
+ - spec/darlingtonia/validator_spec.rb
163
+ - spec/darlingtonia/version_spec.rb
164
+ - spec/fixtures/example.csv
165
+ - spec/integration/import_csv_spec.rb
166
+ - spec/spec_helper.rb
167
+ - spec/support/fakes/fake_parser.rb
168
+ - spec/support/shared_examples/a_mapper.rb
169
+ - spec/support/shared_examples/a_parser.rb
170
+ - spec/support/shared_examples/a_validator.rb
171
+ homepage:
172
+ licenses:
173
+ - Apache-2.0
174
+ metadata: {}
175
+ post_install_message:
176
+ rdoc_options: []
177
+ require_paths:
178
+ - lib
179
+ required_ruby_version: !ruby/object:Gem::Requirement
180
+ requirements:
181
+ - - ">="
182
+ - !ruby/object:Gem::Version
183
+ version: 2.3.4
184
+ required_rubygems_version: !ruby/object:Gem::Requirement
185
+ requirements:
186
+ - - ">="
187
+ - !ruby/object:Gem::Version
188
+ version: '0'
189
+ requirements: []
190
+ rubyforge_project:
191
+ rubygems_version: 2.6.13
192
+ signing_key:
193
+ specification_version: 4
194
+ summary: Hyrax importers.
195
+ test_files: []