Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Decompose ActiveFedora::Model into a generic model mapper and a class…
…ifier
- Loading branch information
Showing
10 changed files
with
199 additions
and
84 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
module ActiveFedora | ||
# Create model classifiers for resources or solr documents | ||
class DefaultModelMapper | ||
attr_reader :classifier_class, :solr_field, :predicate | ||
|
||
def initialize(classifier_class: ActiveFedora::ModelClassifier, solr_field: ActiveFedora::QueryResultBuilder::HAS_MODEL_SOLR_FIELD, predicate: ActiveFedora::RDF::Fcrepo::Model.hasModel) | ||
@classifier_class = classifier_class | ||
@solr_field = solr_field | ||
@predicate = predicate | ||
end | ||
|
||
def classifier(resource) | ||
models = if resource.respond_to? :graph | ||
resource.graph.query([nil, predicate, nil]).map { |rg| rg.object.to_s } | ||
elsif resource.respond_to? :[] | ||
resource[solr_field] || [] | ||
else | ||
[] | ||
end | ||
|
||
classifier_class.new(models) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
module ActiveFedora | ||
# Translate model names to classes | ||
class ModelClassifier | ||
# Convenience method for getting class constant based on a string | ||
# @example | ||
# ActiveFedora::Model.class_from_string("Om") | ||
# => Om | ||
# ActiveFedora::Model.class_from_string("ActiveFedora::RdfNode::TermProxy") | ||
# => ActiveFedora::RdfNode::TermProxy | ||
# @example Search within ActiveFedora::RdfNode for a class called "TermProxy" | ||
# ActiveFedora::Model.class_from_string("TermProxy", ActiveFedora::RdfNode) | ||
# => ActiveFedora::RdfNode::TermProxy | ||
def self.class_from_string(full_class_name, container_class = Kernel) | ||
container_class = container_class.name if container_class.is_a? Module | ||
container_parts = container_class.split('::') | ||
(container_parts + full_class_name.split('::')).flatten.inject(Kernel) do |mod, class_name| | ||
if mod == Kernel | ||
Object.const_get(class_name) | ||
elsif mod.const_defined? class_name.to_sym | ||
mod.const_get(class_name) | ||
else | ||
container_parts.pop | ||
class_from_string(class_name, container_parts.join('::')) | ||
end | ||
end | ||
end | ||
|
||
attr_reader :class_names, :default | ||
|
||
def initialize(class_names, default: ActiveFedora::Base) | ||
@class_names = Array(class_names) | ||
@default = default | ||
end | ||
|
||
## | ||
# Convert all the provided class names to class instances | ||
def models | ||
class_names.map do |uri| | ||
classify(uri) | ||
end.compact | ||
end | ||
|
||
## | ||
# Select the "best" class from the list of class names. We define | ||
# the "best" class as: | ||
# - a subclass of the given default, base class | ||
# - preferring subclasses over the parent class | ||
def best_model(opts = {}) | ||
best_model_match = opts.fetch(:default, default) | ||
|
||
models.each do |model_value| | ||
# If there is an inheritance structure, use the most specific case. | ||
best_model_match = model_value if best_model_match.nil? || best_model_match > model_value | ||
end | ||
|
||
best_model_match | ||
end | ||
|
||
private | ||
|
||
def classify(model_value) | ||
unless class_exists?(model_value) | ||
ActiveFedora::Base.logger.warn "'#{model_value}' is not a real class" if ActiveFedora::Base.logger | ||
return nil | ||
end | ||
ActiveFedora::ModelClassifier.class_from_string(model_value) | ||
end | ||
|
||
def class_exists?(class_name) | ||
return false if class_name.empty? | ||
klass = class_name.constantize | ||
return klass.is_a?(Class) | ||
rescue NameError | ||
return false | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
require 'spec_helper' | ||
|
||
describe ActiveFedora::DefaultModelMapper do | ||
let(:classifier) { double } | ||
let(:classifier_instance) { double } | ||
let(:solr_field) { 'solr_field' } | ||
let(:predicate) { 'info:predicate' } | ||
subject { described_class.new classifier_class: classifier, solr_field: solr_field, predicate: predicate } | ||
|
||
describe '#classifier' do | ||
context 'with a solr document' do | ||
let(:solr_document) { { 'solr_field' => ['xyz'] } } | ||
|
||
before do | ||
expect(classifier).to receive(:new).with(['xyz']).and_return(classifier_instance) | ||
end | ||
|
||
it 'creates a classifier from the solr field data' do | ||
expect(subject.classifier(solr_document)).to eq classifier_instance | ||
end | ||
end | ||
|
||
context 'with a resource' do | ||
let(:graph) do | ||
RDF::Graph.new << [:hello, predicate, 'xyz'] | ||
end | ||
|
||
let(:resource) { double(graph: graph) } | ||
|
||
before do | ||
expect(classifier).to receive(:new).with(['xyz']).and_return(classifier_instance) | ||
end | ||
|
||
it 'creates a classifier from the resource model predicate' do | ||
expect(subject.classifier(resource)).to eq classifier_instance | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
require 'spec_helper' | ||
|
||
describe ActiveFedora::ModelClassifier do | ||
module ParentClass | ||
class SiblingClass | ||
end | ||
class OtherSiblingClass | ||
end | ||
class SubclassClass < SiblingClass | ||
end | ||
end | ||
|
||
let(:class_names) { ["ParentClass::SiblingClass", "ParentClass::OtherSiblingClass", "ParentClass::SubclassClass", "ParentClass::NoSuchClass"] } | ||
subject { described_class.new class_names } | ||
|
||
describe ".class_from_string" do | ||
it "returns class constants based on strings" do | ||
expect(described_class.class_from_string("Om")).to eq Om | ||
expect(described_class.class_from_string("ActiveFedora::RDF::IndexingService")).to eq ActiveFedora::RDF::IndexingService | ||
expect(described_class.class_from_string("IndexingService", ActiveFedora::RDF)).to eq ActiveFedora::RDF::IndexingService | ||
end | ||
|
||
it "finds sibling classes" do | ||
expect(described_class.class_from_string("SiblingClass", ParentClass::OtherSiblingClass)).to eq ParentClass::SiblingClass | ||
end | ||
|
||
it "raises a NameError if the class isn't found" do | ||
expect { | ||
described_class.class_from_string("FooClass", ParentClass::OtherSiblingClass) | ||
}.to raise_error NameError, /uninitialized constant (Object::)?FooClass/ | ||
end | ||
end | ||
|
||
describe '#models' do | ||
it 'converts class names to classes' do | ||
expect(subject.models).to match_array [ParentClass::SiblingClass, ParentClass::OtherSiblingClass, ParentClass::SubclassClass] | ||
end | ||
end | ||
|
||
describe '#best_model' do | ||
it 'selects the most specific matching model' do | ||
expect(subject.best_model(default: nil)).to eq ParentClass::SubclassClass | ||
end | ||
|
||
it 'filters models to subclasses of the default' do | ||
expect(subject.best_model(default: ActiveFedora::Base)).to eq ActiveFedora::Base | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters