Skip to content

Commit

Permalink
dymanic loading of the model with m3 profiles
Browse files Browse the repository at this point in the history
  • Loading branch information
orangewolf committed Jun 6, 2024
1 parent 3d32fa7 commit 01b8ed1
Show file tree
Hide file tree
Showing 12 changed files with 428 additions and 12 deletions.
10 changes: 10 additions & 0 deletions .dassie/db/migrate/20240606205215_create_hyrax_flexible_schemas.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class CreateHyraxFlexibleSchemas < ActiveRecord::Migration[6.1]
def change
create_table :hyrax_flexible_schemas do |t|
t.string :version, index: { unique: true }
t.text :profile

t.timestamps
end
end
end
9 changes: 8 additions & 1 deletion .dassie/db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 2024_05_06_070809) do
ActiveRecord::Schema.define(version: 2024_06_06_205215) do

# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
Expand Down Expand Up @@ -172,6 +172,13 @@
t.datetime "updated_at", null: false
end

create_table "hyrax_flexible_schemas", force: :cascade do |t|
t.string "version"
t.text "profile"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end

create_table "job_io_wrappers", force: :cascade do |t|
t.bigint "user_id"
t.bigint "uploaded_file_id"
Expand Down
10 changes: 7 additions & 3 deletions app/models/concerns/hyrax/flexibility.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
module Hyrax
module Flexibility
extend ActiveSupport::Concern
included do
attribute :schema_version, Valkyrie::Types::String
end

class_methods do
## Override dry-struct 1.6.0 to enable redefining schemas on the fly
def attributes(new_schema)
Expand Down Expand Up @@ -55,10 +59,10 @@ def new(attributes = default_attributes, safe = false, &block) # rubocop:disable

## Read the schema from the database and load the correct schemas for the instance in to the class
def load(attributes, safe = false)
attributes[:schemas] ||= 'core_metadata:1'
attributes[:schema_version] ||= Hyrax::FlexibleSchema.order('created_at DESC').pick(:version)
struct = allocate
schema_name, schema_version = attributes[:schemas].split(':')
struct.singleton_class.attributes(Hyrax::Schema(schema_name, schema_version:).attributes)
schema_version = attributes[:schema_version]
struct.singleton_class.attributes(Hyrax::Schema(self, schema_version:).attributes)
clean_attributes = safe ? struct.singleton_class.schema.call_safe(attributes) { |output = attributes| return yield output } : struct.singleton_class.schema.call_unsafe(attributes)
struct.__send__(:initialize, clean_attributes)
struct
Expand Down
33 changes: 33 additions & 0 deletions app/models/hyrax/flexible_schema.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
class Hyrax::FlexibleSchema < ApplicationRecord
serialize :profile, coder: YAML

def attributes_for(class_name)
class_names[class_name]
end

def class_names
return @class_names if @class_names
@class_names = {}
profile['classes'].keys.each do |class_name|
@class_names[class_name] = {}
end
profile['properties'].each do |key, value|
value['available_on']['class'].each do |property_class|
# map some m3 items to what Hyrax expects
value['type'] = lookup_type(value['range'])
value['predicate'] = value['property_uri']
@class_names[property_class][key] = value
end
end
@class_names
end

def lookup_type(range)
case range
when "http://www.w3.org/2001/XMLSchema#dateTime"
'date_time'
else
range.split('#').last.underscore
end
end
end
1 change: 0 additions & 1 deletion app/models/hyrax/resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ class Resource < Valkyrie::Resource
attribute :alternate_ids, Valkyrie::Types::Array.of(Valkyrie::Types::ID)
attribute :embargo_id, Valkyrie::Types::Params::ID
attribute :lease_id, Valkyrie::Types::Params::ID
attribute :schemas, Valkyrie::Types::String

delegate :edit_groups, :edit_groups=,
:edit_users, :edit_users=,
Expand Down
140 changes: 140 additions & 0 deletions app/services/hyrax/m3_schema_loader.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# frozen_string_literal: true

module Hyrax
##
# @api private
#
# Read m3 profiles from the database
#
# @see config/metadata/m3_profile.yaml for an example configuration
class M3SchemaLoader
##
# @param [Symbol] schema
#
# @return [Hash<Symbol, Dry::Types::Type>] a map from attribute names to
# types
def attributes_for(schema:, version: 1)
definitions(schema, version).each_with_object({}) do |definition, hash|
hash[definition.name] = definition.type.meta(definition.config)
end
end

##
# @param [Symbol] schema
#
# @return [Hash{Symbol => Hash{Symbol => Object}}]
def form_definitions_for(schema:, version: 1)
definitions(schema, version).each_with_object({}) do |definition, hash|
next if definition.form_options.empty?

hash[definition.name] = definition.form_options
end
end

##
# @param [Symbol] schema
#
# @return [{Symbol => Symbol}] a map from index keys to attribute names
def index_rules_for(schema:, version: 1)
definitions(schema, version).each_with_object({}) do |definition, hash|
definition.index_keys.each do |key|
hash[key] = definition.name
end
end
end

##
# @api private
class AttributeDefinition
##
# @!attr_reader :config
# @return [Hash<String, Object>]
# @!attr_reader :name
# @return [#to_sym]
attr_reader :config, :name

##
# @param [#to_sym] name
# @param [Hash<String, Object>] config
def initialize(name, config)
@config = config
@name = name.to_sym
end

##
# @return [Hash{Symbol => Object}]
def form_options
config.fetch('form', {}).symbolize_keys
end

##
# @return [Enumerable<Symbol>]
def index_keys
config.fetch('indexing', []).map(&:to_sym)
end

##
# @return [Dry::Types::Type]
def type
collection_type = if config['multi_value']
Valkyrie::Types::Array.constructor { |v| Array(v).select(&:present?) }
else
Identity
end
collection_type.of(type_for(config['type']))
end

##
# @api private
#
# This class acts as a Valkyrie/Dry::Types collection with typed members,
# but instead of wrapping the given type with itself as the collection type
# (as in `Valkyrie::Types::Array.of(MyType)`), it returns the given type.
#
# @example
# Identity.of(Valkyrie::Types::String) # => Valkyrie::Types::String
#
class Identity
##
# @param [Dry::Types::Type]
# @return [Dry::Types::Type] the type passed in
def self.of(type)
type
end
end

private

##
# Maps a configuration string value to a `Valkyrie::Type`.
#
# @param [String]
# @return [Dry::Types::Type]
def type_for(type)
case type
when 'id'
Valkyrie::Types::ID
when 'uri'
Valkyrie::Types::URI
when 'date_time'
Valkyrie::Types::DateTime
else
"Valkyrie::Types::#{type.capitalize}".constantize
end
end
end

class UndefinedSchemaError < ArgumentError; end

private

##
# @param [#to_s] schema_name
# @return [Enumerable<AttributeDefinition]
def definitions(schema_name, version)
Hyrax::FlexibleSchema.find_by(version: version).attributes_for(schema_name).map do |name, config|
AttributeDefinition.new(name, config)
end
end
end
end
6 changes: 3 additions & 3 deletions app/services/hyrax/simple_schema_loader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class SimpleSchemaLoader
#
# @return [Hash<Symbol, Dry::Types::Type>] a map from attribute names to
# types
def attributes_for(schema:)
def attributes_for(schema:, version: 1)
definitions(schema).each_with_object({}) do |definition, hash|
hash[definition.name] = definition.type.meta(definition.config)
end
Expand All @@ -23,7 +23,7 @@ def attributes_for(schema:)
# @param [Symbol] schema
#
# @return [Hash{Symbol => Hash{Symbol => Object}}]
def form_definitions_for(schema:)
def form_definitions_for(schema:, version: 1)
definitions(schema).each_with_object({}) do |definition, hash|
next if definition.form_options.empty?

Expand All @@ -35,7 +35,7 @@ def form_definitions_for(schema:)
# @param [Symbol] schema
#
# @return [{Symbol => Symbol}] a map from index keys to attribute names
def index_rules_for(schema:)
def index_rules_for(schema:, version: 1)
definitions(schema).each_with_object({}) do |definition, hash|
definition.index_keys.each do |key|
hash[key] = definition.name
Expand Down
Loading

0 comments on commit 01b8ed1

Please sign in to comment.