zizia 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.rubocop.yml +65 -0
  4. data/.rubocop_todo.yml +21 -0
  5. data/.solr_wrapper +8 -0
  6. data/.travis.yml +11 -0
  7. data/Gemfile +12 -0
  8. data/README.md +77 -0
  9. data/Rakefile +34 -0
  10. data/docs/_config.yml +1 -0
  11. data/docs/index.md +98 -0
  12. data/lib/zizia/always_invalid_validator.rb +17 -0
  13. data/lib/zizia/hash_mapper.rb +44 -0
  14. data/lib/zizia/hyrax_basic_metadata_mapper.rb +149 -0
  15. data/lib/zizia/hyrax_record_importer.rb +261 -0
  16. data/lib/zizia/importer.rb +61 -0
  17. data/lib/zizia/input_record.rb +65 -0
  18. data/lib/zizia/log_stream.rb +43 -0
  19. data/lib/zizia/metadata_mapper.rb +83 -0
  20. data/lib/zizia/metadata_only_stack.rb +70 -0
  21. data/lib/zizia/parser.rb +132 -0
  22. data/lib/zizia/parsers/csv_parser.rb +45 -0
  23. data/lib/zizia/record_importer.rb +57 -0
  24. data/lib/zizia/spec/fakes/fake_parser.rb +22 -0
  25. data/lib/zizia/spec/shared_examples/a_mapper.rb +32 -0
  26. data/lib/zizia/spec/shared_examples/a_message_stream.rb +11 -0
  27. data/lib/zizia/spec/shared_examples/a_parser.rb +73 -0
  28. data/lib/zizia/spec/shared_examples/a_validator.rb +46 -0
  29. data/lib/zizia/spec.rb +15 -0
  30. data/lib/zizia/streams/formatted_message_stream.rb +70 -0
  31. data/lib/zizia/validator.rb +117 -0
  32. data/lib/zizia/validators/csv_format_validator.rb +26 -0
  33. data/lib/zizia/validators/title_validator.rb +30 -0
  34. data/lib/zizia/version.rb +5 -0
  35. data/lib/zizia.rb +73 -0
  36. data/log/.keep +0 -0
  37. data/spec/fixtures/bad_example.csv +2 -0
  38. data/spec/fixtures/example.csv +4 -0
  39. data/spec/fixtures/hyrax/example.csv +3 -0
  40. data/spec/fixtures/images/animals/cat.png +0 -0
  41. data/spec/fixtures/images/zizia.png +0 -0
  42. data/spec/fixtures/zizia.png +0 -0
  43. data/spec/integration/import_csv_spec.rb +28 -0
  44. data/spec/integration/import_hyrax_csv.rb +71 -0
  45. data/spec/spec_helper.rb +18 -0
  46. data/spec/stdout_stream_spec.rb +9 -0
  47. data/spec/support/hyrax/basic_metadata.rb +30 -0
  48. data/spec/support/hyrax/core_metadata.rb +15 -0
  49. data/spec/support/shared_contexts/with_work_type.rb +101 -0
  50. data/spec/zizia/csv_format_validator_spec.rb +38 -0
  51. data/spec/zizia/csv_parser_spec.rb +73 -0
  52. data/spec/zizia/formatted_message_stream_spec.rb +35 -0
  53. data/spec/zizia/hash_mapper_spec.rb +8 -0
  54. data/spec/zizia/hyrax_basic_metadata_mapper_spec.rb +190 -0
  55. data/spec/zizia/hyrax_record_importer_spec.rb +178 -0
  56. data/spec/zizia/importer_spec.rb +46 -0
  57. data/spec/zizia/input_record_spec.rb +71 -0
  58. data/spec/zizia/parser_spec.rb +47 -0
  59. data/spec/zizia/record_importer_spec.rb +70 -0
  60. data/spec/zizia/title_validator_spec.rb +23 -0
  61. data/spec/zizia/validator_spec.rb +9 -0
  62. data/spec/zizia/version_spec.rb +7 -0
  63. data/spec/zizia_spec.rb +19 -0
  64. data/zizia.gemspec +34 -0
  65. 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