From 39d8d020d141e6f8161fef529e06839d5a8a884f Mon Sep 17 00:00:00 2001 From: Eric O Date: Thu, 2 Jan 2025 17:43:02 -0500 Subject: [PATCH] All jobs now extend ActiveJob::Base --- app/jobs/export_search_results_to_csv_job.rb | 10 +- app/jobs/process_digital_object_import_job.rb | 29 ++-- app/jobs/reindex_digital_object_job.rb | 6 +- lib/hyacinth/csv/flatten.rb | 98 ++++++------ lib/hyacinth/queue.rb | 12 +- .../features/digital_object_editor_ui_spec.rb | 1 - .../csv_export_import_round_trip_spec.rb | 4 +- .../export_search_results_to_csv_job_spec.rb | 8 +- .../process_digital_object_import_job_spec.rb | 140 +++++++++--------- .../digital_object/dynamic_field_spec.rb | 2 +- 10 files changed, 155 insertions(+), 155 deletions(-) diff --git a/app/jobs/export_search_results_to_csv_job.rb b/app/jobs/export_search_results_to_csv_job.rb index bc4416ad4..3d8b8d6d6 100644 --- a/app/jobs/export_search_results_to_csv_job.rb +++ b/app/jobs/export_search_results_to_csv_job.rb @@ -1,4 +1,4 @@ -class ExportSearchResultsToCsvJob +class ExportSearchResultsToCsvJob < ActiveJob::Base include Hyacinth::Csv::Flatten SUPPRESSED_ON_EXPORT = ['_uuid', '_data_file_path', '_dc_type', '_state', '_title', '_created', '_modified', '_created_by', '_modified_by', '_project.uri', '_project.short_label'] @@ -16,9 +16,9 @@ class ExportSearchResultsToCsvJob ] CONTROLLED_TERM_CORE_SUBFIELDS_ALLOWED_ON_IMPORT = ['uri', 'value', 'authority', 'type'] - @queue = Hyacinth::Queue::DIGITAL_OBJECT_CSV_EXPORT + queue_as Hyacinth::Queue::DIGITAL_OBJECT_CSV_EXPORT - def self.perform(csv_export_id) + def perform(csv_export_id) start_time = Time.now csv_export = CsvExport.find(csv_export_id) @@ -59,7 +59,7 @@ def self.perform(csv_export_id) csv_export.save end - def self.map_temp_field_indexes(search_params, user, map = {}) + def map_temp_field_indexes(search_params, user, map = {}) # Common fields to all objects map['_pid'] ||= map.length map['_project.string_key'] ||= map.length @@ -111,7 +111,7 @@ def self.map_temp_field_indexes(search_params, user, map = {}) map end - def self.write_csv(path_to_csv_file, field_list, field_index_map) + def write_csv(path_to_csv_file, field_list, field_index_map) # Open new CSV for writing CSV.open(path_to_csv_file, 'wb') do |final_csv| # Write out human-friendly column display labels diff --git a/app/jobs/process_digital_object_import_job.rb b/app/jobs/process_digital_object_import_job.rb index 281b02c58..04c00de08 100644 --- a/app/jobs/process_digital_object_import_job.rb +++ b/app/jobs/process_digital_object_import_job.rb @@ -1,9 +1,10 @@ -class ProcessDigitalObjectImportJob - @queue = Hyacinth::Queue::DIGITAL_OBJECT_IMPORT_LOW +class ProcessDigitalObjectImportJob < ActiveJob::Base + queue_as Hyacinth::Queue::DIGITAL_OBJECT_IMPORT_LOW + UNEXPECTED_PROCESSING_ERROR_RETRY_DELAY = 60 FIND_DIGITAL_OBJECT_IMPORT_RETRY_DELAY = 10 - def self.perform(digital_object_import_id) + def perform(digital_object_import_id) # If the import job was previously deleted (and does not exist in the database), return immediately return unless DigitalObjectImport.exists?(digital_object_import_id) @@ -44,7 +45,7 @@ def self.perform(digital_object_import_id) handle_unexpected_processing_error(digital_object_import_id, e) end - def self.find_or_create_digital_object(digital_object_data, user, digital_object_import) + def find_or_create_digital_object(digital_object_data, user, digital_object_import) if existing_object?(digital_object_data) # We're updating data for an existing object existing_object_for_update(digital_object_data, user) @@ -58,7 +59,7 @@ def self.find_or_create_digital_object(digital_object_data, user, digital_object # digital_object_import instance because when it's called, we can't guarantee # that we were able to successfully obtain a digital_object_import instance. # We try multiple times, within this method, to obtain an instance. - def self.handle_unexpected_processing_error(digital_object_import_id, e, queue_long_jobs = HYACINTH[:queue_long_jobs]) + def handle_unexpected_processing_error(digital_object_import_id, e, queue_long_jobs = HYACINTH[:queue_long_jobs]) # In the case of some unexpected, otherwise unhandled error, mark this job # as a failure so that it doesn't get stuck as pending forever, causing # other jobs that depend on it to be requeued forever. @@ -77,18 +78,18 @@ def self.handle_unexpected_processing_error(digital_object_import_id, e, queue_l end end - def self.find_digital_object_import_with_retry(digital_object_import_id) + def find_digital_object_import_with_retry(digital_object_import_id) # Retry in case database is briefly unavailable Retriable.retriable(tries: 3, base_interval: FIND_DIGITAL_OBJECT_IMPORT_RETRY_DELAY) do return DigitalObjectImport.find(digital_object_import_id) end end - def self.existing_object?(digital_object_data) + def existing_object?(digital_object_data) digital_object_data['pid'].present? && DigitalObject::Base.exists?(digital_object_data['pid']) end - def self.assign_data(digital_object, digital_object_data, digital_object_import) + def assign_data(digital_object, digital_object_data, digital_object_import) digital_object.set_digital_object_data(digital_object_data, true) :success rescue Hyacinth::Exceptions::ParentDigitalObjectNotFoundError => e @@ -99,17 +100,17 @@ def self.assign_data(digital_object, digital_object_data, digital_object_import) :failure end - def self.exception_with_backtrace_as_error_message(e) + def exception_with_backtrace_as_error_message(e) e.message + "\nBacktrace:\n\t#{e.backtrace.join("\n\t")}" end - def self.existing_object_for_update(digital_object_data, user) + def existing_object_for_update(digital_object_data, user) digital_object = DigitalObject::Base.find(digital_object_data['pid']) digital_object.updated_by = user digital_object end - def self.new_object(digital_object_type, user, digital_object_import) + def new_object(digital_object_type, user, digital_object_import) digital_object = DigitalObjectType.get_model_for_string_key(digital_object_type || :missing).new digital_object.created_by = user digital_object.updated_by = user @@ -119,7 +120,7 @@ def self.new_object(digital_object_type, user, digital_object_import) nil end - def self.handle_success_or_failure(status, digital_object, digital_object_import, do_solr_commit) + def handle_success_or_failure(status, digital_object, digital_object_import, do_solr_commit) if status == :success && digital_object.save(do_solr_commit) digital_object_import.digital_object_errors = [] digital_object_import.status = :success @@ -137,7 +138,7 @@ def self.handle_success_or_failure(status, digital_object, digital_object_import # If prerequisite rows are pending, then this digital_object_import will be requeued. # If prerequisite rows failed, then this digital_object_import will also fail with # a prerequisite-related error message. - def self.prerequisite_row_check(digital_object_import, queue_long_jobs = HYACINTH[:queue_long_jobs]) + def prerequisite_row_check(digital_object_import, queue_long_jobs = HYACINTH[:queue_long_jobs]) # If this import has prerequisite_csv_row_numbers, make sure that the job # with that prerequisite_csv_row_numbers has been completed. If it hasn't, # we'll re-queue this job. @@ -192,7 +193,7 @@ def self.prerequisite_row_check(digital_object_import, queue_long_jobs = HYACINT true end - def self.handle_remaining_prerequisite_case(digital_object_import, queue_long_jobs) + def handle_remaining_prerequisite_case(digital_object_import, queue_long_jobs) if queue_long_jobs # If prerequisite are still pending, then re-queue this import digital_object_import.digital_object_errors = [] # clear earlier errors if we're re-queueing diff --git a/app/jobs/reindex_digital_object_job.rb b/app/jobs/reindex_digital_object_job.rb index a49b9a252..8c9b3c4d9 100644 --- a/app/jobs/reindex_digital_object_job.rb +++ b/app/jobs/reindex_digital_object_job.rb @@ -1,7 +1,7 @@ -class ReindexDigitalObjectJob - @queue = Hyacinth::Queue::DIGITAL_OBJECT_REINDEX +class ReindexDigitalObjectJob < ActiveJob::Base + queue_as Hyacinth::Queue::DIGITAL_OBJECT_REINDEX - def self.perform(digital_object_pid) + def perform(digital_object_pid) # Pass false param to update_index() method so that we don't do a Solr commit for each update (because that would be inefficient). DigitalObject::Base.find(digital_object_pid).update_index(false) end diff --git a/lib/hyacinth/csv/flatten.rb b/lib/hyacinth/csv/flatten.rb index 34ffc0c38..273159562 100644 --- a/lib/hyacinth/csv/flatten.rb +++ b/lib/hyacinth/csv/flatten.rb @@ -1,65 +1,63 @@ module Hyacinth::Csv::Flatten extend ActiveSupport::Concern - module ClassMethods - def keys_for_document(document, omit_blank_values = false) - document = document.clone - df_data = document.delete(DigitalObject::DynamicField::DATA_KEY) || {} + def keys_for_document(document, omit_blank_values = false) + document = document.clone + df_data = document.delete(DigitalObject::DynamicField::DATA_KEY) || {} - internals = pointers_for_hash(document, omit_blank_values) - internals.map! { |pointer| Hyacinth::Csv::Fields::Internal.new(pointer) } - dynamics = pointers_for_hash(df_data, omit_blank_values) - dynamics.map! { |pointer| Hyacinth::Csv::Fields::Dynamic.new(pointer) } - internals.map(&:to_header) + dynamics.map(&:to_header) - end + internals = pointers_for_hash(document, omit_blank_values) + internals.map! { |pointer| Hyacinth::Csv::Fields::Internal.new(pointer) } + dynamics = pointers_for_hash(df_data, omit_blank_values) + dynamics.map! { |pointer| Hyacinth::Csv::Fields::Dynamic.new(pointer) } + internals.map(&:to_header) + dynamics.map(&:to_header) + end - def pointers_for_hash(hash, omit_blank_values, prefix = []) - keys = [] - hash.each do |key, value| - if value.is_a?(Array) - key_prefix = prefix + [key] - keys += pointers_for_array(value, omit_blank_values, key_prefix) - elsif uri_hash?(value) - key_prefix = prefix + [key] - keys += pointers_for_uri(value, omit_blank_values, key_prefix) - elsif value.is_a?(Hash) - key_prefix = prefix + [key] - keys += pointers_for_hash(value, omit_blank_values, key_prefix) - else - key = pointer_for_value(key, value, omit_blank_values, prefix) - keys << key unless keys.include?(key) || key.nil? - end + def pointers_for_hash(hash, omit_blank_values, prefix = []) + keys = [] + hash.each do |key, value| + if value.is_a?(Array) + key_prefix = prefix + [key] + keys += pointers_for_array(value, omit_blank_values, key_prefix) + elsif uri_hash?(value) + key_prefix = prefix + [key] + keys += pointers_for_uri(value, omit_blank_values, key_prefix) + elsif value.is_a?(Hash) + key_prefix = prefix + [key] + keys += pointers_for_hash(value, omit_blank_values, key_prefix) + else + key = pointer_for_value(key, value, omit_blank_values, prefix) + keys << key unless keys.include?(key) || key.nil? end - keys.uniq end + keys.uniq + end - def pointers_for_array(array, omit_blank_values, prefix) - keys = [] - array.each_with_index do |value, index| - if value.is_a? Hash - keys += pointers_for_hash(value, omit_blank_values, prefix + [index]) - else - key = pointer_for_value(index, value, omit_blank_values, prefix) - keys << key unless keys.include?(key) || key.nil? - end + def pointers_for_array(array, omit_blank_values, prefix) + keys = [] + array.each_with_index do |value, index| + if value.is_a? Hash + keys += pointers_for_hash(value, omit_blank_values, prefix + [index]) + else + key = pointer_for_value(index, value, omit_blank_values, prefix) + keys << key unless keys.include?(key) || key.nil? end - keys end + keys + end - def pointer_for_value(key, value, omit_blank_values, prefix = []) - return nil if omit_blank_values && !value.is_a?(TrueClass) && !value.is_a?(FalseClass) && value.blank? - prefix + [key] - end + def pointer_for_value(key, value, omit_blank_values, prefix = []) + return nil if omit_blank_values && !value.is_a?(TrueClass) && !value.is_a?(FalseClass) && value.blank? + prefix + [key] + end - def uri_hash?(hash) - hash.is_a?(Hash) && hash.key?('uri') && !hash['uri'].is_a?(Hash) - end + def uri_hash?(hash) + hash.is_a?(Hash) && hash.key?('uri') && !hash['uri'].is_a?(Hash) + end - def pointers_for_uri(hash, omit_blank_values, prefix = []) - return nil if omit_blank_values && hash.blank? - hash.map do |key, value| - pointer_for_value("#{prefix[-1]}.#{key}", value, omit_blank_values, prefix[0...-1]) - end.compact - end + def pointers_for_uri(hash, omit_blank_values, prefix = []) + return nil if omit_blank_values && hash.blank? + hash.map do |key, value| + pointer_for_value("#{prefix[-1]}.#{key}", value, omit_blank_values, prefix[0...-1]) + end.compact end end diff --git a/lib/hyacinth/queue.rb b/lib/hyacinth/queue.rb index 486b9de4b..8d335d5ab 100644 --- a/lib/hyacinth/queue.rb +++ b/lib/hyacinth/queue.rb @@ -35,25 +35,25 @@ def self.process_digital_object_import(digital_object_import) raise 'Invalid priority: ' + priority.inspect end - Resque.enqueue_to(queue_name, ProcessDigitalObjectImportJob, digital_object_import_id) + ProcessDigitalObjectImportJob.set(queue: queue_name).perform_later(digital_object_import_id) else - ProcessDigitalObjectImportJob.perform(digital_object_import_id) + ProcessDigitalObjectImportJob.perform_now(digital_object_import_id) end end def self.export_search_results_to_csv(csv_export_id) if HYACINTH[:queue_long_jobs] - Resque.enqueue(ExportSearchResultsToCsvJob, csv_export_id) + ExportSearchResultsToCsvJob.perform_later(csv_export_id) else - ExportSearchResultsToCsvJob.perform(csv_export_id) + ExportSearchResultsToCsvJob.perform_now(csv_export_id) end end def self.reindex_digital_object(digital_object_pid) if HYACINTH[:queue_long_jobs] - Resque.enqueue(ReindexDigitalObjectJob, digital_object_pid) + ReindexDigitalObjectJob.perform_later(digital_object_pid) else - ReindexDigitalObjectJob.perform(digital_object_pid) + ReindexDigitalObjectJob.perform_now(digital_object_pid) end end end diff --git a/spec/features/digital_object_editor_ui_spec.rb b/spec/features/digital_object_editor_ui_spec.rb index 62c957cc5..4e6bc1c5a 100644 --- a/spec/features/digital_object_editor_ui_spec.rb +++ b/spec/features/digital_object_editor_ui_spec.rb @@ -9,6 +9,5 @@ it "can create a new Digital Object", :js => true do expect(page).to have_content 'New Digital Object' - end end diff --git a/spec/integration/csv_export_import_round_trip_spec.rb b/spec/integration/csv_export_import_round_trip_spec.rb index 8b1d12078..8c2dac4a1 100644 --- a/spec/integration/csv_export_import_round_trip_spec.rb +++ b/spec/integration/csv_export_import_round_trip_spec.rb @@ -95,7 +95,7 @@ 'fq' => { 'hyacinth_type_sim' => [{ 'does_not_equal' => 'publish_target' }] } }) ) - ExportSearchResultsToCsvJob.perform(first_csv_export.id) + ExportSearchResultsToCsvJob.perform_now(first_csv_export.id) first_csv_export.reload # Reload the ActiveRecord object, getting the latest data in the DB (so we have the path to the csv file) path_to_first_csv_file = first_csv_export.path_to_csv_file @@ -131,7 +131,7 @@ 'fq' => { 'hyacinth_type_sim' => [{ 'does_not_equal' => 'publish_target' }] } }) ) - ExportSearchResultsToCsvJob.perform(second_csv_export.id) + ExportSearchResultsToCsvJob.perform_now(second_csv_export.id) second_csv_export.reload # Reload the ActiveRecord object, getting the latest data in the DB (so we have the path to the csv file) path_to_second_csv_file = second_csv_export.path_to_csv_file expect(CSV.read(path_to_first_csv_file)).to eq(CSV.read(path_to_second_csv_file)) diff --git a/spec/jobs/export_search_results_to_csv_job_spec.rb b/spec/jobs/export_search_results_to_csv_job_spec.rb index 7c862c563..b4dc24a79 100644 --- a/spec/jobs/export_search_results_to_csv_job_spec.rb +++ b/spec/jobs/export_search_results_to_csv_job_spec.rb @@ -10,12 +10,14 @@ let(:user) { User.new } let(:search_params) { Hash.new } + let(:instance) { described_class.new } + before do allow(DigitalObject::Base).to receive(:search_in_batches) .and_yield(doc1).and_yield(doc2) end - describe '.map_temp_field_indexes' do + describe '#map_temp_field_indexes' do let(:expected) do { '_pid' => 0, @@ -27,7 +29,7 @@ } end - subject { ExportSearchResultsToCsvJob.map_temp_field_indexes(search_params, user) } + subject { instance.map_temp_field_indexes(search_params, user) } it do is_expected.to eql expected @@ -79,7 +81,7 @@ allow(CsvExport).to receive(:find).with(export_id).and_return(export) allow(DigitalObject::Base).to receive(:search_in_batches).and_yield(sample_json_document) expect(export).to receive(:path_to_csv_file).and_call_original - described_class.perform(export_id) + instance.perform(export_id) expect(export.number_of_records_processed).to eq(1) actual_csv = CSV.read(export.path_to_csv_file) diff --git a/spec/jobs/process_digital_object_import_job_spec.rb b/spec/jobs/process_digital_object_import_job_spec.rb index 400fb766a..1ced73776 100644 --- a/spec/jobs/process_digital_object_import_job_spec.rb +++ b/spec/jobs/process_digital_object_import_job_spec.rb @@ -1,12 +1,12 @@ require 'rails_helper' -RSpec.describe ProcessDigitalObjectImportJob, :type => :job do +RSpec.describe ProcessDigitalObjectImportJob, type: :job do before(:example) { stub_const("ProcessDigitalObjectImportJob::UNEXPECTED_PROCESSING_ERROR_RETRY_DELAY", 0) # Make tests run more quickly stub_const("ProcessDigitalObjectImportJob::FIND_DIGITAL_OBJECT_IMPORT_RETRY_DELAY", 0) # Make tests run more quickly } - let (:klass) { ProcessDigitalObjectImportJob } + let (:instance) { described_class.new } let(:user) { User.new( :email => 'abc@example.com', @@ -33,19 +33,19 @@ } } - describe ".existing_object?" do + describe "#existing_object?" do it "returns true if digital object data contains a pid field for an existing object" do existing_pid = 'it:exists' nonexisting_pid = 'it:doesnoteist' allow(DigitalObject::Base).to receive(:'exists?').with(existing_pid).and_return(true) allow(DigitalObject::Base).to receive(:'exists?').with(nonexisting_pid).and_return(false) - expect(klass.existing_object?({'pid' => existing_pid})).to eq(true) - expect(klass.existing_object?({'pid' => nonexisting_pid})).to eq(false) - expect(klass.existing_object?({})).to eq(false) + expect(instance.existing_object?({'pid' => existing_pid})).to eq(true) + expect(instance.existing_object?({'pid' => nonexisting_pid})).to eq(false) + expect(instance.existing_object?({})).to eq(false) end end - describe ".find_digital_object_import_with_retry" do + describe "#find_digital_object_import_with_retry" do context "finds and returns a DigitalObjectImport when no error is raised by internal call to DigitalObjectImport.find" do let(:digital_object_import_id) { allow(DigitalObjectImport).to receive(:find).with(12345).and_return(digital_object_import) @@ -55,7 +55,7 @@ DigitalObjectImport.new } it do - expect(klass.find_digital_object_import_with_retry(digital_object_import_id)).to eq(digital_object_import) + expect(instance.find_digital_object_import_with_retry(digital_object_import_id)).to eq(digital_object_import) end end context "retries multiple times when an error is raised by internal call to DigitalObjectImport.find" do @@ -65,27 +65,27 @@ } it do expect(DigitalObjectImport).to receive(:find).exactly(3).times - expect{klass.find_digital_object_import_with_retry(digital_object_import_id)}.to raise_error(StandardError) + expect{instance.find_digital_object_import_with_retry(digital_object_import_id)}.to raise_error(StandardError) end end end - describe ".find_or_create_digital_object" do + describe "#find_or_create_digital_object" do let(:digital_object_import) { DigitalObjectImport.new } it "internally calls .existing_object_for_update when .existing_object? returns true" do - allow(klass).to receive(:existing_object?).and_return(true) - expect(klass).to receive(:existing_object_for_update) - klass.find_or_create_digital_object(digital_object_data, user, digital_object_import) + allow(instance).to receive(:existing_object?).and_return(true) + expect(instance).to receive(:existing_object_for_update) + instance.find_or_create_digital_object(digital_object_data, user, digital_object_import) end it "internally calls .new_object when .existing_object? returns false" do - allow(klass).to receive(:existing_object?).and_return(false) - expect(klass).to receive(:new_object) - klass.find_or_create_digital_object(digital_object_data, user, digital_object_import) + allow(instance).to receive(:existing_object?).and_return(false) + expect(instance).to receive(:new_object) + instance.find_or_create_digital_object(digital_object_data, user, digital_object_import) end context "no digital_object_type given for a new object" do - let(:digital_object) { klass.find_or_create_digital_object(digital_object_data, user, digital_object_import) } + let(:digital_object) { instance.find_or_create_digital_object(digital_object_data, user, digital_object_import) } let(:expected_message) { "Invalid DigitalObjectType string key: missing" } before do digital_object_data.delete('digital_object_type') @@ -98,7 +98,7 @@ end end - describe ".handle_unexpected_processing_error" do + describe "#handle_unexpected_processing_error" do let(:digital_object_import_id) { 12345 } let(:digital_object_import) { DigitalObjectImport.new @@ -111,67 +111,67 @@ end } it "marks the DigitalObjectImport with given id as failure, and stores the exception object's message in the DigitalObjectImport's digital_object_errors" do - allow(klass).to receive(:find_digital_object_import_with_retry).and_return(digital_object_import) + allow(instance).to receive(:find_digital_object_import_with_retry).and_return(digital_object_import) expect(digital_object_import).to receive(:save!).once - klass.handle_unexpected_processing_error(digital_object_import_id, e, false) - expect(digital_object_import.digital_object_errors).to eq([klass.exception_with_backtrace_as_error_message(e)]) + instance.handle_unexpected_processing_error(digital_object_import_id, e, false) + expect(digital_object_import.digital_object_errors).to eq([instance.exception_with_backtrace_as_error_message(e)]) end it "when encountering an error retrieving or saving the DigitalObjectImport, retries retrieval/saving code three times, and then raises the error encountered on the final retry" do - allow(klass).to receive(:find_digital_object_import_with_retry).and_raise(StandardError) - expect(klass).to receive(:find_digital_object_import_with_retry).exactly(3).times - expect{klass.handle_unexpected_processing_error(digital_object_import_id, e, false)}.to raise_error(StandardError) + allow(instance).to receive(:find_digital_object_import_with_retry).and_raise(StandardError) + expect(instance).to receive(:find_digital_object_import_with_retry).exactly(3).times + expect{instance.handle_unexpected_processing_error(digital_object_import_id, e, false)}.to raise_error(StandardError) end end - describe ".exception_with_backtrace_as_error_message" do + describe "#exception_with_backtrace_as_error_message" do let(:error_message) { 'This is the error message' } it "formats as expected" do begin raise StandardError, error_message rescue StandardError => e - expect(klass.exception_with_backtrace_as_error_message(e)).to eq(e.message + "\nBacktrace:\n\t#{e.backtrace.join("\n\t")}") + expect(instance.exception_with_backtrace_as_error_message(e)).to eq(e.message + "\nBacktrace:\n\t#{e.backtrace.join("\n\t")}") end end end - describe ".assign_data" do + describe "#assign_data" do let(:digital_object) { DigitalObject::Item.new } let(:digital_object_import) { DigitalObjectImport.new } it "returns :success when everything works as expected and doesn't set any errors on passed DigitalObjectImport arg" do allow_any_instance_of(DigitalObject::Item).to receive(:set_digital_object_data).and_return({}) - expect(klass.assign_data(digital_object, digital_object_data, digital_object_import)).to eq(:success) + expect(instance.assign_data(digital_object, digital_object_data, digital_object_import)).to eq(:success) expect(digital_object_import.digital_object_errors).to be_blank end it "returns :parent_not_found and sets digital_object_errors on passed DigitalObjectImport arg when DigitalObject::Base#set_digital_object_data raises Hyacinth::Exceptions::ParentDigitalObjectNotFoundError" do allow_any_instance_of(DigitalObject::Item).to receive(:set_digital_object_data).and_raise(Hyacinth::Exceptions::ParentDigitalObjectNotFoundError) - expect(klass.assign_data(digital_object, digital_object_data, digital_object_import)).to eq(:parent_not_found) + expect(instance.assign_data(digital_object, digital_object_data, digital_object_import)).to eq(:parent_not_found) expect(digital_object_import.digital_object_errors).to be_present end it "returns :failure and sets digital_object_errors on passed DigitalObjectImport arg when DigitalObject::Base#set_digital_object_data raises any StandardError other than Hyacinth::Exceptions::ParentDigitalObjectNotFoundError" do [StandardError, Hyacinth::Exceptions::NotFoundError, Hyacinth::Exceptions::MalformedControlledTermFieldValue, UriService::Error].each do |exception_class| allow_any_instance_of(DigitalObject::Item).to receive(:set_digital_object_data).and_raise(exception_class) - expect(klass.assign_data(digital_object, digital_object_data, digital_object_import)).to eq(:failure) + expect(instance.assign_data(digital_object, digital_object_data, digital_object_import)).to eq(:failure) expect(digital_object_import.digital_object_errors).to be_present end end end - describe ".existing_object_for_update" do + describe "#existing_object_for_update" do let(:pid) { 'abc:123' } let(:digital_object_data) { {'pid' => pid} } it "returns an existing object with given user assigned to updated_by field" do allow(DigitalObject::Base).to receive(:find).with(pid).and_return(DigitalObject::Item.new) - obj = klass.existing_object_for_update(digital_object_data, user) + obj = instance.existing_object_for_update(digital_object_data, user) expect(obj).to be_a(DigitalObject::Item) expect(obj.updated_by).to eq(user) end end - describe ".new_object" do + describe "#new_object" do let(:digital_object_import) { DigitalObjectImport.new } @@ -180,7 +180,7 @@ let(:digital_object_type) { 'item' } it "returns a new object with expected created_by and updated_by values and correct type" do - obj = klass.new_object(digital_object_type, user, digital_object_import) + obj = instance.new_object(digital_object_type, user, digital_object_import) expect(obj).to be_a(DigitalObject::Item) expect(obj.created_by).to eq(user) expect(obj.updated_by).to eq(user) @@ -191,14 +191,14 @@ context "valid digital object type" do let(:digital_object_type) { 'invalid_item_type' } it "passes an error to the digital_object_import.digital_object_errors array when an invalid digital object type is given" do - klass.new_object(digital_object_type, user, digital_object_import) + instance.new_object(digital_object_type, user, digital_object_import) expect(digital_object_import.digital_object_errors.length).to eq(1) expect(digital_object_import.digital_object_errors[0]).to eq("Invalid DigitalObjectType string key: #{digital_object_type}") end end end - describe ".handle_success_or_failure" do + describe "#handle_success_or_failure" do let(:digital_object_import) { DigitalObjectImport.new } @@ -209,26 +209,26 @@ it "sets success status on digital_object_import when passed in status is :success and digital object save operation is successful" do allow_any_instance_of(DigitalObject::Item).to receive(:save).and_return(true) allow_any_instance_of(DigitalObjectImport).to receive(:save!).and_return(true) - klass.handle_success_or_failure(:success, digital_object, digital_object_import, :success) + instance.handle_success_or_failure(:success, digital_object, digital_object_import, :success) expect(digital_object_import.status).to eq('success') end it "sets failure status on digital_object_import when passed in status is :failure, even if digital object save would have returned true" do allow_any_instance_of(DigitalObject::Item).to receive(:save).and_return(true) allow_any_instance_of(DigitalObjectImport).to receive(:save!).and_return(true) - klass.handle_success_or_failure(:failure, digital_object, digital_object_import, :success) + instance.handle_success_or_failure(:failure, digital_object, digital_object_import, :success) expect(digital_object_import.status).to eq('failure') end it "sets failure status on digital_object_import when passed in status is :success, but digital object save returns false" do allow_any_instance_of(DigitalObject::Item).to receive(:save).and_return(false) allow_any_instance_of(DigitalObjectImport).to receive(:save!).and_return(true) - klass.handle_success_or_failure(:success, digital_object, digital_object_import, :success) + instance.handle_success_or_failure(:success, digital_object, digital_object_import, :success) expect(digital_object_import.status).to eq('failure') end end - describe ".handle_remaining_prerequisite_case" do + describe "#handle_remaining_prerequisite_case" do let(:import_job) { import_job = ImportJob.new import_job.user = user @@ -244,7 +244,7 @@ allow_any_instance_of(DigitalObjectImport).to receive(:save!).and_return(true) expect(Hyacinth::Queue).to receive(:process_digital_object_import).with(digital_object_import) digital_object_import.digital_object_errors << 'An error' - klass.handle_remaining_prerequisite_case(digital_object_import, queue_long_jobs) + instance.handle_remaining_prerequisite_case(digital_object_import, queue_long_jobs) expect(digital_object_import.digital_object_errors).to be_blank end end @@ -255,14 +255,14 @@ error_message = 'An error' allow_any_instance_of(DigitalObjectImport).to receive(:save!).and_return(true) digital_object_import.digital_object_errors << error_message - klass.handle_remaining_prerequisite_case(digital_object_import, queue_long_jobs) + instance.handle_remaining_prerequisite_case(digital_object_import, queue_long_jobs) expect(digital_object_import.digital_object_errors).to include(error_message) expect(digital_object_import.digital_object_errors.length).to eq(2) end end end - describe ".prerequisite_row_check" do + describe "#prerequisite_row_check" do let(:digital_object_import_1) { digital_object_import = DigitalObjectImport.new digital_object_import.csv_row_number = 1 @@ -281,7 +281,7 @@ context "returns true if digital_object_import has no prerequisite rows" do let(:digital_object_import) { digital_object_import_1 } it do - expect(klass.prerequisite_row_check(digital_object_import, queue_long_jobs)).to eq(true) + expect(instance.prerequisite_row_check(digital_object_import, queue_long_jobs)).to eq(true) end end context "returns true if digital_object_import has prerequisite rows that have already been successfully processed" do @@ -292,7 +292,7 @@ } it do allow(DigitalObjectImport).to receive(:where).and_return(prerequisite_digital_object_imports) - expect(klass.prerequisite_row_check(digital_object_import, queue_long_jobs)).to eq(true) + expect(instance.prerequisite_row_check(digital_object_import, queue_long_jobs)).to eq(true) end end @@ -304,7 +304,7 @@ } it do allow_any_instance_of(DigitalObjectImport).to receive(:save!).and_return(true) - expect(klass.prerequisite_row_check(digital_object_import, queue_long_jobs)).to eq(false) + expect(instance.prerequisite_row_check(digital_object_import, queue_long_jobs)).to eq(false) expect(digital_object_import.digital_object_errors).to include('A CSV row cannot have itself as a prerequisite row. (Did you accidentally try to make this object its own parent?)') end end @@ -316,8 +316,8 @@ } it do allow(DigitalObjectImport).to receive(:where).and_return(prerequisite_digital_object_imports) - expect(klass).to receive(:handle_remaining_prerequisite_case).with(digital_object_import, queue_long_jobs) - expect(klass.prerequisite_row_check(digital_object_import, queue_long_jobs)).to eq(false) + expect(instance).to receive(:handle_remaining_prerequisite_case).with(digital_object_import, queue_long_jobs) + expect(instance.prerequisite_row_check(digital_object_import, queue_long_jobs)).to eq(false) end end context "returns false if any of this digital_object_import's prerequisite rows have failed, and also adds an error to this digital_object_import that describes the failure, and marks the digital_object_import as a failure" do @@ -329,7 +329,7 @@ it do allow(DigitalObjectImport).to receive(:where).and_return(prerequisite_digital_object_imports) allow_any_instance_of(DigitalObjectImport).to receive(:save!).and_return(true) - expect(klass.prerequisite_row_check(digital_object_import, queue_long_jobs)).to eq(false) + expect(instance.prerequisite_row_check(digital_object_import, queue_long_jobs)).to eq(false) expect(digital_object_import.status).to eq('failure') expect(digital_object_import.digital_object_errors.length).to eq(1) expect(digital_object_import.digital_object_errors[0]).to eq("Failed because prerequisite row 1 failed to import properly") @@ -337,7 +337,7 @@ end end - describe ".perform" do + describe "#perform" do let(:digital_object_import_id) { import_id = 12345 allow(DigitalObjectImport).to receive(:find).with(import_id).and_return(digital_object_import) @@ -357,44 +357,44 @@ digital_object_import } it "calls .handle_unexpected_processing_error when an unexpected error occurs" do - expect(klass).to receive(:handle_unexpected_processing_error) - allow(klass).to receive(:find_digital_object_import_with_retry).and_raise(StandardError) - klass.perform(digital_object_import_id) + expect(instance).to receive(:handle_unexpected_processing_error) + allow(instance).to receive(:find_digital_object_import_with_retry).and_raise(StandardError) + instance.perform(digital_object_import_id) end it "returns early when .prerequisite_row_check fails" do - allow(klass).to receive(:prerequisite_row_check).and_return(false) - expect(klass).not_to receive(:existing_object?) - klass.perform(digital_object_import_id) + allow(instance).to receive(:prerequisite_row_check).and_return(false) + expect(instance).not_to receive(:existing_object?) + instance.perform(digital_object_import_id) end context "successful .prerequisite_row_check" do before { - allow(klass).to receive(:prerequisite_row_check).and_return(true) + allow(instance).to receive(:prerequisite_row_check).and_return(true) } context "internally creates new digital object or retrieves existing digital object" do context "internally calls handle_success_or_failure if the result returned by .assign_data is not :parent_not_found" do it ":success" do - allow(klass).to receive(:assign_data).and_return(:success) - expect(klass).to receive(:handle_success_or_failure) - klass.perform(digital_object_import_id) + allow(instance).to receive(:assign_data).and_return(:success) + expect(instance).to receive(:handle_success_or_failure) + instance.perform(digital_object_import_id) end it ":failure" do - allow(klass).to receive(:assign_data).and_return(:failure) - expect(klass).to receive(:handle_success_or_failure) - klass.perform(digital_object_import_id) + allow(instance).to receive(:assign_data).and_return(:failure) + expect(instance).to receive(:handle_success_or_failure) + instance.perform(digital_object_import_id) end end context "when receiving :parent_not_found from first internal call to .assign_data" do it "sleep when :parent_not_found is returned the first time, and then internally call handle_success_or_failure when :success is returned after the second .assign_data call (to simulate scenario when parent object was in the middle of concurrent processing, perhaps coming from a different csv import job)" do - allow(klass).to receive(:assign_data).and_return(:parent_not_found, :success).twice - expect(klass).to receive(:sleep).once - expect(klass).to receive(:handle_success_or_failure) - klass.perform(digital_object_import_id) + allow(instance).to receive(:assign_data).and_return(:parent_not_found, :success).twice + expect(instance).to receive(:sleep).once + expect(instance).to receive(:handle_success_or_failure) + instance.perform(digital_object_import_id) end it "sleep when :parent_not_found is returned the first time, and then sleep two more times after :parent_not_found is returned again and again for .assign_data, and sets failure status and an error message on the digital_object_import" do - allow(klass).to receive(:assign_data).and_return(:parent_not_found).exactly(4).times + allow(instance).to receive(:assign_data).and_return(:parent_not_found).exactly(4).times allow_any_instance_of(DigitalObjectImport).to receive(:save!).and_return(true) - expect(klass).to receive(:sleep).exactly(3).times - klass.perform(digital_object_import_id) + expect(instance).to receive(:sleep).exactly(3).times + instance.perform(digital_object_import_id) expect(digital_object_import.status).to eq('failure') expect(digital_object_import.digital_object_errors.length).to eq(1) expect(digital_object_import.digital_object_errors[0]).to eq("Failed because referenced parent object could not be found.") diff --git a/spec/models/concerns/digital_object/dynamic_field_spec.rb b/spec/models/concerns/digital_object/dynamic_field_spec.rb index 00af920b3..37e80d701 100644 --- a/spec/models/concerns/digital_object/dynamic_field_spec.rb +++ b/spec/models/concerns/digital_object/dynamic_field_spec.rb @@ -501,7 +501,7 @@ _c = Class.new _c.send :include, Hyacinth::Csv::Flatten end - subject { test_class.keys_for_document('dynamic_field_data' => new_dynamic_field_data) } + subject { test_class.new.keys_for_document('dynamic_field_data' => new_dynamic_field_data) } it { is_expected.to eql(flattened_csv_style_dynamic_field_data.keys)} end end