From 74853d611499176845b01657ccbd237fb560b622 Mon Sep 17 00:00:00 2001 From: Rob Kaufman Date: Wed, 3 Jun 2026 16:20:01 -0700 Subject: [PATCH 01/11] update profile to match dassie metadata classes --- .../config/metadata_profiles/m3_profile.yaml | 89 +++++++------------ documentation/flexible_metadata.md | 3 +- 2 files changed, 32 insertions(+), 60 deletions(-) diff --git a/.dassie/config/metadata_profiles/m3_profile.yaml b/.dassie/config/metadata_profiles/m3_profile.yaml index 758c7e3320..9efa2e09f3 100644 --- a/.dassie/config/metadata_profiles/m3_profile.yaml +++ b/.dassie/config/metadata_profiles/m3_profile.yaml @@ -7,13 +7,11 @@ profile: type: Initial Profile version: 1 classes: - Hyrax::Work: - display_label: Work GenericWorkResource: display_label: Work Monograph: display_label: Work - AdministrativeSetResource: + AdminSetResource: display_label: AdministrativeSet CollectionResource: display_label: PcdmCollection @@ -39,10 +37,9 @@ properties: title: available_on: class: - - AdministrativeSetResource + - AdminSetResource - Hyrax::FileSet - CollectionResource - - Hyrax::Work - Monograph - GenericWorkResource cardinality: @@ -77,10 +74,9 @@ properties: date_modified: available_on: class: - - AdministrativeSetResource + - AdminSetResource - Hyrax::FileSet - CollectionResource - - Hyrax::Work - Monograph - GenericWorkResource cardinality: @@ -105,10 +101,9 @@ properties: date_uploaded: available_on: class: - - AdministrativeSetResource + - AdminSetResource - Hyrax::FileSet - CollectionResource - - Hyrax::Work - Monograph - GenericWorkResource cardinality: @@ -124,10 +119,9 @@ properties: depositor: available_on: class: - - AdministrativeSetResource + - AdminSetResource - Hyrax::FileSet - CollectionResource - - Hyrax::Work - Monograph - GenericWorkResource cardinality: @@ -143,6 +137,7 @@ properties: index_documentation: searchable indexing: - depositor_tesim + - depositor_ssim property_uri: http://id.loc.gov/vocabulary/relators/dpt range: http://www.w3.org/2001/XMLSchema#string sample_values: @@ -152,7 +147,6 @@ properties: class: - Hyrax::FileSet - CollectionResource - - Hyrax::Work - Monograph - GenericWorkResource cardinality: @@ -183,7 +177,7 @@ properties: name: creator available_on: class: - - AdministrativeSetResource + - AdminSetResource cardinality: minimum: 0 data_type: array @@ -209,10 +203,9 @@ properties: license: available_on: class: - - AdministrativeSetResource + - AdminSetResource - Hyrax::FileSet - CollectionResource - - Hyrax::Work - Monograph - GenericWorkResource cardinality: @@ -240,10 +233,9 @@ properties: abstract: available_on: class: - - AdministrativeSetResource + - AdminSetResource - Hyrax::FileSet - CollectionResource - - Hyrax::Work - Monograph - GenericWorkResource cardinality: @@ -270,9 +262,8 @@ properties: access_right: available_on: class: - - AdministrativeSetResource + - AdminSetResource - CollectionResource - - Hyrax::Work - Monograph - GenericWorkResource cardinality: @@ -299,9 +290,8 @@ properties: alternative_title: available_on: class: - - AdministrativeSetResource + - AdminSetResource - CollectionResource - - Hyrax::Work - Monograph - GenericWorkResource cardinality: @@ -356,9 +346,8 @@ properties: arkivo_checksum: available_on: class: - - AdministrativeSetResource + - AdminSetResource - CollectionResource - - Hyrax::Work - Monograph - GenericWorkResource cardinality: @@ -380,10 +369,9 @@ properties: based_near: available_on: class: - - AdministrativeSetResource + - AdminSetResource - Hyrax::FileSet - CollectionResource - - Hyrax::Work - Monograph - GenericWorkResource cardinality: @@ -411,9 +399,8 @@ properties: bibliographic_citation: available_on: class: - - AdministrativeSetResource + - AdminSetResource - CollectionResource - - Hyrax::Work - Monograph - GenericWorkResource cardinality: @@ -440,10 +427,9 @@ properties: contributor: available_on: class: - - AdministrativeSetResource + - AdminSetResource - Hyrax::FileSet - CollectionResource - - Hyrax::Work - Monograph - GenericWorkResource cardinality: @@ -471,10 +457,9 @@ properties: date_created: available_on: class: - - AdministrativeSetResource + - AdminSetResource - Hyrax::FileSet - CollectionResource - - Hyrax::Work - Monograph - GenericWorkResource cardinality: @@ -503,10 +488,9 @@ properties: description: available_on: class: - - AdministrativeSetResource + - AdminSetResource - Hyrax::FileSet - CollectionResource - - Hyrax::Work - Monograph - GenericWorkResource cardinality: @@ -531,10 +515,9 @@ properties: identifier: available_on: class: - - AdministrativeSetResource + - AdminSetResource - Hyrax::FileSet - CollectionResource - - Hyrax::Work - Monograph - GenericWorkResource cardinality: @@ -563,9 +546,8 @@ properties: import_url: available_on: class: - - AdministrativeSetResource + - AdminSetResource - CollectionResource - - Hyrax::Work - Monograph - GenericWorkResource cardinality: @@ -585,10 +567,9 @@ properties: keyword: available_on: class: - - AdministrativeSetResource + - AdminSetResource - Hyrax::FileSet - CollectionResource - - Hyrax::Work - Monograph - GenericWorkResource cardinality: @@ -641,10 +622,9 @@ properties: language: available_on: class: - - AdministrativeSetResource + - AdminSetResource - Hyrax::FileSet - CollectionResource - - Hyrax::Work - Monograph - GenericWorkResource cardinality: @@ -673,10 +653,9 @@ properties: publisher: available_on: class: - - AdministrativeSetResource + - AdminSetResource - Hyrax::FileSet - CollectionResource - - Hyrax::Work - Monograph - GenericWorkResource cardinality: @@ -704,10 +683,9 @@ properties: related_url: available_on: class: - - AdministrativeSetResource + - AdminSetResource - Hyrax::FileSet - CollectionResource - - Hyrax::Work - Monograph - GenericWorkResource cardinality: @@ -735,10 +713,9 @@ properties: relative_path: available_on: class: - - AdministrativeSetResource + - AdminSetResource - Hyrax::FileSet - CollectionResource - - Hyrax::Work - Monograph - GenericWorkResource cardinality: @@ -758,10 +735,9 @@ properties: resource_type: available_on: class: - - AdministrativeSetResource + - AdminSetResource - Hyrax::FileSet - CollectionResource - - Hyrax::Work - Monograph - GenericWorkResource cardinality: @@ -790,10 +766,9 @@ properties: rights_notes: available_on: class: - - AdministrativeSetResource + - AdminSetResource - Hyrax::FileSet - CollectionResource - - Hyrax::Work - Monograph - GenericWorkResource cardinality: @@ -820,10 +795,9 @@ properties: rights_statement: available_on: class: - - AdministrativeSetResource + - AdminSetResource - Hyrax::FileSet - CollectionResource - - Hyrax::Work - Monograph - GenericWorkResource cardinality: @@ -851,10 +825,9 @@ properties: source: available_on: class: - - AdministrativeSetResource + - AdminSetResource - Hyrax::FileSet - CollectionResource - - Hyrax::Work - Monograph - GenericWorkResource cardinality: @@ -882,10 +855,9 @@ properties: subject: available_on: class: - - AdministrativeSetResource + - AdminSetResource - Hyrax::FileSet - CollectionResource - - Hyrax::Work - Monograph - GenericWorkResource cardinality: @@ -914,7 +886,6 @@ properties: dimensions: available_on: class: - - Hyrax::Work - Monograph - GenericWorkResource context: @@ -955,4 +926,4 @@ properties: display: true primary: false range: http://www.w3.org/2001/XMLSchema#string - property_uri: http://vocabulary.samvera.org/ns#transcriptIds \ No newline at end of file + property_uri: http://vocabulary.samvera.org/ns#transcriptIds diff --git a/documentation/flexible_metadata.md b/documentation/flexible_metadata.md index 12ee78e418..a980eedf61 100644 --- a/documentation/flexible_metadata.md +++ b/documentation/flexible_metadata.md @@ -29,8 +29,9 @@ HYRAX_DISABLE_INCLUDE_METADATA=true ### Dassie Flexible export HYRAX_FLEXIBLE=true -export HYRAX_FLEXIBLE_CLASSES=AdministrativeSetResource,CollectionResource,Hyrax::FileSet,GenericWorkResource,Monograph +export HYRAX_FLEXIBLE_CLASSES=AdminSetResource,CollectionResource,Hyrax::FileSet,GenericWorkResource,Monograph export HYRAX_DISABLE_INCLUDE_METADATA=true +export VALKYRIE_TRANSITION=true # this is needed to properly load Valkyrie models in Hyrax config and Bulkrax ## Documentation From 5663021d116a0df1a3a32ae83e301219464024d5 Mon Sep 17 00:00:00 2001 From: Rob Kaufman Date: Wed, 3 Jun 2026 16:20:35 -0700 Subject: [PATCH 02/11] fix bulkrax loading when valkyrie transition is on, fix possible missing wings require --- .dassie/config/initializers/bulkrax.rb | 2 +- app/models/concerns/hyrax/valkyrie_lazy_migration.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.dassie/config/initializers/bulkrax.rb b/.dassie/config/initializers/bulkrax.rb index a9d18c6b97..5e33064bc5 100644 --- a/.dassie/config/initializers/bulkrax.rb +++ b/.dassie/config/initializers/bulkrax.rb @@ -11,7 +11,7 @@ # config.default_work_type = "MyWork" # Factory Class to use when generating and saving objects - config.object_factory = Bulkrax::ObjectFactory + config.object_factory = Hyrax.config.valkyrie_transition? ? Bulkrax::ValkyrieObjectFactory : Bulkrax::ObjectFactory # Use this for a Postgres-backed Valkyrized Hyrax # config.object_factory = Bulkrax::ValkyrieObjectFactory diff --git a/app/models/concerns/hyrax/valkyrie_lazy_migration.rb b/app/models/concerns/hyrax/valkyrie_lazy_migration.rb index 929dfe9aed..9d07719b6e 100644 --- a/app/models/concerns/hyrax/valkyrie_lazy_migration.rb +++ b/app/models/concerns/hyrax/valkyrie_lazy_migration.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +require 'wings' module Hyrax ## From 157d405702233b5e11e87750bdd79c0527a49716 Mon Sep 17 00:00:00 2001 From: Rob Kaufman Date: Fri, 5 Jun 2026 11:21:32 -0700 Subject: [PATCH 03/11] rework wings loading order --- app/models/concerns/hyrax/valkyrie_lazy_migration.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/models/concerns/hyrax/valkyrie_lazy_migration.rb b/app/models/concerns/hyrax/valkyrie_lazy_migration.rb index 9d07719b6e..0877979da0 100644 --- a/app/models/concerns/hyrax/valkyrie_lazy_migration.rb +++ b/app/models/concerns/hyrax/valkyrie_lazy_migration.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require 'wings' module Hyrax ## @@ -51,7 +50,7 @@ def initialize(klass, *args) # Hyrax::ValkyrieLazyMigration.migrating(self, from: MyWork) # end def self.migrating(klass, from:, name_class: Hyrax::ValkyrieLazyMigration::ResourceName) - Wings::ModelRegistry.register(klass, from) + Wings::ModelRegistry.register(klass, from) if defined?(Wings::ModelRegistry) from.singleton_class.define_method(:migrating_from) { from } from.singleton_class.define_method(:migrating_to) { klass } klass.singleton_class.define_method(:migrating_from) { from } From a54d3f7abd4547e273409888de2b0321c89574ac Mon Sep 17 00:00:00 2001 From: LaRita Robinson Date: Fri, 5 Jun 2026 21:06:50 -0400 Subject: [PATCH 04/11] Rename compound "subfield" to "subproperty" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Compound (hierarchical) metadata now calls its nested members "subproperties" instead of "subfields" — the YAML key is `subproperties:`, and the supporting classes, methods, i18n keys, and CSS classes follow the same name. This is a vocabulary rename only; the declaration structure, the normalized internal shape, indexing, rendering, and validation are unchanged. It sets up a later change that expresses subproperties as composable, first-class properties. --- .../config/metadata_profiles/m3_profile.yaml | 8 +- .../javascripts/hyrax/compound_metadata.js | 2 +- .../stylesheets/hyrax/_compound_metadata.scss | 16 ++-- .../qa/authorities/compound_works.rb | 2 +- .../concerns/hyrax/compound_field_behavior.rb | 8 +- .../concerns/hyrax/flexible_form_behavior.rb | 6 +- app/forms/hyrax/forms/resource_form.rb | 2 +- app/helpers/hyrax/collections_helper.rb | 2 +- app/helpers/hyrax/compound_fields_helper.rb | 20 ++-- .../hyrax/indexers/compound_indexer.rb | 22 ++--- .../hyrax/indexers/pcdm_collection_indexer.rb | 2 +- .../hyrax/indexers/pcdm_object_indexer.rb | 2 +- .../concerns/hyrax/compound_normalization.rb | 2 +- app/presenters/hyrax/presents_attributes.rb | 12 +-- .../renderers/compound_attribute_renderer.rb | 54 +++++------ .../hyrax/compound_work_picker_builder.rb | 2 +- .../hyrax/compound_entry_validation.rb | 9 +- app/services/hyrax/compound_schema.rb | 41 ++++---- ...ler.rb => compound_subproperty_labeler.rb} | 12 +-- app/services/hyrax/compound_work_resolver.rb | 2 +- .../flexible_schema_validator_service.rb | 4 +- .../compound_validator.rb | 30 +++--- app/services/hyrax/schema_loader.rb | 2 +- .../hyrax/compound_entry_validator.rb | 7 +- .../hyrax/compounds/_compound_row.html.erb | 22 ++--- .../compounds/_compound_section.html.erb | 4 +- config/locales/hyrax.en.yml | 10 +- config/metadata/compound_metadata.yaml | 24 ++--- config/metadata_profiles/m3_profile.yaml | 12 +-- documentation/forms/compound_fields.md | 96 +++++++++---------- documentation/forms/field_behaviors.md | 12 +-- lib/hyrax/form_fields.rb | 8 +- .../hyrax/compound_field_behavior_spec.rb | 4 +- .../forms/compound_metadata_form_spec.rb | 12 +-- .../hyrax/compound_fields_helper_spec.rb | 26 ++--- .../hyrax/indexers/compound_indexer_spec.rb | 12 +-- .../hyrax/compound_normalization_spec.rb | 2 +- .../compound_attribute_renderer_spec.rb | 50 +++++----- .../hyrax/compound_entry_validation_spec.rb | 22 ++--- spec/services/hyrax/compound_schema_spec.rb | 62 ++++++------ ...b => compound_subproperty_labeler_spec.rb} | 4 +- .../compound_validator_spec.rb | 30 +++--- .../hyrax/compound_entry_validator_spec.rb | 10 +- 43 files changed, 347 insertions(+), 344 deletions(-) rename app/services/hyrax/{compound_subfield_labeler.rb => compound_subproperty_labeler.rb} (79%) rename spec/services/hyrax/{compound_subfield_labeler_spec.rb => compound_subproperty_labeler_spec.rb} (93%) diff --git a/.koppie/config/metadata_profiles/m3_profile.yaml b/.koppie/config/metadata_profiles/m3_profile.yaml index 083f7873d0..d8f58af69f 100644 --- a/.koppie/config/metadata_profiles/m3_profile.yaml +++ b/.koppie/config/metadata_profiles/m3_profile.yaml @@ -943,7 +943,7 @@ properties: form: primary: false property_uri: http://id.loc.gov/vocabulary/relators/asn - subfields: + subproperties: title: { type: string } agent_name: type: string @@ -972,7 +972,7 @@ properties: form: primary: false property_uri: http://purl.org/dc/terms/identifier - subfields: + subproperties: identifier: type: string required: true @@ -998,7 +998,7 @@ properties: form: primary: false property_uri: http://purl.org/dc/terms/rights - subfields: + subproperties: rights_statement: type: controlled authority: rights_statements @@ -1026,7 +1026,7 @@ properties: form: primary: false property_uri: http://purl.org/dc/terms/relation - subfields: + subproperties: related_item: type: work_or_url required: true diff --git a/app/assets/javascripts/hyrax/compound_metadata.js b/app/assets/javascripts/hyrax/compound_metadata.js index b5a7099de2..3f09bb7ed6 100644 --- a/app/assets/javascripts/hyrax/compound_metadata.js +++ b/app/assets/javascripts/hyrax/compound_metadata.js @@ -7,7 +7,7 @@ if (document.hyraxCompoundsBound) return; document.hyraxCompoundsBound = true; - // Bind select2 to a `work_or_url` sub-field. The v3 API (Hyrax bundles + // Bind select2 to a `work_or_url` sub-property. The v3 API (Hyrax bundles // select2-rails 3.x) binds to a hidden input; createSearchChoice lets a // typed external URL be selected as-is. function bindWorkOrUrlInputs(root) { diff --git a/app/assets/stylesheets/hyrax/_compound_metadata.scss b/app/assets/stylesheets/hyrax/_compound_metadata.scss index 15e54343b7..17e8c6fcee 100644 --- a/app/assets/stylesheets/hyrax/_compound_metadata.scss +++ b/app/assets/stylesheets/hyrax/_compound_metadata.scss @@ -12,17 +12,17 @@ padding: 0 !important; max-width: 100%; - // Force block flow on the entries/sub-fields; the label/value spans stay - // inline so each sub-field reads as "Label: value" on one wrapping line. + // Force block flow on the entries/sub-properties; the label/value spans stay + // inline so each sub-property reads as "Label: value" on one wrapping line. .hyrax-compound-entry, - .hyrax-compound-subfield { + .hyrax-compound-subproperty { display: block !important; } .hyrax-compound-entry, - .hyrax-compound-subfield, - .hyrax-compound-subfield-label, - .hyrax-compound-subfield-value { + .hyrax-compound-subproperty, + .hyrax-compound-subproperty-label, + .hyrax-compound-subproperty-value { border: 0 !important; padding: 0 !important; margin: 0; @@ -36,14 +36,14 @@ border-top: 1px solid $gray-200 !important; } - .hyrax-compound-subfield { + .hyrax-compound-subproperty { display: block; line-height: 1.4; overflow-wrap: break-word; word-break: break-word; } - .hyrax-compound-subfield-label { + .hyrax-compound-subproperty-label { font-weight: 600; margin-right: 0.25rem; } diff --git a/app/authorities/qa/authorities/compound_works.rb b/app/authorities/qa/authorities/compound_works.rb index 66e70cba84..3f84882291 100644 --- a/app/authorities/qa/authorities/compound_works.rb +++ b/app/authorities/qa/authorities/compound_works.rb @@ -2,7 +2,7 @@ module Qa::Authorities ## - # Autocomplete authority for the compound `work_or_url` sub-field's work + # Autocomplete authority for the compound `work_or_url` sub-property's work # picker, mounted at `/authorities/search/compound_works`. Returns readable # works matched by {Hyrax::CompoundWorkPickerBuilder}. class CompoundWorks < Qa::Authorities::Base diff --git a/app/forms/concerns/hyrax/compound_field_behavior.rb b/app/forms/concerns/hyrax/compound_field_behavior.rb index f5ca4bf3c0..1897f53ef6 100644 --- a/app/forms/concerns/hyrax/compound_field_behavior.rb +++ b/app/forms/concerns/hyrax/compound_field_behavior.rb @@ -53,13 +53,13 @@ def compound_field_names end # One populator serves every compound (Reform passes the property name as - # `as:`). Builds the replacement array of plain hashes — declared sub-field - # keys only, dropping `_destroy` and all-blank rows. + # `as:`). Builds the replacement array of plain hashes — declared + # sub-property keys only, dropping `_destroy` and all-blank rows. def compound_attributes_populator(fragment:, as:, **_options) name = as.to_s.delete_suffix('_attributes') return unless respond_to?(name) - allowed = Hyrax::CompoundSchema.for(model).subfield_keys(name) + allowed = Hyrax::CompoundSchema.for(model).subproperty_keys(name) public_send(:"#{name}=", build_compound_rows(fragment, allowed)) end @@ -75,7 +75,7 @@ def compound_fragment_pairs(fragment) fragment.respond_to?(:to_unsafe_h) ? fragment.to_unsafe_h : fragment.to_h end - # Returns nil for a row marked for destruction or whose declared sub-fields + # Returns nil for a row marked for destruction or whose declared sub-properties # are all blank, otherwise the persisted hash for that row. def compound_row_from(row, allowed_keys) row = row.respond_to?(:to_unsafe_h) ? row.to_unsafe_h : row.to_h diff --git a/app/forms/concerns/hyrax/flexible_form_behavior.rb b/app/forms/concerns/hyrax/flexible_form_behavior.rb index fa59896c4d..f0874b1a23 100644 --- a/app/forms/concerns/hyrax/flexible_form_behavior.rb +++ b/app/forms/concerns/hyrax/flexible_form_behavior.rb @@ -13,7 +13,7 @@ def validate_flexible_required_fields required_fields = singleton_class.schema_definitions.select { |_, opts| opts[:required] }.keys required_fields.each do |field| - # Compound fields carry their own requiredness rules (per-row sub-field + # Compound fields carry their own requiredness rules (per-row sub-property # checks); Hyrax::CompoundEntryValidator owns them, so skip the generic # blank check to avoid a duplicate "can't be blank" error. next if compound_field?(field) @@ -29,9 +29,9 @@ def schema private - # Whether the field is a compound (declares `subfields:`), looked up from + # Whether the field is a compound (declares `subproperties:`), looked up from # the resource's compound schema — the flex form definitions don't carry - # `subfields`. Memoized per form instance. + # `subproperties`. Memoized per form instance. def compound_field?(field) return false unless Hyrax.config.respond_to?(:compound_metadata_enabled?) && Hyrax.config.compound_metadata_enabled? @compound_field_names ||= Hyrax::CompoundSchema.for(model).compound_names diff --git a/app/forms/hyrax/forms/resource_form.rb b/app/forms/hyrax/forms/resource_form.rb index c2cbab8578..d0c2c4adff 100644 --- a/app/forms/hyrax/forms/resource_form.rb +++ b/app/forms/hyrax/forms/resource_form.rb @@ -64,7 +64,7 @@ class ResourceForm < Hyrax::ChangeSet # rubocop:disable Metrics/ClassLength end end - # Required-compound / required-sub-field validation. Wired through a + # Required-compound / required-sub-property validation. Wired through a # `validation { ... }` block (not a bare `validates_with`) because these # are Reform/Disposable forms — a bare `validates_with` does not hook into # Reform's `validate`, so it would never run. Record-level (no diff --git a/app/helpers/hyrax/collections_helper.rb b/app/helpers/hyrax/collections_helper.rb index 6fb60d1c79..70cfbc3307 100644 --- a/app/helpers/hyrax/collections_helper.rb +++ b/app/helpers/hyrax/collections_helper.rb @@ -49,7 +49,7 @@ def collection_metadata_value(collection, field) if collection.respond_to?(:compound_term?) && collection.compound_term?(field) definition = compound_schema_for(collection).definition_for(field) return Hyrax::Renderers::CompoundAttributeRenderer - .new(field, collection[field], subfields: definition&.fetch(:subfields, nil)) + .new(field, collection[field], subproperties: definition&.fetch(:subproperties, nil)) .render_value end diff --git a/app/helpers/hyrax/compound_fields_helper.rb b/app/helpers/hyrax/compound_fields_helper.rb index 6e8c5f0635..aac4e5722b 100644 --- a/app/helpers/hyrax/compound_fields_helper.rb +++ b/app/helpers/hyrax/compound_fields_helper.rb @@ -5,7 +5,7 @@ module Hyrax # and show pages. See documentation/forms/compound_fields.md. module CompoundFieldsHelper ## - # Renders one compound section (a repeatable stack of sub-field rows) for the + # Renders one compound section (a repeatable stack of sub-property rows) for the # given attribute via the `hyrax/compounds/*` partials. # # @return [String, nil] rendered HTML, or nil when the attribute is not a @@ -65,29 +65,29 @@ def compound_schema_for(presenter) end ## - # Options for a `controlled` sub-field's ``: an inline `values:` # list when present, otherwise the named QA authority. A stored value not # among the options is appended so it still renders (`include_current_value`). # # @return [Array] `[[label, id], ...]` - def compound_subfield_options(spec, current_value = nil) + def compound_subproperty_options(spec, current_value = nil) options = spec[:values].presence || authority_options(spec[:authority]) ensure_current_value(options, current_value) end ## # @return [Boolean] whether +current_value+ is present but not among the - # sub-field's offered options — i.e. a forced/stale value. The select + # sub-property's offered options — i.e. a forced/stale value. The select # gets the +force-select+ class in that case, matching the ordinary # controlled-field convention. - def compound_subfield_forced?(spec, current_value = nil) + def compound_subproperty_forced?(spec, current_value = nil) return false if current_value.blank? base = spec[:values].presence || authority_options(spec[:authority]) base.none? { |(_label, id)| id.to_s == current_value.to_s } end ## - # The pre-selected `[label, value]` option for a `work_or_url` sub-field's + # The pre-selected `[label, value]` option for a `work_or_url` sub-property's # select2, or nil when empty. An internal work id resolves to its title; an # external URL is shown as-is. # @@ -121,9 +121,9 @@ def compound_card_label(presenter, field) compound_field_label(field, display_label: compound_schema_for(presenter).definition_for(field)&.dig(:display_label)) end - def compound_subfield_label(compound_name, sub_field) - t("hyrax.compound_fields.#{compound_name}.#{sub_field}", - default: sub_field.to_s.humanize) + def compound_subproperty_label(compound_name, sub_property) + t("hyrax.compound_fields.#{compound_name}.#{sub_property}", + default: sub_property.to_s.humanize) end private @@ -135,7 +135,7 @@ def authority_options(authority_name) return [] if authority_name.blank? Hyrax::TolerantSelectService.new(authority_name).select_active_options rescue StandardError => e - Hyrax.logger.debug("compound_subfield_options: #{authority_name}: #{e.message}") + Hyrax.logger.debug("compound_subproperty_options: #{authority_name}: #{e.message}") [] end diff --git a/app/indexers/hyrax/indexers/compound_indexer.rb b/app/indexers/hyrax/indexers/compound_indexer.rb index 3983166396..33fd72e80f 100644 --- a/app/indexers/hyrax/indexers/compound_indexer.rb +++ b/app/indexers/hyrax/indexers/compound_indexer.rb @@ -3,11 +3,11 @@ module Hyrax module Indexers ## - # Indexer mixin that projects compound metadata sub-fields into Solr. For + # Indexer mixin that projects compound metadata sub-properties into Solr. For # every compound on the resource (see {Hyrax::CompoundSchema}), it writes - # each sub-field's declared `index_keys:`/`indexing:` Solr fields and stores - # the displayable rows as a `_json_ss` blob the show page renders - # from. See documentation/forms/compound_fields.md. + # each sub-property's declared `index_keys:`/`indexing:` Solr fields and + # stores the displayable rows as a `_json_ss` blob the show page + # renders from. See documentation/forms/compound_fields.md. # # @example # class WorkIndexer < Hyrax::Indexers::PcdmObjectIndexer @@ -31,15 +31,15 @@ def compound_schema def index_compound(document, compound_name, definition) rows = Array(resource.public_send(compound_name)) - index_searchable_subfields(document, definition, rows) + index_searchable_subproperties(document, definition, rows) index_display_blob(document, compound_name, definition, rows) end - def index_searchable_subfields(document, definition, rows) - definition[:subfields].each do |sub_field, spec| + def index_searchable_subproperties(document, definition, rows) + definition[:subproperties].each do |sub_property, spec| next if spec[:index_keys].blank? - values = rows.map { |row| compound_entry_value(row, sub_field) }.reject(&:blank?) + values = rows.map { |row| compound_entry_value(row, sub_property) }.reject(&:blank?) next if values.empty? spec[:index_keys].each { |index_key| document[index_key] = values } @@ -47,7 +47,7 @@ def index_searchable_subfields(document, definition, rows) end def index_display_blob(document, compound_name, definition, rows) - display_keys = definition[:subfields].select { |_k, spec| spec[:display] }.keys + display_keys = definition[:subproperties].select { |_k, spec| spec[:display] }.keys normalized = rows.map { |row| display_entry(row, display_keys) }.reject(&:empty?) document["#{compound_name}_json_ss"] = normalized.to_json unless normalized.empty? end @@ -60,9 +60,9 @@ def display_entry(row, display_keys) end end - def compound_entry_value(row, sub_field) + def compound_entry_value(row, sub_property) return nil unless row.respond_to?(:[]) - row[sub_field] || row[sub_field.to_sym] + row[sub_property] || row[sub_property.to_sym] end end end diff --git a/app/indexers/hyrax/indexers/pcdm_collection_indexer.rb b/app/indexers/hyrax/indexers/pcdm_collection_indexer.rb index d356fdb934..ca3daa6726 100644 --- a/app/indexers/hyrax/indexers/pcdm_collection_indexer.rb +++ b/app/indexers/hyrax/indexers/pcdm_collection_indexer.rb @@ -11,7 +11,7 @@ class PcdmCollectionIndexer < Hyrax::Indexers::ResourceIndexer include Hyrax::ThumbnailIndexer include Hyrax::Indexer(:core_metadata) if Hyrax.config.collection_include_metadata? include Hyrax::Indexers::RedirectsIndexer if Hyrax.config.redirects_enabled? - # Flatten compound (hierarchical) metadata sub-fields into Solr. No-op for + # Flatten compound (hierarchical) metadata sub-properties into Solr. No-op for # collections without compounds. See documentation/forms/compound_fields.md. include Hyrax::Indexers::CompoundIndexer if Hyrax.config.compound_metadata_enabled? check_if_flexible(Hyrax::PcdmCollection) diff --git a/app/indexers/hyrax/indexers/pcdm_object_indexer.rb b/app/indexers/hyrax/indexers/pcdm_object_indexer.rb index 58100c64bb..7cb547a646 100644 --- a/app/indexers/hyrax/indexers/pcdm_object_indexer.rb +++ b/app/indexers/hyrax/indexers/pcdm_object_indexer.rb @@ -11,7 +11,7 @@ class PcdmObjectIndexer < Hyrax::Indexers::ResourceIndexer include Hyrax::ThumbnailIndexer include Hyrax::WorkflowIndexer include Hyrax::Indexers::RedirectsIndexer if Hyrax.config.redirects_enabled? - # Flatten compound (hierarchical) metadata sub-fields into Solr. No-op for + # Flatten compound (hierarchical) metadata sub-properties into Solr. No-op for # resources without compounds. See documentation/forms/compound_fields.md. include Hyrax::Indexers::CompoundIndexer if Hyrax.config.compound_metadata_enabled? diff --git a/app/models/concerns/hyrax/compound_normalization.rb b/app/models/concerns/hyrax/compound_normalization.rb index 62f7fdf5af..1cec6b78fc 100644 --- a/app/models/concerns/hyrax/compound_normalization.rb +++ b/app/models/concerns/hyrax/compound_normalization.rb @@ -2,7 +2,7 @@ module Hyrax ## - # Defends compound attributes (`type: hash, multiple: true` with `subfields:` — + # Defends compound attributes (`type: hash, multiple: true` with `subproperties:` — # see {Hyrax::CompoundSchema}) against a read-path quirk in Valkyrie's Postgres # orm_converter: `JSONValueMapper` unwraps a single-element array to its first # element and re-symbolizes the Hash's keys, then dry-struct's `Array.of(Hash)` diff --git a/app/presenters/hyrax/presents_attributes.rb b/app/presenters/hyrax/presents_attributes.rb index 54925cb013..18f70b0db3 100644 --- a/app/presenters/hyrax/presents_attributes.rb +++ b/app/presenters/hyrax/presents_attributes.rb @@ -22,7 +22,7 @@ def attribute_to_html(field, options = {}) return end - options = options.merge(subfields: compound_subfields_for(field)) if options[:render_as].to_s == 'compound' + options = options.merge(subproperties: compound_subproperties_for(field)) if options[:render_as].to_s == 'compound' renderer = renderer_for(field, options).new(field, send(field), options) if options[:value_only] && renderer.respond_to?(:render_value) @@ -54,18 +54,18 @@ def microdata_type_to_html private - # Normalized sub-field specs for a compound, so the renderer can translate + # Normalized sub-property specs for a compound, so the renderer can translate # controlled ids to their terms; nil if the resource class can't be # resolved (the renderer then renders raw values). - def compound_subfields_for(field) + def compound_subproperties_for(field) return nil unless respond_to?(:solr_document) && solr_document.respond_to?(:hydra_model) # Resolve from the backing document, not the class: in flexible mode the - # class carries no compounds, so a class lookup would drop the sub-field + # class carries no compounds, so a class lookup would drop the sub-property # specs and the renderer would fall back to raw (unlinked, untranslated) # values. - Hyrax::CompoundSchema.for_solr_document(solr_document).definition_for(field)&.fetch(:subfields, nil) + Hyrax::CompoundSchema.for_solr_document(solr_document).definition_for(field)&.fetch(:subproperties, nil) rescue StandardError => e - Hyrax.logger.debug("compound_subfields_for(#{field}): #{e.message}") + Hyrax.logger.debug("compound_subproperties_for(#{field}): #{e.message}") nil end diff --git a/app/renderers/hyrax/renderers/compound_attribute_renderer.rb b/app/renderers/hyrax/renderers/compound_attribute_renderer.rb index d474ef5ca5..4807632b0a 100644 --- a/app/renderers/hyrax/renderers/compound_attribute_renderer.rb +++ b/app/renderers/hyrax/renderers/compound_attribute_renderer.rb @@ -5,9 +5,9 @@ module Renderers ## # Renders a compound metadata attribute on a show page (selected via # `view: { render_as: compound }`). Each value is one entry — a hash of - # sub-fields produced by the SolrDocument `compound_attribute` reader — and - # renders as a block of its populated sub-fields. Sub-field labels come from - # the `hyrax.compound_fields..` i18n keys. + # sub-properties produced by the SolrDocument `compound_attribute` reader — + # and renders as a block of its populated sub-properties. Sub-property labels + # come from the `hyrax.compound_fields..` i18n keys. class CompoundAttributeRenderer < AttributeRenderer def render return '' if blank_values? && !options[:include_empty] @@ -51,33 +51,33 @@ def entry_markup(entry) pairs = entry_to_pairs(entry) return '' if pairs.empty? - items = pairs.map { |sub_field, value| subfield_markup(sub_field, value) }.join + items = pairs.map { |sub_property, value| subproperty_markup(sub_property, value) }.join %(
#{items}
) end - def subfield_markup(sub_field, value) - label_html = ERB::Util.h(sub_field_label(sub_field)) - %(
) + - %(#{label_html}: ) + - %(#{value_markup(sub_field, value)}) + + def subproperty_markup(sub_property, value) + label_html = ERB::Util.h(sub_property_label(sub_property)) + %(
) + + %(#{label_html}: ) + + %(#{value_markup(sub_property, value)}) + %(
) end - # Display markup for one sub-field value, by sub-field type: `url` and + # Display markup for one sub-property value, by sub-property type: `url` and # `work_or_url` are linked; otherwise escaped text with controlled ids # translated to their term. - def value_markup(sub_field, value) - return ERB::Util.h(display_value(sub_field, value)) if value.blank? + def value_markup(sub_property, value) + return ERB::Util.h(display_value(sub_property, value)) if value.blank? - case subfield_spec(sub_field)&.dig(:type).to_s + case subproperty_spec(sub_property)&.dig(:type).to_s when 'url' auto_link(ERB::Util.h(value.to_s)) when 'work_or_url' work_or_url_markup(value) when 'controlled' - controlled_markup(sub_field, value) + controlled_markup(sub_property, value) else - ERB::Util.h(display_value(sub_field, value)) + ERB::Util.h(display_value(sub_property, value)) end end @@ -85,8 +85,8 @@ def value_markup(sub_field, value) # stored value is itself a linkable URI (e.g. a rights-statement or license # URI), link the term to that URI — mirroring the ordinary rights/license # renderer. Non-URI controlled values (e.g. inline option ids) stay plain. - def controlled_markup(sub_field, value) - label = display_value(sub_field, value) + def controlled_markup(sub_property, value) + label = display_value(sub_property, value) if Hyrax::AuthorityRenderingHelper.linkable_uri?(value) %(#{ERB::Util.h(label)}).html_safe else @@ -104,19 +104,19 @@ def work_or_url_markup(value) link_to(ERB::Util.h(title), path) end - def display_value(sub_field, value) - Hyrax::CompoundSubfieldLabeler.label_for(subfield_spec(sub_field), value) + def display_value(sub_property, value) + Hyrax::CompoundSubpropertyLabeler.label_for(subproperty_spec(sub_property), value) end - # The normalized sub-field spec, supplied by the caller via - # `options[:subfields]`. - def subfield_spec(sub_field) - specs = options[:subfields] + # The normalized sub-property spec, supplied by the caller via + # `options[:subproperties]`. + def subproperty_spec(sub_property) + specs = options[:subproperties] return nil unless specs.is_a?(Hash) - specs[sub_field] || specs[sub_field.to_sym] + specs[sub_property] || specs[sub_property.to_sym] end - # Populated [sub_field, value] pairs for one entry; blanks dropped. + # Populated [sub_property, value] pairs for one entry; blanks dropped. def entry_to_pairs(entry) return [] unless entry.respond_to?(:each_pair) || entry.is_a?(::Hash) entry.to_h.each_with_object([]) do |(key, value), memo| @@ -125,8 +125,8 @@ def entry_to_pairs(entry) end end - def sub_field_label(sub_field) - I18n.t("hyrax.compound_fields.#{field}.#{sub_field}", default: sub_field.to_s.humanize) + def sub_property_label(sub_property) + I18n.t("hyrax.compound_fields.#{field}.#{sub_property}", default: sub_property.to_s.humanize) end end end diff --git a/app/search_builders/hyrax/compound_work_picker_builder.rb b/app/search_builders/hyrax/compound_work_picker_builder.rb index 78299d3f50..7c215df118 100644 --- a/app/search_builders/hyrax/compound_work_picker_builder.rb +++ b/app/search_builders/hyrax/compound_work_picker_builder.rb @@ -2,7 +2,7 @@ module Hyrax ## - # Search builder for the compound `work_or_url` sub-field's work picker. Finds + # Search builder for the compound `work_or_url` sub-property's work picker. Finds # works the current user can read (it subclasses {Hyrax::SearchBuilder}, so # permission filtering is retained), matching any indexed query term OR a # partial/prefix title. diff --git a/app/services/hyrax/compound_entry_validation.rb b/app/services/hyrax/compound_entry_validation.rb index b8d5d04e26..737ebe906a 100644 --- a/app/services/hyrax/compound_entry_validation.rb +++ b/app/services/hyrax/compound_entry_validation.rb @@ -11,7 +11,8 @@ module Hyrax # # Rules (driven by the normalized definition from {Hyrax::CompoundSchema}): # * a compound marked `required` must have at least one populated row; - # * every populated row must fill all of the compound's `required` sub-fields. + # * every populated row must fill all of the compound's `required` + # sub-properties. # # Rows are the post-populator persisted hashes (all-blank rows already # dropped), so a no-required compound with no rows is valid. @@ -25,11 +26,11 @@ def initialize(definition, entries) # @return [Array] one violation per problem, each # `{ type:, missing: [keys] }`. Empty when the compound is valid. - # `type` is `:required_but_empty` or `:missing_required_subfields`. + # `type` is `:required_but_empty` or `:missing_required_subproperties`. def violations return [{ type: :required_but_empty, missing: required_keys }] if required_but_empty? - rows_missing_required.map { |missing| { type: :missing_required_subfields, missing: missing } } + rows_missing_required.map { |missing| { type: :missing_required_subproperties, missing: missing } } end # @return [Boolean] @@ -42,7 +43,7 @@ def valid? attr_reader :definition, :entries def required_keys - definition.fetch(:subfields, {}).select { |_k, spec| spec[:required] }.keys + definition.fetch(:subproperties, {}).select { |_k, spec| spec[:required] }.keys end def required_but_empty? diff --git a/app/services/hyrax/compound_schema.rb b/app/services/hyrax/compound_schema.rb index d45fe1ece6..51a63ea8c4 100644 --- a/app/services/hyrax/compound_schema.rb +++ b/app/services/hyrax/compound_schema.rb @@ -5,9 +5,10 @@ module Hyrax # @api public # # Reads the compound metadata declarations off a resource's schema. A compound - # is a `type: hash, multiple: true` attribute carrying a `subfields:` map; the - # declaration drives the generic form, indexer, and renderer so a hierarchical - # field can be defined in YAML alone. See documentation/forms/compound_fields.md. + # is a `type: hash, multiple: true` attribute carrying a `subproperties:` map; + # the declaration drives the generic form, indexer, and renderer so a + # hierarchical field can be defined in YAML alone. See + # documentation/forms/compound_fields.md. # # Declarations are read from each attribute's Dry type `meta`, which both # schema loaders populate identically, so the result is the same in both flex @@ -86,7 +87,7 @@ def initialize(*schema_sources) ## # @return [Boolean] whether the given attribute is a compound (declares - # `subfields:`) + # `subproperties:`) def compound?(attribute_name) definitions.key?(attribute_name.to_sym) end @@ -122,8 +123,8 @@ def card?(attribute_name) # @param [#to_sym] attribute_name # # @return [Hash{Symbol => Object}, nil] the compound's declaration: - # `{ subfields:, groups:, index_subfields: }`, or nil when the attribute - # is not a compound. + # `{ subproperties:, groups: }`, or nil when the attribute is not a + # compound. def definition_for(attribute_name) definitions[attribute_name.to_sym] end @@ -131,12 +132,12 @@ def definition_for(attribute_name) ## # @param [#to_sym] attribute_name # - # @return [Array] the ordered sub-field keys declared for the + # @return [Array] the ordered sub-property keys declared for the # compound (empty when not a compound). - def subfield_keys(attribute_name) + def subproperty_keys(attribute_name) definition = definition_for(attribute_name) return [] unless definition - definition[:subfields].keys + definition[:subproperties].keys end ## @@ -147,12 +148,12 @@ def required?(attribute_name) end ## - # @return [Array] the sub-field keys declared `required: true` for + # @return [Array] the sub-property keys declared `required: true` for # the compound (each must be filled in every populated row). - def required_subfield_keys(attribute_name) + def required_subproperty_keys(attribute_name) definition = definition_for(attribute_name) return [] unless definition - definition[:subfields].select { |_key, spec| spec[:required] }.keys + definition[:subproperties].select { |_key, spec| spec[:required] }.keys end ## @@ -170,10 +171,10 @@ def build_definitions next if meta.nil? || memo.key?(name) config = meta.with_indifferent_access - subfields = config['subfields'] - next if subfields.blank? + subproperties = config['subproperties'] + next if subproperties.blank? - memo[name] = normalize(config, subfields) + memo[name] = normalize(config, subproperties) end end end @@ -193,14 +194,14 @@ def name_meta_pairs(schema) # Normalizes the raw declaration into the symbol-keyed shape the form, # indexer, and renderer consume. See documentation/forms/compound_fields.md - # for the meaning of each sub-field key. - def normalize(config, subfields) - sub = subfields.each_with_object({}) { |(key, opts), memo| memo[key.to_s] = normalize_subfield(opts) } + # for the meaning of each sub-property key. + def normalize(config, subproperties) + sub = subproperties.each_with_object({}) { |(key, opts), memo| memo[key.to_s] = normalize_subproperty(opts) } view = config['view'] display_mode = view.is_a?(Hash) && view['display'].to_s == 'card' ? :card : :inline - { subfields: sub, + { subproperties: sub, groups: normalize_groups(config['groups'], sub.keys), display_mode: display_mode, required: compound_required?(config), @@ -215,7 +216,7 @@ def normalize_display_label(config) raw.is_a?(Hash) ? raw.with_indifferent_access : { default: raw.to_s }.with_indifferent_access end - def normalize_subfield(opts) + def normalize_subproperty(opts) opts = (opts.is_a?(Hash) ? opts : {}).with_indifferent_access { type: (opts['type'] || 'string').to_s, authority: opts['authority']&.to_s, diff --git a/app/services/hyrax/compound_subfield_labeler.rb b/app/services/hyrax/compound_subproperty_labeler.rb similarity index 79% rename from app/services/hyrax/compound_subfield_labeler.rb rename to app/services/hyrax/compound_subproperty_labeler.rb index 4d0e2d4663..caed10e306 100644 --- a/app/services/hyrax/compound_subfield_labeler.rb +++ b/app/services/hyrax/compound_subproperty_labeler.rb @@ -4,14 +4,14 @@ module Hyrax ## # @api public # - # Resolves a `controlled` compound sub-field's stored id to its display term - # (via inline `values:` or a QA `authority:`). Non-controlled sub-fields and + # Resolves a `controlled` compound sub-property's stored id to its display term + # (via inline `values:` or a QA `authority:`). Non-controlled sub-properties and # ids with no matching term fall back to the value itself. - class CompoundSubfieldLabeler + class CompoundSubpropertyLabeler ## - # @param spec [Hash, nil] the normalized sub-field spec + # @param spec [Hash, nil] the normalized sub-property spec # (`{ type:, authority:, values: }`) from {Hyrax::CompoundSchema} - # @param value [Object] the stored value (id for controlled sub-fields) + # @param value [Object] the stored value (id for controlled sub-properties) # # @return [String] the term to display def self.label_for(spec, value) @@ -35,7 +35,7 @@ def self.label_from_values(values, value) def self.label_from_authority(authority_name, value) Hyrax::TolerantSelectService.new(authority_name).label(value.to_s) { value.to_s } rescue StandardError => e - Hyrax.logger.debug("CompoundSubfieldLabeler: #{authority_name}: #{e.message}") + Hyrax.logger.debug("CompoundSubpropertyLabeler: #{authority_name}: #{e.message}") value.to_s end private_class_method :label_from_authority diff --git a/app/services/hyrax/compound_work_resolver.rb b/app/services/hyrax/compound_work_resolver.rb index f25235b94f..b4cdec0a18 100644 --- a/app/services/hyrax/compound_work_resolver.rb +++ b/app/services/hyrax/compound_work_resolver.rb @@ -4,7 +4,7 @@ module Hyrax ## # @api public # - # Helpers for the `work_or_url` compound sub-field, whose stored value is + # Helpers for the `work_or_url` compound sub-property, whose stored value is # either an external URL or an internal work id. Distinguishes the two and # resolves an internal work id to a display title (from Solr) and a show path. class CompoundWorkResolver diff --git a/app/services/hyrax/flexible_schema_validator_service.rb b/app/services/hyrax/flexible_schema_validator_service.rb index 45db289b9c..f79a3c434d 100644 --- a/app/services/hyrax/flexible_schema_validator_service.rb +++ b/app/services/hyrax/flexible_schema_validator_service.rb @@ -134,8 +134,8 @@ def validate_redirects end # Validates compound (hierarchical) metadata properties — those declaring - # `subfields:` — for well-formed sub-fields and correct (per-sub-field) - # indexing declaration. + # `subproperties:` — for well-formed sub-properties and correct + # (per-sub-property) indexing declaration. # # @return [void] def validate_compound diff --git a/app/services/hyrax/flexible_schema_validators/compound_validator.rb b/app/services/hyrax/flexible_schema_validators/compound_validator.rb index e2bda68a32..0089f2de06 100644 --- a/app/services/hyrax/flexible_schema_validators/compound_validator.rb +++ b/app/services/hyrax/flexible_schema_validators/compound_validator.rb @@ -6,9 +6,9 @@ module FlexibleSchemaValidators # @api private # # Validates compound metadata properties (a `type: hash` property declaring - # `subfields:` — see {Hyrax::CompoundSchema}) in an m3 profile at save time, - # so a misconfiguration fails with a clear message instead of producing dead - # Solr fields or unrenderable values. See + # `subproperties:` — see {Hyrax::CompoundSchema}) in an m3 profile at save + # time, so a misconfiguration fails with a clear message instead of producing + # dead Solr fields or unrenderable values. See # documentation/forms/compound_fields.md for the rules. class CompoundValidator ## @@ -22,44 +22,44 @@ def initialize(profile:, errors:) # @return [void] def validate! compound_properties.each do |name, config| - validate_subfields(name, config['subfields']) + validate_subproperties(name, config['subproperties']) validate_no_top_level_indexing(name, config) end end private - # Compounds are detected by `subfields:` presence (not `type`), so other - # hash fields like redirects stay out of scope. + # Compounds are detected by `subproperties:` presence (not `type`), so + # other hash fields like redirects stay out of scope. def compound_properties (@profile&.dig('properties') || {}).select do |_name, config| - config.is_a?(Hash) && config['subfields'].present? + config.is_a?(Hash) && config['subproperties'].present? end end - def validate_subfields(name, subfields) - unless subfields.is_a?(Hash) - @errors << t('subfields_not_hash', property: name) + def validate_subproperties(name, subproperties) + unless subproperties.is_a?(Hash) + @errors << t('subproperties_not_hash', property: name) return end - subfields.each { |sub_name, sub_config| validate_subfield(name, sub_name, sub_config) } + subproperties.each { |sub_name, sub_config| validate_subproperty(name, sub_name, sub_config) } end - def validate_subfield(name, sub_name, sub_config) + def validate_subproperty(name, sub_name, sub_config) unless sub_config.is_a?(Hash) - @errors << t('subfield_not_hash', property: name, subfield: sub_name, actual: sub_config.class.to_s) + @errors << t('subproperty_not_hash', property: name, subproperty: sub_name, actual: sub_config.class.to_s) return end return unless sub_config['type'].to_s == 'controlled' return if sub_config['authority'].present? || sub_config['values'].present? - @errors << t('controlled_without_source', property: name, subfield: sub_name) + @errors << t('controlled_without_source', property: name, subproperty: sub_name) end # A top-level `indexing:` would point the catalog at a `_tesim` - # field the indexer never writes; indexing is per sub-field. + # field the indexer never writes; indexing is per sub-property. def validate_no_top_level_indexing(name, config) return if config['indexing'].blank? @errors << t('top_level_indexing', property: name) diff --git a/app/services/hyrax/schema_loader.rb b/app/services/hyrax/schema_loader.rb index a23deb946c..9641f1a115 100644 --- a/app/services/hyrax/schema_loader.rb +++ b/app/services/hyrax/schema_loader.rb @@ -162,7 +162,7 @@ def self.of(type) # # Recognized values: # - `id`, `uri`, `date_time` — Valkyrie type shortcuts. - # - `hash` — for attributes whose entries carry multiple sub-fields + # - `hash` — for attributes whose entries carry multiple sub-properties # (e.g. redirects, with path / canonical / sequence). Use this # instead of nesting a Valkyrie::Resource. See # `documentation/redirects.md` for a worked example. diff --git a/app/validators/hyrax/compound_entry_validator.rb b/app/validators/hyrax/compound_entry_validator.rb index eea7da423e..5b47afc1da 100644 --- a/app/validators/hyrax/compound_entry_validator.rb +++ b/app/validators/hyrax/compound_entry_validator.rb @@ -3,7 +3,8 @@ module Hyrax # Validates every compound (hierarchical) metadata attribute on a form, # blocking save when a required compound has no row or a populated row omits a - # required sub-field. Adds one error per compound, keyed on the compound name. + # required sub-property. Adds one error per compound, keyed on the compound + # name. # # Record-level (not an EachValidator) because the compound set is schema-driven # and not known at form-class-definition time. The per-compound rules live in @@ -49,14 +50,14 @@ def validate_compound(record, name, definition) def message_for(name, violation) I18n.t("hyrax.compound_fields.errors.#{violation[:type]}", compound: compound_label(name), - fields: subfield_labels(name, violation[:missing])) + fields: subproperty_labels(name, violation[:missing])) end def compound_label(name) I18n.t("hyrax.compound_fields.#{name}.label", default: name.to_s.humanize) end - def subfield_labels(name, keys) + def subproperty_labels(name, keys) Array(keys).map do |key| I18n.t("hyrax.compound_fields.#{name}.#{key}", default: key.to_s.humanize) end.join(', ') diff --git a/app/views/hyrax/compounds/_compound_row.html.erb b/app/views/hyrax/compounds/_compound_row.html.erb index a5ea6cd834..c3bdf0bd3c 100644 --- a/app/views/hyrax/compounds/_compound_row.html.erb +++ b/app/views/hyrax/compounds/_compound_row.html.erb @@ -3,17 +3,17 @@ Locals: f - form builder for the parent resource compound_name - Symbol, e.g. :contributors - definition - Hash{ subfields:, groups:, index_subfields: } + definition - Hash{ subproperties:, groups: } row - Hash of persisted values, or nil for a new row index - Integer position, or the literal "__INDEX__" placeholder when rendered inside the JS template row_label_singular - "Contributor", "License", etc. - Each sub-field renders according to its declared `type:` (string, + Each sub-property renders according to its declared `type:` (string, controlled, url, work_or_url). db_table / geocode are future types; the `else` branch is the extension seam. %> -<% subfields = definition[:subfields] %> +<% subproperties = definition[:subproperties] %> <% groups = definition[:groups] %> <% row_position = index.is_a?(String) ? '' : (index.to_i + 1).to_s %> <% row_value = ->(key) { row.is_a?(Hash) ? (row[key] || row[key.to_sym]) : nil } %> @@ -34,22 +34,22 @@

<%= group[:label] %>

<% end %>
- <% group[:fields].each do |sub_field| %> - <% spec = subfields[sub_field] || { type: 'string' } %> - <% input_id = "#{f.object_name}_#{compound_name}_attributes_#{index}_#{sub_field}" %> - <% input_name = "#{f.object_name}[#{compound_name}_attributes][#{index}][#{sub_field}]" %> - <% value = row_value.call(sub_field) %> + <% group[:fields].each do |sub_property| %> + <% spec = subproperties[sub_property] || { type: 'string' } %> + <% input_id = "#{f.object_name}_#{compound_name}_attributes_#{index}_#{sub_property}" %> + <% input_name = "#{f.object_name}[#{compound_name}_attributes][#{index}][#{sub_property}]" %> + <% value = row_value.call(sub_property) %>
<%= label_tag input_id, class: 'form-label small text-muted' do %> - <%= compound_subfield_label(compound_name, sub_field) %><% + <%= compound_subproperty_label(compound_name, sub_property) %><% if spec[:required] %>*<% end %> <% end %> <% case spec[:type] %> <% when 'controlled' %> <% select_classes = ['form-control', 'form-control-sm'] %> - <% select_classes << 'force-select' if compound_subfield_forced?(spec, value) %> + <% select_classes << 'force-select' if compound_subproperty_forced?(spec, value) %> <%= select_tag input_name, - options_for_select(compound_subfield_options(spec, value), value), + options_for_select(compound_subproperty_options(spec, value), value), id: input_id, include_blank: true, class: select_classes.join(' ') %> diff --git a/app/views/hyrax/compounds/_compound_section.html.erb b/app/views/hyrax/compounds/_compound_section.html.erb index 0eec2654dd..bd80b032f7 100644 --- a/app/views/hyrax/compounds/_compound_section.html.erb +++ b/app/views/hyrax/compounds/_compound_section.html.erb @@ -5,10 +5,10 @@ Locals: f - form builder for the parent resource compound_name - Symbol, e.g. :contributors - definition - Hash{ subfields:, groups: } from CompoundSchema#definition_for + definition - Hash{ subproperties:, groups: } from CompoundSchema#definition_for display_label - String shown as the section heading - Form params: [_attributes][][] + Form params: [_attributes][][] %> <%# Read rows from the form when it exposes the compound reader, else the model. %> <% source = f.object.respond_to?(compound_name) ? f.object : f.object.model %> diff --git a/config/locales/hyrax.en.yml b/config/locales/hyrax.en.yml index 14f6d5498e..98d9f54139 100644 --- a/config/locales/hyrax.en.yml +++ b/config/locales/hyrax.en.yml @@ -864,7 +864,7 @@ en: remove_row_with_label: Remove %{label} errors: required_but_empty: "%{compound} requires at least one entry" - missing_required_subfields: "Each %{compound} entry requires: %{fields}" + missing_required_subproperties: "Each %{compound} entry requires: %{fields}" participants: label: Participants title: Title @@ -1417,10 +1417,10 @@ en: flexible_schema_validators: compound_validator: errors: - subfields_not_hash: "m3 profile compound property `%{property}` must declare `subfields` as a mapping of sub-field name to its configuration" - subfield_not_hash: "m3 profile compound property `%{property}` sub-field `%{subfield}` must be a mapping (got `%{actual}`)" - controlled_without_source: "m3 profile compound property `%{property}` sub-field `%{subfield}` is `type: controlled` but declares neither `authority` nor `values`" - top_level_indexing: "m3 profile compound property `%{property}` must not declare top-level `indexing`; declare `indexing` (or `index_keys`) on each sub-field instead" + subproperties_not_hash: "m3 profile compound property `%{property}` must declare `subproperties` as a mapping of sub-property name to its configuration" + subproperty_not_hash: "m3 profile compound property `%{property}` sub-property `%{subproperty}` must be a mapping (got `%{actual}`)" + controlled_without_source: "m3 profile compound property `%{property}` sub-property `%{subproperty}` is `type: controlled` but declares neither `authority` nor `values`" + top_level_indexing: "m3 profile compound property `%{property}` must not declare top-level `indexing`; declare `indexing` (or `index_keys`) on each sub-property instead" redirects_validator: errors: invalid_available_on: "m3 profile `redirects` property must be available on at least one work or collection class declared in this profile" diff --git a/config/metadata/compound_metadata.yaml b/config/metadata/compound_metadata.yaml index c62dc16793..5f091cf1d8 100644 --- a/config/metadata/compound_metadata.yaml +++ b/config/metadata/compound_metadata.yaml @@ -1,13 +1,13 @@ # Sample compound (hierarchical) metadata that ships with Hyrax. # # Each attribute here is a compound: a `type: hash, multiple: true` field whose -# entries are plain hashes of named sub-fields. The sub-fields are declared in -# `subfields:` and rendered, populated, and indexed generically by the compound +# entries are plain hashes of named sub-properties. The sub-properties are declared in +# `subproperties:` and rendered, populated, and indexed generically by the compound # foundation — no per-field Ruby or ERB. See # `documentation/forms/compound_fields.md`. # # These are intentionally generic examples demonstrating the supported -# sub-field input types (open-entry `string` and `controlled` vocabulary). +# sub-property input types (open-entry `string` and `controlled` vocabulary). # Applications can add their own compounds the same way (in this file, an # app-level schema, or the m3 profile under HYRAX_FLEXIBLE=true) and may remove # these samples by setting `Hyrax.config.compound_metadata_enabled = false`. @@ -20,9 +20,9 @@ attributes: predicate: http://id.loc.gov/vocabulary/relators/asn form: primary: false - subfields: - # Indexing is declared per sub-field via `index_keys:` (the literal Solr - # field names + suffixes), exactly like scalar fields. A sub-field with + subproperties: + # Indexing is declared per sub-property via `index_keys:` (the literal Solr + # field names + suffixes), exactly like scalar fields. A sub-property with # no `index_keys:` is display-only (rendered from the json blob, not its # own searchable field). `display: false` would omit it from the blob. title: { type: string } @@ -30,7 +30,7 @@ attributes: type: string required: true index_keys: [participant_name_sim, participant_name_tesim] - # A controlled sub-field can take its options from an inline list (below) + # A controlled sub-property can take its options from an inline list (below) # or from an existing QA local authority (`authority: `). participant_role: type: controlled @@ -51,7 +51,7 @@ attributes: predicate: http://purl.org/dc/terms/identifier form: primary: false - subfields: + subproperties: identifier: type: string required: true @@ -64,7 +64,7 @@ attributes: view: render_as: compound html_dl: true - # A rights grouping demonstrating controlled sub-fields backed by *existing* + # A rights grouping demonstrating controlled sub-properties backed by *existing* # QA local authorities (rather than inline `values:`), alongside an # open-ended note. compound_rights: @@ -75,7 +75,7 @@ attributes: default: Rights form: primary: false - subfields: + subproperties: rights_statement: type: controlled authority: rights_statements @@ -90,7 +90,7 @@ attributes: view: render_as: compound html_dl: true - # A related-item grouping demonstrating the `work_or_url` sub-field: the user + # A related-item grouping demonstrating the `work_or_url` sub-property: the user # searches for an internal work (typeahead) OR types an external URL. The # stored value is a work id or a URL; show pages link to the internal work # (by title) or auto-link the external URL. @@ -100,7 +100,7 @@ attributes: predicate: http://purl.org/dc/terms/relation form: primary: false - subfields: + subproperties: related_item: type: work_or_url required: true diff --git a/config/metadata_profiles/m3_profile.yaml b/config/metadata_profiles/m3_profile.yaml index 1bb100d144..3d1acb5a21 100644 --- a/config/metadata_profiles/m3_profile.yaml +++ b/config/metadata_profiles/m3_profile.yaml @@ -954,7 +954,7 @@ properties: property_uri: http://vocabulary.samvera.org/ns#transcriptIds # Sample compound (hierarchical) metadata shipped with Hyrax. Each is a # `type: hash, multiple: true` property whose entries are hashes of the - # `subfields:` keys, rendered/populated/indexed generically by the compound + # `subproperties:` keys, rendered/populated/indexed generically by the compound # foundation. See documentation/forms/compound_fields.md. participants: available_on: @@ -969,8 +969,8 @@ properties: form: primary: false property_uri: http://id.loc.gov/vocabulary/relators/asn - subfields: - # Indexing per sub-field via `indexing:` (literal Solr field names), the + subproperties: + # Indexing per sub-property via `indexing:` (literal Solr field names), the # same mechanism scalar properties use. No `indexing:` => display-only. title: { type: string } participant_name: @@ -1000,7 +1000,7 @@ properties: form: primary: false property_uri: http://purl.org/dc/terms/identifier - subfields: + subproperties: identifier: type: string required: true @@ -1026,7 +1026,7 @@ properties: form: primary: false property_uri: http://purl.org/dc/terms/rights - subfields: + subproperties: rights_statement: type: controlled authority: rights_statements @@ -1054,7 +1054,7 @@ properties: form: primary: false property_uri: http://purl.org/dc/terms/relation - subfields: + subproperties: related_item: type: work_or_url required: true diff --git a/documentation/forms/compound_fields.md b/documentation/forms/compound_fields.md index 6d0cb5d8e7..50b0b5829e 100644 --- a/documentation/forms/compound_fields.md +++ b/documentation/forms/compound_fields.md @@ -1,7 +1,7 @@ # Compound (hierarchical) metadata fields A **compound field** is a metadata attribute whose value is a list of entries, -where each entry is itself a small set of named **sub-fields**. It is the +where each entry is itself a small set of named **sub-properties**. It is the Hyrax foundation for hierarchical metadata: contributors with a name, role and identifier; dates with a value and type; funding references with a funder name and award number; and so on. @@ -15,14 +15,14 @@ and behave identically in both flex modes. ## Declaring a compound A compound is a `type: hash, multiple: true` attribute that also carries a -`subfields:` map: +`subproperties:` map: ```yaml contributors: type: hash multiple: true predicate: https://prvoices.org/terms/contributor - subfields: + subproperties: given_name: type: string index_keys: [contributors_given_name_sim, contributors_given_name_tesim] @@ -42,7 +42,7 @@ contributors: In the m3 profile the same declaration uses `data_type: array`, `type: hash`, and `available_on: class:` to scope the property, with the identical -`subfields:` / `groups:` keys; sub-field index targets are declared with +`subproperties:` / `groups:` keys; sub-property index targets are declared with `indexing:` (the flexible-mode spelling of `index_keys:`). > **Flexible mode:** the active schema is the `Hyrax::FlexibleSchema` row in the @@ -53,9 +53,9 @@ and `available_on: class:` to scope the property, with the identical > singleton class at load time, so a stale running process serves the old > attribute set even when the profile YAML is current. -### `subfields:` +### `subproperties:` -An ordered map of `sub_field_key => { type:, ... }`. Supported `type:` values: +An ordered map of `sub_property_key => { type:, ... }`. Supported `type:` values: | `type:` | Renders as | Notes | |--------------|---------------------|-------| @@ -64,10 +64,10 @@ An ordered map of `sub_field_key => { type:, ... }`. Supported `type:` values: | `work_or_url` | select2 typeahead → linked on show | Searches internal works (via the `compound_works` QA authority, backed by `Hyrax::CompoundWorkPickerBuilder`) **or** accepts a typed external URL. The stored value is a work id or a URL; on show, a work id links to the work's show page with its title (resolved by `Hyrax::CompoundWorkResolver`), a URL is auto-linked. | | `controlled` | `