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

[wings] Round trip registered valkyrie native classes #4012

Merged
merged 2 commits into from
Sep 18, 2019
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
43 changes: 43 additions & 0 deletions lib/wings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,50 @@
# However, these dependencies should be considered temprorary: this code will
# be deprecated for removal in a future release.
#
# @example casting an ActiveFedora model to Valkyrie
# work = GenericWork.create(title: ['Comet in Moominland'])
# resource = work.valkyrie_resource
#
# resource.title # => ["Comet in Moominland"]
# resource.title = ["Mumintrollet på kometjakt"]
#
# Hyrax.persister.save(resource: resource)
#
# work.reload
# work.title # => ["Mumintrollet på kometjakt"]
#
# @example defining a native Valkyrie model for use with Wings
# # given an `ActiveFodora` model like
# class Book < ActiveFedora::Base
# property :author, predicate: ::RDF::URI('http://example.com/ns/author')
# property :title, predicate: ::RDF::URI('http://example.com/ns/title')
# end
#
# # define a `Valkyrie` model with matching attributes,
# class BookResource < Hyrax::Resource
# attribute :author, Valkyrie::Types::String
# attribute :title, Valkyrie::Types::String
# end
#
# # and register the relationship with `Wings`
# Wings::ModelRegistry.register(BookResource, Book)
#
# # `Wings` will cast the `BookResource` to a `Book` to persist via `ActiveFedora`
# resource = BookResource.new(author: 'Tove Jansson', title: 'Comet in Moominland')
# adapter = Wings::Valkyrie::MetadataAdapter.new
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i chose to use Wings::Valkyrie::MetadataAdapter.new here to show this working explicitly with the wings adapter. i guess we could change either this or the Hyrax.persister on L25, but honestly I'm not thinking it matters. It might be good to show the ways of selecting persisters, and just get better inline documentation for Hyrax.persister in at the Hyrax level.

# resource = adapter.persister.save(resource: resource)
#
# resource.title # => ["Comet in Moominland"]
# resource.author # => ["Tove Jansson"]
#
# resource.is_a?(BookResource) # => true
#
# @see https://wiki.duraspace.org/display/samvera/Hyrax-Valkyrie+Development+Working+Group
# for further context regarding the approach
module Wings; end

require 'valkyrie'
require 'wings/model_registry'
require 'wings/model_transformer'
require 'wings/orm_converter'
require 'wings/attribute_transformer'
Expand Down Expand Up @@ -52,6 +91,10 @@ module Wings; end
Valkyrie.config.metadata_adapter.query_service.custom_queries.register_query_handler(query_handler)
end

Wings::ModelRegistry.register(Hyrax::AccessControl, Hydra::AccessControl)
Wings::ModelRegistry.register(Hyrax::Embargo, Hydra::AccessControls::Embargo)
Wings::ModelRegistry.register(Hyrax::Lease, Hydra::AccessControls::Lease)

Hydra::AccessControl.send(:define_method, :valkyrie_resource) do
attrs = attributes.symbolize_keys
attrs[:new_record] = new_record?
Expand Down
7 changes: 3 additions & 4 deletions lib/wings/active_fedora_converter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,10 @@ def convert

def active_fedora_class
klass = resource.internal_resource.constantize

return klass if klass <= ActiveFedora::Base
return Hydra::AccessControl if klass <= Hyrax::AccessControl
return Hydra::AccessControls::Embargo if klass <= Hyrax::Embargo
return Hydra::AccessControls::Lease if klass <= Hyrax::Lease
DefaultWork

ModelRegistry.lookup(klass) || DefaultWork
end

##
Expand Down
51 changes: 51 additions & 0 deletions lib/wings/model_registry.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# frozen_string_literal: true

module Wings
##
# This registry provides an off-ramp for legacy ActiveFedora models.
#
# New valkyrie models can be defined manually and registered as an analogue
# of an existing `ActiveFedora::Base` implementer. This allows Wings to cast
# the new model to the legacy model when saving, ensuring backwards compatible
# data on save.
#
# Several models from Hyrax components have default mappings provided by Wings.
#
# @example
# Wings::ModelRegistry.register(NewValkyrieModel, OldActiveFedoraModel)
#
# Wings::ModelRegistry.lookup(NewValkyrieModel) # => OldActiveFedoraModel
# Wings::ModelRegistry.reverse_lookup(OldActiveFedoraModel) # => NewValkyrieModel
#
class ModelRegistry
include Singleton

def self.register(*args)
instance.register(*args)
end

def self.lookup(*args)
instance.lookup(*args)
end

def self.reverse_lookup(*args)
instance.reverse_lookup(*args)
end

def initialize
@map = {}
end

def register(valkyrie, active_fedora)
@map[valkyrie] = active_fedora
end

def lookup(valkyrie)
@map[valkyrie]
end

def reverse_lookup(active_fedora)
@map.rassoc(active_fedora)&.first
end
end
end
8 changes: 1 addition & 7 deletions lib/wings/orm_converter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,7 @@ def self.relationship_keys_for(reflections:)
#
# @return [Class]
def self.base_for(klass:)
if klass == Hydra::AccessControls::Embargo
Hyrax::Embargo
elsif klass == Hydra::AccessControls::Lease
Hyrax::Lease
else
Hyrax::Resource
end
ModelRegistry.reverse_lookup(klass) || Hyrax::Resource
end

##
Expand Down
22 changes: 22 additions & 0 deletions spec/support/book_resource.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# frozen_string_literal: true
module Hyrax
module Test
class BookResource < Hyrax::Resource
attribute :author, Valkyrie::Types::String
attribute :created, Valkyrie::Types::Date
attribute :isbn, Valkyrie::Types::String
attribute :pubisher, Valkyrie::Types::String
attribute :title, Valkyrie::Types::String
end

class Book < ActiveFedora::Base
property :author, predicate: ::RDF::URI('http://example.com/ns/author')
property :created, predicate: ::RDF::URI('http://example.com/ns/created')
property :isbn, predicate: ::RDF::URI('http://example.com/ns/isbn')
property :publisher, predicate: ::RDF::URI('http://example.com/ns/publisher')
property :title, predicate: ::RDF::URI("http://example.com/ns/title")
end
end
end

Wings::ModelRegistry.register(Hyrax::Test::BookResource, Hyrax::Test::Book)
22 changes: 22 additions & 0 deletions spec/wings/active_fedora_converter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,28 @@
expect(converter.convert).to eq work
end

context 'when given a valkyrie native model' do
let(:resource) { klass.new }

let(:klass) do
class ConverterDummyResource < Valkyrie::Resource; end
ConverterDummyResource
end

it 'gives a default work' do
expect(converter.convert)
.to be_a Wings::ActiveFedoraConverter::DefaultWork
end

context 'and it is registered' do
let(:resource) { Hyrax::Test::BookResource.new }

it 'maps to the registered ActiveFedora class' do
expect(converter.convert).to be_a Hyrax::Test::Book
end
end
end

context 'with attributes' do
let(:attributes) do
FactoryBot.attributes_for(:generic_work)
Expand Down
4 changes: 2 additions & 2 deletions spec/wings/model_transformer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@
let(:page2) { page_class.new(id: 'pg2') }

let(:book_class) do
Book = Class.new(ActiveFedora::Base) do
Monograph = Class.new(ActiveFedora::Base) do
has_many :pages
property :title, predicate: ::RDF::Vocab::DC.title
property :contributor, predicate: ::RDF::Vocab::DC.contributor
Expand All @@ -273,7 +273,7 @@

after do
Object.send(:remove_const, :Page)
Object.send(:remove_const, :Book)
Object.send(:remove_const, :Monograph)
end

let(:attributes) do
Expand Down
9 changes: 9 additions & 0 deletions spec/wings/orm_converter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,14 @@
expect { described_class.to_valkyrie_resource_class(klass: String) }.to raise_error
end
end

context 'when given a registered class' do
it 'returns a subclass of the corresponding native valkyrie resource' do
klass = Wings::ModelRegistry.lookup(Hyrax::Test::BookResource)

expect(described_class.to_valkyrie_resource_class(klass: klass).ancestors)
.to include Hyrax::Test::BookResource
end
end
end
end
18 changes: 11 additions & 7 deletions spec/wings/valkyrie/persister_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,26 @@
RSpec.describe Wings::Valkyrie::Persister do
context "When passing a Valkyrie::Resource converted from an ActiveFedora::Base" do
before do
class Book < ActiveFedora::Base
include Hydra::AccessControls::Permissions
include Hyrax::CoreMetadata
include Hydra::WithDepositor
property :title, predicate: ::RDF::Vocab::DC.title, multiple: true
module Hyrax::Test
module Persister
class Book < ActiveFedora::Base
include Hydra::AccessControls::Permissions
include Hyrax::CoreMetadata
include Hydra::WithDepositor
property :title, predicate: ::RDF::Vocab::DC.title, multiple: true
end
end
end
end

after do
Object.send(:remove_const, :Book)
Hyrax::Test.send(:remove_const, :Persister)
end

subject(:persister) { described_class.new(adapter: adapter) }
let(:adapter) { Wings::Valkyrie::MetadataAdapter.new }
let(:query_service) { adapter.query_service }
let(:af_resource_class) { Book }
let(:af_resource_class) { Hyrax::Test::Persister::Book }
let(:resource_class) { Wings::OrmConverter.to_valkyrie_resource_class(klass: af_resource_class) }
let(:resource) { resource_class.new(title: ['Foo']) }

Expand Down
34 changes: 19 additions & 15 deletions spec/wings/valkyrie/query_service_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,35 @@

RSpec.describe Wings::Valkyrie::QueryService do
before do
class Book < ActiveFedora::Base
include Hyrax::WorkBehavior
include Hydra::AccessControls::Permissions
property :title, predicate: ::RDF::Vocab::DC.title, multiple: true
property :a_member_of, predicate: ::RDF::URI.new('http://www.example.com/a_member_of'), multiple: true do |index|
index.as :symbol
module Hyrax::Test
module QueryService
class Book < ActiveFedora::Base
include Hyrax::WorkBehavior
include Hydra::AccessControls::Permissions
property :title, predicate: ::RDF::Vocab::DC.title, multiple: true
property :a_member_of, predicate: ::RDF::URI.new('http://www.example.com/a_member_of'), multiple: true do |index|
index.as :symbol
end
property :an_ordered_member_of, predicate: ::RDF::URI.new('http://www.example.com/an_ordered_member_of'), multiple: true
end

class Image < ActiveFedora::Base
include Hydra::AccessControls::Permissions
property :title, predicate: ::RDF::Vocab::DC.title, multiple: true
end
end
property :an_ordered_member_of, predicate: ::RDF::URI.new('http://www.example.com/an_ordered_member_of'), multiple: true
end
class Image < ActiveFedora::Base
include Hydra::AccessControls::Permissions
property :title, predicate: ::RDF::Vocab::DC.title, multiple: true
end
end

after do
Object.send(:remove_const, :Book)
Object.send(:remove_const, :Image)
Hyrax::Test.send(:remove_const, :QueryService)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this! Makes for easier removal of constants defined in tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, we should probably start using Hyrax::Test everywhere test constants are needed

end

subject(:query_service) { described_class.new(adapter: adapter) }
let(:adapter) { Wings::Valkyrie::MetadataAdapter.new }
let(:persister) { Wings::Valkyrie::Persister.new(adapter: adapter) }
let(:af_resource_class) { Book }
let(:af_image_resource_class) { Image }
let(:af_resource_class) { Hyrax::Test::QueryService::Book }
let(:af_image_resource_class) { Hyrax::Test::QueryService::Image }
let(:resource_class) { Wings::OrmConverter.to_valkyrie_resource_class(klass: af_resource_class) }
let(:image_resource_class) { Wings::OrmConverter.to_valkyrie_resource_class(klass: af_image_resource_class) }
let(:resource) { resource_class.new(title: ['Foo']) }
Expand Down