Skip to content

Commit 01b8ed1

Browse files
committed
dymanic loading of the model with m3 profiles
1 parent 3d32fa7 commit 01b8ed1

File tree

12 files changed

+428
-12
lines changed

12 files changed

+428
-12
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
class CreateHyraxFlexibleSchemas < ActiveRecord::Migration[6.1]
2+
def change
3+
create_table :hyrax_flexible_schemas do |t|
4+
t.string :version, index: { unique: true }
5+
t.text :profile
6+
7+
t.timestamps
8+
end
9+
end
10+
end

.dassie/db/schema.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
#
1111
# It's strongly recommended that you check this file into your version control system.
1212

13-
ActiveRecord::Schema.define(version: 2024_05_06_070809) do
13+
ActiveRecord::Schema.define(version: 2024_06_06_205215) do
1414

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

175+
create_table "hyrax_flexible_schemas", force: :cascade do |t|
176+
t.string "version"
177+
t.text "profile"
178+
t.datetime "created_at", precision: 6, null: false
179+
t.datetime "updated_at", precision: 6, null: false
180+
end
181+
175182
create_table "job_io_wrappers", force: :cascade do |t|
176183
t.bigint "user_id"
177184
t.bigint "uploaded_file_id"

app/models/concerns/hyrax/flexibility.rb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
module Hyrax
44
module Flexibility
55
extend ActiveSupport::Concern
6+
included do
7+
attribute :schema_version, Valkyrie::Types::String
8+
end
9+
610
class_methods do
711
## Override dry-struct 1.6.0 to enable redefining schemas on the fly
812
def attributes(new_schema)
@@ -55,10 +59,10 @@ def new(attributes = default_attributes, safe = false, &block) # rubocop:disable
5559

5660
## Read the schema from the database and load the correct schemas for the instance in to the class
5761
def load(attributes, safe = false)
58-
attributes[:schemas] ||= 'core_metadata:1'
62+
attributes[:schema_version] ||= Hyrax::FlexibleSchema.order('created_at DESC').pick(:version)
5963
struct = allocate
60-
schema_name, schema_version = attributes[:schemas].split(':')
61-
struct.singleton_class.attributes(Hyrax::Schema(schema_name, schema_version:).attributes)
64+
schema_version = attributes[:schema_version]
65+
struct.singleton_class.attributes(Hyrax::Schema(self, schema_version:).attributes)
6266
clean_attributes = safe ? struct.singleton_class.schema.call_safe(attributes) { |output = attributes| return yield output } : struct.singleton_class.schema.call_unsafe(attributes)
6367
struct.__send__(:initialize, clean_attributes)
6468
struct

app/models/hyrax/flexible_schema.rb

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
class Hyrax::FlexibleSchema < ApplicationRecord
2+
serialize :profile, coder: YAML
3+
4+
def attributes_for(class_name)
5+
class_names[class_name]
6+
end
7+
8+
def class_names
9+
return @class_names if @class_names
10+
@class_names = {}
11+
profile['classes'].keys.each do |class_name|
12+
@class_names[class_name] = {}
13+
end
14+
profile['properties'].each do |key, value|
15+
value['available_on']['class'].each do |property_class|
16+
# map some m3 items to what Hyrax expects
17+
value['type'] = lookup_type(value['range'])
18+
value['predicate'] = value['property_uri']
19+
@class_names[property_class][key] = value
20+
end
21+
end
22+
@class_names
23+
end
24+
25+
def lookup_type(range)
26+
case range
27+
when "http://www.w3.org/2001/XMLSchema#dateTime"
28+
'date_time'
29+
else
30+
range.split('#').last.underscore
31+
end
32+
end
33+
end

app/models/hyrax/resource.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ class Resource < Valkyrie::Resource
3838
attribute :alternate_ids, Valkyrie::Types::Array.of(Valkyrie::Types::ID)
3939
attribute :embargo_id, Valkyrie::Types::Params::ID
4040
attribute :lease_id, Valkyrie::Types::Params::ID
41-
attribute :schemas, Valkyrie::Types::String
4241

4342
delegate :edit_groups, :edit_groups=,
4443
:edit_users, :edit_users=,
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
# frozen_string_literal: true
2+
3+
module Hyrax
4+
##
5+
# @api private
6+
#
7+
# Read m3 profiles from the database
8+
#
9+
# @see config/metadata/m3_profile.yaml for an example configuration
10+
class M3SchemaLoader
11+
##
12+
# @param [Symbol] schema
13+
#
14+
# @return [Hash<Symbol, Dry::Types::Type>] a map from attribute names to
15+
# types
16+
def attributes_for(schema:, version: 1)
17+
definitions(schema, version).each_with_object({}) do |definition, hash|
18+
hash[definition.name] = definition.type.meta(definition.config)
19+
end
20+
end
21+
22+
##
23+
# @param [Symbol] schema
24+
#
25+
# @return [Hash{Symbol => Hash{Symbol => Object}}]
26+
def form_definitions_for(schema:, version: 1)
27+
definitions(schema, version).each_with_object({}) do |definition, hash|
28+
next if definition.form_options.empty?
29+
30+
hash[definition.name] = definition.form_options
31+
end
32+
end
33+
34+
##
35+
# @param [Symbol] schema
36+
#
37+
# @return [{Symbol => Symbol}] a map from index keys to attribute names
38+
def index_rules_for(schema:, version: 1)
39+
definitions(schema, version).each_with_object({}) do |definition, hash|
40+
definition.index_keys.each do |key|
41+
hash[key] = definition.name
42+
end
43+
end
44+
end
45+
46+
##
47+
# @api private
48+
class AttributeDefinition
49+
##
50+
# @!attr_reader :config
51+
# @return [Hash<String, Object>]
52+
# @!attr_reader :name
53+
# @return [#to_sym]
54+
attr_reader :config, :name
55+
56+
##
57+
# @param [#to_sym] name
58+
# @param [Hash<String, Object>] config
59+
def initialize(name, config)
60+
@config = config
61+
@name = name.to_sym
62+
end
63+
64+
##
65+
# @return [Hash{Symbol => Object}]
66+
def form_options
67+
config.fetch('form', {}).symbolize_keys
68+
end
69+
70+
##
71+
# @return [Enumerable<Symbol>]
72+
def index_keys
73+
config.fetch('indexing', []).map(&:to_sym)
74+
end
75+
76+
##
77+
# @return [Dry::Types::Type]
78+
def type
79+
collection_type = if config['multi_value']
80+
Valkyrie::Types::Array.constructor { |v| Array(v).select(&:present?) }
81+
else
82+
Identity
83+
end
84+
collection_type.of(type_for(config['type']))
85+
end
86+
87+
##
88+
# @api private
89+
#
90+
# This class acts as a Valkyrie/Dry::Types collection with typed members,
91+
# but instead of wrapping the given type with itself as the collection type
92+
# (as in `Valkyrie::Types::Array.of(MyType)`), it returns the given type.
93+
#
94+
# @example
95+
# Identity.of(Valkyrie::Types::String) # => Valkyrie::Types::String
96+
#
97+
class Identity
98+
##
99+
# @param [Dry::Types::Type]
100+
# @return [Dry::Types::Type] the type passed in
101+
def self.of(type)
102+
type
103+
end
104+
end
105+
106+
private
107+
108+
##
109+
# Maps a configuration string value to a `Valkyrie::Type`.
110+
#
111+
# @param [String]
112+
# @return [Dry::Types::Type]
113+
def type_for(type)
114+
case type
115+
when 'id'
116+
Valkyrie::Types::ID
117+
when 'uri'
118+
Valkyrie::Types::URI
119+
when 'date_time'
120+
Valkyrie::Types::DateTime
121+
else
122+
"Valkyrie::Types::#{type.capitalize}".constantize
123+
end
124+
end
125+
end
126+
127+
class UndefinedSchemaError < ArgumentError; end
128+
129+
private
130+
131+
##
132+
# @param [#to_s] schema_name
133+
# @return [Enumerable<AttributeDefinition]
134+
def definitions(schema_name, version)
135+
Hyrax::FlexibleSchema.find_by(version: version).attributes_for(schema_name).map do |name, config|
136+
AttributeDefinition.new(name, config)
137+
end
138+
end
139+
end
140+
end

app/services/hyrax/simple_schema_loader.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class SimpleSchemaLoader
1313
#
1414
# @return [Hash<Symbol, Dry::Types::Type>] a map from attribute names to
1515
# types
16-
def attributes_for(schema:)
16+
def attributes_for(schema:, version: 1)
1717
definitions(schema).each_with_object({}) do |definition, hash|
1818
hash[definition.name] = definition.type.meta(definition.config)
1919
end
@@ -23,7 +23,7 @@ def attributes_for(schema:)
2323
# @param [Symbol] schema
2424
#
2525
# @return [Hash{Symbol => Hash{Symbol => Object}}]
26-
def form_definitions_for(schema:)
26+
def form_definitions_for(schema:, version: 1)
2727
definitions(schema).each_with_object({}) do |definition, hash|
2828
next if definition.form_options.empty?
2929

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

0 commit comments

Comments
 (0)