Skip to content

Commit 71d19fe

Browse files
authored
Merge pull request #654 from notch8/add-controlled-vocab-validator
🎁 Add controlled vocab validator
2 parents 77a19dd + 278d98f commit 71d19fe

4 files changed

Lines changed: 91 additions & 0 deletions

File tree

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# frozen_string_literal: true
2+
3+
# OVERRIDE Hyrax v5.2.0 to add validation of controlled vocabularies
4+
# can be removed when it is in core Hyrax https://github.com/samvera/hyrax/pull/7423
5+
6+
module Hyrax
7+
module ChangeSetDecorator
8+
extend ActiveSupport::Concern
9+
10+
prepended do
11+
validates_with Hyrax::ControlledVocabularyValidator
12+
end
13+
end
14+
end
15+
16+
Hyrax::ChangeSet.prepend(Hyrax::ChangeSetDecorator)
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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

β€Žconfig/features.rbβ€Ž

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# frozen_string_literal: true
2+
3+
Flipflop.configure do
4+
feature :validate_local_controlled_vocabulary,
5+
default: false,
6+
description: "Validate local controlled vocabulary."
7+
end

β€Žlib/hyku_knapsack/engine.rbβ€Ž

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ class Engine < ::Rails::Engine
1010
end
1111
end
1212

13+
initializer 'hyku_knapsack.flipflop' do
14+
Flipflop::FeatureLoader.current.append(self)
15+
end
16+
1317
def self.load_translations!
1418
HykuKnapsack::Engine.root.glob("config/locales/**/*.yml").each do |path|
1519
I18n.load_path << path.to_s

0 commit comments

Comments
Β (0)