Skip to content

Hyrax Valkyrie Usage Guide

tamsin johnson edited this page Jun 29, 2022 · 49 revisions

Reference:

Overview

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.

Who is this guide for?

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.

Persistence

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.

Adapters

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.

Using the Hyrax adapters

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.

Models

Using Valkyrie Models Natively

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

Identifiers

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.

Casting ActiveFedora models to Valkyrie

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.

Queries

Valkyrie Queries

The default Valkyrie Queries are supported in Hyrax, including in the Wings adapter.

Custom Queries

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.

For information about custom query development, see Custom Queries Development Pattern

Navigators

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.

Controllers

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

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, primary: 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.

When configuring properties, arbitrary keys and values can be added to clarify field definition. This can be useful for applications to customize how the form is built by the view. By default, Hyrax respects :primary and :required, as boolean flags.

Indexing

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.

Permissions

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.

Using Permissions

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.

Visibility

Permission Manager

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.

Access Control List

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: {}>
#add/#delete

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: {}>
#grant/#revoke

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>}>

Persisting ACLs

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>}

Querying access control for a Resource

Hyrax provides a custom query for finding the AccessControl for any given resource in the form of find_access_control_for(resource:).

Resources

Clone this wiki locally