Skip to content

Hyrax Valkyrie Usage Guide

Phil Suda edited this page Oct 21, 2019 · 49 revisions

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.

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

TK

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.

Navigators

These are a class of "custom queries" that allow for navigation from a resource to children collections, filesets, works, etc.

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

Indexing

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