qa-ldf 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rubocop.yml +21 -0
- data/.travis.yml +15 -0
- data/.yardopts +9 -0
- data/CONTRIBUTING.md +109 -0
- data/Gemfile +18 -0
- data/Guardfile +27 -0
- data/LICENSE +14 -0
- data/README.md +42 -0
- data/Rakefile +30 -0
- data/VERSION +1 -0
- data/config/ldf.yml +15 -0
- data/lib/qa/ldf.rb +43 -0
- data/lib/qa/ldf/authorities.rb +1 -0
- data/lib/qa/ldf/authorities/lc_names.rb +31 -0
- data/lib/qa/ldf/authority.rb +104 -0
- data/lib/qa/ldf/client.rb +39 -0
- data/lib/qa/ldf/configuration.rb +67 -0
- data/lib/qa/ldf/empty_search_service.rb +18 -0
- data/lib/qa/ldf/json_mapper.rb +35 -0
- data/lib/qa/ldf/model.rb +49 -0
- data/lib/qa/ldf/spec.rb +3 -0
- data/lib/qa/ldf/spec/authority.rb +112 -0
- data/lib/qa/ldf/spec/model.rb +9 -0
- data/lib/qa/ldf/spec/search_service.rb +30 -0
- data/lib/qa/ldf/version.rb +34 -0
- data/qa-ldf.gemspec +35 -0
- data/spec/contracts/qa_loc_as_search_service_spec.rb +38 -0
- data/spec/qa/ldf/authorities/lc_names_spec.rb +52 -0
- data/spec/qa/ldf/authority_spec.rb +29 -0
- data/spec/qa/ldf/client_spec.rb +23 -0
- data/spec/qa/ldf/configuration_spec.rb +93 -0
- data/spec/qa/ldf/empty_search_service_spec.rb +11 -0
- data/spec/qa/ldf/json_mapper_spec.rb +22 -0
- data/spec/qa/ldf/model_spec.rb +53 -0
- data/spec/qa/ldf_spec.rb +43 -0
- data/spec/spec_helper.rb +18 -0
- data/spec/support/cache_server.rb +25 -0
- data/spec/support/fake_client.rb +31 -0
- data/spec/support/fake_search_service.rb +40 -0
- data/spec/support/shared_examples/ld_cache_client.rb +35 -0
- metadata +232 -0
@@ -0,0 +1,39 @@
|
|
1
|
+
module Qa
|
2
|
+
module LDF
|
3
|
+
##
|
4
|
+
# A client for the LD cache server
|
5
|
+
class Client
|
6
|
+
##
|
7
|
+
# @yield [Client] yields self to a block if given
|
8
|
+
def initialize
|
9
|
+
yield self if block_given?
|
10
|
+
end
|
11
|
+
|
12
|
+
##
|
13
|
+
# @param uri [String] a URI-like string
|
14
|
+
# @param dataset [Symbol]
|
15
|
+
#
|
16
|
+
# @return [RDF::Graph]
|
17
|
+
#
|
18
|
+
# @see RDF::Mutable#load
|
19
|
+
# @see RDF::LinkedDataFragments::CacheServer
|
20
|
+
def get(uri:, dataset: :'')
|
21
|
+
RDF::Graph.load(cache_uri(uri, dataset))
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
##
|
27
|
+
# @param uri [String] a URI-like string
|
28
|
+
# @param dataset [Symbol]
|
29
|
+
#
|
30
|
+
# @return [RDF::URI]
|
31
|
+
def cache_uri(uri, dataset)
|
32
|
+
cache_uri = RDF::URI(Qa::LDF::Configuration.instance[:endpoint])
|
33
|
+
cache_uri.query = "subject=#{uri}"
|
34
|
+
cache_uri = cache_uri / 'dataset' / dataset unless dataset.empty?
|
35
|
+
cache_uri
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'singleton'
|
3
|
+
|
4
|
+
module Qa
|
5
|
+
module LDF
|
6
|
+
##
|
7
|
+
# A singleton configuration class.
|
8
|
+
#
|
9
|
+
# This behaves much like a hash, but guarantees that only one configuration
|
10
|
+
# exists and accepts a block sytax for configuration.
|
11
|
+
#
|
12
|
+
# @example configuring with a configuration hash
|
13
|
+
# Qa::LDF::Configuration.instance
|
14
|
+
# .configure!(endpoint: 'http://example.org/ldf/')
|
15
|
+
#
|
16
|
+
# @example configuring with block syntax
|
17
|
+
# Qa::LDF::Configuration.instance.configure! do |config|
|
18
|
+
# config[:endpoint] = 'http://example.org/ldf/'
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# @see Qa::LDF.config
|
22
|
+
# @see Qa::LDF.configure!
|
23
|
+
class Configuration
|
24
|
+
extend Forwardable
|
25
|
+
include Singleton
|
26
|
+
|
27
|
+
##
|
28
|
+
# @!macro [attach] def_delegator
|
29
|
+
# @!method $3()
|
30
|
+
# Delegates to the underlying options
|
31
|
+
def_delegator :@options, :'[]', :'[]'
|
32
|
+
def_delegator :@options, :'[]=', :'[]='
|
33
|
+
def_delegator :@options, :any?, :any?
|
34
|
+
def_delegator :@options, :each, :each
|
35
|
+
def_delegator :@options, :fetch, :fetch
|
36
|
+
def_delegator :@options, :to_a, :to_a
|
37
|
+
def_delegator :@options, :to_h, :to_h
|
38
|
+
|
39
|
+
##
|
40
|
+
# @private
|
41
|
+
# @see Singleton
|
42
|
+
def initialize
|
43
|
+
@options = {}
|
44
|
+
end
|
45
|
+
|
46
|
+
##
|
47
|
+
# @param opts [Hash]
|
48
|
+
# @option endpoint [String]
|
49
|
+
#
|
50
|
+
# @yield yields self to block
|
51
|
+
# @yieldparam config [Configuration] self
|
52
|
+
def configure!(**opts)
|
53
|
+
@options = opts
|
54
|
+
yield self if block_given?
|
55
|
+
self
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# Empties all configuration options
|
60
|
+
#
|
61
|
+
# @return opts [Hash]
|
62
|
+
def reset!
|
63
|
+
@options = {}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Qa
|
3
|
+
module LDF
|
4
|
+
##
|
5
|
+
# A search service that always returns no results.
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# EmptySearchService.new.search('any query') # => {}
|
9
|
+
#
|
10
|
+
class EmptySearchService
|
11
|
+
##
|
12
|
+
# @see Qa::Authority::Base#search
|
13
|
+
def search(_query)
|
14
|
+
[]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Qa
|
3
|
+
module LDF
|
4
|
+
##
|
5
|
+
# A customizable mapper from {RDF::Graph}s to JSON-like hashes for use in
|
6
|
+
# Questioning Authority.
|
7
|
+
#
|
8
|
+
# @todo: Add configuration
|
9
|
+
#
|
10
|
+
# @example maps a graphs to QA JSON objects.
|
11
|
+
# mapper = JsonMapper.new
|
12
|
+
#
|
13
|
+
# uri = 'http://id.loc.gov/authorities/subjects/sh2004002557'
|
14
|
+
# graph = RDF::Graph.load(uri)
|
15
|
+
#
|
16
|
+
# mapper.map_resource(uri, graph)
|
17
|
+
# # => { id: 'http://id.loc.gov/authorities/subjects/sh2004002557',
|
18
|
+
# # label: 'Marble Island (Nunavut)' }
|
19
|
+
#
|
20
|
+
class JsonMapper
|
21
|
+
##
|
22
|
+
# @param uri [String] a URI-like string
|
23
|
+
# @param graph [RDF::Queryable]
|
24
|
+
#
|
25
|
+
# @return [Hash<Symbol, String>]
|
26
|
+
def map_resource(uri, graph)
|
27
|
+
labels =
|
28
|
+
graph.query(subject: RDF::URI.intern(uri),
|
29
|
+
predicate: RDF::Vocab::SKOS.prefLabel).objects
|
30
|
+
|
31
|
+
{ id: uri, label: labels.first.to_s }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/qa/ldf/model.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'active_triples'
|
2
|
+
|
3
|
+
module Qa
|
4
|
+
module LDF
|
5
|
+
##
|
6
|
+
# A base model for validatable authority values.
|
7
|
+
class Model
|
8
|
+
include ActiveTriples::RDFSource
|
9
|
+
|
10
|
+
class << self
|
11
|
+
##
|
12
|
+
# Builds a model from the graph.
|
13
|
+
#
|
14
|
+
# @param graph [RDF::Graph]
|
15
|
+
#
|
16
|
+
# @return [Qa::LDF::Model] the built model
|
17
|
+
def from_graph(uri:, graph:)
|
18
|
+
new(uri) << graph
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# Builds a model from a QA result hash.
|
23
|
+
#
|
24
|
+
# @example
|
25
|
+
# model =
|
26
|
+
# Qa::LDF::Model.from_qa_result(id: 'http://example.com/moomin',
|
27
|
+
# label: 'Moomin'
|
28
|
+
# model.to_uri # => #<RDF::URI:0x... URI:http://example.com/moomin>
|
29
|
+
# model.rdf_label # => ['Moomin']
|
30
|
+
#
|
31
|
+
#
|
32
|
+
# @param qa_result [Hash<Symbol, String>]
|
33
|
+
#
|
34
|
+
# @return [Qa::LDF::Model] the built model
|
35
|
+
#
|
36
|
+
# @todo Make ActiveTriples::RDFSource#default_labels public or
|
37
|
+
# protected.
|
38
|
+
def from_qa_result(qa_result:)
|
39
|
+
qa_result.dup
|
40
|
+
model = new(qa_result.delete(:id))
|
41
|
+
model.set_value(model.send(:default_labels).first,
|
42
|
+
qa_result.delete(:label))
|
43
|
+
|
44
|
+
model
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/qa/ldf/spec.rb
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
shared_examples 'an ldf authority' do
|
2
|
+
before do
|
3
|
+
unless defined?(authority)
|
4
|
+
raise ArgumentError,
|
5
|
+
"#{authority} must be defined with `let(:authority)` before " \
|
6
|
+
'using LDF::Authority shared examples.'
|
7
|
+
end
|
8
|
+
|
9
|
+
unless defined?(ldf_uri)
|
10
|
+
raise ArgumentError,
|
11
|
+
"#{ldf_uri} must be defined with `let(:ldf_uri)` before using " \
|
12
|
+
'LDF::Authority shared examples.'
|
13
|
+
end
|
14
|
+
unless defined?(ldf_label)
|
15
|
+
raise ArgumentError,
|
16
|
+
"#{ldf_label} must be defined with `let(:ldf_label)` before " \
|
17
|
+
'using LDF::Authority shared examples.'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'is aliased to Qa::Authorities' do
|
22
|
+
unless described_class.eql?(Qa::LDF::Authority)
|
23
|
+
name = described_class.to_s.split('::').last.downcase.camelize
|
24
|
+
expect("Qa::Authorities::#{name}".constantize).to eql described_class
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '#all' do
|
29
|
+
it 'is enumerable' do
|
30
|
+
expect(authority.all).to respond_to :each
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'enumerates the vocabulary'
|
34
|
+
end
|
35
|
+
|
36
|
+
describe '#client' do
|
37
|
+
it 'defaults to an instance of DEFAULT_CLIENT' do
|
38
|
+
expect(described_class.new.client)
|
39
|
+
.to be_a described_class::DEFAULT_CLIENT
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'sets to other instances' do
|
43
|
+
client = described_class::DEFAULT_CLIENT.new
|
44
|
+
|
45
|
+
expect { authority.client = client }
|
46
|
+
.to change { authority.client }
|
47
|
+
.to equal client
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe '#dataset' do
|
52
|
+
it 'defaults DEFAULT_DATASET_NAME' do
|
53
|
+
expect(described_class.new.dataset)
|
54
|
+
.to eq described_class::DEFAULT_DATASET_NAME
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'sets to other symbols' do
|
58
|
+
dataset_name = :new_name
|
59
|
+
|
60
|
+
expect { authority.dataset = dataset_name }
|
61
|
+
.to change { authority.dataset }
|
62
|
+
.to eq dataset_name
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe '#find' do
|
67
|
+
it 'finds a uri' do
|
68
|
+
expect(authority.find(ldf_uri))
|
69
|
+
.to include id: ldf_uri.to_s, label: ldf_label
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'maps to a json-friendly hash' do
|
73
|
+
expect { JSON.generate(authority.find(ldf_uri)) }.not_to raise_error
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe '#mapper' do
|
78
|
+
it 'defaults to an instance of DEFAULT_MAPPER' do
|
79
|
+
expect(described_class.new.mapper)
|
80
|
+
.to be_a described_class::DEFAULT_MAPPER
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'sets to other instances' do
|
84
|
+
mapper = described_class::DEFAULT_MAPPER.new
|
85
|
+
|
86
|
+
expect { authority.mapper = mapper }
|
87
|
+
.to change { authority.mapper }
|
88
|
+
.to equal mapper
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe '#search' do
|
93
|
+
let(:query) { 'My Fake Query' }
|
94
|
+
let(:response) { { my: 'response' } }
|
95
|
+
|
96
|
+
it 'gives an array' do
|
97
|
+
expect(authority.search('tove')).to respond_to :to_ary
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'gives a json-friendly array' do
|
101
|
+
expect { JSON.generate(authority.search('tove')) }.not_to raise_error
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'searches the search service' do
|
105
|
+
authority.search_service = FakeSearchService.new do |service|
|
106
|
+
service.queries[query] = response
|
107
|
+
end
|
108
|
+
|
109
|
+
expect(authority.search(query)).to eq response
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
shared_examples 'an ldf search service' do
|
2
|
+
before do
|
3
|
+
unless defined?(search_service)
|
4
|
+
raise ArgumentError,
|
5
|
+
"#{search_service} must be defined with `let(:search_service)` " \
|
6
|
+
'before using LDF::Authority shared examples.'
|
7
|
+
end
|
8
|
+
|
9
|
+
unless defined?(searches)
|
10
|
+
warn 'You did not provide any valid searches to the ldf search service ' \
|
11
|
+
"shared examples for #{described_class}. Use `let(:searches) = " \
|
12
|
+
'{"query" => {key: "value"} to configure searches to test'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '#search' do
|
17
|
+
it 'gives a json-friendly array' do
|
18
|
+
JSON.generate(search_service.search('tove'))
|
19
|
+
expect { JSON.generate(search_service.search('tove')) }.not_to raise_error
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'responds to searches' do
|
23
|
+
if defined?(searches)
|
24
|
+
searches.each do |query, response|
|
25
|
+
expect(search_service.search(query)).to eq response
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Qa
|
3
|
+
module LDF
|
4
|
+
##
|
5
|
+
# Gives the current version, parsed from `VERSION`.
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# "The current version is: #{Qa::LDF::VERSION}"
|
9
|
+
#
|
10
|
+
module VERSION
|
11
|
+
FILE = File.expand_path('../../../../VERSION', __FILE__)
|
12
|
+
MAJOR, MINOR, TINY = File.read(FILE).chomp.split('.')
|
13
|
+
STRING = [MAJOR, MINOR, TINY].compact.join('.').freeze
|
14
|
+
|
15
|
+
##
|
16
|
+
# @return [String]
|
17
|
+
def self.to_s
|
18
|
+
STRING
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# @return [String]
|
23
|
+
def self.to_str
|
24
|
+
STRING
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# @return [Array(String, String, String, String)]
|
29
|
+
def self.to_a
|
30
|
+
[MAJOR, MINOR, TINY].compact
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/qa-ldf.gemspec
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
#!/usr/bin/env ruby -rubygems
|
2
|
+
# -*- encoding: utf-8 -*-
|
3
|
+
|
4
|
+
Gem::Specification.new do |spec|
|
5
|
+
spec.authors = ['Tom Johnson']
|
6
|
+
spec.email = ['tom@curationexperts.com']
|
7
|
+
|
8
|
+
spec.name = 'qa-ldf'
|
9
|
+
spec.homepage = 'http://github.com/curationexperts/qa-ldf'
|
10
|
+
spec.description = 'Questioning Authority cached with a Linked Data ' \
|
11
|
+
'Fragment.'
|
12
|
+
spec.summary = 'Provides a bridge between questioning authority and ' \
|
13
|
+
'a caching linked data fragment for URI authorities.'
|
14
|
+
|
15
|
+
spec.version = File.read('VERSION').chomp
|
16
|
+
spec.date = File.mtime('VERSION').strftime('%Y-%m-%d')
|
17
|
+
spec.license = 'Apache-2.0'
|
18
|
+
|
19
|
+
spec.files = `git ls-files`.split($OUTPUT_RECORD_SEPARATOR)
|
20
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
21
|
+
spec.require_paths = %w(lib)
|
22
|
+
|
23
|
+
spec.required_ruby_version = '>= 2.2.2'
|
24
|
+
|
25
|
+
spec.add_dependency 'qa', '~> 0.11.0'
|
26
|
+
|
27
|
+
spec.add_development_dependency 'guard', '~> 2.14'
|
28
|
+
spec.add_development_dependency 'ld_cache_fragment', '~> 0.1'
|
29
|
+
spec.add_development_dependency 'rake', '~> 12.0'
|
30
|
+
spec.add_development_dependency 'rspec', '~> 3.5'
|
31
|
+
spec.add_development_dependency 'rubocop-rspec', '1.10.0'
|
32
|
+
spec.add_development_dependency 'sham_rack', '~> 1.4'
|
33
|
+
spec.add_development_dependency 'webmock', '~> 2.3', '>= 2.3.2'
|
34
|
+
spec.add_development_dependency 'yard', '~> 0'
|
35
|
+
end
|