bulkrax 9.1.0 → 9.2.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 29af1006486f6d0a48b2ff2f9f0417ca1dd3fbf0cea791de156cf5e37ec19e53
4
- data.tar.gz: b3f2d406e03c22e5ed6a7b2f1d075e1babbd46bf82c05fa96237e2fc56490fbc
3
+ metadata.gz: fb04fb1689c90c0cb7b96d0fbab95f371e4e4718509638812e62b0bffa1cc9e0
4
+ data.tar.gz: b3df0b413a151f7c49c6a39e07a705ec0e8c87d181a66de2a0ae75d567297f08
5
5
  SHA512:
6
- metadata.gz: 43f29db766c379a8c1d5f03ec8584a0492cccdcdcc63b5cf2936fcf97ebd8115adcb8a104944ff082c2fc2e15691f104047a0396d74e00124e6f4eb22f33f71e
7
- data.tar.gz: a7cbacf2d07dcd987bd7935485023d1a856dc0372ab2ef98f4cd0dc4c54297640ec8e401180dd2d366d69bbf469635881f589ae3b9a01ce5a4bb626b4e21dadb
6
+ metadata.gz: 89a766b0116729946c1ffba1252362fff3f29fd57ab774aebab39fa5b6f2b207a511de3da801b47bce7352a8ef2ca2e3811f306ca0f701ef592acd2a66103f34
7
+ data.tar.gz: b559f0441c55c23a64568d95d1bcb533dc5b11cf12c9ad8aaed3ab852a0f9ea88b1e9140968b6272af54d05d2edae3ff3dadb5433e07fa6c55d90e615f42447d
data/README.md CHANGED
@@ -208,6 +208,32 @@ We encourage everyone to help improve this project. Bug reports and pull reques
208
208
 
209
209
  This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](https://contributor-covenant.org) code of conduct.
210
210
 
211
+ ### Running tests
212
+ - The tests use sqlite as the database, so no extra dependencies are required.
213
+ - Ensure you are using a supported version of Ruby (`ruby -v` should be greater or equal to 2.7)
214
+ - If you are using a ruby version earlier than 3.0.0, install bundler explicitly (newer versions of ruby will automatically install an appropriate bundler version)
215
+ ```bash
216
+ /path/to/ruby/install/bin/gem install bundler -v '~> 2.4.0'
217
+ ```
218
+ - Decide on your version of Hyrax to test against and export it to your environment, then bundle install. The Hyrax version should be greater or equal to 2.3.
219
+ ```bash
220
+ export HYRAX_VERSION="~> 4.0.0"
221
+ bundle install
222
+ ```
223
+ - Run the test migrations
224
+ ```bash
225
+ bundle exec bin/rails db:migrate RAILS_ENV=test
226
+ ```
227
+ - Run the tests
228
+ ```bash
229
+ bundle exec rspec
230
+ ```
231
+
232
+ - Run the style checker / linter
233
+ ```bash
234
+ bundle exec rubocop
235
+ ```
236
+
211
237
  ## Questions
212
238
  Questions can be sent to support@notch8.com. Please make sure to include "Bulkrax" in the subject line of your email.
213
239
 
@@ -15,7 +15,10 @@ function prepBulkrax(event) {
15
15
  // Initialize the uploader only if hyraxUploader is defined
16
16
  if (typeof $.fn.hyraxUploader === 'function') {
17
17
  // Initialize the uploader
18
- $('.fileupload-bulkrax').hyraxUploader({ maxNumberOfFiles: 1 });
18
+ $('.fileupload-bulkrax').hyraxUploader({
19
+ maxNumberOfFiles: 1,
20
+ maxFileSize: <%= (defined?(Hyrax) && Hyrax.config.uploader[:maxFileSize]) || 524288000 %>
21
+ });
19
22
 
20
23
  // Function to toggle 'required' attribute based on uploaded files
21
24
  function toggleRequiredAttribute() {
@@ -28,6 +28,40 @@ module Bulkrax
28
28
  resource.file_sets.each(&:update_index) if resource.respond_to?(:file_sets)
29
29
  end
30
30
 
31
+ ##
32
+ # @return [String] the name of the model class for the given resource/object.
33
+ def self.model_name(resource:)
34
+ resource.has_model.first
35
+ end
36
+
37
+ ##
38
+ # A thumbnail is linked to a work rather than the file set itself.
39
+ # @return [File or FileMetadata] the thumbnail file for the given resource
40
+ def self.thumbnail_for(resource:)
41
+ return nil unless resource.respond_to?(:thumbnail)
42
+ return resource.thumbnail if resource.thumbnail.present?
43
+ return nil unless resource.respond_to?(:parent) && resource.parent.present?
44
+ return nil unless resource.parent.respond_to?(:thumbnail)
45
+ resource.parent.thumbnail
46
+ end
47
+
48
+ ##
49
+ # @input [Fileset]
50
+ # @return [File] the original file.
51
+ def self.original_file(fileset:)
52
+ fileset.try(:original_file)
53
+ end
54
+
55
+ ##
56
+ # #input [Fileset or FileMetadata]
57
+ # @return [String] the file name for the given fileset
58
+ def self.filename_for(fileset:)
59
+ file = original_file(fileset: fileset)
60
+ file.file_name.first
61
+ rescue NoMethodError
62
+ nil
63
+ end
64
+
31
65
  ##
32
66
  # @see Bulkrax::ObjectFactoryInterface
33
67
  def self.export_properties
@@ -166,7 +200,7 @@ module Bulkrax
166
200
 
167
201
  def delete(_user)
168
202
  obj = find
169
- return false unless obj
203
+ raise ObjectFactoryInterface::ObjectNotFoundError, "Object not found to delete" unless obj
170
204
 
171
205
  obj.delete(eradicate: true)
172
206
  end
@@ -209,6 +209,32 @@ module Bulkrax
209
209
  end
210
210
  # rubocop:enable Metrics/ParameterLists
211
211
 
212
+ ##
213
+ # @return [String] the name of the model class for the given resource/object.
214
+ def self.model_name(resource:)
215
+ raise NotImplementedError, "#{self}.#{__method__}"
216
+ end
217
+
218
+ ##
219
+ # @return [File or FileMetadata] the thumbnail file for the given resource
220
+ def self.thumbnail_for(resource:)
221
+ raise NotImplementedError, "#{self}.#{__method__}"
222
+ end
223
+
224
+ ##
225
+ # @input [Fileset or FileMetadata]
226
+ # @return [File or FileMetadata] the original file
227
+ def self.original_file(fileset:)
228
+ raise NotImplementedError, "#{self}.#{__method__}"
229
+ end
230
+
231
+ ##
232
+ # #input [Fileset or FileMetadata]
233
+ # @return [String] the file name for the given fileset
234
+ def self.filename_for(fileset:)
235
+ raise NotImplementedError, "#{self}.#{__method__}"
236
+ end
237
+
212
238
  ##
213
239
  # @api private
214
240
  #
@@ -155,7 +155,7 @@ module Bulkrax
155
155
  Hyrax.query_service.find_by(id: id)
156
156
  # Because Hyrax is not a hard dependency, we need to transform the Hyrax exception into a
157
157
  # common exception so that callers can handle a generalize exception.
158
- rescue Hyrax::ObjectNotFoundError => e
158
+ rescue Hyrax::ObjectNotFoundError, Valkyrie::Persistence::ObjectNotFoundError => e
159
159
  raise ObjectFactoryInterface::ObjectNotFoundError, e.message
160
160
  end
161
161
 
@@ -210,6 +210,46 @@ module Bulkrax
210
210
  update_index(resources: file_sets)
211
211
  end
212
212
 
213
+ ##
214
+ # If we always want the valkyrized resource name, even for unmigrated objects, we can
215
+ # simply use resource.model_name.name. At this point, we are differentiating
216
+ # to help identify items which have been migrated to Valkyrie vs those which have not.
217
+ #
218
+ # @return [String] the name of the model class for the given resource/object.
219
+ def self.model_name(resource:)
220
+ resource.class.to_s
221
+ end
222
+
223
+ ##
224
+ # @return [File or FileMetadata] the thumbnail file for the given resource
225
+ def self.thumbnail_for(resource:)
226
+ # recursive call to parent if resource is a fileset - we want the work's thumbnail
227
+ return thumbnail_for(resource: resource&.parent) if resource.is_a?(Bulkrax.file_model_class)
228
+
229
+ return nil unless resource.respond_to?(:thumbnail_id) && resource.thumbnail_id.present?
230
+ Bulkrax.object_factory.find(resource.thumbnail_id.to_s)
231
+ rescue Bulkrax::ObjectFactoryInterface::ObjectNotFoundError
232
+ nil
233
+ end
234
+
235
+ ##
236
+ # @input [Fileset or FileMetadata]
237
+ # @return [FileMetadata] the original file
238
+ def self.original_file(fileset:)
239
+ fileset.try(:original_file) || fileset
240
+ end
241
+
242
+ ##
243
+ # #input [Fileset or FileMetadata]
244
+ # @return [String] the file name for the given fileset
245
+ def self.filename_for(fileset:)
246
+ file = original_file(fileset: fileset)
247
+ return nil unless file
248
+ file.original_filename
249
+ rescue NoMethodError
250
+ nil
251
+ end
252
+
213
253
  ##
214
254
  # @param value [String]
215
255
  # @param klass [Class, #where]
@@ -252,7 +292,7 @@ module Bulkrax
252
292
 
253
293
  def delete(user)
254
294
  obj = find
255
- return false unless obj
295
+ raise ObjectFactoryInterface::ObjectNotFoundError, "Object not found to delete" unless obj
256
296
 
257
297
  Hyrax.persister.delete(resource: obj)
258
298
  Hyrax.index_adapter.delete(resource: obj)
@@ -292,6 +332,7 @@ module Bulkrax
292
332
 
293
333
  def create_file_set(attrs)
294
334
  # TODO: Make it work for Valkyrie
335
+ raise NotImplementedError, __method__.to_s
295
336
  end
296
337
 
297
338
  def create_work(attrs)
@@ -381,7 +422,7 @@ module Bulkrax
381
422
  end
382
423
 
383
424
  def find_by_id
384
- find(id: attributes[:id]) if attributes.key? :id
425
+ self.class.find(attributes[:id]) if attributes.key? :id
385
426
  end
386
427
 
387
428
  ##
@@ -6,6 +6,17 @@ module Bulkrax
6
6
 
7
7
  def perform(entry, importer_run)
8
8
  user = importer_run.importer.user
9
+
10
+ # When we delete, we don't go through the build process.
11
+ # However, we need the identifier to be set for the entry.
12
+ # This enables us to delete based on the ID, not just the source_identifier.
13
+ if entry.respond_to?(:build_metadata_for_delete) &&
14
+ entry.parsed_metadata.nil? &&
15
+ entry.raw_metadata.present?
16
+ entry.build_metadata_for_delete
17
+ entry.save!
18
+ end
19
+
9
20
  entry.factory.delete(user)
10
21
 
11
22
  # rubocop:disable Rails/SkipsModelValidations
@@ -5,6 +5,23 @@ module Bulkrax
5
5
  # We do too much in these entry classes. We need to extract the common logic from the various
6
6
  # entry models into a module that can be shared between them.
7
7
  class CsvEntry < Entry # rubocop:disable Metrics/ClassLength
8
+ class CsvPathError < StandardError
9
+ def initialize(message)
10
+ super(message)
11
+ end
12
+ end
13
+
14
+ class RecordNotFound < StandardError
15
+ def initialize(message)
16
+ super(message)
17
+ end
18
+ end
19
+
20
+ class MissingMetadata < StandardError
21
+ def initialize(message)
22
+ super(message)
23
+ end
24
+ end
8
25
  serialize :raw_metadata, Bulkrax::NormalizedJson
9
26
 
10
27
  def self.fields_from_data(data)
@@ -16,7 +33,7 @@ module Bulkrax
16
33
  # there's a risk that this reads the whole file into memory and could cause a memory leak
17
34
  # we strip any special characters out of the headers. looking at you Excel
18
35
  def self.read_data(path)
19
- raise StandardError, 'CSV path empty' if path.blank?
36
+ raise CsvPathError, 'CSV path empty' if path.blank?
20
37
  options = {
21
38
  headers: true,
22
39
  header_converters: ->(h) { h.to_s.gsub(/[^\w\d\. -]+/, '').strip.to_sym },
@@ -85,10 +102,18 @@ module Bulkrax
85
102
  self.parsed_metadata
86
103
  end
87
104
 
105
+ # limited metadata is needed for delete jobs
106
+ def build_metadata_for_delete
107
+ self.parsed_metadata = {}
108
+ establish_factory_class
109
+ add_ingested_metadata
110
+ self.parsed_metadata
111
+ end
112
+
88
113
  def validate_record
89
- raise StandardError, 'Record not found' if record.nil?
114
+ raise RecordNotFound, 'Record not found' if record.nil?
90
115
  unless importerexporter.parser.required_elements?(record)
91
- raise StandardError, "Missing required elements, missing element(s) are: "\
116
+ raise MissingMetadata, "Missing required elements, missing element(s) are: "\
92
117
  "#{importerexporter.parser.missing_elements(record).join(', ')}"
93
118
  end
94
119
  end
@@ -160,7 +185,7 @@ module Bulkrax
160
185
  source_id = source_id.to_a if source_id.is_a?(ActiveTriples::Relation)
161
186
  source_id = Array.wrap(source_id).first
162
187
  self.parsed_metadata[source_identifier] = source_id
163
- model_name = hyrax_record.respond_to?(:to_rdf_representation) ? hyrax_record.to_rdf_representation : hyrax_record.has_model.first
188
+ model_name = Bulkrax.object_factory.model_name(resource: hyrax_record)
164
189
  self.parsed_metadata[key_for_export('model')] = model_name
165
190
  end
166
191
 
@@ -179,9 +204,13 @@ module Bulkrax
179
204
 
180
205
  def build_relationship_metadata
181
206
  # Includes all relationship methods for all exportable record types (works, Collections, FileSets)
207
+ # @TODO: this logic assumes that the relationships are all available via a method that can be called
208
+ # on the object. With Valkyrie, this is only true for Hyrax-based models which include the
209
+ # ArResource module. We need to consider reworking this logic into an object factory method
210
+ # that can handle different types of models.
182
211
  relationship_methods = {
183
- related_parents_parsed_mapping => %i[member_of_collection_ids member_of_work_ids in_work_ids],
184
- related_children_parsed_mapping => %i[member_collection_ids member_work_ids file_set_ids]
212
+ related_parents_parsed_mapping => %i[member_of_collection_ids member_of_work_ids in_work_ids parent],
213
+ related_children_parsed_mapping => %i[member_collection_ids member_work_ids file_set_ids member_ids]
185
214
  }
186
215
 
187
216
  relationship_methods.each do |relationship_key, methods|
@@ -189,7 +218,9 @@ module Bulkrax
189
218
 
190
219
  values = []
191
220
  methods.each do |m|
192
- values << hyrax_record.public_send(m) if hyrax_record.respond_to?(m)
221
+ value = hyrax_record.public_send(m) if hyrax_record.respond_to?(m)
222
+ value_id = value.try(:id)&.to_s || value # get the id if it's an object
223
+ values << value_id if value_id.present?
193
224
  end
194
225
  values = values.flatten.uniq
195
226
  next if values.blank?
@@ -316,11 +347,11 @@ module Bulkrax
316
347
 
317
348
  def build_thumbnail_files
318
349
  return unless importerexporter.include_thumbnails
350
+ thumbnail = Bulkrax.object_factory.thumbnail_for(resource: hyrax_record)
351
+ return unless thumbnail
319
352
 
353
+ filenames = map_file_sets(Array.wrap(thumbnail))
320
354
  thumbnail_mapping = 'thumbnail_file'
321
- file_sets = Array.wrap(hyrax_record.thumbnail)
322
-
323
- filenames = map_file_sets(file_sets)
324
355
  handle_join_on_export(thumbnail_mapping, filenames, false)
325
356
  end
326
357
 
@@ -26,25 +26,38 @@ module Bulkrax
26
26
 
27
27
  # Prepend the file_set id to ensure a unique filename and also one that is not longer than 255 characters
28
28
  def filename(file_set)
29
- return if file_set.original_file.blank?
30
- if file_set.original_file.respond_to?(:original_filename) # valkyrie
31
- fn = file_set.original_file.original_filename
32
- mime = ::Marcel::MimeType.for(file_set.original_file.file.io)
33
- else # original non valkyrie version
34
- fn = file_set.original_file.file_name.first
35
- mime = ::Marcel::MimeType.for(declared_type: file_set.original_file.mime_type)
36
- end
37
- ext_mime = ::Marcel::MimeType.for(name: fn)
29
+ # return if there are no files on the fileset
30
+ return if Bulkrax.object_factory.original_file(fileset: file_set).blank?
31
+
32
+ fn = Bulkrax.object_factory.filename_for(fileset: file_set)
33
+ file = Bulkrax.object_factory.original_file(fileset: file_set)
34
+ ext = file_extension(file: file, filename: fn)
35
+
36
+ # Prepend the file_set id to ensure a unique filename
37
+ filename = File.basename(fn, ".*")
38
+ # Skip modification if file already has ID or we're in metadata-only mode
38
39
  if fn.include?(file_set.id) || importerexporter.metadata_only?
39
- filename = "#{fn}.#{mime.to_sym}"
40
- filename = fn if mime.to_s == ext_mime.to_s
40
+ # keep filename as is
41
41
  else
42
- filename = "#{file_set.id}_#{fn}.#{mime.to_sym}"
43
- filename = "#{file_set.id}_#{fn}" if mime.to_s == ext_mime.to_s
42
+ filename = "#{file_set.id}_#{filename}"
44
43
  end
45
- # Remove extention truncate and reattach
46
- ext = File.extname(filename)
44
+ filename = ext.present? ? "#{filename}.#{ext}" : fn
45
+
46
+ # Remove extension, truncate and reattach
47
47
  "#{File.basename(filename, ext)[0...(220 - ext.length)]}#{ext}"
48
48
  end
49
+
50
+ ##
51
+ # Generate the appropriate file extension based on the mime type of the file
52
+ # @return [String] the file extension for the given file
53
+ def file_extension(file:, filename:)
54
+ declared_mime = ::Marcel::MimeType.for(declared_type: file.mime_type)
55
+ # validate the declared mime type
56
+ declared_mime = ::Marcel::MimeType.for(name: filename) if declared_mime.nil? || declared_mime == "application/octet-stream"
57
+ # convert the mime type to a file extension
58
+ Mime::Type.lookup(declared_mime).symbol.to_s
59
+ rescue Mime::Type::InvalidMimeType
60
+ nil
61
+ end
49
62
  end
50
63
  end
@@ -2,6 +2,15 @@
2
2
 
3
3
  module Bulkrax
4
4
  module FileSetEntryBehavior
5
+ class FileNameError < StandardError
6
+ end
7
+
8
+ class OrphanFileSetError < StandardError
9
+ end
10
+
11
+ class FilePathError < StandardError
12
+ end
13
+
5
14
  extend ActiveSupport::Concern
6
15
 
7
16
  included do
@@ -21,11 +30,11 @@ module Bulkrax
21
30
 
22
31
  path_to_file = parser.path_to_files(filename: filename)
23
32
 
24
- parsed_metadata['file'][i] = path_to_file
33
+ parsed_metadata['file'][i] = path_to_file if path_to_file.present?
25
34
  end
26
35
  parsed_metadata['file'].delete('')
27
36
 
28
- raise ::StandardError, "one or more file paths are invalid: #{parsed_metadata['file'].join(', ')}" unless parsed_metadata['file'].map { |file_path| ::File.file?(file_path) }.all?
37
+ raise FilePathError, "one or more file paths are invalid: #{parsed_metadata['file'].join(', ')}" unless parsed_metadata['file'].map { |file_path| ::File.file?(file_path) }.all?
29
38
 
30
39
  parsed_metadata['file']
31
40
  end
@@ -33,13 +42,13 @@ module Bulkrax
33
42
  def validate_presence_of_filename!
34
43
  return if parsed_metadata&.[](file_reference)&.map(&:present?)&.any?
35
44
 
36
- raise StandardError, 'File set must have a filename'
45
+ raise FileNameError, 'File set must have a filename'
37
46
  end
38
47
 
39
48
  def validate_presence_of_parent!
40
49
  return if parsed_metadata[related_parents_parsed_mapping]&.map(&:present?)&.any?
41
50
 
42
- raise StandardError, 'File set must be related to at least one work'
51
+ raise OrphanFileSetError, 'File set must be related to at least one work'
43
52
  end
44
53
 
45
54
  def parent_jobs
@@ -56,7 +56,7 @@ module Bulkrax
56
56
 
57
57
  returning_value = false
58
58
  File.open(filename) do |file|
59
- mime_type = ::Marcel::MimeType.for(file)
59
+ mime_type = ::Marcel::MimeType.for(name: file)
60
60
  returning_value = mime_type.include?('application/zip') || mime_type.include?('application/gzip')
61
61
  end
62
62
  returning_value
@@ -209,8 +209,11 @@ module Bulkrax
209
209
  def rebuild_entries(types_array = nil)
210
210
  index = 0
211
211
  (types_array || %w[collection work file_set relationship]).each do |type|
212
- # works are not gurneteed to have Work in the type
213
-
212
+ # works are not guaranteed to have Work in the type
213
+ if type.eql?('relationship')
214
+ ScheduleRelationshipsJob.set(wait: 5.minutes).perform_later(importer_id: importerexporter.id)
215
+ next
216
+ end
214
217
  importer.entries.where(rebuild_entry_query(type, parser_fields['entry_statuses'])).find_each do |e|
215
218
  seen[e.identifier] = true
216
219
  e.status_info('Pending', importer.current_run)
@@ -38,7 +38,7 @@ module Bulkrax
38
38
  # We aren't right now because so many Bulkrax users are in between Fedora and Valkyrie
39
39
  if model.casecmp('collection').zero? || model.casecmp('collectionresource').zero?
40
40
  @collections << r
41
- elsif model.casecmp('fileset').zero?
41
+ elsif model.casecmp('fileset').zero? || model.casecmp('hyrax::fileset').zero?
42
42
  @file_sets << r
43
43
  else
44
44
  @works << r
@@ -248,14 +248,22 @@ module Bulkrax
248
248
  if file_sets.nil? # for valkyrie
249
249
  file_sets = record.respond_to?(:file_sets) ? record.file_sets : record.members&.select(&:file_set?)
250
250
  end
251
- file_sets << record.thumbnail if exporter.include_thumbnails && record.thumbnail.present? && record.work?
251
+
252
+ if importerexporter.include_thumbnails?
253
+ thumbnail = Bulkrax.object_factory.thumbnail_for(resource: record)
254
+ file_sets << thumbnail if thumbnail.present?
255
+ end
256
+
252
257
  file_sets.each do |fs|
253
258
  path = File.join(exporter_export_path, folder_count, 'files')
254
259
  FileUtils.mkdir_p(path) unless File.exist? path
260
+
261
+ original_file = Bulkrax.object_factory.original_file(fileset: fs)
262
+ next if original_file.blank?
255
263
  file = filename(fs)
256
- next if file.blank? || fs.original_file.blank?
257
264
 
258
- io = fs.original_file.respond_to?(:uri) ? open(fs.original_file.uri) : fs.original_file.file.io
265
+ io = original_file.respond_to?(:uri) ? open(original_file.uri) : original_file.file.io
266
+
259
267
  File.open(File.join(path, file), 'wb') do |f|
260
268
  f.write(io.read)
261
269
  f.close
@@ -263,6 +271,8 @@ module Bulkrax
263
271
  end
264
272
  rescue Ldp::Gone
265
273
  return
274
+ rescue StandardError => e
275
+ raise StandardError, "Unable to retrieve files for identifier #{identifier} - #{e.message}"
266
276
  end
267
277
 
268
278
  def export_key_allowed(key)
@@ -372,6 +382,7 @@ module Bulkrax
372
382
 
373
383
  return @path_to_files if File.exist?(@path_to_files)
374
384
 
385
+ # TODO: This method silently returns nil if there is no file & no zip file
375
386
  File.join(importer_unzip_path, 'files', filename) if file? && zip?
376
387
  end
377
388
 
@@ -103,7 +103,7 @@ module Bulkrax
103
103
  end
104
104
 
105
105
  def good_file_type?(path)
106
- %w[.xml .xls .xsd].include?(File.extname(path)) || ::Marcel::MimeType.for(path).include?('application/xml')
106
+ %w[.xml .xls .xsd].include?(File.extname(path)) || ::Marcel::MimeType.for(path: path).include?('application/xml')
107
107
  end
108
108
 
109
109
  def total
@@ -66,27 +66,68 @@ module Bulkrax
66
66
  entry.default_work_type.constantize
67
67
  end
68
68
 
69
+ private
70
+
69
71
  ##
70
72
  # @api private
71
73
  # @return [String]
72
74
  def name
73
- fc = if entry.parsed_metadata&.[]('model').present?
74
- Array.wrap(entry.parsed_metadata['model']).first
75
- elsif entry.importerexporter&.mapping&.[]('work_type').present?
76
- # Because of delegation's nil guard, we're reaching rather far into the implementation
77
- # details.
78
- Array.wrap(entry.parsed_metadata['work_type']).first
79
- else
80
- entry.default_work_type
81
- end
82
-
83
- # Let's coerce this into the right shape; we're not mutating the string because it might well
84
- # be frozen.
85
- fc = fc.tr(' ', '_')
86
- fc = fc.downcase if fc.match?(/[-_]/)
87
- fc.camelcase
75
+ # Try each strategy in order until one returns a value
76
+ fc = find_factory_class_name || entry.default_work_type
77
+
78
+ # Normalize the string format
79
+ normalize_class_name(fc)
88
80
  rescue
89
81
  entry.default_work_type
90
82
  end
83
+
84
+ ##
85
+ # Try each strategy in sequence to find a factory class name
86
+ # @return [String, nil] the factory class name or nil if none found
87
+ def find_factory_class_name
88
+ prioritized_strategies = [
89
+ :model_from_parsed_metadata,
90
+ :work_type_from_parsed_metadata,
91
+ :model_from_raw_metadata,
92
+ :model_from_mapped_field
93
+ ]
94
+
95
+ # Return the first non-nil result
96
+ prioritized_strategies.each do |strategy|
97
+ result = send(strategy)
98
+ return result if result.present?
99
+ end
100
+
101
+ nil
102
+ end
103
+
104
+ def model_from_parsed_metadata
105
+ Array.wrap(entry.parsed_metadata['model']).first if entry.parsed_metadata&.[]('model').present?
106
+ end
107
+
108
+ def work_type_from_parsed_metadata
109
+ Array.wrap(entry.parsed_metadata['work_type']).first if entry.importerexporter&.mapping&.[]('work_type').present?
110
+ end
111
+
112
+ def model_from_raw_metadata
113
+ Array.wrap(entry.raw_metadata&.[]('model'))&.first if entry.raw_metadata&.[]('model').present?
114
+ end
115
+
116
+ def model_from_mapped_field
117
+ return nil unless entry.parser.model_field_mappings.any? { |field| entry.raw_metadata&.[](field).present? }
118
+ field = entry.parser.model_field_mappings.find { |f| entry.raw_metadata&.[](f).present? }
119
+ Array.wrap(entry.raw_metadata[field]).first
120
+ end
121
+
122
+ ##
123
+ # Normalize a class name string to proper format
124
+ # @param name [String] the class name to normalize
125
+ # @return [String] the normalized class name
126
+ def normalize_class_name(name)
127
+ name = name.to_s
128
+ name = name.tr(' ', '_')
129
+ name = name.downcase if name.match?(/[-_]/)
130
+ name.camelcase
131
+ end
91
132
  end
92
133
  end
@@ -22,8 +22,14 @@ module Wings
22
22
  # NOTE: This is using the Bulkrax::ObjectFactory (e.g. the one envisioned for ActiveFedora).
23
23
  # In doing this, we avoid the situation where Bulkrax::ValkyrieObjectFactory calls this custom query.
24
24
 
25
- # This is doing a solr search so we have to use the search_field instead of the property
26
- af_object = Bulkrax::ObjectFactory.search_by_property(value: value, klass: ActiveFedora::Base, field: search_field)
25
+ # This is doing a solr search first, so we have to use the search_field instead of the property for "field"
26
+ # If it cannot find the object in Solr, it falls back to an ActiveFedora search, if the object is an ActiveFedora object
27
+ af_object = Bulkrax::ObjectFactory.search_by_property(
28
+ value: value,
29
+ klass: ActiveFedora::Base,
30
+ field: search_field,
31
+ name_field: property
32
+ )
27
33
 
28
34
  return if af_object.blank?
29
35
  return af_object unless use_valkyrie
@@ -34,18 +34,22 @@
34
34
  <p class='bulkrax-p-align'>
35
35
  <% if @importer.present? %>
36
36
  <%# TODO Consider how to account for Bulkrax.collection_model_class %>
37
- <% factory_record = @entry.factory.find %>
38
- <% if factory_record.present? %>
39
- <% factory_record_class = factory_record.class %>
40
- <% factory_record_class_human = factory_record_class.model_name.human %>
41
- <strong><%= factory_record_class_human %> Link:</strong>
42
- <% if defined?(Hyrax) && factory_record_class_human == 'Collection' %>
43
- <%= link_to factory_record_class_human, hyrax.polymorphic_path(factory_record) %>
37
+ <% begin %>
38
+ <% factory_record = @entry.factory.find rescue nil %>
39
+ <% if factory_record.present? %>
40
+ <% factory_record_class = factory_record.class %>
41
+ <% factory_record_class_human = factory_record_class.model_name.human %>
42
+ <strong><%= factory_record_class_human %> Link:</strong>
43
+ <% if defined?(Hyrax) && factory_record_class_human == 'Collection' %>
44
+ <%= link_to factory_record_class_human, hyrax.polymorphic_path(factory_record) %>
45
+ <% else %>
46
+ <%= link_to factory_record_class_human, main_app.polymorphic_path(factory_record) %>
47
+ <% end %>
44
48
  <% else %>
45
- <%= link_to factory_record_class_human, main_app.polymorphic_path(factory_record) %>
49
+ <strong>Item Link:</strong> Item has not yet been imported successfully
46
50
  <% end %>
47
- <% else %>
48
- <strong>Item Link:</strong> Item has not yet been imported successfully
51
+ <% rescue => e %>
52
+ <strong>Item Link:</strong> Unable to retrieve item (<%= e.message %>)
49
53
  <% end %>
50
54
  <% else %>
51
55
  <% record = @entry&.hyrax_record %>
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bulkrax
4
- VERSION = '9.1.0'
4
+ VERSION = '9.2.0'
5
5
  end
data/lib/bulkrax.rb CHANGED
@@ -107,11 +107,8 @@ module Bulkrax
107
107
  # Hyrax::FileSet.try(:internal_resource) || 'hi'
108
108
  # => #<Dry::Types::Result::Failure input=:internal_resource error=...
109
109
  # ```
110
- if collection_model_class.respond_to?(:internal_resource)
111
- collection_model_class.internal_resource
112
- else
113
- collection_model_class.to_s
114
- end
110
+ instance = collection_model_class.new
111
+ instance.respond_to?(:internal_resource) ? instance.internal_resource : collection_model_class.to_s
115
112
  end
116
113
 
117
114
  def file_model_class
@@ -130,11 +127,8 @@ module Bulkrax
130
127
  # Hyrax::FileSet.try(:internal_resource) || 'hi'
131
128
  # => #<Dry::Types::Result::Failure input=:internal_resource error=...
132
129
  # ```
133
- if file_model_class.respond_to?(:internal_resource)
134
- file_model_class.internal_resource
135
- else
136
- file_model_class.to_s
137
- end
130
+ instance = file_model_class.new
131
+ instance.respond_to?(:internal_resource) ? instance.internal_resource : file_model_class.to_s
138
132
  end
139
133
 
140
134
  def curation_concerns
@@ -154,7 +148,8 @@ module Bulkrax
154
148
  # Hyrax::FileSet.try(:internal_resource) || 'hi'
155
149
  # => #<Dry::Types::Result::Failure input=:internal_resource error=...
156
150
  # ```
157
- cc.respond_to?(:internal_resource) ? cc.internal_resource : cc.to_s
151
+ instance = cc.new
152
+ instance.respond_to?(:internal_resource) ? instance.internal_resource : cc.to_s
158
153
  end.uniq
159
154
  end
160
155
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bulkrax
3
3
  version: !ruby/object:Gem::Version
4
- version: 9.1.0
4
+ version: 9.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rob Kaufman
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-05-29 00:00:00.000000000 Z
11
+ date: 2025-09-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -17,6 +17,9 @@ dependencies:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: 5.1.6
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: 7.0.0
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -24,6 +27,9 @@ dependencies:
24
27
  - - ">="
25
28
  - !ruby/object:Gem::Version
26
29
  version: 5.1.6
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: 7.0.0
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: bagit
29
35
  requirement: !ruby/object:Gem::Requirement
@@ -345,7 +351,6 @@ files:
345
351
  - app/factories/bulkrax/object_factory.rb
346
352
  - app/factories/bulkrax/object_factory_interface.rb
347
353
  - app/factories/bulkrax/valkyrie_object_factory.rb
348
- - app/factories/bulkrax/valkyrize-hyku.code-workspace
349
354
  - app/helpers/bulkrax/application_helper.rb
350
355
  - app/helpers/bulkrax/exporters_helper.rb
351
356
  - app/helpers/bulkrax/importers_helper.rb
@@ -1,19 +0,0 @@
1
- {
2
- "folders": [
3
- {
4
- "path": "../../../../../hyku"
5
- },
6
- {
7
- "path": "../../../../hyrax"
8
- },
9
- {
10
- "path": "../../.."
11
- },
12
- {
13
- "path": "../../../../iiif_print"
14
- }
15
- ],
16
- "settings": {
17
- "github.copilot.inlineSuggest.enable": true
18
- }
19
- }