• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pulibrary / pdc_describe / 7031ff5b-e2d7-4285-af8b-e7b55adfc48a

pending completion
7031ff5b-e2d7-4285-af8b-e7b55adfc48a

Pull #899

circleci

mccalluc
Finish removal of explicit if-then in js
Pull Request #899: Move messages up to sidebar on the right which can be toggled

1623 of 1788 relevant lines covered (90.77%)

105.5 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.3
/app/models/work.rb
1
# frozen_string_literal: true
2

3
# rubocop:disable Metrics/ClassLength
4
class Work < ApplicationRecord
1✔
5
  MAX_UPLOADS = 20
1✔
6

7
  # Errors for cases where there is no valid Collection
8
  class InvalidCollectionError < ::ArgumentError; end
1✔
9

10
  has_many :work_activity, -> { order(updated_at: :desc) }, dependent: :destroy
126✔
11
  has_many :user_work, -> { order(updated_at: :desc) }, dependent: :destroy
8✔
12
  has_many_attached :pre_curation_uploads, service: :amazon_pre_curation
1✔
13

14
  belongs_to :collection
1✔
15
  belongs_to :curator, class_name: "User", foreign_key: "curator_user_id", optional: true
1✔
16

17
  attribute :work_type, :string, default: "DATASET"
1✔
18
  attribute :profile, :string, default: "DATACITE"
1✔
19

20
  attr_accessor :user_entered_doi
1✔
21

22
  alias state_history user_work
1✔
23

24
  include AASM
1✔
25

26
  aasm column: :state do
1✔
27
    state :none, inital: true
1✔
28
    state :draft, :awaiting_approval, :approved, :withdrawn, :tombstone
1✔
29

30
    event :draft, after: :draft_doi do
1✔
31
      transitions from: :none, to: :draft, guard: :valid_to_draft
1✔
32
    end
33

34
    event :complete_submission do
1✔
35
      transitions from: :draft, to: :awaiting_approval, guard: :valid_to_submit
1✔
36
    end
37

38
    event :request_changes do
1✔
39
      transitions from: :awaiting_approval, to: :awaiting_approval, guard: :valid_to_submit
1✔
40
    end
41

42
    event :approve do
1✔
43
      transitions from: :awaiting_approval, to: :approved, guard: :valid_to_approve, after: :publish
1✔
44
    end
45

46
    event :withdraw do
1✔
47
      transitions from: [:draft, :awaiting_approval, :approved], to: :withdrawn
1✔
48
    end
49

50
    event :resubmit do
1✔
51
      transitions from: :withdrawn, to: :draft
1✔
52
    end
53

54
    event :remove do
1✔
55
      transitions from: :withdrawn, to: :tombstone
1✔
56
    end
57

58
    after_all_events :track_state_change
1✔
59
  end
60

61
  def state=(new_state)
1✔
62
    new_state_sym = new_state.to_sym
163✔
63
    valid_states = self.class.aasm.states.map(&:name)
163✔
64
    raise(StandardError, "Invalid state '#{new_state}'") unless valid_states.include?(new_state_sym)
163✔
65
    aasm_write_state_without_persistence(new_state_sym)
162✔
66
  end
67

68
  ##
69
  # Is this work editable by a given user?
70
  # A work is editable when:
71
  # * it is being edited by the person who made it
72
  # * it is being edited by a collection admin of the collection where is resides
73
  # * it is being edited by a super admin
74
  # @param [User]
75
  # @return [Boolean]
76
  def editable_by?(user)
1✔
77
    submitted_by?(user) || administered_by?(user)
42✔
78
  end
79

80
  def submitted_by?(user)
1✔
81
    created_by_user_id == user.id
42✔
82
  end
83

84
  def administered_by?(user)
1✔
85
    user.has_role?(:collection_admin, collection)
12✔
86
  end
87

88
  class << self
1✔
89
    def find_by_doi(doi)
1✔
90
      prefix = "10.34770/"
×
91
      doi = "#{prefix}#{doi}" unless doi.start_with?(prefix)
×
92
      Work.find_by!("metadata @> ?", JSON.dump(doi: doi))
×
93
    end
94

95
    def find_by_ark(ark)
1✔
96
      prefix = "ark:/"
×
97
      ark = "#{prefix}#{ark}" unless ark.start_with?(prefix)
×
98
      Work.find_by!("metadata @> ?", JSON.dump(ark: ark))
×
99
    end
100

101
    delegate :resource_type_general_values, to: PDCMetadata::Resource
1✔
102

103
    # Determines whether or not a test DOI should be referenced
104
    # (this avoids requests to the DOI API endpoint for non-production deployments)
105
    # @return [Boolean]
106
    def publish_test_doi?
1✔
107
      (Rails.env.development? || Rails.env.test?) && Rails.configuration.datacite.user.blank?
33✔
108
    end
109
  end
110

111
  include Rails.application.routes.url_helpers
1✔
112

113
  before_save do |work|
1✔
114
    # Ensure that the metadata JSONB postgres field is persisted properly
115
    work.metadata = JSON.parse(work.resource.to_json)
469✔
116
    work.save_pre_curation_uploads
469✔
117
  end
118

119
  after_save do |work|
1✔
120
    if work.approved?
469✔
121
      work.attach_s3_resources if !work.pre_curation_uploads.empty? && work.pre_curation_uploads.length > work.post_curation_uploads.length
52✔
122
      work.reload
52✔
123
    end
124
  end
125

126
  validate do |work|
1✔
127
    if none?
488✔
128
      work.validate_doi
64✔
129
    elsif draft?
424✔
130
      work.valid_to_draft
251✔
131
    else
132
      work.valid_to_submit
173✔
133
    end
134
  end
135

136
  # Overload ActiveRecord.reload method
137
  # https://apidock.com/rails/ActiveRecord/Base/reload
138
  #
139
  # NOTE: Usually `after_save` is a better place to put this kind of code:
140
  #
141
  #   after_save do |work|
142
  #     work.resource = nil
143
  #   end
144
  #
145
  # but that does not work in this case because the block points to a different
146
  # memory object for `work` than the we want we want to reload.
147
  def reload(options = nil)
1✔
148
    super
116✔
149
    # Force `resource` to be reloaded
150
    @resource = nil
116✔
151
    self
116✔
152
  end
153

154
  def validate_doi
1✔
155
    return true unless user_entered_doi
64✔
156
    if /^10.\d{4,9}\/[-._;()\/:a-z0-9\-]+$/.match?(doi.downcase)
9✔
157
      response = Faraday.get("#{Rails.configuration.datacite.doi_url}#{doi}")
8✔
158
      errors.add(:base, "Invalid DOI: can not verify it's authenticity") unless response.success? || response.status == 302
8✔
159
    else
160
      errors.add(:base, "Invalid DOI: does not match format")
1✔
161
    end
162
    errors.count == 0
9✔
163
  end
164

165
  def valid_to_draft
1✔
166
    errors.add(:base, "Must provide a title") if resource.main_title.blank?
500✔
167
    validate_ark
500✔
168
    validate_creators
500✔
169
    validate_uploads
500✔
170
    errors.count == 0
500✔
171
  end
172

173
  def valid_to_submit
1✔
174
    valid_to_draft
225✔
175
    validate_metadata
225✔
176
    errors.count == 0
225✔
177
  end
178

179
  def valid_to_approve(user)
1✔
180
    valid_to_submit
20✔
181
    unless user.has_role? :collection_admin, collection
20✔
182
      errors.add :base, "Unauthorized to Approve"
1✔
183
    end
184
    errors.count == 0
20✔
185
  end
186

187
  def title
1✔
188
    resource.main_title
205✔
189
  end
190

191
  def uploads_attributes
1✔
192
    return [] if approved? # once approved we no longer allow the updating of uploads via the application
34✔
193
    uploads.map do |upload|
32✔
194
      {
195
        id: upload.id,
10✔
196
        key: upload.key,
197
        filename: upload.filename.to_s,
198
        created_at: upload.created_at,
199
        url: rails_blob_path(upload, disposition: "attachment")
200
      }
201
    end
202
  end
203

204
  def form_attributes
1✔
205
    {
206
      uploads: uploads_attributes
34✔
207
    }
208
  end
209

210
  def draft_doi
1✔
211
    return if resource.doi.present?
24✔
212
    resource.doi = if self.class.publish_test_doi?
15✔
213
                     Rails.logger.info "Using hard-coded test DOI during development."
1✔
214
                     "10.34770/tbd"
1✔
215
                   else
216
                     result = data_cite_connection.autogenerate_doi(prefix: Rails.configuration.datacite.prefix)
14✔
217
                     if result.success?
14✔
218
                       result.success.doi
13✔
219
                     else
220
                       raise("Error generating DOI. #{result.failure.status} / #{result.failure.reason_phrase}")
1✔
221
                     end
222
                   end
223
    save!
14✔
224
  end
225

226
  def created_by_user
1✔
227
    User.find(created_by_user_id)
193✔
228
  rescue ActiveRecord::RecordNotFound
229
    nil
1✔
230
  end
231

232
  def resource=(resource)
1✔
233
    @resource = resource
239✔
234
    # Ensure that the metadata JSONB postgres field is persisted properly
235
    self.metadata = JSON.parse(resource.to_json)
239✔
236
  end
237

238
  def resource
1✔
239
    @resource ||= PDCMetadata::Resource.new_from_jsonb(metadata)
9,047✔
240
  end
241

242
  def url
1✔
243
    return unless persisted?
3✔
244

245
    @url ||= url_for(self)
3✔
246
  end
247

248
  def files_location_upload?
1✔
249
    files_location.blank? || files_location == "file_upload"
5✔
250
  end
251

252
  def files_location_cluster?
1✔
253
    files_location == "file_cluster"
38✔
254
  end
255

256
  def files_location_other?
1✔
257
    files_location == "file_other"
38✔
258
  end
259

260
  def change_curator(curator_user_id, current_user)
1✔
261
    if curator_user_id == "no-one"
2✔
262
      clear_curator(current_user)
×
263
    else
264
      update_curator(curator_user_id, current_user)
2✔
265
    end
266
  end
267

268
  def clear_curator(current_user)
1✔
269
    # Update the curator on the Work
270
    self.curator_user_id = nil
1✔
271
    save!
1✔
272

273
    # ...and log the activity
274
    WorkActivity.add_work_activity(id, "Unassigned existing curator", current_user.id, activity_type: WorkActivity::SYSTEM)
1✔
275
  end
276

277
  def update_curator(curator_user_id, current_user)
1✔
278
    # Update the curator on the Work
279
    self.curator_user_id = curator_user_id
3✔
280
    save!
3✔
281

282
    # ...and log the activity
283
    new_curator = User.find(curator_user_id)
3✔
284
    message = if curator_user_id == current_user.id
3✔
285
                "Self-assigned as curator"
1✔
286
              else
287
                "Set curator to @#{new_curator.uid}"
2✔
288
              end
289
    WorkActivity.add_work_activity(id, message, current_user.id, activity_type: WorkActivity::SYSTEM)
3✔
290
  end
291

292
  def curator_or_current_uid(user)
1✔
293
    persisted = if curator.nil?
3✔
294
                  user
2✔
295
                else
296
                  curator
1✔
297
                end
298
    persisted.uid
3✔
299
  end
300

301
  def add_message(message, current_user_id)
1✔
302
    WorkActivity.add_work_activity(id, message, current_user_id, activity_type: WorkActivity::MESSAGE)
8✔
303
  end
304

305
  def add_provenance_note(date, note, current_user_id)
1✔
306
    WorkActivity.add_work_activity(id, note, current_user_id, activity_type: WorkActivity::PROVENANCE_NOTES, created_at: date)
×
307
  end
308

309
  def log_changes(resource_compare, current_user_id)
1✔
310
    return if resource_compare.identical?
14✔
311
    WorkActivity.add_work_activity(id, resource_compare.differences.to_json, current_user_id, activity_type: WorkActivity::CHANGES)
14✔
312
  end
313

314
  def log_file_changes(changes, current_user_id)
1✔
315
    return if changes.count == 0
49✔
316
    WorkActivity.add_work_activity(id, changes.to_json, current_user_id, activity_type: WorkActivity::FILE_CHANGES)
12✔
317
  end
318

319
  def activities
1✔
320
    WorkActivity.activities_for_work(id, WorkActivity::MESSAGE_ACTIVITY_TYPES + WorkActivity::CHANGE_LOG_ACTIVITY_TYPES)
48✔
321
  end
322

323
  def new_notification_count_for_user(user_id)
1✔
324
    WorkActivityNotification.joins(:work_activity)
58✔
325
                            .where(user_id: user_id, read_at: nil)
326
                            .where(work_activity: { work_id: id })
327
                            .count
328
  end
329

330
  # Marks as read the notifications for the given user_id in this work.
331
  # In practice, the user_id is the id of the current user and therefore this method marks the current's user
332
  # notifications as read.
333
  def mark_new_notifications_as_read(user_id)
1✔
334
    activities.each do |activity|
48✔
335
      unread_notifications = WorkActivityNotification.where(user_id: user_id, work_activity_id: activity.id, read_at: nil)
58✔
336
      unread_notifications.each do |notification|
58✔
337
        notification.read_at = Time.now.utc
18✔
338
        notification.save
18✔
339
      end
340
    end
341
  end
342

343
  def current_transition
1✔
344
    aasm.current_event.to_s.humanize.delete("!")
4✔
345
  end
346

347
  def uploads
1✔
348
    return post_curation_uploads if approved?
126✔
349

350
    pre_curation_uploads
118✔
351
  end
352

353
  # This ensures that new ActiveStorage::Attachment objects can be modified before they are persisted
354
  def save_pre_curation_uploads
1✔
355
    return if pre_curation_uploads.empty?
469✔
356

357
    new_attachments = pre_curation_uploads.reject(&:persisted?)
140✔
358
    return if new_attachments.empty?
140✔
359

360
    save_new_attachments(new_attachments: new_attachments)
121✔
361
  end
362

363
  # Accesses post-curation S3 Bucket Objects
364
  def post_curation_s3_resources
1✔
365
    return [] unless approved?
59✔
366

367
    s3_resources
52✔
368
  end
369
  alias post_curation_uploads post_curation_s3_resources
1✔
370

371
  def s3_client
1✔
372
    s3_query_service.client
55✔
373
  end
374

375
  delegate :bucket_name, to: :s3_query_service
1✔
376

377
  # Transmit a HEAD request for an S3 Object in the post-curation Bucket
378
  # @param key [String]
379
  # @param bucket_name [String]
380
  # @return [Aws::S3::Types::HeadObjectOutput]
381
  def find_post_curation_s3_object(bucket_name:, key:)
1✔
382
    s3_client.head_object({
19✔
383
                            bucket: bucket_name,
384
                            key: key
385
                          })
386
    true
19✔
387
  rescue Aws::S3::Errors::NotFound
388
    nil
×
389
  end
390

391
  # Generates the S3 Object key
392
  # @return [String]
393
  def s3_object_key
1✔
394
    "#{doi}/#{id}"
234✔
395
  end
396

397
  # Transmit a HEAD request for the S3 Bucket directory for this Work
398
  # @param bucket_name location to be checked to be found
399
  # @return [Aws::S3::Types::HeadObjectOutput]
400
  def find_post_curation_s3_dir(bucket_name:)
1✔
401
    s3_client.head_object({
18✔
402
                            bucket: bucket_name,
403
                            key: s3_object_key
404
                          })
405
    true
×
406
  rescue Aws::S3::Errors::NotFound
407
    nil
18✔
408
  end
409

410
  # Transmit a DELETE request for the S3 directory in the pre-curation Bucket
411
  # @return [Aws::S3::Types::DeleteObjectOutput]
412
  def delete_pre_curation_s3_dir
1✔
413
    s3_client.delete_object({
18✔
414
                              bucket: bucket_name,
415
                              key: s3_object_key
416
                            })
417
  rescue Aws::S3::Errors::ServiceError => error
418
    raise(StandardError, "Failed to delete the pre-curation S3 Bucket directory #{s3_object_key}: #{error}")
×
419
  end
420

421
  # This is invoked within the scope of #after_save. Attachment objects require that the parent record be persisted (hence, #before_save is not an option).
422
  # However, a consequence of this is that #after_save is invoked whenever a new attached Blob or Attachment object is persisted.
423
  def attach_s3_resources
1✔
424
    return if approved?
68✔
425
    changes = []
43✔
426
    # This retrieves and adds S3 uploads if they do not exist
427
    pre_curation_s3_resources.each do |s3_file|
43✔
428
      if add_pre_curation_s3_object(s3_file)
14✔
429
        changes << { action: :added, filename: s3_file.filename }
11✔
430
      end
431
    end
432

433
    # Log the new files, but don't link the change to the current_user since we really don't know
434
    # who added the files directly to AWS S3.
435
    log_file_changes(changes, nil)
43✔
436
  end
437

438
  def as_json(options = nil)
1✔
439
    if options&.present?
10✔
440
      raise(StandardError, "Received options #{options}, but not supported")
×
441
      # Included in signature for compatibility with Rails.
442
    end
443

444
    # Pre-curation files are not accessible externally,
445
    # so we are not interested in listing them in JSON.
446
    # (The items in pre_curation_uploads also have different properties.)
447
    files = post_curation_uploads.map do |upload|
10✔
448
      {
449
        "filename": upload.filename,
6✔
450
        "size": upload.size
451
      }
452
    end
453

454
    # to_json returns a string of serialized JSON.
455
    # as_json returns the corresponding hash.
456
    {
457
      "resource" => resource.as_json,
10✔
458
      "files" => files,
459
      "collection" => collection.as_json.except("id")
460
    }
461
  end
462

463
  delegate :ark, :doi, :resource_type, :resource_type=, :resource_type_general, :resource_type_general=,
1✔
464
           :to_xml, to: :resource
465

466
  protected
1✔
467

468
    # This must be protected, NOT private for ActiveRecord to work properly with this attribute.
469
    #   Protected will still keep others from setting the metatdata, but allows ActiveRecord the access it needs
470
    def metadata=(metadata)
1✔
471
      super
708✔
472
      @resource = PDCMetadata::Resource.new_from_jsonb(metadata)
708✔
473
    end
474

475
  private
1✔
476

477
    def publish(user)
1✔
478
      publish_doi(user)
18✔
479
      update_ark_information
18✔
480
      publish_precurated_files
18✔
481
      save!
18✔
482
    end
483

484
    # Update EZID (our provider of ARKs) with the new information for this work.
485
    def update_ark_information
1✔
486
      # We only want to update the ark url under certain conditions.
487
      # Set this value in config/update_ark_url.yml
488
      if Rails.configuration.update_ark_url
18✔
489
        if ark.present?
7✔
490
          Ark.update(ark, url)
3✔
491
        end
492
      end
493
    end
494

495
    # Generates the key for ActiveStorage::Attachment and Attachment::Blob objects
496
    # @param attachment [ActiveStorage::Attachment]
497
    # @return [String]
498
    def generate_attachment_key(attachment)
1✔
499
      attachment_filename = attachment.filename.to_s
80✔
500
      attachment_key = attachment.key
80✔
501

502
      # Files actually coming from S3 include the DOI and bucket as part of the file name
503
      #  Files being attached in another manner may not have it, so we should include it.
504
      #  This is really for testing only.
505
      key_base = "#{doi}/#{id}"
80✔
506
      attachment_key = [key_base, attachment_filename].join("/") unless attachment_key.include?(key_base)
80✔
507

508
      attachment_ext = File.extname(attachment_filename)
80✔
509
      attachment_query = attachment_key.gsub(attachment_ext, "")
80✔
510
      results = ActiveStorage::Blob.where("key LIKE :query", query: "%#{attachment_query}%")
80✔
511
      blobs = results.to_a
80✔
512

513
      if blobs.present?
80✔
514
        index = blobs.length + 1
22✔
515
        attachment_key = attachment_key.gsub(/\.([a-zA-Z0-9\.]+)$/, "_#{index}.\\1")
22✔
516
      end
517

518
      attachment_key
80✔
519
    end
520

521
    def track_state_change(user, state = aasm.to_state)
1✔
522
      uw = UserWork.new(user_id: user.id, work_id: id, state: state)
100✔
523
      uw.save!
100✔
524
      WorkActivity.add_work_activity(id, "marked as #{state.to_s.titleize}", user.id, activity_type: WorkActivity::SYSTEM)
100✔
525
      WorkStateTransitionNotification.new(self, user.id).send
100✔
526
    end
527

528
    def data_cite_connection
1✔
529
      @data_cite_connection ||= Datacite::Client.new(username: Rails.configuration.datacite.user,
30✔
530
                                                     password: Rails.configuration.datacite.password,
531
                                                     host: Rails.configuration.datacite.host)
532
    end
533

534
    def validate_ark
1✔
535
      if ark.present?
500✔
536
        errors.add(:base, "Invalid ARK provided for the Work: #{ark}") unless Ark.valid?(ark)
88✔
537
      end
538
    end
539

540
    # rubocop:disable Metrics/AbcSize
541
    def validate_metadata
1✔
542
      return if metadata.blank?
225✔
543
      errors.add(:base, "Must provide a title") if resource.main_title.blank?
225✔
544
      errors.add(:base, "Must provide a description") if resource.description.blank?
225✔
545
      errors.add(:base, "Must indicate the Publisher") if resource.publisher.blank?
225✔
546
      errors.add(:base, "Must indicate the Publication Year") if resource.publication_year.blank?
225✔
547
      errors.add(:base, "Must indicate a Rights statement") if resource.rights.nil?
225✔
548
      errors.add(:base, "Must provide a Version number") if resource.version_number.blank?
225✔
549
      validate_creators
225✔
550
      validate_related_objects
225✔
551
    end
552
    # rubocop:enable Metrics/AbcSize
553

554
    def validate_creators
1✔
555
      if resource.creators.count == 0
725✔
556
        errors.add(:base, "Must provide at least one Creator")
1✔
557
      else
558
        resource.creators.each do |creator|
724✔
559
          if creator.orcid.present? && Orcid.invalid?(creator.orcid)
1,081✔
560
            errors.add(:base, "ORCID for creator #{creator.value} is not in format 0000-0000-0000-0000")
1✔
561
          end
562
        end
563
      end
564
    end
565

566
    def validate_related_objects
1✔
567
      return if resource.related_objects.empty?
225✔
568
      invalid = resource.related_objects.reject(&:valid?)
4✔
569
      errors.add(:base, "Related Objects are invalid: #{invalid.map(&:errors).join(', ')}") if invalid.count.positive?
4✔
570
    end
571

572
    def publish_doi(user)
1✔
573
      return Rails.logger.info("Publishing hard-coded test DOI during development.") if self.class.publish_test_doi?
18✔
574

575
      if doi.starts_with?(Rails.configuration.datacite.prefix)
18✔
576
        result = data_cite_connection.update(id: doi, attributes: doi_attributes)
16✔
577
        if result.failure?
16✔
578
          resolved_user = curator_or_current_uid(user)
2✔
579
          message = "@#{resolved_user} Error publishing DOI. #{result.failure.status} / #{result.failure.reason_phrase}"
2✔
580
          WorkActivity.add_work_activity(id, message, user.id, activity_type: WorkActivity::DATACITE_ERROR)
2✔
581
        end
582
      elsif ark.blank? # we can not update the url anywhere
2✔
583
        Honeybadger.notify("Publishing for a DOI we do not own and no ARK is present: #{doi}")
1✔
584
      end
585
    end
586

587
    def doi_attribute_url
1✔
588
      "https://datacommons.princeton.edu/discovery/doi/#{doi}"
16✔
589
    end
590

591
    def doi_attribute_resource
1✔
592
      PDCMetadata::Resource.new_from_jsonb(metadata)
16✔
593
    end
594

595
    def doi_attribute_xml
1✔
596
      unencoded = doi_attribute_resource.to_xml
16✔
597
      Base64.encode64(unencoded)
16✔
598
    end
599

600
    def doi_attributes
1✔
601
      {
602
        "event" => "publish",
16✔
603
        "xml" => doi_attribute_xml,
604
        "url" => doi_attribute_url
605
      }
606
    end
607

608
    def validate_uploads
1✔
609
      # The number of pre-curation uploads should be validated, as these are mutated directly
610
      if pre_curation_uploads.length > MAX_UPLOADS
500✔
611
        errors.add(:base, "Only #{MAX_UPLOADS} files may be uploaded by a user to a given Work. #{pre_curation_uploads.length} files were uploaded for the Work: #{ark}")
2✔
612
      end
613
    end
614

615
    # This needs to be called #before_save
616
    # This ensures that new ActiveStorage::Attachment objects are persisted with custom keys (which are generated from the file name and DOI)
617
    # @param new_attachments [Array<ActiveStorage::Attachment>]
618
    def save_new_attachments(new_attachments:)
1✔
619
      new_attachments.each do |attachment|
121✔
620
        # There are cases (race conditions?) where the ActiveStorage::Blob objects are not persisted
621
        next if attachment.frozen?
130✔
622

623
        # This ensures that the custom key for the ActiveStorage::Attachment and ActiveStorage::Blob objects are generated
624
        generated_key = generate_attachment_key(attachment)
80✔
625
        attachment.blob.key = generated_key
80✔
626
        attachment.blob.save
80✔
627

628
        attachment.save
80✔
629
      end
630
    end
631

632
    # S3QueryService object associated with this Work
633
    # @return [S3QueryService]
634
    def s3_query_service
1✔
635
      @s3_query_service = S3QueryService.new(self, !approved?)
168✔
636
    end
637

638
    # Request S3 Bucket Objects associated with this Work
639
    # @return [Array<S3File>]
640
    def s3_resources
1✔
641
      data_profile = s3_query_service.data_profile
95✔
642
      data_profile.fetch(:objects, [])
95✔
643
    end
644
    alias pre_curation_s3_resources s3_resources
1✔
645

646
    def s3_object_persisted?(s3_file)
1✔
647
      uploads_keys = uploads.map(&:key)
14✔
648
      uploads_keys.include?(s3_file.key)
14✔
649
    end
650

651
    def add_pre_curation_s3_object(s3_file)
1✔
652
      return if s3_object_persisted?(s3_file)
14✔
653

654
      persisted = s3_file.to_blob
11✔
655
      pre_curation_uploads.attach(persisted)
11✔
656
    end
657

658
    def publish_precurated_files
1✔
659
      # An error is raised if there are no files to be moved
660
      raise(StandardError, "Attempting to publish a Work without attached uploads for #{s3_object_key}") if pre_curation_uploads.empty? && post_curation_uploads.empty?
18✔
661

662
      # We need to explicitly access to post-curation services here.
663
      # Lets explicitly create it so the state of the work does not have any impact.
664
      s3_post_curation_query_service = S3QueryService.new(self, false)
18✔
665

666
      s3_dir = find_post_curation_s3_dir(bucket_name: s3_post_curation_query_service.bucket_name)
18✔
667
      raise(StandardError, "Attempting to publish a Work with an existing S3 Bucket directory for: #{s3_object_key}") unless s3_dir.nil?
18✔
668

669
      # Copy the pre-curation S3 Objects to the post-curation S3 Bucket...
670
      transferred_files = s3_post_curation_query_service.publish_files
18✔
671

672
      # ...check that the files are indeed now in the post-curation bucket...
673
      pre_curation_uploads.each do |attachment|
18✔
674
        s3_object = find_post_curation_s3_object(bucket_name: s3_post_curation_query_service.bucket_name, key: attachment.key)
19✔
675
        raise(StandardError, "Failed to validate the uploaded S3 Object #{attachment.key}") if s3_object.nil?
19✔
676
      end
677

678
      # ...and delete them from the pre-curation bucket.
679
      transferred_files.each(&:purge)
18✔
680
      delete_pre_curation_s3_dir
18✔
681
    end
682
end
683
# rubocop:enable Metrics/ClassLength
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc