zizia 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rubocop.yml +65 -0
- data/.rubocop_todo.yml +21 -0
- data/.solr_wrapper +8 -0
- data/.travis.yml +11 -0
- data/Gemfile +12 -0
- data/README.md +77 -0
- data/Rakefile +34 -0
- data/docs/_config.yml +1 -0
- data/docs/index.md +98 -0
- data/lib/zizia/always_invalid_validator.rb +17 -0
- data/lib/zizia/hash_mapper.rb +44 -0
- data/lib/zizia/hyrax_basic_metadata_mapper.rb +149 -0
- data/lib/zizia/hyrax_record_importer.rb +261 -0
- data/lib/zizia/importer.rb +61 -0
- data/lib/zizia/input_record.rb +65 -0
- data/lib/zizia/log_stream.rb +43 -0
- data/lib/zizia/metadata_mapper.rb +83 -0
- data/lib/zizia/metadata_only_stack.rb +70 -0
- data/lib/zizia/parser.rb +132 -0
- data/lib/zizia/parsers/csv_parser.rb +45 -0
- data/lib/zizia/record_importer.rb +57 -0
- data/lib/zizia/spec/fakes/fake_parser.rb +22 -0
- data/lib/zizia/spec/shared_examples/a_mapper.rb +32 -0
- data/lib/zizia/spec/shared_examples/a_message_stream.rb +11 -0
- data/lib/zizia/spec/shared_examples/a_parser.rb +73 -0
- data/lib/zizia/spec/shared_examples/a_validator.rb +46 -0
- data/lib/zizia/spec.rb +15 -0
- data/lib/zizia/streams/formatted_message_stream.rb +70 -0
- data/lib/zizia/validator.rb +117 -0
- data/lib/zizia/validators/csv_format_validator.rb +26 -0
- data/lib/zizia/validators/title_validator.rb +30 -0
- data/lib/zizia/version.rb +5 -0
- data/lib/zizia.rb +73 -0
- data/log/.keep +0 -0
- data/spec/fixtures/bad_example.csv +2 -0
- data/spec/fixtures/example.csv +4 -0
- data/spec/fixtures/hyrax/example.csv +3 -0
- data/spec/fixtures/images/animals/cat.png +0 -0
- data/spec/fixtures/images/zizia.png +0 -0
- data/spec/fixtures/zizia.png +0 -0
- data/spec/integration/import_csv_spec.rb +28 -0
- data/spec/integration/import_hyrax_csv.rb +71 -0
- data/spec/spec_helper.rb +18 -0
- data/spec/stdout_stream_spec.rb +9 -0
- data/spec/support/hyrax/basic_metadata.rb +30 -0
- data/spec/support/hyrax/core_metadata.rb +15 -0
- data/spec/support/shared_contexts/with_work_type.rb +101 -0
- data/spec/zizia/csv_format_validator_spec.rb +38 -0
- data/spec/zizia/csv_parser_spec.rb +73 -0
- data/spec/zizia/formatted_message_stream_spec.rb +35 -0
- data/spec/zizia/hash_mapper_spec.rb +8 -0
- data/spec/zizia/hyrax_basic_metadata_mapper_spec.rb +190 -0
- data/spec/zizia/hyrax_record_importer_spec.rb +178 -0
- data/spec/zizia/importer_spec.rb +46 -0
- data/spec/zizia/input_record_spec.rb +71 -0
- data/spec/zizia/parser_spec.rb +47 -0
- data/spec/zizia/record_importer_spec.rb +70 -0
- data/spec/zizia/title_validator_spec.rb +23 -0
- data/spec/zizia/validator_spec.rb +9 -0
- data/spec/zizia/version_spec.rb +7 -0
- data/spec/zizia_spec.rb +19 -0
- data/zizia.gemspec +34 -0
- metadata +246 -0
@@ -0,0 +1,261 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Zizia
|
4
|
+
class HyraxRecordImporter < RecordImporter
|
5
|
+
# TODO: Get this from Hyrax config
|
6
|
+
DEFAULT_CREATOR_KEY = 'batchuser@example.com'
|
7
|
+
|
8
|
+
# @!attribute [rw] depositor
|
9
|
+
# @return [User]
|
10
|
+
attr_accessor :depositor
|
11
|
+
|
12
|
+
# @!attribute [rw] collection_id
|
13
|
+
# @return [String] The fedora ID for a Collection.
|
14
|
+
attr_accessor :collection_id
|
15
|
+
|
16
|
+
# @!attribute [rw] batch_id
|
17
|
+
# @return [String] an id number associated with the process that kicked off this import run
|
18
|
+
attr_accessor :batch_id
|
19
|
+
|
20
|
+
# @!attribute [rw] deduplication_field
|
21
|
+
# @return [String] if this is set, look for records with a match in this field
|
22
|
+
# and update the metadata instead of creating a new record. This will NOT re-import file attachments.
|
23
|
+
attr_accessor :deduplication_field
|
24
|
+
|
25
|
+
# @!attribute [rw] success_count
|
26
|
+
# @return [String] the number of records this importer has successfully created
|
27
|
+
attr_accessor :success_count
|
28
|
+
|
29
|
+
# @!attribute [rw] failure_count
|
30
|
+
# @return [String] the number of records this importer has failed to create
|
31
|
+
attr_accessor :failure_count
|
32
|
+
|
33
|
+
# @param attributes [Hash] Attributes that come
|
34
|
+
# from the UI or importer rather than from
|
35
|
+
# the CSV/mapper. These are useful for logging
|
36
|
+
# and tracking the output of an import job for
|
37
|
+
# a given collection, user, or batch.
|
38
|
+
# If a deduplication_field is provided, the system will
|
39
|
+
# look for existing works with that field and matching
|
40
|
+
# value and will update the record instead of creating a new record.
|
41
|
+
# @example
|
42
|
+
# attributes: { collection_id: '123',
|
43
|
+
# depositor_id: '456',
|
44
|
+
# batch_id: '789',
|
45
|
+
# deduplication_field: 'legacy_id'
|
46
|
+
# }
|
47
|
+
def initialize(error_stream: Zizia.config.default_error_stream,
|
48
|
+
info_stream: Zizia.config.default_info_stream,
|
49
|
+
attributes: {})
|
50
|
+
self.collection_id = attributes[:collection_id]
|
51
|
+
self.batch_id = attributes[:batch_id]
|
52
|
+
self.deduplication_field = attributes[:deduplication_field]
|
53
|
+
set_depositor(attributes[:depositor_id])
|
54
|
+
@success_count = 0
|
55
|
+
@failure_count = 0
|
56
|
+
super(error_stream: error_stream, info_stream: info_stream)
|
57
|
+
end
|
58
|
+
|
59
|
+
# "depositor" is a required field for Hyrax. If
|
60
|
+
# it hasn't been set, set it to the Hyrax default
|
61
|
+
# batch user.
|
62
|
+
def set_depositor(user_key)
|
63
|
+
user = ::User.find_by_user_key(user_key) if user_key
|
64
|
+
user ||= ::User.find(user_key) if user_key
|
65
|
+
user ||= ::User.find_or_create_system_user(DEFAULT_CREATOR_KEY)
|
66
|
+
self.depositor = user
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# @param record [ImportRecord]
|
71
|
+
# @return [ActiveFedora::Base]
|
72
|
+
# Search for any existing records that match on the deduplication_field
|
73
|
+
def find_existing_record(record)
|
74
|
+
return unless deduplication_field
|
75
|
+
return unless record.respond_to?(deduplication_field)
|
76
|
+
return if record.mapper.send(deduplication_field).nil?
|
77
|
+
return if record.mapper.send(deduplication_field).empty?
|
78
|
+
existing_records = import_type.where("#{deduplication_field}": record.mapper.send(deduplication_field).to_s)
|
79
|
+
raise "More than one record matches deduplication_field #{deduplication_field} with value #{record.mapper.send(deduplication_field)}" if existing_records.count > 1
|
80
|
+
existing_records&.first
|
81
|
+
end
|
82
|
+
|
83
|
+
##
|
84
|
+
# @param record [ImportRecord]
|
85
|
+
#
|
86
|
+
# @return [void]
|
87
|
+
def import(record:)
|
88
|
+
existing_record = find_existing_record(record)
|
89
|
+
create_for(record: record) unless existing_record
|
90
|
+
update_for(existing_record: existing_record, update_record: record) if existing_record
|
91
|
+
rescue Faraday::ConnectionFailed, Ldp::HttpError => e
|
92
|
+
error_stream << e
|
93
|
+
rescue RuntimeError => e
|
94
|
+
error_stream << e
|
95
|
+
raise e
|
96
|
+
end
|
97
|
+
|
98
|
+
# TODO: You should be able to specify the import type in the import
|
99
|
+
def import_type
|
100
|
+
raise 'No curation_concern found for import' unless
|
101
|
+
defined?(Hyrax) && Hyrax&.config&.curation_concerns&.any?
|
102
|
+
|
103
|
+
Hyrax.config.curation_concerns.first
|
104
|
+
end
|
105
|
+
|
106
|
+
# The path on disk where file attachments can be found
|
107
|
+
def file_attachments_path
|
108
|
+
ENV['IMPORT_PATH'] || '/opt/data'
|
109
|
+
end
|
110
|
+
|
111
|
+
# Create a Hyrax::UploadedFile for each file attachment
|
112
|
+
# TODO: What if we can't find the file?
|
113
|
+
# TODO: How do we specify where the files can be found?
|
114
|
+
# @param [Zizia::InputRecord]
|
115
|
+
# @return [Array] an array of Hyrax::UploadedFile ids
|
116
|
+
def create_upload_files(record)
|
117
|
+
return unless record.mapper.respond_to?(:files)
|
118
|
+
files_to_attach = record.mapper.files
|
119
|
+
return [] if files_to_attach.nil? || files_to_attach.empty?
|
120
|
+
|
121
|
+
uploaded_file_ids = []
|
122
|
+
files_to_attach.each do |filename|
|
123
|
+
file = File.open(find_file_path(filename))
|
124
|
+
uploaded_file = Hyrax::UploadedFile.create(user: @depositor, file: file)
|
125
|
+
uploaded_file_ids << uploaded_file.id
|
126
|
+
file.close
|
127
|
+
end
|
128
|
+
uploaded_file_ids
|
129
|
+
end
|
130
|
+
|
131
|
+
##
|
132
|
+
# Within the directory specified by ENV['IMPORT_PATH'], find the first
|
133
|
+
# instance of a file matching the given filename.
|
134
|
+
# If there is no matching file, raise an exception.
|
135
|
+
# @param [String] filename
|
136
|
+
# @return [String] a full pathname to the found file
|
137
|
+
def find_file_path(filename)
|
138
|
+
filepath = Dir.glob("#{ENV['IMPORT_PATH']}/**/#{filename}").first
|
139
|
+
raise "Cannot find file #{filename}... Are you sure it has been uploaded and that the filename matches?" if filepath.nil?
|
140
|
+
filepath
|
141
|
+
end
|
142
|
+
|
143
|
+
##
|
144
|
+
# When submitting location data (a.k.a. the "based near" attribute) via the UI,
|
145
|
+
# Hyrax expects to receive a `based_near_attributes` hash in a specific format.
|
146
|
+
# We need to take geonames urls as provided by the customer and transform them to
|
147
|
+
# mimic what the Hyrax UI would ordinarily produce. These will get turned into
|
148
|
+
# Hyrax::ControlledVocabularies::Location objects upon ingest.
|
149
|
+
# The expected hash looks like this:
|
150
|
+
# "based_near_attributes"=>
|
151
|
+
# {
|
152
|
+
# "0"=> {
|
153
|
+
# "id"=>"http://sws.geonames.org/5667009/", "_destroy"=>""
|
154
|
+
# },
|
155
|
+
# "1"=> {
|
156
|
+
# "id"=>"http://sws.geonames.org/6252001/", "_destroy"=>""
|
157
|
+
# },
|
158
|
+
# }
|
159
|
+
# @return [Hash] a "based_near_attributes" hash as
|
160
|
+
def based_near_attributes(based_near)
|
161
|
+
original_geonames_uris = based_near
|
162
|
+
return if original_geonames_uris.empty?
|
163
|
+
based_near_attributes = {}
|
164
|
+
original_geonames_uris.each_with_index do |uri, i|
|
165
|
+
based_near_attributes[i.to_s] = { 'id' => uri_to_sws(uri), "_destroy" => "" }
|
166
|
+
end
|
167
|
+
based_near_attributes
|
168
|
+
end
|
169
|
+
|
170
|
+
#
|
171
|
+
# Take a user-facing geonames URI and return an sws URI, of the form Hyrax expects
|
172
|
+
# (e.g., "http://sws.geonames.org/6252001/")
|
173
|
+
# @param [String] uri
|
174
|
+
# @return [String] an sws style geonames uri
|
175
|
+
def uri_to_sws(uri)
|
176
|
+
uri = URI(uri)
|
177
|
+
geonames_number = uri.path.split('/')[1]
|
178
|
+
"http://sws.geonames.org/#{geonames_number}/"
|
179
|
+
end
|
180
|
+
|
181
|
+
private
|
182
|
+
|
183
|
+
# Build a pared down actor stack that will not re-attach files,
|
184
|
+
# or set workflow, or do anything except update metadata.
|
185
|
+
# TODO: We should be able to set an environment variable that would allow updates to go through the regular
|
186
|
+
# actor stack instead of the stripped down one, in case we want to re-import files.
|
187
|
+
def metadata_only_middleware
|
188
|
+
terminator = Hyrax::Actors::Terminator.new
|
189
|
+
Zizia::MetadataOnlyStack.build_stack.build(terminator)
|
190
|
+
end
|
191
|
+
|
192
|
+
# Update an existing object using the Hyrax actor stack
|
193
|
+
# We assume the object was created as expected if the actor stack returns true.
|
194
|
+
# Note that for now the update stack will only update metadata and update collection membership, it will not re-import files.
|
195
|
+
def update_for(existing_record:, update_record:)
|
196
|
+
info_stream << "event: record_update_started, batch_id: #{batch_id}, collection_id: #{collection_id}, #{deduplication_field}: #{update_record.respond_to?(deduplication_field) ? update_record.send(deduplication_field) : update_record}"
|
197
|
+
additional_attrs = {
|
198
|
+
depositor: @depositor.user_key
|
199
|
+
}
|
200
|
+
attrs = update_record.attributes.merge(additional_attrs)
|
201
|
+
attrs = attrs.merge(member_of_collections_attributes: { '0' => { id: collection_id } }) if collection_id
|
202
|
+
# Ensure nothing is passed in the files field,
|
203
|
+
# since this is reserved for Hyrax and is where uploaded_files will be attached
|
204
|
+
attrs.delete(:files)
|
205
|
+
|
206
|
+
# We aren't using the attach remote files actor, so make sure any remote files are removed from the params before we try to save the object.
|
207
|
+
attrs.delete(:remote_files)
|
208
|
+
|
209
|
+
based_near = attrs.delete(:based_near)
|
210
|
+
attrs = attrs.merge(based_near_attributes: based_near_attributes(based_near)) unless based_near.nil? || based_near.empty?
|
211
|
+
|
212
|
+
actor_env = Hyrax::Actors::Environment.new(existing_record, ::Ability.new(@depositor), attrs)
|
213
|
+
if metadata_only_middleware.update(actor_env)
|
214
|
+
info_stream << "event: record_updated, batch_id: #{batch_id}, record_id: #{existing_record.id}, collection_id: #{collection_id}, #{deduplication_field}: #{existing_record.respond_to?(deduplication_field) ? existing_record.send(deduplication_field) : existing_record}"
|
215
|
+
@success_count += 1
|
216
|
+
else
|
217
|
+
existing_record.errors.each do |attr, msg|
|
218
|
+
error_stream << "event: validation_failed, batch_id: #{batch_id}, collection_id: #{collection_id}, attribute: #{attr.capitalize}, message: #{msg}, record_title: record_title: #{attrs[:title] ? attrs[:title] : attrs}"
|
219
|
+
end
|
220
|
+
@failure_count += 1
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
# Create an object using the Hyrax actor stack
|
225
|
+
# We assume the object was created as expected if the actor stack returns true.
|
226
|
+
def create_for(record:)
|
227
|
+
info_stream << "event: record_import_started, batch_id: #{batch_id}, collection_id: #{collection_id}, record_title: #{record.respond_to?(:title) ? record.title : record}"
|
228
|
+
|
229
|
+
additional_attrs = {
|
230
|
+
uploaded_files: create_upload_files(record),
|
231
|
+
depositor: @depositor.user_key
|
232
|
+
}
|
233
|
+
|
234
|
+
created = import_type.new
|
235
|
+
|
236
|
+
attrs = record.attributes.merge(additional_attrs)
|
237
|
+
attrs = attrs.merge(member_of_collections_attributes: { '0' => { id: collection_id } }) if collection_id
|
238
|
+
|
239
|
+
# Ensure nothing is passed in the files field,
|
240
|
+
# since this is reserved for Hyrax and is where uploaded_files will be attached
|
241
|
+
attrs.delete(:files)
|
242
|
+
|
243
|
+
based_near = attrs.delete(:based_near)
|
244
|
+
attrs = attrs.merge(based_near_attributes: based_near_attributes(based_near)) unless based_near.nil? || based_near.empty?
|
245
|
+
|
246
|
+
actor_env = Hyrax::Actors::Environment.new(created,
|
247
|
+
::Ability.new(@depositor),
|
248
|
+
attrs)
|
249
|
+
|
250
|
+
if Hyrax::CurationConcern.actor.create(actor_env)
|
251
|
+
info_stream << "event: record_created, batch_id: #{batch_id}, record_id: #{created.id}, collection_id: #{collection_id}, record_title: #{attrs[:title]&.first}"
|
252
|
+
@success_count += 1
|
253
|
+
else
|
254
|
+
created.errors.each do |attr, msg|
|
255
|
+
error_stream << "event: validation_failed, batch_id: #{batch_id}, collection_id: #{collection_id}, attribute: #{attr.capitalize}, message: #{msg}, record_title: record_title: #{attrs[:title] ? attrs[:title] : attrs}"
|
256
|
+
end
|
257
|
+
@failure_count += 1
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Zizia
|
4
|
+
##
|
5
|
+
# The chief entry point for bulk import of records. `Importer` accepts a
|
6
|
+
# {Parser} on initialization and iterates through its {Parser#records}, importing
|
7
|
+
# each using a given {RecordImporter}.
|
8
|
+
#
|
9
|
+
# @example Importing in bulk from a CSV file
|
10
|
+
# parser = Zizia::Parser.for(file: File.new('path/to/import.csv'))
|
11
|
+
#
|
12
|
+
# Zizia::Importer.new(parser: parser).import if parser.validate
|
13
|
+
#
|
14
|
+
class Importer
|
15
|
+
extend Forwardable
|
16
|
+
|
17
|
+
##
|
18
|
+
# @!attribute [rw] parser
|
19
|
+
# @return [Parser]
|
20
|
+
# @!attribute [rw] record_importer
|
21
|
+
# @return [RecordImporter]
|
22
|
+
attr_accessor :parser, :record_importer
|
23
|
+
|
24
|
+
##
|
25
|
+
# @!method records()
|
26
|
+
# @see Parser#records
|
27
|
+
def_delegator :parser, :records, :records
|
28
|
+
|
29
|
+
##
|
30
|
+
# @param parser [Parser] The parser to use as the source for import
|
31
|
+
# records.
|
32
|
+
# @param record_importer [RecordImporter] An object to handle import of
|
33
|
+
# each record
|
34
|
+
def initialize(parser:, record_importer: RecordImporter.new, info_stream: Zizia.config.default_info_stream, error_stream: Zizia.config.default_error_stream)
|
35
|
+
self.parser = parser
|
36
|
+
self.record_importer = record_importer
|
37
|
+
@info_stream = info_stream
|
38
|
+
@error_stream = error_stream
|
39
|
+
end
|
40
|
+
|
41
|
+
# Do not attempt to run an import if there are no records. Instead, just write to the log.
|
42
|
+
def no_records_message
|
43
|
+
@info_stream << "event: empty_import, batch_id: #{record_importer.batch_id}"
|
44
|
+
@error_stream << "event: empty_import, batch_id: #{record_importer.batch_id}"
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# Import each record in {#records}.
|
49
|
+
#
|
50
|
+
# @return [void]
|
51
|
+
def import
|
52
|
+
no_records_message && return unless records.count.positive?
|
53
|
+
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
54
|
+
@info_stream << "event: start_import, batch_id: #{record_importer.batch_id}, expecting to import #{records.count} records."
|
55
|
+
records.each { |record| record_importer.import(record: record) }
|
56
|
+
end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
57
|
+
elapsed_time = end_time - start_time
|
58
|
+
@info_stream << "event: finish_import, batch_id: #{record_importer.batch_id}, successful_record_count: #{record_importer.success_count}, failed_record_count: #{record_importer.failure_count}, elapsed_time: #{elapsed_time}, elapsed_time_per_record: #{elapsed_time / records.count}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Zizia
|
4
|
+
##
|
5
|
+
# @example Building an importer with the factory
|
6
|
+
# record = InputRecord.from({some: :metadata}, mapper: MyMapper.new)
|
7
|
+
# record.some # => :metadata
|
8
|
+
#
|
9
|
+
class InputRecord
|
10
|
+
##
|
11
|
+
# @!attribute [rw] mapper
|
12
|
+
# @return [#map_fields]
|
13
|
+
attr_accessor :mapper
|
14
|
+
|
15
|
+
##
|
16
|
+
# @param mapper [#map_fields]
|
17
|
+
def initialize(mapper: HyraxBasicMetadataMapper.new)
|
18
|
+
self.mapper = mapper
|
19
|
+
end
|
20
|
+
|
21
|
+
class << self
|
22
|
+
##
|
23
|
+
# @param metadata [Object]
|
24
|
+
# @param mapper [#map_fields]
|
25
|
+
#
|
26
|
+
# @return [InputRecord] an input record mapping metadata with the given
|
27
|
+
# mapper
|
28
|
+
def from(metadata:, mapper: HyraxBasicMetadataMapper.new)
|
29
|
+
mapper.metadata = metadata
|
30
|
+
new(mapper: mapper)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# @return [Hash<Symbol, Object>]
|
36
|
+
def attributes
|
37
|
+
mapper.fields.each_with_object({}) do |field, attrs|
|
38
|
+
attrs[field] = public_send(field)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
##
|
43
|
+
# @return [String, nil] an identifier for the representative file; nil if
|
44
|
+
# none is given.
|
45
|
+
def representative_file
|
46
|
+
return mapper.representative_file if
|
47
|
+
mapper.respond_to?(:representative_file)
|
48
|
+
|
49
|
+
nil
|
50
|
+
end
|
51
|
+
|
52
|
+
##
|
53
|
+
# Respond to methods matching mapper fields
|
54
|
+
def method_missing(method_name, *args, &block)
|
55
|
+
return super unless mapper.field?(method_name)
|
56
|
+
mapper.public_send(method_name)
|
57
|
+
end
|
58
|
+
|
59
|
+
##
|
60
|
+
# @see #method_missing
|
61
|
+
def respond_to_missing?(method_name, include_private = false)
|
62
|
+
mapper.field?(method_name) || super
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Zizia
|
4
|
+
class LogStream
|
5
|
+
##
|
6
|
+
# @!attribute [rw] logger
|
7
|
+
# @return [Logger]
|
8
|
+
# @!attribute [rw] severity
|
9
|
+
# @return [Logger::Serverity]
|
10
|
+
attr_accessor :logger, :severity
|
11
|
+
|
12
|
+
def initialize(logger: nil, severity: nil)
|
13
|
+
self.logger = logger || Logger.new(build_filename)
|
14
|
+
self.severity = severity || Logger::INFO
|
15
|
+
end
|
16
|
+
|
17
|
+
def <<(msg)
|
18
|
+
logger.add(severity, msg)
|
19
|
+
STDOUT << msg
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def build_filename
|
25
|
+
return ENV['IMPORT_LOG'] if ENV['IMPORT_LOG']
|
26
|
+
return rails_log_name if rails_log_name
|
27
|
+
'./log/zizia_import.log'
|
28
|
+
end
|
29
|
+
|
30
|
+
def rails_log_name
|
31
|
+
case Rails.env
|
32
|
+
when 'production'
|
33
|
+
Rails.root.join('log', "csv_import.log").to_s
|
34
|
+
when 'development'
|
35
|
+
Rails.root.join('log', "dev_csv_import.log").to_s
|
36
|
+
when 'test'
|
37
|
+
Rails.root.join('log', "test_csv_import.log").to_s
|
38
|
+
end
|
39
|
+
rescue ::NameError
|
40
|
+
false
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Zizia
|
4
|
+
##
|
5
|
+
# A null/base mapper that maps no fields.
|
6
|
+
#
|
7
|
+
# Real mapper implementations need to provide `#fields`, enumerating over
|
8
|
+
# `Symbols` that represent the fields on the target object (e.g. an
|
9
|
+
# `ActiveFedora::Base`/`Hyrax::WorkBehavior`) that the mapper can handle.
|
10
|
+
# For each field in `#fields`, the mapper must respond to a matching method
|
11
|
+
# (i.e. `:title` => `#title`), and return the value(s) that should be set to
|
12
|
+
# the target's attributes upon mapping.
|
13
|
+
#
|
14
|
+
# To ease the implementation of field methods, this base class provides
|
15
|
+
# a `#method_missing` that forwards missing method names to a `#map_field`
|
16
|
+
# method. `#map_field` can be implemented to provide a generalized field
|
17
|
+
# mapping when a common pattern will be used for many methods. Callers should
|
18
|
+
# avoid relying on this protected method directly, since mappers may implement
|
19
|
+
# individual field methods in any other way (e.g. `def title; end`) to route
|
20
|
+
# around `#map_field`. Implementations are also free to override
|
21
|
+
# `#method_missing` if desired.
|
22
|
+
#
|
23
|
+
# Mappers generally operate over some input `#metadata`. Example metadata
|
24
|
+
# types that mappers could be implemented over include `Hash`, `CSV`, `XML`,
|
25
|
+
# `RDF::Graph`, etc...; mappers are free to interpret or ignore the contents
|
26
|
+
# of their underlying metadata data structures at their leisure. Values for
|
27
|
+
# fields are /usually/ derived from the `#metadata`, but can also be generated
|
28
|
+
# from complex logic or even hard-coded.
|
29
|
+
#
|
30
|
+
# @example Using a MetadataMapper
|
31
|
+
# mapper = MyMapper.new
|
32
|
+
# mapper.metadata = some_metadata_object
|
33
|
+
# mapper.fields # => [:title, :author, :description]
|
34
|
+
#
|
35
|
+
# mapper.title # => 'Some Title'
|
36
|
+
#
|
37
|
+
# mapper.fields.map { |field| mapper.send(field) }
|
38
|
+
#
|
39
|
+
# @see ImportRecord#attributes for the canonical usage of a `MetadataMapper`.
|
40
|
+
# @see HashMapper for an example implementation with dynamically generated
|
41
|
+
# fields
|
42
|
+
class MetadataMapper
|
43
|
+
##
|
44
|
+
# @!attribute [rw] metadata
|
45
|
+
# @return [Object]
|
46
|
+
attr_accessor :metadata
|
47
|
+
|
48
|
+
##
|
49
|
+
# @param name [Symbol]
|
50
|
+
#
|
51
|
+
# @return [Boolean]
|
52
|
+
def field?(name)
|
53
|
+
fields.include?(name)
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# @return [Enumerable<Symbol>] The fields the mapper can process
|
58
|
+
def fields
|
59
|
+
[]
|
60
|
+
end
|
61
|
+
|
62
|
+
def method_missing(method_name, *args, &block)
|
63
|
+
return map_field(method_name) if fields.include?(method_name)
|
64
|
+
super
|
65
|
+
end
|
66
|
+
|
67
|
+
def respond_to_missing?(method_name, include_private = false)
|
68
|
+
field?(method_name) || super
|
69
|
+
end
|
70
|
+
|
71
|
+
protected
|
72
|
+
|
73
|
+
##
|
74
|
+
# @private
|
75
|
+
#
|
76
|
+
# @param name [Symbol]
|
77
|
+
#
|
78
|
+
# @return [Object]
|
79
|
+
def map_field(_name)
|
80
|
+
raise NotImplementedError
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Zizia
|
4
|
+
class MetadataOnlyStack
|
5
|
+
def self.build_stack
|
6
|
+
ActionDispatch::MiddlewareStack.new.tap do |middleware|
|
7
|
+
# Wrap everything in a database transaction, if the save of the resource
|
8
|
+
# fails then roll back any database AdminSetChangeSet
|
9
|
+
middleware.use Hyrax::Actors::TransactionalRequest
|
10
|
+
|
11
|
+
# Ensure you are mutating the most recent version
|
12
|
+
# middleware.use Hyrax::Actors::OptimisticLockValidator
|
13
|
+
|
14
|
+
# Attach files from a URI (for BrowseEverything)
|
15
|
+
# middleware.use Hyrax::Actors::CreateWithRemoteFilesActor
|
16
|
+
|
17
|
+
# Attach files uploaded in the form to the UploadsController
|
18
|
+
# In Californica, for command line import,
|
19
|
+
# we are using the CreateWithFilesActor to attach
|
20
|
+
# local files with a file:// url, not via the UploadsController,
|
21
|
+
# so we never use the CreateWithFilesActor
|
22
|
+
# middleware.use Hyrax::Actors::CreateWithFilesActor
|
23
|
+
|
24
|
+
# Add/remove the resource to/from a collection
|
25
|
+
middleware.use Hyrax::Actors::CollectionsMembershipActor
|
26
|
+
|
27
|
+
# Add/remove to parent work
|
28
|
+
# middleware.use Hyrax::Actors::AddToWorkActor
|
29
|
+
|
30
|
+
# Add/remove children (works or file_sets)
|
31
|
+
# middleware.use Hyrax::Actors::AttachMembersActor
|
32
|
+
|
33
|
+
# Set the order of the children (works or file_sets)
|
34
|
+
# middleware.use Hyrax::Actors::ApplyOrderActor
|
35
|
+
|
36
|
+
# Sets the default admin set if they didn't supply one
|
37
|
+
# middleware.use Hyrax::Actors::DefaultAdminSetActor
|
38
|
+
|
39
|
+
# Decode the private/public/institution on the form into permisisons on
|
40
|
+
# the model
|
41
|
+
# We aren't using this in Californica, we're just setting the visibility
|
42
|
+
# at import time
|
43
|
+
# middleware.use Hyrax::Actors::InterpretVisibilityActor
|
44
|
+
|
45
|
+
# Handles transfering ownership of works from one user to another
|
46
|
+
# We aren't using this in Californica
|
47
|
+
# middleware.use Hyrax::Actors::TransferRequestActor
|
48
|
+
|
49
|
+
# Copies default permissions from the PermissionTemplate to the work
|
50
|
+
# middleware.use Hyrax::Actors::ApplyPermissionTemplateActor
|
51
|
+
|
52
|
+
# Remove attached FileSets when destroying a work
|
53
|
+
# middleware.use Hyrax::Actors::CleanupFileSetsActor
|
54
|
+
|
55
|
+
# Destroys the trophies in the database when the work is destroyed
|
56
|
+
# middleware.use Hyrax::Actors::CleanupTrophiesActor
|
57
|
+
|
58
|
+
# Destroys the feature tag in the database when the work is destroyed
|
59
|
+
# middleware.use Hyrax::Actors::FeaturedWorkActor
|
60
|
+
|
61
|
+
# Persist the metadata changes on the resource
|
62
|
+
middleware.use Hyrax::Actors::ModelActor
|
63
|
+
|
64
|
+
# Start the workflow for this work
|
65
|
+
# middleware.use Hyrax::Actors::InitializeWorkflowActor
|
66
|
+
end
|
67
|
+
end
|
68
|
+
# rubocop:enable Metrics/MethodLength
|
69
|
+
end
|
70
|
+
end
|