Skip to content

Commit

Permalink
♻️ Migrate persistence layer methods to object factory
Browse files Browse the repository at this point in the history
In review of the code and in brief discussion with @orangewolf, the
methods of the persistence layer could be added to the object factory.

We already were configuring the corresponding object factory for each
implementation of Bulkrax; so leveraging that configuration made
tremendous sense.

The methods on the persistence layer remain helpful (perhaps necessary)
for documented reasons in the `Bulkrax::ObjectFactoryInterface` module.

See:

- #895 and its discussion
  • Loading branch information
jeremyf committed Feb 7, 2024
1 parent c8f87ad commit a04640b
Show file tree
Hide file tree
Showing 18 changed files with 72 additions and 116 deletions.
34 changes: 32 additions & 2 deletions app/factories/bulkrax/object_factory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,42 @@

module Bulkrax
class ObjectFactory # rubocop:disable Metrics/ClassLength
include ObjectFactoryInterface

extend ActiveModel::Callbacks
include Bulkrax::FileFactory
include DynamicRecordLookup

##
# @!group Class Method Interface
#
# @see Bulkrax::ObjectFactoryInterface
def self.find(id)
ActiveFedora::Base.find(id)
rescue ActiveFedora::ObjectNotFoundError => e
raise ObjectFactoryInterface::ObjectNotFoundError, e.message
end

def self.query(q, **kwargs)
ActiveFedora::SolrService.query(q, **kwargs)
end

def self.clean!
super do
ActiveFedora::Cleaner.clean!
end
end

def self.solr_name(field_name)
if Module.const_defined?(:Solrizer)
::Solrizer.solr_name(base_name)
else
ActiveFedora.index_field_mapper.solr_name(field_name)
end
end
# @!endgroup Class Method Interface
##

# @api private
#
# These are the attributes that we assume all "work type" classes (e.g. the given :klass) will
Expand Down Expand Up @@ -95,7 +127,6 @@ def find
end

def find_by_id
# TODO: Push logic into Bulkrax.persistence_adapter; maybe
# Rails / Ruby upgrade, we moved from :exists? to :exist? However we want to continue (for a
# bit) to support older versions.
method_name = klass.respond_to?(:exist?) ? :exist? : :exists?
Expand All @@ -111,7 +142,6 @@ def find_or_create
end

def search_by_identifier
# TODO: Push logic into Bulkrax.persistence_adapter; maybe
query = { work_identifier_search_field =>
source_identifier_value }
# Query can return partial matches (something6 matches both something6 and something68)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,15 @@

module Bulkrax
##
# The target data layer where we write and read our imported {Bulkrax::Entry} objects.
module PersistenceLayer
# A module that helps define the expected interface for object factory interactions.
#
# The abstract class methods are useful for querying the underlying persistence layer when you are
# not in the context of an instance of an {Bulkrax::ObjectFactory} and therefore don't have access
# to it's {#find} instance method.
#
# @abstract
module ObjectFactoryInterface
extend ActiveSupport::Concern
# We're inheriting from an ActiveRecord exception as that is something we know will be here; and
# something that the main_app will be expect to be able to handle.
class ObjectNotFoundError < ActiveRecord::RecordNotFound
Expand All @@ -14,23 +21,24 @@ class ObjectNotFoundError < ActiveRecord::RecordNotFound
class RecordInvalid < ActiveRecord::RecordInvalid
end

class AbstractAdapter
class_methods do
##
# @see ActiveFedora::Base.find
def self.find(id)
def find(id)
raise NotImplementedError, "#{self}.#{__method__}"
end

def self.solr_name(field_name)
def solr_name(field_name)
raise NotImplementedError, "#{self}.#{__method__}"
end

# @yield when Rails application is running in test environment.
def self.clean!
def clean!
return true unless Rails.env.test?
yield
end

def self.query(q, **kwargs)
def query(q, **kwargs)
raise NotImplementedError, "#{self}.#{__method__}"
end
end
Expand Down
2 changes: 2 additions & 0 deletions app/factories/bulkrax/valkyrie_object_factory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

module Bulkrax
class ValkyrieObjectFactory < ObjectFactory
include ObjectFactoryInterface

##
# Retrieve properties from M3 model
# @param klass the model
Expand Down
2 changes: 1 addition & 1 deletion app/helpers/bulkrax/validation_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def check_admin_set
AdminSet.find(params[:importer][:admin_set_id])
end
return true
rescue ActiveFedora::ObjectNotFoundError, Bulkrax::PersistenceLayer::ObjectNotFoundError
rescue ActiveFedora::ObjectNotFoundError, Bulkrax::ObjectFactoryInterface::ObjectNotFoundError
logger.warn("AdminSet #{params[:importer][:admin_set_id]} not found. Using default admin set.")
params[:importer][:admin_set_id] = AdminSet::DEFAULT_ID
return true
Expand Down
2 changes: 1 addition & 1 deletion app/jobs/bulkrax/create_relationships_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def perform(parent_identifier:, importer_run_id:) # rubocop:disable Metrics/AbcS
# save record if members were added
if @parent_record_members_added
parent_record.save!
# TODO: Push logic into Bulkrax.persistence_adapter
# TODO: Push logic into Bulkrax.object_factory
# Ensure that the new relationship gets indexed onto the children
if parent_record.is_a?(Valkyrie::Resource)
@child_members_added.each do |child|
Expand Down
4 changes: 2 additions & 2 deletions app/models/concerns/bulkrax/dynamic_record_lookup.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ def find_record(identifier, importer_run_id = nil)
begin
# the identifier parameter can be a :source_identifier or the id of an object
record = Entry.find_by(default_scope.merge({ importerexporter_id: importer_id })) || Entry.find_by(default_scope)
record ||= Bulkrax.persistence_adapter.find(identifier)
record ||= Bulkrax.object_factory.find(identifier)
# NameError for if ActiveFedora isn't installed
rescue NameError, ActiveFedora::ObjectNotFoundError, Bulkrax::PersistenceLayer::ObjectNotFoundError
rescue NameError, ActiveFedora::ObjectNotFoundError, Bulkrax::OjbectFactoryInterface::ObjectNotFoundError
record = nil
end

Expand Down
2 changes: 1 addition & 1 deletion app/models/concerns/bulkrax/export_behavior.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def build_export_metadata
end

def hyrax_record
@hyrax_record ||= Bulkrax.persistence_adapter.find(self.identifier)
@hyrax_record ||= Bulkrax.object_factory.find(self.identifier)
end

# Prepend the file_set id to ensure a unique filename and also one that is not longer than 255 characters
Expand Down
2 changes: 1 addition & 1 deletion app/parsers/bulkrax/bagit_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def write_files
file_set_entries = importerexporter.entries.where(type: file_set_entry_class.to_s)

work_entries[0..limit || total].each do |entry|
record = Bulkrax.persistence_adapter.find(entry.identifier)
record = Bulkrax.object_factory.find(entry.identifier)
next unless record

bag_entries = [entry]
Expand Down
2 changes: 1 addition & 1 deletion app/parsers/bulkrax/csv_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ def write_files
end

def store_files(identifier, folder_count)
record = Bulkrax.persistence_adapter.find(identifier)
record = Bulkrax.object_factory.find(identifier)
return unless record

file_sets = record.file_set? ? Array.wrap(record) : record.file_sets
Expand Down
14 changes: 7 additions & 7 deletions app/parsers/bulkrax/parser_export_record_set.rb
Original file line number Diff line number Diff line change
Expand Up @@ -149,12 +149,12 @@ def extra_filters
end

def works
@works ||= Bulkrax.persistence_adapter.query(works_query, **works_query_kwargs)
@works ||= Bulkrax.object_factory.query(works_query, **works_query_kwargs)
end

def collections
@collections ||= if collections_query
Bulkrax.persistence_adapter.query(collections_query, **collections_query_kwargs)
Bulkrax.object_factory.query(collections_query, **collections_query_kwargs)
else
[]
end
Expand All @@ -175,15 +175,15 @@ def file_sets
@file_sets ||= ParserExportRecordSet.in_batches(candidate_file_set_ids) do |batch_of_ids|
fsq = "has_model_ssim:#{Bulkrax.file_model_class} AND id:(\"" + batch_of_ids.join('" OR "') + "\")"
fsq += extra_filters if extra_filters.present?
Bulkrax.persistence_adapter.query(
Bulkrax.object_factory.query(
fsq,
{ fl: "id", method: :post, rows: batch_of_ids.size }
)
end
end

def solr_name(base_name)
Bulkrax.persistence_adapter.solr_name(base_name)
Bulkrax.object_factory.solr_name(base_name)
end
end

Expand Down Expand Up @@ -243,7 +243,7 @@ def complete_entry_identifiers

def works
@works ||= ParserExportRecordSet.in_batches(complete_entry_identifiers) do |ids|
Bulkrax.persistence_adapter.query(
Bulkrax.object_factory.query(
extra_filters.to_s,
**query_kwargs.merge(
fq: [
Expand All @@ -258,7 +258,7 @@ def works

def collections
@collections ||= ParserExportRecordSet.in_batches(complete_entry_identifiers) do |ids|
Bulkrax.persistence_adapter.query(
Bulkrax.object_factory.query(
"has_model_ssim:Collection #{extra_filters}",
**query_kwargs.merge(
fq: [
Expand All @@ -277,7 +277,7 @@ def collections
# @see Bulkrax::ParserExportRecordSet::Base#file_sets
def file_sets
@file_sets ||= ParserExportRecordSet.in_batches(complete_entry_identifiers) do |ids|
Bulkrax.persistence_adapter.query(
Bulkrax.object_factory.query(
extra_filters,
query_kwargs.merge(
fq: [
Expand Down
36 changes: 1 addition & 35 deletions lib/bulkrax.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,7 @@ class Configuration
# @return [#call] with arity 2. The first parameter is a {Bulkrax::ApplicationParser} and the
# second parameter is an Integer for the index of the record encountered in the import.
attr_accessor :fill_in_blank_source_identifiers

##
# @param adapter [Class<Bulkrax::PersistenceLayer::AbstractAdapter>]
attr_writer :persistence_adapter

allow(Bulkrax.persistence_adapter).to receive(:find).and_return(nil)
##
# @param coercer [#call]
# @see Bulkrax::FactoryClassFinder
Expand All @@ -62,34 +58,6 @@ def factory_class_name_coercer
@factory_class_name_coercer || Bulkrax::FactoryClassFinder::DefaultCoercer
end

##
# Configure the persistence adapter used for persisting imported data.
#
# @return [Class<Bulkrax::PersistenceLayer::AbstractAdapter>]
# @see Bulkrax::PersistenceLayer
def persistence_adapter
@persistence_adapter || derived_persistence_adapter
end

def derived_persistence_adapter
if defined?(Hyrax)
# There's probably some configuration of Hyrax we could use to better refine this; but it's
# likely a reasonable guess. The main goal is to not break existing implementations and
# maintain an upgrade path.
if Gem::Version.new(Hyrax::VERSION) >= Gem::Version.new('6.0.0')
Bulkrax::PersistenceLayer::ValkyrieAdapter
else
Bulkrax::PersistenceLayer::ActiveFedoraAdapter
end
elsif defined?(ActiveFedora)
Bulkrax::PersistenceLayer::ActiveFedoraAdapter
elsif defined?(Valkyrie)
Bulkrax::PersistenceLayer::ValkyrieAdapter
else
raise "Unable to derive a persistence adapter"
end
end

attr_writer :use_locking

def use_locking
Expand Down Expand Up @@ -138,8 +106,6 @@ def config
:object_factory=,
:parsers,
:parsers=,
:persistence_adapter,
:persistence_adapter=,
:qa_controlled_properties,
:qa_controlled_properties=,
:related_children_field_mapping,
Expand Down
3 changes: 0 additions & 3 deletions lib/bulkrax/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@ class Engine < ::Rails::Engine
end

initializer 'requires' do
require 'bulkrax/persistence_layer'
require 'bulkrax/persistence_layer/active_fedora_adapter' if defined?(ActiveFedora)
require 'bulkrax/persistence_layer/valkyrie_adapter' if defined?(Valkyrie)
require 'bulkrax/transactions' if defined?(Hyrax::Transactions)
end

Expand Down
31 changes: 0 additions & 31 deletions lib/bulkrax/persistence_layer/active_fedora_adapter.rb

This file was deleted.

8 changes: 0 additions & 8 deletions lib/bulkrax/persistence_layer/valkyrie_adapter.rb

This file was deleted.

8 changes: 0 additions & 8 deletions spec/bulkrax_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -198,14 +198,6 @@
end
end

context '.persistence_adapter' do
subject { described_class.persistence_adapter }
it { is_expected.to respond_to(:find) }
it { is_expected.to respond_to(:query) }
it { is_expected.to respond_to(:solr_name) }
it { is_expected.to respond_to(:clean!) }
end

context '.factory_class_name_coercer' do
subject { described_class.factory_class_name_coercer }

Expand Down
4 changes: 2 additions & 2 deletions spec/parsers/bulkrax/bagit_parser_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -288,12 +288,12 @@ module Bulkrax
let(:fileset_entry_2) { FactoryBot.create(:bulkrax_csv_entry_file_set, importerexporter: exporter) }

before do
allow(Bulkrax.persistence_adapter).to receive(:query).and_return(work_ids_solr)
allow(Bulkrax.object_factory).to receive(:query).and_return(work_ids_solr)
allow(exporter.entries).to receive(:where).and_return([work_entry_1, work_entry_2, fileset_entry_1, fileset_entry_2])
end

it 'attempts to find the related record' do
expect(Bulkrax.persistence_adapter).to receive(:find).with('csv_entry').and_return(nil)
expect(Bulkrax.object_factory).to receive(:find).with('csv_entry').and_return(nil)

subject.write_files
end
Expand Down
2 changes: 1 addition & 1 deletion spec/parsers/bulkrax/csv_parser_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -633,7 +633,7 @@ module Bulkrax
end

before do
allow(Bulkrax.persistence_adapter).to receive(:query).and_return(SolrDocument.new(id: work_id))
allow(Bulkrax.object_factory).to receive(:query).and_return(SolrDocument.new(id: work_id))
allow(exporter.entries).to receive(:where).and_return([entry])
allow(parser).to receive(:headers).and_return(entry.parsed_metadata.keys)
end
Expand Down
Loading

0 comments on commit a04640b

Please sign in to comment.