zizia 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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