|
| 1 | +# frozen_string_literal: true |
| 2 | +module Hyrax |
| 3 | + ## |
| 4 | + # Validates that controlled vocabulary properties contain only active |
| 5 | + # terms from their corresponding local QA authority. |
| 6 | + # |
| 7 | + # Properties are matched to authorities dynamically via |
| 8 | + # +Qa::Authorities::Local.subauthorities+, using the singularized |
| 9 | + # authority name to match property names on the change set. |
| 10 | + # |
| 11 | + # Only covers local authorities (file-based and table-based). |
| 12 | + # Remote authorities (e.g. Geonames) are out of scope. |
| 13 | + class ControlledVocabularyValidator < ActiveModel::Validator |
| 14 | + def validate(record) |
| 15 | + return unless Flipflop.validate_local_controlled_vocabulary? |
| 16 | + |
| 17 | + active_terms_by_property(record).each do |property, terms| |
| 18 | + values = Array.wrap(record.public_send(property)).reject(&:blank?) |
| 19 | + next if values.empty? |
| 20 | + |
| 21 | + invalid = values.reject { |v| terms.include?(v) } |
| 22 | + invalid.each do |v| |
| 23 | + record.errors.add(property, "#{property.to_s.humanize} contains unrecognized value: #{v}") |
| 24 | + end |
| 25 | + end |
| 26 | + end |
| 27 | + |
| 28 | + private |
| 29 | + |
| 30 | + ## |
| 31 | + # @example |
| 32 | + # # { "license" => ["http://creativecommons.org/licenses/by/4.0/", ...], |
| 33 | + # # "resource_type" => ["Article", "Book", ...] } |
| 34 | + # |
| 35 | + # @return [Hash{String => Array<String>}] |
| 36 | + def active_terms_by_property(record) |
| 37 | + authorities = Qa::Authorities::Local.subauthorities |
| 38 | + |
| 39 | + # { "license" => "licenses", "resource_type" => "resource_types", ... } |
| 40 | + property_to_authority = {}.tap do |hash| |
| 41 | + authorities.each { |name| hash[name.singularize] = name } |
| 42 | + end |
| 43 | + |
| 44 | + properties = record.fields.keys.map(&:to_s) |
| 45 | + |
| 46 | + {}.tap do |result| |
| 47 | + (properties & property_to_authority.keys).each do |property| |
| 48 | + authority = Qa::Authorities::Local.subauthority_for(property_to_authority[property]) |
| 49 | + terms = authority.all |
| 50 | + next if terms.empty? |
| 51 | + |
| 52 | + result[property] = terms.filter_map { |term| term[:id] if active?(term) } |
| 53 | + end |
| 54 | + end |
| 55 | + end |
| 56 | + |
| 57 | + ## |
| 58 | + # Terms without an +active+ field (e.g. resource_types) are |
| 59 | + # treated as active, matching QA's +FileBasedAuthority#all+ behavior. |
| 60 | + def active?(term) |
| 61 | + term.fetch(:active, true) != false |
| 62 | + end |
| 63 | + end |
| 64 | +end |
0 commit comments