-
Notifications
You must be signed in to change notification settings - Fork 124
Hyrax Valkyrie Usage Guide
As of 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").
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.
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.
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. Hyrax sets this 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
Defining custom models for use with Valkyrie has good support as of Hyrax 3.0.0-rc2.
class Book < Hyrax::Resource
include Hyrax::Schema(:core_metadata)
attribute :author, Valkyrie::Types::String
end
TK
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 for 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.
TK
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
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:)
.