Skip to content

Commit

Permalink
refactor schema loader to reduce code duplication
Browse files Browse the repository at this point in the history
  • Loading branch information
orangewolf committed Jun 6, 2024
1 parent 01b8ed1 commit 7e06f18
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 244 deletions.
17 changes: 12 additions & 5 deletions app/models/hyrax/flexible_schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,24 @@ def 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|
profile['properties'].each do |key, values|
values['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
values = values_map(values)
@class_names[property_class][key] = values
end
end
@class_names
end

def values_map(values)
values['type'] = lookup_type(value['range'])
values['predicate'] = value['property_uri']
values['index_keys'] = values['indexing']
values['multiple'] = values['multi_value']
values
end

def lookup_type(range)
case range
when "http://www.w3.org/2001/XMLSchema#dateTime"
Expand Down
120 changes: 1 addition & 119 deletions app/services/hyrax/m3_schema_loader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,125 +7,7 @@ module Hyrax
# 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

class M3SchemaLoader < Hyrax::SchemaLoader
private

##
Expand Down
130 changes: 130 additions & 0 deletions app/services/hyrax/schema_loader.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# frozen_string_literal: true

module Hyrax
##
# @api private
#
# This is a simple yaml config-driven schema loader
#
# @see config/metadata/basic_metadata.yaml for an example configuration
class SchemaLoader
##
# @param [Symbol] schema
#
# @return [Hash<Symbol, Dry::Types::Type>] a map from attribute names to
# types
def attributes_for(schema:, version: 1)
definitions(schema).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).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).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('index_keys', []).map(&:to_sym)
end

##
# @return [Dry::Types::Type]
def type
collection_type = if config['multiple']
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

end
end
Loading

0 comments on commit 7e06f18

Please sign in to comment.