From dcd9d738d330aff8e9101ab45a135e5900ab4f56 Mon Sep 17 00:00:00 2001 From: Rob Kaufman Date: Mon, 7 Oct 2024 11:48:54 -0700 Subject: [PATCH] add contexts to the flexible metadata model. they can be assigned on admin sets and then apply the contexts to the works made with that admin set selected --- .../hyrax/works_controller_behavior.rb | 6 ++- .../concerns/hyrax/flexible_form_behavior.rb | 39 +++++++++++++++++++ .../hyrax/forms/administrative_set_form.rb | 2 +- app/forms/hyrax/forms/resource_form.rb | 35 ++--------------- app/helpers/hyrax/attributes_helper.rb | 2 +- app/models/concerns/hyrax/flexibility.rb | 9 ++++- .../concerns/hyrax/solr_document_behavior.rb | 6 ++- app/models/hyrax/file_set.rb | 1 + app/models/hyrax/flexible_schema.rb | 18 +++++++-- app/services/hyrax/m3_schema_loader.rb | 16 ++++++-- app/services/hyrax/schema_loader.rb | 14 +++---- app/services/hyrax/simple_schema_loader.rb | 4 +- .../admin/admin_sets/_form_metadata.html.erb | 3 ++ .../records/edit_fields/_contexts.html.erb | 1 + app/views/shared/_schema_version.html.erb | 2 +- config/metadata_profiles/m3_profile.yaml | 32 ++++++++++++++- ..._contexts_to_hyrax_flexible_schemas.rb.erb | 5 +++ lib/hyrax/form_fields.rb | 8 ++-- lib/hyrax/indexer.rb | 3 +- lib/hyrax/schema.rb | 7 ++-- 20 files changed, 150 insertions(+), 63 deletions(-) create mode 100644 app/forms/concerns/hyrax/flexible_form_behavior.rb create mode 100644 app/views/records/edit_fields/_contexts.html.erb create mode 100644 lib/generators/hyrax/templates/db/migrate/20240606205216_add_contexts_to_hyrax_flexible_schemas.rb.erb diff --git a/app/controllers/concerns/hyrax/works_controller_behavior.rb b/app/controllers/concerns/hyrax/works_controller_behavior.rb index bdeab896d5..cc9b00fc21 100644 --- a/app/controllers/concerns/hyrax/works_controller_behavior.rb +++ b/app/controllers/concerns/hyrax/works_controller_behavior.rb @@ -60,6 +60,7 @@ def new # TODO: move these lines to the work form builder in Hyrax curation_concern.depositor = current_user.user_key curation_concern.admin_set_id = params[:admin_set_id] || admin_set_id_for_new + curation_concern.contexts = Hyrax.query_service.find_by(id: curation_concern.admin_set_id)&.contexts build_form end @@ -178,7 +179,8 @@ def admin_set_id_for_new Hyrax::AdminSetCreateService.find_or_create_default_admin_set.id.to_s end - def build_form + def build_form(contexts: []) + curation_concern.contexts = contexts unless contexts.blank? @form = work_form_service.build(curation_concern, current_ability, self) end @@ -190,7 +192,7 @@ def actor # @return [#errors] # rubocop:disable Metrics/MethodLength def create_valkyrie_work - form = build_form + form = build_form(contexts: params[hash_key_for_curation_concern]['contexts']) action = create_valkyrie_work_action.new(form: form, transactions: transactions, user: current_user, diff --git a/app/forms/concerns/hyrax/flexible_form_behavior.rb b/app/forms/concerns/hyrax/flexible_form_behavior.rb new file mode 100644 index 0000000000..01255a7359 --- /dev/null +++ b/app/forms/concerns/hyrax/flexible_form_behavior.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true +module Hyrax + module FlexibleFormBehavior + extend ActiveSupport::Concern + + included do + include Hyrax::BasedNearFieldBehavior + property :contexts + end + + # OVERRIDE disposable 0.6.3 to make schema dynamic + def schema + if Hyrax.config.flexible? + Hyrax::Forms::ResourceForm::Definition::Each.new(singleton_class.schema_definitions) + else + super + end + end + + private + + # OVERRIDE valkyrie 3.0.1 to make schema dynamic + def field(field_name) + if Hyrax.config.flexible? + singleton_class.schema_definitions.fetch(field_name.to_s) + else + super + end + end + + def _form_field_definitions + if Hyrax.config.flexible? + singleton_class.schema_definitions + else + self.class.definitions + end + end + end +end diff --git a/app/forms/hyrax/forms/administrative_set_form.rb b/app/forms/hyrax/forms/administrative_set_form.rb index 2c96c1e960..50879a32de 100644 --- a/app/forms/hyrax/forms/administrative_set_form.rb +++ b/app/forms/hyrax/forms/administrative_set_form.rb @@ -22,7 +22,7 @@ class AdministrativeSetForm < Hyrax::Forms::ResourceForm property :title, required: true, primary: true property :description, primary: true - + property :contexts, primary: true if Hyrax.config.flexible? property :creator validates :title, presence: true diff --git a/app/forms/hyrax/forms/resource_form.rb b/app/forms/hyrax/forms/resource_form.rb index 1ad2a27dac..8431b4aceb 100644 --- a/app/forms/hyrax/forms/resource_form.rb +++ b/app/forms/hyrax/forms/resource_form.rb @@ -10,7 +10,7 @@ class ResourceForm < Hyrax::ChangeSet # rubocop:disable Metrics/ClassLength # These do not get auto loaded when using a flexible schema and should instead # be added to the individual Form classes for a work type or smart enough # to be selective as to when they trigger - include BasedNearFieldBehavior if Hyrax.config.flexible? + include FlexibleFormBehavior if Hyrax.config.flexible? ## # @api private @@ -56,8 +56,7 @@ def initialize(deprecated_resource = nil, resource: nil) # rubocop:disable Metri if Hyrax.config.flexible? singleton_class.schema_definitions = self.class.definitions r = resource || deprecated_resource - - Hyrax::Schema.default_schema_loader.form_definitions_for(schema: r.class.to_s, version: Hyrax::FlexibleSchema.current_schema_id).map do |field_name, options| + Hyrax::Schema.default_schema_loader.form_definitions_for(schema: r.class.to_s, version: Hyrax::FlexibleSchema.current_schema_id, contexts: r.contexts).map do |field_name, options| singleton_class.property field_name.to_sym, options.merge(display: options.fetch(:display, true), default: []) singleton_class.validates field_name.to_sym, presence: true if options.fetch(:required, false) end @@ -169,7 +168,7 @@ def primary_terms .select { |_, definition| definition[:primary] } .keys.map(&:to_sym) - terms = [:schema_version] + terms if Hyrax.config.flexible? + terms = [:schema_version, :contexts] + terms if Hyrax.config.flexible? terms end @@ -186,34 +185,6 @@ def secondary_terms def display_additional_fields? secondary_terms.any? end - - # OVERRIDE disposable 0.6.3 to make schema dynamic - def schema - if Hyrax.config.flexible? - Definition::Each.new(singleton_class.schema_definitions) - else - super - end - end - - private - - # OVERRIDE valkyrie 3.0.1 to make schema dynamic - def field(field_name) - if Hyrax.config.flexible? - singleton_class.schema_definitions.fetch(field_name.to_s) - else - super - end - end - - def _form_field_definitions - if Hyrax.config.flexible? - singleton_class.schema_definitions - else - self.class.definitions - end - end end end end diff --git a/app/helpers/hyrax/attributes_helper.rb b/app/helpers/hyrax/attributes_helper.rb index c139b4098d..796cebf216 100644 --- a/app/helpers/hyrax/attributes_helper.rb +++ b/app/helpers/hyrax/attributes_helper.rb @@ -6,7 +6,7 @@ def view_options_for(presenter) model_name = presenter.model.model_name.name if Hyrax.config.flexible? - Hyrax::Schema.default_schema_loader.view_definitions_for(schema: model_name, version: presenter.solr_document.schema_version) + Hyrax::Schema.default_schema_loader.view_definitions_for(schema: model_name, version: presenter.solr_document.schema_version, contexts: presenter.solr_document.contexts) else schema = model_name.constantize.schema || (model_name + 'Resource').safe_constantize.schema Hyrax::Schema.default_schema_loader.view_definitions_for(schema:) diff --git a/app/models/concerns/hyrax/flexibility.rb b/app/models/concerns/hyrax/flexibility.rb index 235b920adb..7580097094 100644 --- a/app/models/concerns/hyrax/flexibility.rb +++ b/app/models/concerns/hyrax/flexibility.rb @@ -5,6 +5,7 @@ module Flexibility extend ActiveSupport::Concern included do attribute :schema_version, Valkyrie::Types::String + attribute :contexts, Valkyrie::Types::Set.of(Valkyrie::Types::String) end class_methods do @@ -70,13 +71,19 @@ def load(attributes, safe = false) attributes[:schema_version] ||= Hyrax::FlexibleSchema.order('id DESC').pick(:id) struct = allocate schema_version = attributes[:schema_version] - struct.singleton_class.attributes(Hyrax::Schema(self, schema_version:).attributes) + contexts = attributes[:contexts] || [] + struct.singleton_class.attributes(Hyrax::Schema(self, schema_version:, contexts:).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 end end + def contexts=(value) + val = Array.wrap(value).map { |v| v.split }.flatten + @attributes[:contexts] = val + end + # Override set_value from valkyrie 3.1.1 to enable dynamic schema loading def set_value(key, value) @attributes[key.to_sym] = self.singleton_class.schema.key(key.to_sym).type.call(value) diff --git a/app/models/concerns/hyrax/solr_document_behavior.rb b/app/models/concerns/hyrax/solr_document_behavior.rb index 0acbfc01f5..337a0b924f 100644 --- a/app/models/concerns/hyrax/solr_document_behavior.rb +++ b/app/models/concerns/hyrax/solr_document_behavior.rb @@ -84,7 +84,7 @@ def valkyrie? def hydra_model(classifier: nil) model_name = first('has_model_ssim') valkyrie_model = "#{model_name}Resource".safe_constantize if Hyrax.config.valkyrie_transition - + valkyrie_model || model_name&.safe_constantize || model_classifier(classifier).classifier(self).best_model @@ -153,6 +153,10 @@ def schema_version self['schema_version_ssi'] end + def contexts + self['contexts_ssim'] + end + private def model_classifier(classifier) diff --git a/app/models/hyrax/file_set.rb b/app/models/hyrax/file_set.rb index 8c15a27579..2033e8c475 100644 --- a/app/models/hyrax/file_set.rb +++ b/app/models/hyrax/file_set.rb @@ -51,6 +51,7 @@ module Hyrax class FileSet < Hyrax::Resource include Hyrax::Schema(:core_metadata) unless Hyrax.config.flexible? include Hyrax::Schema(:file_set_metadata) unless Hyrax.config.flexible? + # TODO why isn't this automatic? include Hyrax::Schema('Hyrax::FileSet') if Hyrax.config.flexible? def self.model_name(name_class: Hyrax::Name) diff --git a/app/models/hyrax/flexible_schema.rb b/app/models/hyrax/flexible_schema.rb index 1fe3636e52..19ec615685 100644 --- a/app/models/hyrax/flexible_schema.rb +++ b/app/models/hyrax/flexible_schema.rb @@ -1,9 +1,12 @@ # frozen_string_literal: true class Hyrax::FlexibleSchema < ApplicationRecord serialize :profile, coder: YAML - + serialize :contexts, coder: YAML + validate :validate_profile_classes + before_save :update_contexts + def self.current_version order("created_at asc").last.profile end @@ -30,7 +33,11 @@ def self.default_properties rescue StandardError => e [] end - + + def update_contexts + self.contexts = profile['contexts'] + end + def title "#{profile['profile']['responsibility_statement']} - version #{id}" end @@ -43,6 +50,10 @@ def schema_version profile['m3_version'] end + def context_select + contexts.map { |k, v| [v&.[]('display_label'), k] } + end + def metadata_profile_type profile['profile']['type'] end @@ -61,7 +72,7 @@ def validate_profile_classes required_classes = [ Hyrax.config.collection_model, Hyrax.config.file_set_model, - Hyrax.config.admin_set_model + Hyrax.config.admin_set_model ] if profile['classes'].blank? @@ -97,6 +108,7 @@ def values_map(values) values['predicate'] = values['property_uri'] values['index_keys'] = values['indexing'] values['multiple'] = values['multi_value'] + values['context'] = values['available_on']['context'] values end diff --git a/app/services/hyrax/m3_schema_loader.rb b/app/services/hyrax/m3_schema_loader.rb index a38c4debda..1fc9c18734 100644 --- a/app/services/hyrax/m3_schema_loader.rb +++ b/app/services/hyrax/m3_schema_loader.rb @@ -8,8 +8,8 @@ module Hyrax # # @see config/metadata_profiles/m3_profile.yaml for an example configuration class M3SchemaLoader < Hyrax::SchemaLoader - def view_definitions_for(schema:, version: 1) - definitions(schema, version).each_with_object({}) do |definition, hash| + def view_definitions_for(schema:, version: 1, contexts: nil) + definitions(schema, version, contexts).each_with_object({}) do |definition, hash| next if definition.view_options.empty? hash[definition.name] = definition.view_options @@ -21,11 +21,19 @@ def view_definitions_for(schema:, version: 1) ## # @param [#to_s] schema_name # @return [Enumerable] a map from attribute names to # types - def attributes_for(schema:, version: 1) - definitions(schema, version).each_with_object({}) do |definition, hash| + def attributes_for(schema:, version: 1, contexts: nil) + definitions(schema, version, contexts).each_with_object({}) do |definition, hash| hash[definition.name] = definition.type.meta(definition.config) end end @@ -25,8 +25,8 @@ def attributes_for(schema:, version: 1) # @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| + def form_definitions_for(schema:, version: 1, contexts: nil) + definitions(schema, version, contexts).each_with_object({}) do |definition, hash| next if definition.form_options.empty? hash[definition.name] = definition.form_options @@ -37,8 +37,8 @@ def form_definitions_for(schema:, version: 1) # @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| + def index_rules_for(schema:, version: 1, contexts: nil) + definitions(schema, version, contexts).each_with_object({}) do |definition, hash| definition.index_keys.each do |key| hash[key] = definition.name end @@ -140,7 +140,7 @@ class UndefinedSchemaError < ArgumentError; end private - def definitions(_schema_name, _version) + def definitions(_schema_name, _version, _contexts) raise NotImplementedError, 'Implement #definitions in a child class' end end diff --git a/app/services/hyrax/simple_schema_loader.rb b/app/services/hyrax/simple_schema_loader.rb index 0b5851fcd0..6f89b345a2 100644 --- a/app/services/hyrax/simple_schema_loader.rb +++ b/app/services/hyrax/simple_schema_loader.rb @@ -8,7 +8,7 @@ module Hyrax # # @see config/metadata/basic_metadata.yaml for an example configuration class SimpleSchemaLoader < Hyrax::SchemaLoader - def view_definitions_for(schema:, _version: 1) + def view_definitions_for(schema:, version: 1, contexts: nil) schema.each_with_object({}) do |property, metadata| view_options = property.meta['view'] metadata[property.name.to_s] = view_options unless view_options.nil? @@ -26,7 +26,7 @@ def permissive_schema_for_valkrie_adapter ## # @param [#to_s] schema_name # @return [Enumerable <%= f.input :description, as: :text %> +<% if Hyrax.config.flexible? && Hyrax.config.admin_set_class.new.respond_to?(:contexts) %> + <%= f.input :contexts, collection: Hyrax::FlexibleSchema.last.context_select, input_html: { multiple: true } %> +<% end %> diff --git a/app/views/records/edit_fields/_contexts.html.erb b/app/views/records/edit_fields/_contexts.html.erb new file mode 100644 index 0000000000..c55b1ea33f --- /dev/null +++ b/app/views/records/edit_fields/_contexts.html.erb @@ -0,0 +1 @@ +<%= f.input key, as: :hidden %> diff --git a/app/views/shared/_schema_version.html.erb b/app/views/shared/_schema_version.html.erb index 870702b9bf..c9ec753291 100644 --- a/app/views/shared/_schema_version.html.erb +++ b/app/views/shared/_schema_version.html.erb @@ -1,6 +1,6 @@ <% schema_version = SolrDocument.find(f.object.id).schema_version if action_name == 'edit' %> <% schema_version = Hyrax::FlexibleSchema.current_schema_id if action_name == 'new' %> -<% updating = (schema_version.to_f < @latest_schema_version) ? 'updating' : '' %> +<% updating = (schema_version.to_f < @latest_schema_version.to_f) ? 'updating' : '' %>
<% if schema_version.present? %> diff --git a/config/metadata_profiles/m3_profile.yaml b/config/metadata_profiles/m3_profile.yaml index a0f7682a09..3599c4dd39 100644 --- a/config/metadata_profiles/m3_profile.yaml +++ b/config/metadata_profiles/m3_profile.yaml @@ -18,6 +18,8 @@ classes: contexts: flexible_context: display_label: Flexible Metadata Example + special_context: + display_label: Special Case Context mappings: blacklight: name: Additional Blacklight Solr Mappings @@ -803,4 +805,32 @@ properties: view: render_as: "faceted" html_dl: true - \ No newline at end of file + dimensions: + available_on: + class: + - Hyrax::Work + context: + - special_context + cardinality: + minimum: 0 + multi_value: true + controlled_values: + format: http://www.w3.org/2001/XMLSchema#string + sources: + - 'null' + display_label: + default: Dimensions + index_documentation: displayable, searchable + indexing: + - dimensions_sim + - dimensions_tesim + form: + primary: false + property_uri: http://purl.org/dc/terms/format + range: http://www.w3.org/2001/XMLSchema#string + sample_values: + - 50" x 20" + - 5 m x 10 m x 8 m + view: + render_as: "faceted" + html_dl: true diff --git a/lib/generators/hyrax/templates/db/migrate/20240606205216_add_contexts_to_hyrax_flexible_schemas.rb.erb b/lib/generators/hyrax/templates/db/migrate/20240606205216_add_contexts_to_hyrax_flexible_schemas.rb.erb new file mode 100644 index 0000000000..2e14b79330 --- /dev/null +++ b/lib/generators/hyrax/templates/db/migrate/20240606205216_add_contexts_to_hyrax_flexible_schemas.rb.erb @@ -0,0 +1,5 @@ +class AddContextsToHyraxFlexibleSchemas < ActiveRecord::Migration<%= migration_version %> + def change + add_column :hyrax_flexible_schemas, :contexts, :text + end +end diff --git a/lib/hyrax/form_fields.rb b/lib/hyrax/form_fields.rb index ba01a2acd9..bc9de03547 100644 --- a/lib/hyrax/form_fields.rb +++ b/lib/hyrax/form_fields.rb @@ -16,7 +16,7 @@ def self.FormFields(schema_name, **options) # # @see .FormFields class FormFields < Module - attr_reader :name + attr_reader :name, :version, :contexts ## # @api private @@ -25,15 +25,17 @@ class FormFields < Module # @param [#form_definitions_for] definition_loader # # @note use Hyrax::FormFields(:my_schema) instead - def initialize(schema_name, definition_loader: Hyrax::Schema.default_schema_loader) + def initialize(schema_name, definition_loader: Hyrax::Schema.default_schema_loader, version: 1, contexts:) @name = schema_name + @contexts = contexts + @version = version @definition_loader = definition_loader end ## # @return [Hash{Symbol => Hash{Symbol => Object}}] def form_field_definitions - @definition_loader.form_definitions_for(schema: name) + @definition_loader.form_definitions_for(schema: name, version:, contexts:) end ## diff --git a/lib/hyrax/indexer.rb b/lib/hyrax/indexer.rb index c1efaab1a2..8743ebc7f7 100644 --- a/lib/hyrax/indexer.rb +++ b/lib/hyrax/indexer.rb @@ -34,10 +34,11 @@ def initialize(rules) define_method :to_solr do |*args| super(*args).tap do |document| if Hyrax.config.flexible? - Hyrax::Schema.default_schema_loader.index_rules_for(schema: resource.class.to_s, version: resource.schema_version).each do |index_key, method| + Hyrax::Schema.default_schema_loader.index_rules_for(schema: resource.class.to_s, version: resource.schema_version, contexts: resource.contexts).each do |index_key, method| document[index_key] = resource.try(method) end document['schema_version_ssi'] = resource.schema_version + document['contexts_ssim'] = resource.contexts else rules.each do |index_key, method| document[index_key] = resource.try(method) diff --git a/lib/hyrax/schema.rb b/lib/hyrax/schema.rb index d1d73f3eb6..b9325b2cfa 100644 --- a/lib/hyrax/schema.rb +++ b/lib/hyrax/schema.rb @@ -56,7 +56,7 @@ class Schema < Module ## # @!attribute [r] name # @return [Symbol] - attr_reader :name, :version + attr_reader :name, :version, :contexts ## # Pick the default schema loader based on whether flex is on or not @@ -70,16 +70,17 @@ def self.default_schema_loader # @note use Hyrax::Schema(:my_schema) instead # # @api private - def initialize(schema_name, schema_loader: Hyrax::Schema.default_schema_loader, schema_version: '1') + def initialize(schema_name, schema_loader: Hyrax::Schema.default_schema_loader, schema_version: '1', contexts: []) @name = schema_name.to_s @version = schema_version @schema_loader = schema_loader + @contexts = contexts end ## # @return [Hash{Symbol => Dry::Types::Type}] def attributes - @schema_loader.attributes_for(schema: name, version:) + @schema_loader.attributes_for(schema: name, version:, contexts:) end ##