One of the first things I did when I joined the University of Michigan’s Library IT department as the lead developer on Deep Blue Data, a Samvera Hyrax-based data repository, was to add user-specified arbitrary ordering for a number of the metadata fields.
Overview
This technique maintains an arbitrary user-specified order for a given metadata field by adding a new text metadata field to store the order encoded in a JSON string, then overriding the reading and writing of the metadata field to make use of this JSON string.
The following steps are detailed below:
- Create a Utility Module to Encode/Decode JSON Strings
- Create a Utility Module for Ordered Values Methods
- Add Ordered Metadata Field to Work Metadata
- Override Accessors in Model
Create a Utility Module to Encode/Decode JSON Strings
Isolate the serialization process in a utility module.
- module Deepblue
- module OrderedStringHelper
- class DeserializeError < RuntimeError
- end
- #
- # convert a serialized array to a normal array of values
- # assumes values are stored as json converted to strings
- # a failure to deserialize throws a DeserializeError,
- # the exact reason for failure is ignored
- #
- def self.deserialize( serialized_string_containing_an_array )
- if serialized_string_containing_an_array.start_with?('[')
- begin
- arr = ActiveSupport::JSON.decode serialized_string_containing_an_array
- return arr if arr.is_a?( Array )
- rescue ActiveSupport::JSON.parse_error
- # ignore and fall through
- end
- end
- raise OrderedStringHelper::DeserializeError
- end
- #
- # serialize a normal array of values to an array of ordered values
- #
- def self.serialize( arr )
- serialized_string_containing_an_array = ActiveSupport::JSON.encode( arr ).to_s
- return serialized_string_containing_an_array
- end
- end
- end
Create a Utility Module for Ordered Values Methods
The ordered and ordered_values methods filter for nil values and catch runtime serialization errors.
- module Deepblue
- module MetadataHelper
- def self.ordered( ordered_values: nil, values: nil )
- return nil if values.nil?
- unless ordered_values.nil?
- begin
- values = OrderedStringHelper.deserialize( ordered_values )
- rescue OrderedStringHelper::DeserializeError
- # fallback to original values, which are stored in an unspecified order
- return values
- end
- end
- return values
- end
- def self.ordered_values( values: nil )
- return nil if values.nil?
- OrderedStringHelper.serialize( values )
- end
- end
- end
Add Ordered Metadata Field to Work Metadata
The predicate is arbitrary (and could obviously be improved). You’ll need a unique predicate for each ordered metadata field added.
- module Deepblue
- module DeepblueWorkMetadata
- extend ActiveSupport::Concern
- # <snip/>
- property :creator_ordered, predicate: ::RDF::URI.new('https://deepblue.lib.umich.edu/data/help#creator_ordered'), multiple: false do |index|
- index.type :text
- index.as :stored_searchable
- end
- # <snip/>
- end
- end
Override Accessors in Model
The getter returns the deserialized ordered version of the metadata field if it is populated, else it returns the original unordered version of the metadata field.
The setter stores a serialization of the value being stored to the ordered version of the metadata field, then passes the original version to the overridden store method.
Example model class with ordered creator metadata field:
- class ExampleModel < ActiveFedora::Base
- # <snip/>
- def creator
- values = super
- Deepblue::MetadataHelper.ordered( ordered_values: creator_ordered, values: values )
- end
- def creator=( values )
- self.creator_ordered = Deepblue::MetadataHelper.ordered_values( values: values )
- super values
- end
- # <snip/>
- end
Links to Actual Implementation Code
- https://github.com/mlibrary/deepblue/blob/master/app/helpers/deepblue/ordered_string_helper.rb
- https://github.com/mlibrary/deepblue/blob/master/app/helpers/deepblue/metadata_helper.rb
- https://github.com/mlibrary/deepblue/blob/master/app/models/concerns/umrdr/umrdr_work_metadata.rb
- https://github.com/mlibrary/deepblue/blob/master/app/models/data_set.rb