bulkrax 1.0.2 → 2.1.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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/app/controllers/bulkrax/exporters_controller.rb +12 -4
  4. data/app/controllers/bulkrax/importers_controller.rb +23 -17
  5. data/app/factories/bulkrax/object_factory.rb +84 -63
  6. data/app/jobs/bulkrax/create_relationships_job.rb +156 -0
  7. data/app/jobs/bulkrax/delete_work_job.rb +6 -2
  8. data/app/jobs/bulkrax/export_work_job.rb +3 -1
  9. data/app/jobs/bulkrax/exporter_job.rb +1 -0
  10. data/app/jobs/bulkrax/{import_work_collection_job.rb → import_collection_job.rb} +4 -2
  11. data/app/jobs/bulkrax/import_file_set_job.rb +69 -0
  12. data/app/jobs/bulkrax/import_work_job.rb +2 -0
  13. data/app/jobs/bulkrax/importer_job.rb +18 -1
  14. data/app/matchers/bulkrax/application_matcher.rb +5 -5
  15. data/app/models/bulkrax/csv_collection_entry.rb +8 -6
  16. data/app/models/bulkrax/csv_entry.rb +132 -65
  17. data/app/models/bulkrax/csv_file_set_entry.rb +26 -0
  18. data/app/models/bulkrax/entry.rb +19 -8
  19. data/app/models/bulkrax/exporter.rb +12 -5
  20. data/app/models/bulkrax/importer.rb +24 -5
  21. data/app/models/bulkrax/oai_entry.rb +5 -1
  22. data/app/models/bulkrax/rdf_entry.rb +16 -7
  23. data/app/models/bulkrax/xml_entry.rb +4 -0
  24. data/app/models/concerns/bulkrax/dynamic_record_lookup.rb +39 -0
  25. data/app/models/concerns/bulkrax/export_behavior.rb +2 -2
  26. data/app/models/concerns/bulkrax/has_matchers.rb +44 -13
  27. data/app/models/concerns/bulkrax/import_behavior.rb +40 -5
  28. data/app/models/concerns/bulkrax/importer_exporter_behavior.rb +23 -2
  29. data/app/models/concerns/bulkrax/status_info.rb +4 -4
  30. data/app/parsers/bulkrax/application_parser.rb +67 -84
  31. data/app/parsers/bulkrax/bagit_parser.rb +13 -4
  32. data/app/parsers/bulkrax/csv_parser.rb +170 -64
  33. data/app/parsers/bulkrax/oai_dc_parser.rb +6 -3
  34. data/app/parsers/bulkrax/xml_parser.rb +5 -0
  35. data/app/views/bulkrax/exporters/_form.html.erb +1 -1
  36. data/app/views/bulkrax/exporters/show.html.erb +2 -1
  37. data/app/views/bulkrax/importers/index.html.erb +17 -17
  38. data/app/views/bulkrax/importers/show.html.erb +52 -6
  39. data/config/locales/bulkrax.en.yml +1 -0
  40. data/db/migrate/20190731114016_change_importer_and_exporter_to_polymorphic.rb +5 -1
  41. data/db/migrate/20211004170708_change_bulkrax_statuses_error_message_column_type_to_text.rb +5 -0
  42. data/db/migrate/20211203195233_rename_children_counters_to_relationships.rb +6 -0
  43. data/db/migrate/20211220195027_add_file_set_counters_to_importer_runs.rb +7 -0
  44. data/db/migrate/20220118001339_add_import_attempts_to_entries.rb +5 -0
  45. data/db/migrate/20220119213325_add_work_counters_to_importer_runs.rb +6 -0
  46. data/lib/bulkrax/engine.rb +1 -1
  47. data/lib/bulkrax/version.rb +1 -1
  48. data/lib/bulkrax.rb +9 -17
  49. data/lib/generators/bulkrax/templates/bin/importer +17 -11
  50. data/lib/generators/bulkrax/templates/config/bulkrax_api.yml +3 -1
  51. data/lib/generators/bulkrax/templates/config/initializers/bulkrax.rb +7 -12
  52. metadata +22 -10
  53. data/app/jobs/bulkrax/child_relationships_job.rb +0 -128
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2cad330885f5f4d2dce25c989ebc7552b49b2212b68434d5def942fff61ad23a
4
- data.tar.gz: 2b1e71044b26ff6ab9b25822ee969a7e886e095bbd2147b9b240406634e8f832
3
+ metadata.gz: 5a5ca12cf97da052d9855404c783d52e41679587332ac72aac25d85237cf8231
4
+ data.tar.gz: 449209843d001cdac64dd4073db130920bb62a3193b2eefd53b94f2ec15f6a08
5
5
  SHA512:
6
- metadata.gz: 18e70353c64be13d686ff1da35d01c932422da49f9fd0ebf08470fdc0edc9333a9b51633b158cfaedf020ae5e3830574eb192a69ba498dff81e92847de4235f7
7
- data.tar.gz: 6502ec16bc63aefed3c678ec062d3bc923c159260ac39278e692f4628bfe05acd675d49193b92c9e42f2ab50976d48c3d608d77f8db1d45774a664fd74b09bce
6
+ metadata.gz: 296ef3a7a7af8765aaae12353bec7ecfb35a97546955520ac544269525adfe459f3e686886fefbff54c91ba09f7da20c3b6b8c62bcb3fc7be6970f7967defa17
7
+ data.tar.gz: e7a13f384940a89d42c5efb5d52b8e940939c103dceb35890e08cb56ee020c2c664d1bb4d57945ae9d7afb2acd822385861e004ab7d69c776a0816d688cf66e1
data/README.md CHANGED
@@ -16,7 +16,7 @@ gem 'bulkrax', git: 'https://github.com/samvera-labs/bulkrax.git'
16
16
 
17
17
  And then execute:
18
18
  ```bash
19
- $ bundle update
19
+ $ bundle install
20
20
  $ rails generate bulkrax:install
21
21
  ```
22
22
 
@@ -51,9 +51,12 @@ module Bulkrax
51
51
  if @exporter.save
52
52
  if params[:commit] == 'Create and Export'
53
53
  # Use perform now for export
54
- Bulkrax::ExporterJob.perform_now(@exporter.id)
54
+ Bulkrax::ExporterJob.perform_later(@exporter.id)
55
+ message = 'Exporter was successfully created. A download link will appear once it completes.'
56
+ else
57
+ message = 'Exporter was successfully created.'
55
58
  end
56
- redirect_to exporters_path, notice: 'Exporter was successfully created.'
59
+ redirect_to exporters_path, notice: message
57
60
  else
58
61
  render :new
59
62
  end
@@ -63,8 +66,13 @@ module Bulkrax
63
66
  def update
64
67
  field_mapping_params
65
68
  if @exporter.update(exporter_params)
66
- Bulkrax::ExporterJob.perform_now(@exporter.id) if params[:commit] == 'Update and Re-Export All Items'
67
- redirect_to exporters_path, notice: 'Exporter was successfully updated.'
69
+ if params[:commit] == 'Update and Re-Export All Items'
70
+ Bulkrax::ExporterJob.perform_later(@exporter.id)
71
+ message = 'Exporter was successfully updated. A download link will appear once it completes.'
72
+ else
73
+ 'Exporter was successfully updated.'
74
+ end
75
+ redirect_to exporters_path, notice: message
68
76
  else
69
77
  render :edit
70
78
  end
@@ -37,6 +37,7 @@ module Bulkrax
37
37
 
38
38
  @work_entries = @importer.entries.where(type: @importer.parser.entry_class.to_s).page(params[:work_entries_page]).per(30)
39
39
  @collection_entries = @importer.entries.where(type: @importer.parser.collection_entry_class.to_s).page(params[:collections_entries_page]).per(30)
40
+ @file_set_entries = @importer.entries.where(type: @importer.parser.file_set_entry_class.to_s).page(params[:file_set_entries_page]).per(30)
40
41
  end
41
42
  end
42
43
 
@@ -113,23 +114,9 @@ module Bulkrax
113
114
  if @importer.update(importer_params)
114
115
  files_for_import(file, cloud_files) unless file.nil? && cloud_files.nil?
115
116
  # do not perform the import
116
- if params[:commit] == 'Update Importer'
117
- # do nothing
118
- # OAI-only - selective re-harvest
119
- elsif params[:commit] == 'Update and Harvest Updated Items'
120
- Bulkrax::ImporterJob.perform_later(@importer.id, true)
121
- elsif params[:commit] == 'Update Metadata and Files'
122
- @importer.parser_fields['update_files'] = true
123
- @importer.save
124
- Bulkrax::ImporterJob.perform_later(@importer.id)
125
- # Perform a full metadata and files re-import; do the same for an OAI re-harvest of all items
126
- elsif params[:commit] == ('Update and Replace Files' || 'Update and Re-Harvest All Items')
127
- @importer.parser_fields['replace_files'] = true
128
- @importer.save
129
- Bulkrax::ImporterJob.perform_later(@importer.id)
130
- # In all other cases, perform a metadata-only re-import
131
- else
132
- Bulkrax::ImporterJob.perform_later(@importer.id)
117
+ unless params[:commit] == 'Update Importer'
118
+ set_files_parser_fields
119
+ Bulkrax::ImporterJob.send(@importer.parser.perform_method, @importer.id, update_harvest)
133
120
  end
134
121
  if api_request?
135
122
  json_response('updated', :ok, 'Importer was successfully updated.')
@@ -310,6 +297,25 @@ module Bulkrax
310
297
  redirect_to path, notice: message
311
298
  end
312
299
  end
300
+
301
+ # update methods (for commit deciphering)
302
+ def update_harvest
303
+ # OAI-only - selective re-harvest
304
+ params[:commit] == 'Update and Harvest Updated Items'
305
+ end
306
+
307
+ def set_files_parser_fields
308
+ if params[:commit] == 'Update Metadata and Files'
309
+ @importer.parser_fields['update_files'] = true
310
+ elsif params[:commit] == ('Update and Replace Files' || 'Update and Re-Harvest All Items')
311
+ @importer.parser_fields['replace_files'] = true
312
+ elsif params[:commit] == 'Update and Harvest Updated Items'
313
+ return
314
+ else
315
+ @importer.parser_fields['metadata_only'] = true
316
+ end
317
+ @importer.save
318
+ end
313
319
  end
314
320
  # rubocop:enable Metrics/ClassLength
315
321
  end
@@ -4,16 +4,24 @@ module Bulkrax
4
4
  class ObjectFactory
5
5
  extend ActiveModel::Callbacks
6
6
  include Bulkrax::FileFactory
7
+ include DynamicRecordLookup
8
+
7
9
  define_model_callbacks :save, :create
8
- attr_reader :attributes, :object, :source_identifier_value, :klass, :replace_files, :update_files, :work_identifier
10
+ attr_reader :attributes, :object, :source_identifier_value, :klass, :replace_files, :update_files, :work_identifier, :collection_field_mapping, :related_parents_parsed_mapping
9
11
 
10
12
  # rubocop:disable Metrics/ParameterLists
11
- def initialize(attributes:, source_identifier_value:, work_identifier:, replace_files: false, user: nil, klass: nil, update_files: false)
13
+ def initialize(attributes:, source_identifier_value:, work_identifier:, collection_field_mapping:, related_parents_parsed_mapping: nil, replace_files: false, user: nil, klass: nil, update_files: false)
14
+ ActiveSupport::Deprecation.warn(
15
+ 'Creating Collections using the collection_field_mapping will no longer be supported as of Bulkrax version 3.0.' \
16
+ ' Please configure Bulkrax to use related_parents_field_mapping and related_children_field_mapping instead.'
17
+ )
12
18
  @attributes = ActiveSupport::HashWithIndifferentAccess.new(attributes)
13
19
  @replace_files = replace_files
14
20
  @update_files = update_files
15
21
  @user = user || User.batch_user
16
22
  @work_identifier = work_identifier
23
+ @collection_field_mapping = collection_field_mapping
24
+ @related_parents_parsed_mapping = related_parents_parsed_mapping
17
25
  @source_identifier_value = source_identifier_value
18
26
  @klass = klass || Bulkrax.default_work_type.constantize
19
27
  end
@@ -28,7 +36,7 @@ module Bulkrax
28
36
  arg_hash = { id: attributes[:id], name: 'UPDATE', klass: klass }
29
37
  @object = find
30
38
  if object
31
- object.reindex_extent = Hyrax::Adapters::NestingIndexAdapter::LIMITED_REINDEX
39
+ object.reindex_extent = Hyrax::Adapters::NestingIndexAdapter::LIMITED_REINDEX if object.respond_to?(:reindex_extent)
32
40
  ActiveSupport::Notifications.instrument('import.importer', arg_hash) { update }
33
41
  else
34
42
  ActiveSupport::Notifications.instrument('import.importer', arg_hash.merge(name: 'CREATE')) { create }
@@ -41,20 +49,27 @@ module Bulkrax
41
49
  self.run
42
50
  # Create the error exception if the object is not validly saved for some reason
43
51
  raise ActiveFedora::RecordInvalid, object if !object.persisted? || object.changed?
52
+ object
44
53
  end
45
54
 
46
55
  def update
47
56
  raise "Object doesn't exist" unless object
48
- destroy_existing_files if @replace_files && klass != Collection
57
+ destroy_existing_files if @replace_files && ![Collection, FileSet].include?(klass)
49
58
  attrs = attribute_update
50
59
  run_callbacks :save do
51
- klass == Collection ? update_collection(attrs) : work_actor.update(environment(attrs))
60
+ if klass == Collection
61
+ update_collection(attrs)
62
+ elsif klass == FileSet
63
+ update_file_set(attrs)
64
+ else
65
+ work_actor.update(environment(attrs))
66
+ end
52
67
  end
53
68
  log_updated(object)
54
69
  end
55
70
 
56
71
  def find
57
- return find_by_id if attributes[:id]
72
+ return find_by_id if attributes[:id].present?
58
73
  return search_by_identifier if attributes[work_identifier].present?
59
74
  end
60
75
 
@@ -84,10 +99,16 @@ module Bulkrax
84
99
  def create
85
100
  attrs = create_attributes
86
101
  @object = klass.new
87
- object.reindex_extent = Hyrax::Adapters::NestingIndexAdapter::LIMITED_REINDEX
102
+ object.reindex_extent = Hyrax::Adapters::NestingIndexAdapter::LIMITED_REINDEX if object.respond_to?(:reindex_extent)
88
103
  run_callbacks :save do
89
104
  run_callbacks :create do
90
- klass == Collection ? create_collection(attrs) : work_actor.create(environment(attrs))
105
+ if klass == Collection
106
+ create_collection(attrs)
107
+ elsif klass == FileSet
108
+ create_file_set(attrs)
109
+ else
110
+ work_actor.create(environment(attrs))
111
+ end
91
112
  end
92
113
  end
93
114
  log_created(object)
@@ -121,52 +142,62 @@ module Bulkrax
121
142
  end
122
143
 
123
144
  def create_collection(attrs)
145
+ ActiveSupport::Deprecation.warn(
146
+ 'Creating Collections using the collection_field_mapping will no longer be supported as of Bulkrax version 3.0.' \
147
+ ' Please configure Bulkrax to use related_parents_field_mapping and related_children_field_mapping instead.'
148
+ )
124
149
  attrs = collection_type(attrs)
125
- object.members = members
126
- object.member_of_collections = member_of_collections
150
+ persist_collection_memberships(parent: object, child: find_collection(attributes[:child_collection_id])) if attributes[:child_collection_id].present?
151
+ persist_collection_memberships(parent: find_collection(attributes[collection_field_mapping]), child: object) if attributes[collection_field_mapping].present?
127
152
  object.attributes = attrs
128
153
  object.apply_depositor_metadata(@user)
129
154
  object.save!
130
155
  end
131
156
 
132
157
  def update_collection(attrs)
133
- object.members = members
134
- object.member_of_collections = member_of_collections
158
+ ActiveSupport::Deprecation.warn(
159
+ 'Creating Collections using the collection_field_mapping will no longer be supported as of Bulkrax version 3.0.' \
160
+ ' Please configure Bulkrax to use related_parents_field_mapping and related_children_field_mapping instead.'
161
+ )
162
+ persist_collection_memberships(parent: object, child: find_collection(attributes[:child_collection_id])) if attributes[:child_collection_id].present?
163
+ persist_collection_memberships(parent: find_collection(attributes[collection_field_mapping]), child: object) if attributes[collection_field_mapping].present?
135
164
  object.attributes = attrs
136
165
  object.save!
137
166
  end
138
167
 
139
- # Collections don't respond to member_of_collections_attributes or member_of_collection_ids=
140
- # or member_ids=
141
- # Add them directly with members / member_of_collections
142
- # collection should be in the form { id: collection_id }
143
- # and collections [{ id: collection_id }]
144
- # member_ids comes from
145
- # @todo - consider performance implications although we wouldn't expect a Collection to be a member of many Collections
146
- def members
147
- ms = object.members.to_a
148
- [:children].each do |atat|
149
- next if attributes[atat].blank?
150
- ms.concat(
151
- Array.wrap(
152
- find_collection(attributes[atat])
153
- )
154
- )
155
- end
156
- ms.flatten.compact.uniq
157
- end
158
-
159
- def member_of_collections
160
- ms = object.member_of_collection_ids.to_a.map { |id| find_collection(id) }
161
- [:collection, :collections].each do |atat|
162
- next if attributes[atat].blank?
163
- ms.concat(
164
- Array.wrap(
165
- find_collection(attributes[atat])
166
- )
167
- )
168
+ # This method is heavily inspired by Hyrax's AttachFilesToWorkJob
169
+ def create_file_set(attrs)
170
+ work = find_record(attributes[related_parents_parsed_mapping].first)
171
+ work_permissions = work.permissions.map(&:to_hash)
172
+ file_set_attrs = attrs.slice(*object.attributes.keys)
173
+ object.assign_attributes(file_set_attrs)
174
+
175
+ attrs['uploaded_files'].each do |uploaded_file_id|
176
+ uploaded_file = ::Hyrax::UploadedFile.find(uploaded_file_id)
177
+ next if uploaded_file.file_set_uri.present?
178
+
179
+ actor = ::Hyrax::Actors::FileSetActor.new(object, @user)
180
+ uploaded_file.update(file_set_uri: actor.file_set.uri)
181
+ actor.file_set.permissions_attributes = work_permissions
182
+ actor.create_metadata
183
+ actor.create_content(uploaded_file)
184
+ actor.attach_to_work(work)
168
185
  end
169
- ms.flatten.compact.uniq
186
+
187
+ object.save!
188
+ end
189
+
190
+ def update_file_set(attrs)
191
+ file_set_attrs = attrs.slice(*object.attributes.keys)
192
+ actor = ::Hyrax::Actors::FileSetActor.new(object, @user)
193
+
194
+ actor.update_metadata(file_set_attrs)
195
+ end
196
+
197
+ # Add child to parent's #member_collections
198
+ # Add parent to child's #member_of_collections
199
+ def persist_collection_memberships(parent:, child:)
200
+ ::Hyrax::Collections::NestedCollectionPersistenceService.persist_nested_collection_for(parent: parent, child: child)
170
201
  end
171
202
 
172
203
  def find_collection(id)
@@ -192,32 +223,22 @@ module Bulkrax
192
223
  # which is used by Hyrax::Actors::AddAsMemberOfCollectionsActor
193
224
  def create_attributes
194
225
  return transform_attributes if klass == Collection
195
- if attributes[:collection].present?
196
- transform_attributes.except(:collection).merge(member_of_collections_attributes: { 0 => { id: collection.id } })
197
- elsif attributes[:collections].present?
198
- collection_ids = attributes[:collections].each.with_index.each_with_object({}) do |(element, index), ids|
199
- ids[index] = { id: element }
200
- end
201
- transform_attributes.except(:collections).merge(member_of_collections_attributes: collection_ids)
202
- else
203
- transform_attributes
204
- end
226
+ ActiveSupport::Deprecation.warn(
227
+ 'Creating Collections using the collection_field_mapping will no longer be supported as of Bulkrax version 3.0.' \
228
+ ' Please configure Bulkrax to use related_parents_field_mapping and related_children_field_mapping instead.'
229
+ )
230
+ transform_attributes.except(:collections, :collection, collection_field_mapping)
205
231
  end
206
232
 
207
233
  # Strip out the :collection key, and add the member_of_collection_ids,
208
234
  # which is used by Hyrax::Actors::AddAsMemberOfCollectionsActor
209
235
  def attribute_update
210
236
  return transform_attributes.except(:id) if klass == Collection
211
- if attributes[:collection].present?
212
- transform_attributes.except(:id).except(:collection).merge(member_of_collections_attributes: { "0" => { id: collection.id } })
213
- elsif attributes[:collections].present?
214
- collection_ids = attributes[:collections].each.with_index.each_with_object({}) do |(element, index), ids|
215
- ids[index.to_s] = element
216
- end
217
- transform_attributes.except(:id).except(:collections).merge(member_of_collections_attributes: collection_ids)
218
- else
219
- transform_attributes.except(:id)
220
- end
237
+ ActiveSupport::Deprecation.warn(
238
+ 'Creating Collections using the collection_field_mapping will no longer be supported as of Bulkrax version 3.0.' \
239
+ ' Please configure Bulkrax to use related_parents_field_mapping and related_children_field_mapping instead.'
240
+ )
241
+ transform_attributes.except(:id, :collections, :collection, collection_field_mapping)
221
242
  end
222
243
 
223
244
  # Override if we need to map the attributes from the parser in
@@ -230,7 +251,7 @@ module Bulkrax
230
251
 
231
252
  # Regardless of what the Parser gives us, these are the properties we are prepared to accept.
232
253
  def permitted_attributes
233
- klass.properties.keys.map(&:to_sym) + %i[id edit_users edit_groups read_groups visibility work_members_attributes admin_set_id]
254
+ klass.properties.keys.map(&:to_sym) + %i[id edit_users edit_groups read_groups visibility work_members_attributes admin_set_id member_of_collections_attributes]
234
255
  end
235
256
  end
236
257
  end
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bulkrax
4
+ # Responsible for creating parent-child relationships between Works and Collections.
5
+ #
6
+ # Handles three kinds of relationships:
7
+ # - Work to Collection
8
+ # - Collection to Collection
9
+ # - Work to Work
10
+ #
11
+ # These can be established from either side of the relationship (i.e. from parent to child or from child to parent).
12
+ # This job only creates one relationship at a time. If a record needs multiple parents or children or both, individual
13
+ # jobs should be run for each of those relationships.
14
+ #
15
+ # NOTE: In the context of this job, "record" is used to generically refer
16
+ # to either an instance of a Work or an instance of a Collection.
17
+ # NOTE: In the context of this job, "identifier" is used to generically refer
18
+ # to either a record's ID or an Bulkrax::Entry's source_identifier.
19
+ class CreateRelationshipsJob < ApplicationJob
20
+ include DynamicRecordLookup
21
+
22
+ queue_as :import
23
+
24
+ attr_accessor :base_entry, :child_record, :parent_record, :importer_run
25
+
26
+ # @param entry_identifier [String] source_identifier of the base Bulkrax::Entry the job was triggered from (see #build_for_importer)
27
+ # @param parent_identifier [String] Work/Collection ID or Bulkrax::Entry source_identifier
28
+ # @param child_identifier [String] Work/Collection ID or Bulkrax::Entry source_identifier
29
+ # @param importer_run [Bulkrax::ImporterRun] current importer run (needed to properly update counters)
30
+ #
31
+ # The entry_identifier is used to lookup the @base_entry for the job (a.k.a. the entry the job was called from).
32
+ # The @base_entry defines the context of the relationship (e.g. "this entry (@base_entry) should have a parent").
33
+ # Whether the @base_entry is the parent or the child in the relationship is determined by the presence of a
34
+ # parent_identifier or child_identifier param. For example, if a parent_identifier is passed, we know @base_entry
35
+ # is the child in the relationship, and vice versa if a child_identifier is passed.
36
+ def perform(entry_identifier:, parent_identifier: nil, child_identifier: nil, importer_run:)
37
+ @base_entry = Entry.find_by(identifier: entry_identifier)
38
+ @importer_run = importer_run
39
+ if parent_identifier.present?
40
+ @child_record = find_record(entry_identifier)
41
+ @parent_record = find_record(parent_identifier)
42
+ elsif child_identifier.present?
43
+ @parent_record = find_record(entry_identifier)
44
+ @child_record = find_record(child_identifier)
45
+ else
46
+ raise ::StandardError, %("#{entry_identifier}" needs either a child or a parent to create a relationship)
47
+ end
48
+
49
+ if @child_record.blank? || @parent_record.blank?
50
+ reschedule(
51
+ entry_identifier: entry_identifier,
52
+ parent_identifier: parent_identifier,
53
+ child_identifier: child_identifier,
54
+ importer_run: importer_run
55
+ )
56
+ return false # stop current job from continuing to run after rescheduling
57
+ end
58
+
59
+ create_relationship
60
+ rescue ::StandardError => e
61
+ base_entry.status_info(e)
62
+ importer_run.increment!(:failed_relationships) # rubocop:disable Rails/SkipsModelValidations
63
+ end
64
+
65
+ private
66
+
67
+ def create_relationship
68
+ if parent_record.is_a?(::Collection) && child_record.is_a?(::Collection)
69
+ collection_parent_collection_child
70
+ elsif parent_record.is_a?(::Collection) && curation_concern?(child_record)
71
+ collection_parent_work_child
72
+ elsif curation_concern?(parent_record) && child_record.is_a?(::Collection)
73
+ raise ::StandardError, 'a Collection may not be assigned as a child of a Work'
74
+ else
75
+ work_parent_work_child
76
+ end
77
+ end
78
+
79
+ def user
80
+ @user ||= importer_run.importer.user
81
+ end
82
+
83
+ # Work-Collection membership is added to the child as member_of_collection_ids
84
+ # This is adding the reverse relationship, from the child to the parent
85
+ def collection_parent_work_child
86
+ ActiveSupport::Deprecation.warn(
87
+ 'Creating Collections using the collection_field_mapping will no longer be supported as of Bulkrax version 3.0.' \
88
+ ' Please configure Bulkrax to use related_parents_field_mapping and related_children_field_mapping instead.'
89
+ )
90
+ attrs = { id: child_record.id, member_of_collections_attributes: { 0 => { id: parent_record.id } } }
91
+ ObjectFactory.new(
92
+ attributes: attrs,
93
+ source_identifier_value: nil, # sending the :id in the attrs means the factory doesn't need a :source_identifier_value
94
+ work_identifier: base_entry.parser.work_identifier,
95
+ collection_field_mapping: base_entry.parser.collection_field_mapping,
96
+ replace_files: false,
97
+ user: user,
98
+ klass: child_record.class
99
+ ).run
100
+ # TODO: add counters for :processed_parents and :failed_parents
101
+ importer_run.increment!(:processed_relationships) # rubocop:disable Rails/SkipsModelValidations
102
+ end
103
+
104
+ # Collection-Collection membership is added to the as member_ids
105
+ def collection_parent_collection_child
106
+ ActiveSupport::Deprecation.warn(
107
+ 'Creating Collections using the collection_field_mapping will no longer be supported as of Bulkrax version 3.0.' \
108
+ ' Please configure Bulkrax to use related_parents_field_mapping and related_children_field_mapping instead.'
109
+ )
110
+ attrs = { id: parent_record.id, child_collection_id: child_record.id }
111
+ ObjectFactory.new(
112
+ attributes: attrs,
113
+ source_identifier_value: nil, # sending the :id in the attrs means the factory doesn't need a :source_identifier_value
114
+ work_identifier: base_entry.parser.work_identifier,
115
+ collection_field_mapping: base_entry.parser.collection_field_mapping,
116
+ replace_files: false,
117
+ user: user,
118
+ klass: parent_record.class
119
+ ).run
120
+ # TODO: add counters for :processed_parents and :failed_parents
121
+ importer_run.increment!(:processed_relationships) # rubocop:disable Rails/SkipsModelValidations
122
+ end
123
+
124
+ # Work-Work membership is added to the parent as member_ids
125
+ def work_parent_work_child
126
+ ActiveSupport::Deprecation.warn(
127
+ 'Creating Collections using the collection_field_mapping will no longer be supported as of Bulkrax version 3.0.' \
128
+ ' Please configure Bulkrax to use related_parents_field_mapping and related_children_field_mapping instead.'
129
+ )
130
+ attrs = {
131
+ id: parent_record.id,
132
+ work_members_attributes: { 0 => { id: child_record.id } }
133
+ }
134
+ ObjectFactory.new(
135
+ attributes: attrs,
136
+ source_identifier_value: nil, # sending the :id in the attrs means the factory doesn't need a :source_identifier_value
137
+ work_identifier: base_entry.parser.work_identifier,
138
+ collection_field_mapping: base_entry.parser.collection_field_mapping,
139
+ replace_files: false,
140
+ user: user,
141
+ klass: parent_record.class
142
+ ).run
143
+ # TODO: add counters for :processed_parents and :failed_parents
144
+ importer_run.increment!(:processed_relationships) # rubocop:disable Rails/SkipsModelValidations
145
+ end
146
+
147
+ def reschedule(entry_identifier:, parent_identifier:, child_identifier:, importer_run:)
148
+ CreateRelationshipsJob.set(wait: 10.minutes).perform_later(
149
+ entry_identifier: entry_identifier,
150
+ parent_identifier: parent_identifier,
151
+ child_identifier: child_identifier,
152
+ importer_run: importer_run
153
+ )
154
+ end
155
+ end
156
+ end
@@ -8,8 +8,12 @@ module Bulkrax
8
8
  def perform(entry, importer_run)
9
9
  work = entry.factory.find
10
10
  work&.delete
11
- importer_run.increment!(:deleted_records)
12
- importer_run.decrement!(:enqueued_records)
11
+ ImporterRun.find(importer_run.id).increment!(:deleted_records)
12
+ ImporterRun.find(importer_run.id).decrement!(:enqueued_records)
13
+ entry.save!
14
+ entry.importer.current_run = ImporterRun.find(importer_run.id)
15
+ entry.importer.record_status
16
+ entry.status_info("Deleted", ImporterRun.find(importer_run.id))
13
17
  end
14
18
  # rubocop:enable Rails/SkipsModelValidations
15
19
  end
@@ -26,12 +26,14 @@ module Bulkrax
26
26
  # rubocop:enable Rails/SkipsModelValidations
27
27
  end
28
28
  exporter_run = ExporterRun.find(args[1])
29
- return if exporter_run.enqueued_records.positive?
29
+ return entry if exporter_run.enqueued_records.positive?
30
30
  if exporter_run.failed_records.positive?
31
31
  exporter_run.exporter.status_info('Complete (with failures)')
32
32
  else
33
33
  exporter_run.exporter.status_info('Complete')
34
34
  end
35
+
36
+ return entry
35
37
  end
36
38
  end
37
39
  end
@@ -8,6 +8,7 @@ module Bulkrax
8
8
  exporter = Exporter.find(exporter_id)
9
9
  exporter.export
10
10
  exporter.write
11
+ exporter.save
11
12
  true
12
13
  end
13
14
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bulkrax
4
- class ImportWorkCollectionJob < ApplicationJob
4
+ class ImportCollectionJob < ApplicationJob
5
5
  queue_as :import
6
6
 
7
7
  # rubocop:disable Rails/SkipsModelValidations
@@ -11,9 +11,11 @@ module Bulkrax
11
11
  entry.build
12
12
  entry.save
13
13
  add_user_to_permission_template!(entry)
14
+ ImporterRun.find(args[1]).increment!(:processed_records)
14
15
  ImporterRun.find(args[1]).increment!(:processed_collections)
15
16
  ImporterRun.find(args[1]).decrement!(:enqueued_records)
16
17
  rescue => e
18
+ ImporterRun.find(args[1]).increment!(:failed_records)
17
19
  ImporterRun.find(args[1]).increment!(:failed_collections)
18
20
  ImporterRun.find(args[1]).decrement!(:enqueued_records)
19
21
  raise e
@@ -28,7 +30,7 @@ module Bulkrax
28
30
  collection = entry.factory.find
29
31
  permission_template = Hyrax::PermissionTemplate.find_or_create_by!(source_id: collection.id)
30
32
 
31
- Hyrax::PermissionTemplateAccess.create!(
33
+ Hyrax::PermissionTemplateAccess.find_or_create_by!(
32
34
  permission_template_id: permission_template.id,
33
35
  agent_id: user.user_key,
34
36
  agent_type: 'user',
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bulkrax
4
+ class MissingParentError < ::StandardError; end
5
+ class ImportFileSetJob < ApplicationJob
6
+ include DynamicRecordLookup
7
+
8
+ queue_as :import
9
+
10
+ def perform(entry_id, importer_run_id)
11
+ entry = Entry.find(entry_id)
12
+ parent_identifier = entry.raw_metadata[entry.related_parents_raw_mapping]&.strip
13
+
14
+ validate_parent!(parent_identifier)
15
+
16
+ entry.build
17
+ if entry.succeeded?
18
+ # rubocop:disable Rails/SkipsModelValidations
19
+ ImporterRun.find(importer_run_id).increment!(:processed_records)
20
+ ImporterRun.find(importer_run_id).increment!(:processed_file_sets)
21
+ else
22
+ ImporterRun.find(importer_run_id).increment!(:failed_records)
23
+ ImporterRun.find(importer_run_id).increment!(:failed_file_sets)
24
+ # rubocop:enable Rails/SkipsModelValidations
25
+ end
26
+ ImporterRun.find(importer_run_id).decrement!(:enqueued_records) # rubocop:disable Rails/SkipsModelValidations
27
+ entry.save!
28
+
29
+ rescue MissingParentError => e
30
+ # try waiting for the parent record to be created
31
+ entry.import_attempts += 1
32
+ entry.save!
33
+ if entry.import_attempts < 5
34
+ ImportFileSetJob
35
+ .set(wait: (entry.import_attempts + 1).minutes)
36
+ .perform_later(entry_id, importer_run_id)
37
+ else
38
+ ImporterRun.find(importer_run_id).decrement!(:enqueued_records) # rubocop:disable Rails/SkipsModelValidations
39
+ entry.status_info(e)
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ attr_reader :parent_record
46
+
47
+ def validate_parent!(parent_identifier)
48
+ # if parent_identifier is missing, it will be caught by #validate_presence_of_parent!
49
+ return if parent_identifier.blank?
50
+
51
+ find_parent_record(parent_identifier)
52
+ check_parent_exists!(parent_identifier)
53
+ check_parent_is_a_work!(parent_identifier)
54
+ end
55
+
56
+ def check_parent_exists!(parent_identifier)
57
+ raise MissingParentError, %(Unable to find a record with the identifier "#{parent_identifier}") if parent_record.blank?
58
+ end
59
+
60
+ def check_parent_is_a_work!(parent_identifier)
61
+ error_msg = %(A record with the ID "#{parent_identifier}" was found, but it was a #{parent_record.class}, which is not an valid/available work type)
62
+ raise ::StandardError, error_msg unless curation_concern?(parent_record)
63
+ end
64
+
65
+ def find_parent_record(parent_identifier)
66
+ @parent_record ||= find_record(parent_identifier)
67
+ end
68
+ end
69
+ end
@@ -10,11 +10,13 @@ module Bulkrax
10
10
  entry.build
11
11
  if entry.status == "Complete"
12
12
  ImporterRun.find(args[1]).increment!(:processed_records)
13
+ ImporterRun.find(args[1]).increment!(:processed_works)
13
14
  ImporterRun.find(args[1]).decrement!(:enqueued_records) unless ImporterRun.find(args[1]).enqueued_records <= 0 # rubocop:disable Style/IdenticalConditionalBranches
14
15
  else
15
16
  # do not retry here because whatever parse error kept you from creating a work will likely
16
17
  # keep preventing you from doing so.
17
18
  ImporterRun.find(args[1]).increment!(:failed_records)
19
+ ImporterRun.find(args[1]).increment!(:failed_works)
18
20
  ImporterRun.find(args[1]).decrement!(:enqueued_records) unless ImporterRun.find(args[1]).enqueued_records <= 0 # rubocop:disable Style/IdenticalConditionalBranches
19
21
  end
20
22
  entry.save!