-
Notifications
You must be signed in to change notification settings - Fork 124
Hyrax Valkyrie Usage Guide
As of version 3.0.0, Hyrax has limited experimental support for Valkyrie as a replacement for ActiveFedora
. This is provided through a specialized Valkyrie adapter called Wings
(for "Hyrax-on-Wings"). This guide aims to document recommended use of Valkyrie for Hyrax-based applications, and internally within the Hyrax code base. For now, it will also document known limitations of the Wings adapter.
This usage guide is primarily intended for individuals with some experience implementing a Hyrax-based application and specifically working with its patterns and architecture. Direct reference is made to Hyrax components to illustrate integration strategies or to help correlate preexisting Hyrax knowledge to Valkyrie equivalents. These prerequisite topics are well documented in the Hyrax Development and Hyrax Management Guides.
Valkyrie
handles persistence a bit differently from ActiveFedora
(and ActiveRecord
). It implements the Data Mapper pattern, splitting models into a thin layer separated from the database by a data mapper (an "adapter"). This keeps application logic, display, indexing, forms, etc... decoupled from data persistence, offering improved flexibility for applications.
Hyrax currently supports only the internal Wings adapter. This is necessary since the majority of the code base still uses the legacy ActiveFedora models and support code. Wings
targets full compatibility with this code by using ActiveFedora internally. By default, Hyrax sets the :wings_adapter
as the Valkyrie.config.metadata_adapter
on start-up.
Throughout Hyrax, we use localized methods to retrieve the metadata and storage adapters, persisters, and query services. For downstream applications, these methods are the preferred way of retrieving these Valkyrie components. Using them guarantees that the application will use the same components for these tasks as are used by Hyrax, even if Hyrax's defaults change.
These methods are:
Hyrax.persister
Hyrax.metadata_adapter
Hyrax.storage_adapter
Hyrax.query_service
NOTE: currently, :wings_adapter
is the only adapter supported. Errors will occur when attempting to use other adapters.
Defining custom models for use with Valkyrie has good support as of Hyrax 3.0.0-rc2. For example:
class Book < Hyrax::Resource
include Hyrax::Schema(:core_metadata)
attribute :author, Valkyrie::Types::String
end
It is important to understand how Wings provides compatibility with ActiveFedora to ensure that it still functions with Hyrax's strategies for managing identifiers (e.g. custom ID minters via Noid identifier services).
As Wings is an adapter translating on behalf of ActiveFedora, it persists and receives identifiers in normal way, according to the minter configuration. For this to work on the Valkyrie side, the minted identifier is tracked as a secondary alternate identifier in addition to its primary identifier strategy (see PR #419)
In a purely Wings context, this alternate ID presents no special issues, with the caveat that it will require consideration in future migration scenarios.
Existing ActiveFedora
models are extended with Wings::Valkyrizable
. This adds a method #valkyrie_resource
which can be used to retrieve an equivalent resource for any given model instance.
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"]
This works regardless of whether a "native" Valkyrie model is defined (as described above).
Casting to Valkyrie in this way allows you to start using Valkyrie natively in local application code by writing new local services (or converting existing ones) to Valkyrie-only. For example:
class MyService
def initialize(model:, persister: Hyrax.persister)
@model = model
@persister = persister
end
def do_something(args)
# do work based on `args` here
@persister.save(resource: model)
end
end
Calling code that relies on ActiveFedora
can now do MyService.new(model: curation_concern.valkyrie_resource).do_something
.
We recommend this pattern for Hyrax adopters anticipating a Valkyrie migration and seeking to avoid writing greenfield code in ActiveFedora
, as well as for existing users looking to begin a code migration immediately.
The default Valkyrie Queries are supported in Hyrax, including in the Wings adapter.
Hyrax
extends Valkyrie by adding a number of "custom queries". We always ship custom queries with a base implementation in terms of the base Valkyrie queries. This ensures compatibility across adapters, while optimized versions are provided on a per-adapter basis.
There are classes of "custom queries" in Valkyrie that allow for navigation from a resource to children collections, filesets, works, etc. These classes are called Navigators, and in a Wings/Hyrax context they allow you to conveniently navigate PCDM-based models.
WorksControllerBehavior
has extremely limited support for Valkyrie native Hyrax::Work
models. We expect this support to expand quickly, and extend into other Hyrax-provided controllers and mix-ins.
For Works controllers, tell the controller to use your Valkyrie-native model using by setting curation_concern_type
on the controller class:
class BookResourceController < ApplicationController
include Hyrax::WorksControllerBehavior
self.curation_concern_type = Hyrax::Test::BookResource
end
Forms for Valkyrie models use Reform-based Valkyrie::ChangeSet
instances as the basis for forms.
class MonographForm < Hyrax::Forms::ResourceForm(Monograph)
property :title, required: true
end
Forms handle processing and validation of user input with a "dirty tracking" approach; that is, by keeping track of un-persisted changes to models.
The transition to Valkyrie represents a major rework of how Hyrax handles its Solr index. Historically, ActiveFedora
has given us indexing behavior that is tightly coupled to object CRUD. Whenever we write to an model object, the ORM handles that as a non-atomic update to both Fedora and Solr. This has been motivated by Fedora's lack of a query interface; i.e. we need the Solr index to provide real time query and read-optimized access when handling requests.
Valkyrie encourages us to decouple the two data stores.
This interface represents an early version of a Valkyrie Write-Only (indexing) Adapter.
Hyrax permissions are managed via Access Control List (ACL) style permissions. These are managed by Hyrax::AccessControl
resources which, in turn, contain sets of Hyrax::Permission
resources. Hyrax::AccessControl
points back to any Hyrax::Resource
via its #access_to
attribute.
The Permission
objects associated with a Resource
via AccessControl
are the canonical rules for access throughout the system.
To support ergonomic use, Hyrax provides a variety of systems for manipulating access controls. These can be thought of as layers or projections on the base-truth ACLs:
- "visibility" supports the generic "public", "institutional", and "restricted" access groups available in the UI.
- "_users/_groups" supports setting mode/agent pairs with a simple DSL via
Hyrax::PermissionManager
-
Hyrax::AccessControlList
provides a DSL for managing ACL entries directly.
These are listed here in order of decreasing granularity. More specifically: visibility
management depends on PermissionManager
for all its work; PermissionManager
similarly uses AccessControlList
; and AccessControlList
edits the base-truth AccessControl
/Permission
objects directly.
Hyrax::PermissionManager
provides setters and getters for the three most common permission modes: discover
, edit
, and read
. All the permission manager actions take place through accessors of the style [mode]_users=
/[mode]_groups=
.
Permission manager acls can be saved with permission_manager.acl.save
.
The Hyrax::AccessControlList
service provides two DSLs for editing permissions with a high degree of control: #add
/#delete
, and #grant
/#revoke
. Broadly speaking, the #grant
/#revoke
DSL has greater integration with the rest of Hyrax, while the #add
/#delete
DSL offers more direct control over the ACL data.
To use either DSL, begin by initializing a Hyrax::AccessControlList
:
resource = BookResource.new(title: 'Comet in Moominland')
resource = Hyrax.persister.save(resource: resource)
acl = Hyrax::AccessControlList.new(resource: resource)
acl.permissions # => #<Set: {}>
Use the #add
/#delete
API when you prefer to work directly with Permission
objects. For example, if an external API or sophisticated user has provided the access_to
, agent
, and mode
triplets directly.
acl << Hyrax::Permission.new(access_to: resource.id, agent: 'moomin', mode: :read) # add is an alias
acl.permissions # => #<Set: {#<Hyrax::Permission access_to=#<Valkyrie::ID:0x0000557a7c4581b0 @id="e9c4a3a5-d7f4-4e03-af9d-a5d64f0ac90b"> agent="moomin" mode=:read>}>
acl.delete Hyrax::Permission.new(access_to: resource.id, agent: 'moomin', mode: :read)
acl.permissions # => #<Set: {}>
In general, it will be simpler and just as accurate to use the #grant
/#revoke
DSL. This assumes that the resource
passed to your AccessControlList
is the target for each permission (this is an assumption made in permission retrieval, as well). Given this, you can use the #grant
and #revoke
methods to specify the access mode you want to edit, and the chained #to
/#from
methods to specify a user or group.
user = User.create(email: '[email protected]', password: 'password')
group = Hyrax::Group.new('my_group')
acl.grant(:edit).to(user)
acl.grant(:discover).to(group)
acl.permissions
# => #<Set: {#<Hyrax::Permission access_to=#<Valkyrie::ID:0x000055eba6dbeb28 @id="4e0683ac-c216-4190-bd76-221fd34bc4dd"> agent="[email protected]" mode=:edit>,
#<Hyrax::Permission access_to=#<Valkyrie::ID:0x000055eba6e90b50 @id="4e0683ac-c216-4190-bd76-221fd34bc4dd"> agent="group/my_group" mode=:discover>}>
acl.revoke(:discover).from(group)
acl.permissions
# => #<Set: {#<Hyrax::Permission access_to=#<Valkyrie::ID:0x000055c34667ae08 @id="f0274d86-9319-4d89-b9e9-562f6a2329db"> agent="[email protected]" mode=:edit>}>
ACLs can be saved and reloaded as follows:
acl.save
new_acl = Hyrax::AccessControlList.new(resource: resource)
new_acl.permissions
# => #<Set: {#<Hyrax::Permission access_to=#<Valkyrie::ID:0x000055c3473685c0 @id="f0274d86-9319-4d89-b9e9-562f6a2329db"> agent="[email protected]" mode=:edit>}
Hyrax provides a custom query for finding the AccessControl
for any given resource in the form of find_access_control_for(resource:)
.