Transactions Development Pattern - samvera/hyrax GitHub Wiki

< Back to Patterns

NOTE: At this writing, all links are to code in Release 3.3.0.

For a walk through of transactions, see https://github.com/samvera-labs/transitioning-to-valkyrie-workshop and the transaction exercise solution.

Transactions

General Overview

Transactions provide a way to run multiple steps in a predetermined order. Steps in a transactions can modify values in a change set or in the resource associated with the change set once the change set is saved.

Typically, the transaction is initiated with a change set when an action saving a form is executed in a controller. One of the steps will save the change set, which syncs it with a resource. From that point on, the steps operate on the resource.

NOTE: Transactions are not reversible. But if the steps stop anywhere before the change set is saved, then the transaction will be a no-op.

Terminology

Term Description
Container Registry Register each transaction and step under a name space for the type of object to which they will be applied.
Transaction Defines a series of steps applied to a change set and/or its associated resource in a predetermined order. Typically a transaction is defined to perform steps required for an action in a controller (e.g. collection_create transaction is used by the create action in the CollectionController).
Step Code that has a single responsibility. These are often used by multiple transactions (e.g. set_user_as_depositor step is used in collection_create and work_create transactions.
Change Set is_a Valkyrie::ChangeSet See Understanding change sets
Resource is_a Valkyrie::Resource See Understanding resources

Hyrax Specifics

Defining Transactions and Steps

Container Registry

/lib/hyrax/transactions/container.rb - registers transactions and steps

Key parts of the container registration file:

  • require the files where each transaction and step are defined
  • each step is registered under the namespace of the type of object it acts on (e.g. If it acts on the change_set, register it under namespace 'change_set'. If it acts on works and collections, register it under both namespace 'work_resource' and namespace 'collection_resource'.)

Transaction Definition

/lib/hyrax/transactions - other files in the transactions directory are defining multi-step transactions. NOTE: This is not required. The location of the transaction is registered in the container registry.

Key parts of a transaction file:

  • DEFAULT_STEPS - the steps to run for this transaction. If the step acts on a change_set, then prefix the step name with change_set. (e.g. change_set.set_user_as_depositor). If the step acts on a resource, then prefix the name with resource type defined in the Container registry (e.g. collection_resource.save_acl). Steps can be a shared step (e.g. add_to_collections) or another transaction (e.g. apply).
  • #initialize - this is the same for all transactions. It receives the container registry (defaulting to the container registry described above) and steps (defaulting to the steps defined in the DEFAULT_STEPS constant). You shouldn't need to make any changes to the initializer method.

Shared Steps

/lib/hyrax/transactions/steps - single responsibility steps are defined in the steps directory

Key parts of a step file:

  • #call - this is the method that gets called to execute the transaction
    • parameters - The parameters for the #call method can be anything. The first parameter is positional and is always either the change_set or one of the resources (e.g. work_resource, collection_resource, etc.). All other parameters are named and must have default values. The additional parameters are passed to the transaction step when the transaction is started using the #with_step_args method. See Calling a Transaction for more information on passing in values.
    • Success - When the step completes successfully, the last thing to do is call Success(value). Value is often the first parameter. The return value doesn't have to be the object passed in (e.g. change_set or resource), but it typically is as a way to pass along any modifications that were made to the change_set or resource. An example where it is not the passed in object is the save step which syncs the passed in change_set and returns the saved resource.
    • Failure - If the step fails, call Failure([err_msg, first_param]) and pass an array including an error message and the first parameter. All processing of the transaction steps stops once a failure occurs. Again, The caller can process the

Calling a Transaction

Example from #valkyrie_create in CollectionController.

@collection = transactions['change_set.create_collection']
   .with_step_args(
        'change_set.set_user_as_depositor' => { user: current_user },
        'change_set.add_to_collections' => { collection_ids: Array(params[:parent_id]) },
        'collection_resource.apply_collection_type_permissions' => { user: current_user }
)
  • If a step doesn’t have additional parameters beyond the first parameter that is the object (e.g. change_set or resource), it is not in the list for method .with_step_args. Some transactions have no steps with additional methods and will not call .with_step_args at all.
  • If a step does have additional parameters, it is listed in .with_step_args and the additional parameters for the step are passed as a hash.
  • Steps are called in the order listed in the transaction's DEFAULT_STEPS.
  • Processing of a transaction will stop if a failure happens or once all steps have complete successfully

An Example

TBD

Testing

TBD