Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🎁 Config option for coercing a factory class name #901

Merged
merged 1 commit into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 48 additions & 14 deletions app/services/bulkrax/factory_class_finder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,62 @@

module Bulkrax
class FactoryClassFinder
##
# The v6.0.0 default coercer. Responsible for converting a factory class name to a constant.
module DefaultCoercer
##
# @param name [String]
# @return [Class] when the name is a coercible constant.
# @raise [NameError] when the name is not coercible to a constant.
def self.call(name)
name.constantize
end
end

##
# A name coercer that favors classes that end with "Resource" but will attempt to fallback to
# those that don't.
module ValkyrieMigrationCoercer
SUFFIX = "Resource"

##
# @param name [String]
# @param suffix [String] the suffix we use for a naming convention.
#
# @return [Class] when the name is a coercible constant.
# @raise [NameError] when the name is not coercible to a constant.
def self.call(name, suffix: SUFFIX)
if name.end_with?(suffix)
name.constantize
else
begin
"#{name}#{suffix}".constantize
rescue NameError
name.constantize
end
end
end
end

##
# @param entry [Bulkrax::Entry]
# @return [Class]
def self.find(entry:)
new(entry: entry).find
def self.find(entry:, coercer: Bulkrax.factory_class_name_coercer || DefaultCoercer)
new(entry: entry, coercer: coercer).find
end

def initialize(entry:)
def initialize(entry:, coercer:)
@entry = entry
@coercer = coercer
end
attr_reader :entry
attr_reader :entry, :coercer

##
# @return [Class] when we are able to derive the class based on the {#name}.
# @return [Nil] when we encounter errors with constantizing the {#name}.
# @see #name
def find
# TODO: We have a string, now we want to consider how we coerce. Let's say we have Work and
# WorkResource in our upstream application. Work extends ActiveFedora::Base and is legacy.
# And WorkResource extends Valkyrie::Resource and is where we want to be moving. We may want
# to coerce the "Work" name into "WorkResource"
name.constantize
coercer.call(name)
rescue NameError
nil
rescue
Expand All @@ -41,13 +75,13 @@ def name
# details.
Array.wrap(entry.parsed_metadata['work_type']).first
else
# The string might be frozen, so lets duplicate
entry.default_work_type.dup
entry.default_work_type
end

# Let's coerce this into the right shape.
fc.tr!(' ', '_')
fc.downcase! if fc.match?(/[-_]/)
# Let's coerce this into the right shape; we're not mutating the string because it might well
# be frozen.
fc = fc.tr(' ', '_')
fc = fc.downcase if fc.match?(/[-_]/)
fc.camelcase
rescue
entry.default_work_type
Expand Down
21 changes: 21 additions & 0 deletions lib/bulkrax.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,25 @@ class Configuration
# @param adapter [Class<Bulkrax::PersistenceLayer::AbstractAdapter>]
attr_writer :persistence_adapter

##
# @param coercer [#call]
# @see Bulkrax::FactoryClassFinder
attr_writer :factory_class_name_coercer

##
# A function responsible for converting the name of a factory class to the corresponding
# constant.
#
# @return [#call, Bulkrax::FactoryClassFinder::DefaultCoercer] an object responding to call,
# with one positional parameter (e.g. arity == 1)
#
# @example
# Bulkrax.factory_class_name_coercer.call("Work")
# => Work
def factory_class_name_coercer
@factory_class_name_coercer || Bulkrax::FactoryClassFinder::DefaultCoercer
end

##
# Configure the persistence adapter used for persisting imported data.
#
Expand Down Expand Up @@ -99,6 +118,8 @@ def config
:default_work_type=,
:export_path,
:export_path=,
:factory_class_name_coercer,
:factory_class_name_coercer=,
:field_mappings,
:field_mappings=,
:file_model_class,
Expand Down
10 changes: 10 additions & 0 deletions spec/lib/bulkrax_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -205,4 +205,14 @@
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 }

it { is_expected.to respond_to(:call) }

it "has a method arity of 1" do
expect(subject.method(:call).arity).to eq 1
end
end
end
60 changes: 60 additions & 0 deletions spec/services/bulkrax/factory_class_finder_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe Bulkrax::FactoryClassFinder do
let(:legacy_work_class) { Class.new }
let(:legacy_work_resource_class) { Class.new }
let(:valkyrie_only_resource_class) { Class.new }
let(:active_fedora_only_class) { Class.new }

before do
Object.const_set(:LegacyWork, legacy_work_class)
Object.const_set(:LegacyWorkResource, legacy_work_resource_class)
Object.const_set(:ValkyrieOnlyResource, valkyrie_only_resource_class)
Object.const_set(:ActiveFedoraOnly, active_fedora_only_class)
end

after do
Object.send(:remove_const, :LegacyWork)
Object.send(:remove_const, :LegacyWorkResource)
Object.send(:remove_const, :ValkyrieOnlyResource)
Object.send(:remove_const, :ActiveFedoraOnly)
end

describe "DefaultCoercer" do
it "simply constantizes (unsafely) the given string" do
factory_class_name = "Work"
expect(described_class::DefaultCoercer.call(factory_class_name)).to eq(Work)
end
end

describe "ValkyrieMigrationCoercer" do
it 'favors mapping names to those ending in Resource' do
expect(described_class::ValkyrieMigrationCoercer.call("LegacyWork")).to eq(legacy_work_resource_class)
expect(described_class::ValkyrieMigrationCoercer.call("LegacyWorkResource")).to eq(legacy_work_resource_class)
expect(described_class::ValkyrieMigrationCoercer.call("ValkyrieOnlyResource")).to eq(valkyrie_only_resource_class)
expect(described_class::ValkyrieMigrationCoercer.call("ValkyrieOnly")).to eq(valkyrie_only_resource_class)
expect(described_class::ValkyrieMigrationCoercer.call("ActiveFedoraOnly")).to eq(active_fedora_only_class)
expect { described_class::ValkyrieMigrationCoercer.call("ActiveFedoraOnlyResource") }.to raise_error(NameError)
end
end

describe '.find' do
let(:entry) { double(Bulkrax::Entry, parsed_metadata: { "model" => model_name }, default_work_type: "Work") }
subject(:finder) { described_class.find(entry: entry, coercer: coercer) }

[
[Bulkrax::FactoryClassFinder::DefaultCoercer, "Legacy Work", "LegacyWork"],
[Bulkrax::FactoryClassFinder::ValkyrieMigrationCoercer, "Legacy Work", "LegacyWorkResource"],
[Bulkrax::FactoryClassFinder::DefaultCoercer, "Legacy Work Resource", "LegacyWorkResource"]
].each do |given_coercer, given_model_name, expected_class_name|
context "with an entry with model: #{given_model_name} and coercer: #{given_coercer}" do
let(:model_name) { given_model_name }
let(:coercer) { given_coercer }

it { is_expected.to eq expected_class_name.constantize }
end
end
end
end
Loading