Writing an ArchivesSpace plugin

Small picture of Mark
Founder & Expert Button Pusher of Teaspoon Consulting

I worked on ArchivesSpace as a part of the Hudson Molonglo development team. This article gives a worked example of how you can add functionality to ArchivesSpace by defining your own plugins.

Overview

ArchivesSpace allows you to write custom code to add new features, or change the behaviour of existing features. This code takes the form of a plugin: you structure your code according to certain conventions and it is automatically loaded when the ArchivesSpace system starts up. This article looks at the source code behind a plugin for generating accession identifiers.

When you create an accession through ArchivesSpace, you must provide a unique identifier for the record. An identifier can have between one and four components and it's for the institution to decide what these components represent. For example, an institution might structure its identifiers as:

 [year] [sequence]

Where [year] is the current year (e.g. 2013) and [sequence] is a running number (like "1234").

Such an identifier scheme doesn't really require human involvement at all. The system already knows what year it is, and addition is one of the few things computers do well, so it would be reasonable to expect the system to generate these identifiers for us.

In the following sections, we will work through the code required to make ArchivesSpace do this:

Changing the accession form

Loading custom JavaScript from the "Create accession" form

Let's start by adding some JavaScript code that will fire when we open the "Create accession" form. First, we create a directory to house our plugin:

 $ mkdir generate_accession_identifiers

Within this, create a subdirectory to hold our custom view template:

 $ mkdir -p generate_accession_identifiers/frontend/views

Within that directory, we create a file called layout_head.html.erb with the following content:

 # generate_accession_identifiers/frontend/views/layout_head.html.erb
 <% if controller.controller_name == 'accessions' && controller.action_name == 'new' %>
   <%= javascript_include_tag "#{@base_url}/assets/generate_accession_identifiers.js" %>
 <% end %>

The name layout_head.html.erb is special: anything you put in a file under [plugin_name]/frontend/views/layout_head.html.erb will be inserted at the top of every page delivered by ArchivesSpace.

In this case, we use an if statement to restrict the effect of our plugin to the "Create accession" form (by checking the controller and action being requested). If we're looking at that form, our template will insert a <script> tag to pull in a custom JavaScript file.

The controller and javascript_include_tag keywords are standard Rails facilities. The @base_url instance variable may contain a URL prefix for the application, so we include that so our plugin doesn't break if the application isn't mounted at the root URI.

Adding our JavaScript

Now that the "Create accession" form is pulling in our custom JavaScript file, we can handle the remaining form changes there. We create a new subdirectory within our plugin:

 $ mkdir -p generate_accession_identifiers/frontend/assets

And put our generate_accession_identifiers.js file in there:

 /* generate_accession_identifiers/frontend/assets/generate_accession_identifiers.js */
 $(function () {

   var padding = 3;

   var pad_number = function (number, padding) {
     var s = ('' + number);

     var padding_needed = (padding - s.length)

     if (padding_needed > 0) {
       s = (new Array(padding_needed + 1).join("0") + s);
     }

     return s;
   };


   var generate_accession_id = function () {
     $.ajax({
       url: APP_PATH + "plugins/generate_accession_identifier/generate",
       data: {},
       type: "POST",
       success: function(identifier) {
         $('#accession_id_0_').val(identifier.year);
         $('#accession_id_1_').val(pad_number(identifier.number, padding));

         $('#accession_id_1_').enable();
       },
     })
   };


   var identifier_is_blank = function () {
     for (var i = 0; i < 4; i++) {
       if ($("#accession_id_" + i + "_").val() !== "") {
         return false;
       }
     }

     return true;
   };



   if (identifier_is_blank()) {
     generate_accession_id();
   }

 })

That's a fair bit of code, but the steps are pretty simple:

The only non-standard thing here is APP_PATH, which is a JavaScript variable corresponding to the @base_url member we saw back in Ruby land. Again, we include this to make sure our code works when the application isn't mounted at the root URI.

With that file in place, we now have the "Create accession" form loading a custom JavaScript file, and that JavaScript firing an AJAX request. In the next section, we'll add some code to handle that AJAX request.

Adding a controller to handle the AJAX request

Setting up routes

The accession form's AJAX request is going to reach a controller in the frontend. This controller isn't actually going to do very much, since generating unique identifiers is really the ArchivesSpace backend's job. As a result, we'll see that the frontend controller is really just going to mediate between the form and the backend.

The request generated by our JavaScript is going to look like this:

 POST [base url]/plugins/generate_accession_identifier/generate

So we define a new Rails route to handle this URL. Create a file named generate_accession_identifiers/frontend/routes.rb with the following contents:

 # generate_accession_identifiers/frontend/routes.rb
 ArchivesSpace::Application.routes.draw do
   match('/plugins/generate_accession_identifier/generate' => 'generate_accession_identifiers#generate',
         :via => [:post])

 end

When loaded, this file will add a new route handler to the ArchivesSpace application, mapping our POST requests to the generate method of the generate_accession_identifiers controller. This routes.rb file won't be loaded automatically, so we need to add some code to a new file called generate_accession_identifiers/frontend/plugin_init.rb:

 # generate_accession_identifiers/frontend/plugin_init.rb
 my_routes = [File.join(File.dirname(__FILE__), "routes.rb")]
 ArchivesSpace::Application.config.paths['config/routes'].concat(my_routes)

The plugin_init.rb file is executed when the plugin is loaded (as ArchivesSpace starts), so this gives us a place to add our custom routes to the standard ones provided by the application.

Creating a new controller

Create a new directory to hold our controller:

 $ mkdir -p generate_accession_identifiers/frontend/controllers

Then define the controller by adding a file called generate_accession_identifiers_controller.rb. This will be automatically loaded when the system starts, and contains the code that will send a request to the backend and deliver JSON back to the JavaScript code:

 # generate_accession_identifiers/frontend/controllers/generate_accession_identifiers_controller.rb
 class GenerateAccessionIdentifiersController < ApplicationController

   skip_before_filter :unauthorised_access

   def generate
     response = JSONModel::HTTP::post_form('/plugins/generate_accession_identifiers/next')

     if response.code == '200'
       render :json => ASUtils.json_parse(response.body)
     else
       render :status => 500
     end
   end

 end

Walking through this code:

That leaves the final piece: the backend code that handles the POST request for /plugins/generate_accession_identifier/next. Off we go!

Adding ID generation to the ArchivesSpace backend

It's time for a new subdirectory:

 $ mkdir -p generate_accession_identifiers/backend/controllers

Within that, create a file called generate_4part_id.rb to hold the controller we'll add to the backend. That looks like this:

 # generate_accession_identifiers/backend/controllers/generate_4part_id.rb
 require 'time'

 class ArchivesSpaceService < Sinatra::Base

   Endpoint.post('/plugins/generate_accession_identifiers/next')
     .description("Generate a new identifier based on the year and a running number")
     .params()
     .permissions([])
     .returns([200, "{'year', 'YYYY', 'number', N}"]) \
   do
     year = Time.now.strftime('%Y')
     number = Sequence.get("GENERATE_ACCESSION_IDENTIFIER_#{year}")

     json_response(:year => year, :number => number)
   end

 end

Walking through once again:

And that's it! The frontend controller returns that JSON to the JavaScript, and the JavaScript parses it and inserts it into the "Create accession" form.

File locations

Along the way we casually mentioned a couple of files and directories that the ArchivesSpace system treats in a special way. Let's review those now:

Installing the plugin

Now that everything is in place, the final step is to install the custom plugin. To do that, simply copy it into your ArchivesSpace plugins directory:

 $ cp -a generate_accession_identifiers /path/to/archivesspace/plugins/

and then modify your ArchivesSpace config file to load it on startup:

 # /path/to/archivesspace/config/config.rb

 ...
 AppConfig[:plugins] = ['local', 'generate_accession_identifiers']
 ...

Links

If you get stuck, feel free to drop me a line at mark@teaspoon-consulting.com.