qa-ldf 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 855a7a374246e7b3deb9e2a05f275a7e1ac4b39b
4
- data.tar.gz: 6fceb2d6ec1511192f388a7da4edcd37b223088a
3
+ metadata.gz: 4e5b26feeb2a751f305e34082e074a5599325599
4
+ data.tar.gz: 68ac921385f139cf2e261d7c3fb63d2ce88eb81e
5
5
  SHA512:
6
- metadata.gz: e3c38a54bd7ec95604666dc08113e18d8f240976b21f2d39dbb40445d954455860dbf79494dd4811083e754494864fd54cd78582f4707b53f7dfdbc23a8e976e
7
- data.tar.gz: 951a494528c90e84f66df9747a810e3b9a0a92fb9a6643058e91de475885b3b42c8d12017295daaa918e7c3ebefc3c165776ef7ddd881370301749c49ac7392f
6
+ metadata.gz: 7a7b32a4a9406d1974bf2c1c960bac71f337f9d48b0477c03bddf5006db7bd58b07d93e4b06ed01781262e297e83ff7291789b7f0f9ede08e480750463a039f6
7
+ data.tar.gz: 69041a0dda760ad8b2e84543db5f7ecb9e7795aa9fb6663bf6a740268ca6d28112a4e4329a37291b4fbb0658210020f7cbce29d3379030fcd409c0c67c10bf52
data/.rubocop.yml CHANGED
@@ -5,6 +5,7 @@ AllCops:
5
5
 
6
6
  Metric/BlockLength:
7
7
  Exclude:
8
+ - 'qa-ldf.gemspec'
8
9
  - 'spec/**/*'
9
10
  - 'lib/qa/ldf/spec/**/*'
10
11
 
data/README.md CHANGED
@@ -2,40 +2,86 @@ QuestioningAuthority::LDF
2
2
  =========================
3
3
  [![Apache 2.0 License](http://img.shields.io/badge/APACHE2-license-blue.svg)](./LICENSE)
4
4
 
5
- Providies bindings from [Questioning Authority](https://github.com/projecthydra-labs/questioning_authority) to the [linked data caching fragment server](https://github.com/ActiveTriples/linked-data-fragments) for fast query of RDF-based authorities.
5
+ This sofware providies bindings from [Questioning Authority](https://github.com/projecthydra-labs/questioning_authority) to the [linked data caching fragment server](https://github.com/ActiveTriples/linked-data-fragments) for fast query of RDF-based authorities.
6
6
 
7
7
  What Does This Do?
8
8
  ------------------
9
9
 
10
- Provides a caching version of the [Questioning Authority](https://github.com/projecthydra-labs/questioning_authority) interface relying on the ActiveTriples LDF caching service.
10
+ This gem offers a caching version of the [Questioning Authority](https://github.com/projecthydra-labs/questioning_authority) interface relying on the ActiveTriples LDF caching service. A set of linked data "authorities" are exposed through the Questioning Authority API, and cached for fast recall using the Linked Data Fragments Caching Server. Models on the Hydra/Rails side are provided to handle caching and easy presentation of labels.
11
11
 
12
- [TK] Why use this?
12
+ Both the QA and LDF APIs are available in mountable forms, so they can run directly in your Hydra application---see below for details.
13
13
 
14
- Upcoming features include:
14
+ ### Why use this?
15
+
16
+ The main reason you may be interested in this software is to handle local caching of linked data references in your metadata.
17
+
18
+ ### Upcoming features include:
15
19
 
16
20
  - Support for `Qa::Authority::Base#all` for all LDF authorities through [Hydra Core Partial Collection View](http://www.hydra-cg.com/spec/latest/core/#hydra:PartialCollectionView) style paging.
17
- - A default search, accessing cached items.
21
+ - A default search, accessing only cached items.
18
22
 
19
23
  How Does it Work?
20
24
  -----------------
21
25
 
22
26
  ### [TK] Architecture Overview.
23
- ### [TK] Mounting and configuring the LDF caching server.
24
- ### [TK] LDF caching as an external service.
25
- ### [TK] Configuring authorities.
27
+ ### Mounting and configuring the LDF caching server
28
+
29
+ The LDF caching server can run as a mounted application within Rails. To use the caching server, add the following to your `Gemfile`:
30
+
31
+ ```ruby
32
+ gem 'qa-ldf', '~>0.2.0'
33
+
34
+ # for now use the active branch of the linked data caching fragment server
35
+ gem 'ld_cache_fragment', github: 'ActiveTriples/linked-data-fragments', branch: 'feature/multi-dataset'
36
+ ```
37
+
38
+ Then you need to mount the application to an unused route:
39
+
40
+ ```ruby
41
+ # config/routes.rb
42
+
43
+ Rails.application.routes.draw do
44
+ # ...
45
+ mount LinkedDataFragments::CacheServer::APPLICATION => '/ldcache'
46
+ # ...
47
+ end
48
+ ```
49
+
50
+ ### LDF caching as an external service.
51
+
52
+ The LDF caching server can run independently from your Hydra application as a lightweight, generic [Rack](http://www.rubydoc.info/github/rack/rack/file/SPEC) application, or as a standalone Rails app. You may want to deploy the server in this way so it can run on separate hardware, or to segregate Linked Data "follow your nose" network traffic.
53
+
54
+ The simplest way to get a working application that will run on any Rack-compatible server is to create a `./config.ru` containing:
55
+
56
+ ```ruby
57
+ # ./config.ru
58
+ require 'ld_cache_fragment/cache_server'
59
+
60
+ run LinkedDataFragments::CacheServer::APPLICATION
61
+ ```
62
+
63
+ With this in your working directory, you can run `$ rackup` to launch a basic server. More robust deployments are possible using servers like [Puma](https://github.com/puma/puma#rackup).
64
+
65
+ #### [TK] LDF caching as a Rails app
66
+
67
+ ### Configuring authorities.
26
68
  #### [TK] Models.
27
69
  #### [TK] Forms.
28
70
 
29
- [TK] Authority Sources
71
+ Authority Sources
30
72
  ----------------------
31
73
 
32
- ### [TK] LCNAF
33
- ### [TK] FAST
74
+ ### LCNAF
75
+
76
+ The Library of Congress Name Authority File
77
+
78
+ ### FAST
79
+
80
+ Faceted Application of Subject Terminology
34
81
 
35
82
  [TK] Implementing Custom Authorities
36
83
  ------------------------------------
37
84
 
38
-
39
85
  License
40
86
  -------
41
87
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.0
1
+ 0.3.0
@@ -1 +1,2 @@
1
+ require 'qa/ldf/authorities/fast'
1
2
  require 'qa/ldf/authorities/lc_names'
@@ -0,0 +1,54 @@
1
+ require 'qa/ldf/namespaced_search_service'
2
+
3
+ # frozen_string_literal: true
4
+ module Qa
5
+ module LDF
6
+ ##
7
+ # A caching OCLC Faceted Application of Subject Terminology (FAST)
8
+ # authority.
9
+ #
10
+ # @see LinkedDataFragments::CacheServer
11
+ # @see http://www.oclc.org/research/themes/data-science/fast.html
12
+ class FAST < Authority
13
+ DEFAULT_DATASET_NAME = :fast
14
+ NAMESPACE = 'http://id.id.worldcat.org/fast/'.freeze
15
+
16
+ register_namespace(namespace: NAMESPACE,
17
+ klass: self)
18
+
19
+ ##
20
+ # A specialized NamespacedSearchService that strips 'fst' from fast IDs
21
+ class SearchService < NamespacedSearchService
22
+ private
23
+
24
+ def apply_namespace(id)
25
+ super(id.gsub('fst', ''))
26
+ end
27
+ end
28
+
29
+ ##
30
+ # @return [String] the URI namespace associated with this authority
31
+ def self.namespace
32
+ NAMESPACE
33
+ end
34
+
35
+ ##
36
+ # Uses the LC AssignFast 'all' subauthority as the search provider
37
+ def search_service
38
+ @search_service ||= NamespacedSearchService.new do |service|
39
+ service.namespace = NAMESPACE
40
+ service.parent_service =
41
+ Qa::Authorities::AssignFast.subauthority_for('all')
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ ##
48
+ # Alias to hack Qa's namespaced authority logic.
49
+ #
50
+ # @see https://github.com/projecthydra-labs/questioning_authority/issues/137
51
+ module Authorities
52
+ Fast = LDF::FAST
53
+ end
54
+ end
@@ -1,3 +1,5 @@
1
+ require 'qa/ldf/namespaced_search_service'
2
+
1
3
  # frozen_string_literal: true
2
4
  module Qa
3
5
  module LDF
@@ -11,11 +13,24 @@ module Qa
11
13
  class LCNames < Authority
12
14
  DEFAULT_DATASET_NAME = :lcnames
13
15
  NAMESPACE = 'http://id.loc.gov/authorities/names/'.freeze
14
- LC_SUBAUTHORITY = 'names'.freeze
15
16
 
16
17
  register_namespace(namespace: NAMESPACE,
17
18
  klass: self)
18
19
 
20
+ ##
21
+ # A specialized NamespacedSearchService that strips info: uris
22
+ #
23
+ # The basic QA LCNames authority returns the info: URIs, instead of the
24
+ # http: URIs as IDs. This handles conversion between the two.
25
+ # @note This is not resilient to ids other than LCNames info: uris
26
+ class SearchService < NamespacedSearchService
27
+ private
28
+
29
+ def apply_namespace(id)
30
+ super(id.split('/').last)
31
+ end
32
+ end
33
+
19
34
  ##
20
35
  # @return [String] the URI namespace associated with this authority
21
36
  def self.namespace
@@ -25,8 +40,11 @@ module Qa
25
40
  ##
26
41
  # Uses the LC names subauthority as the search provider
27
42
  def search_service
28
- @search_service ||=
29
- Qa::Authorities::Loc.subauthority_for(LC_SUBAUTHORITY)
43
+ @search_service ||= SearchService.new do |service|
44
+ service.namespace = NAMESPACE
45
+ service.parent_service =
46
+ Qa::Authorities::Loc.subauthority_for('names')
47
+ end
30
48
  end
31
49
  end
32
50
  end
@@ -119,7 +119,7 @@ module Qa
119
119
  ##
120
120
  # Search the vocabulary
121
121
  #
122
- # @param _query [String] the query string
122
+ # @param query [String] the query string
123
123
  #
124
124
  # @return [Array<Hash<Symbol, String>>] the response as a JSON friendly
125
125
  # hash
data/lib/qa/ldf/client.rb CHANGED
@@ -32,7 +32,7 @@ module Qa
32
32
  cache_uri = RDF::URI(Qa::LDF::Configuration.instance[:endpoint])
33
33
  cache_uri.query = "subject=#{uri}"
34
34
  cache_uri = cache_uri / 'dataset' / dataset unless dataset.empty?
35
- cache_uri
35
+ cache_uri.dup
36
36
  end
37
37
  end
38
38
  end
data/lib/qa/ldf/model.rb CHANGED
@@ -61,6 +61,23 @@ module Qa
61
61
  self
62
62
  end
63
63
 
64
+ ##
65
+ # @param language [Symbol] a two letter (BCP47) language tag.
66
+ #
67
+ # @see ActiveTriples::RDFSource.rdf_label
68
+ # @see https://www.w3.org/TR/rdf11-concepts/#section-Graph-Literal
69
+ # @see https://tools.ietf.org/html/bcp47
70
+ def lang_label(language: :en)
71
+ labels = rdf_label
72
+
73
+ label = labels.find do |literal|
74
+ literal.respond_to?(:language) && literal.language == language
75
+ end
76
+
77
+ return labels.first unless label
78
+ label
79
+ end
80
+
64
81
  class << self
65
82
  ##
66
83
  # Builds a model from the graph.
@@ -91,9 +108,9 @@ module Qa
91
108
  # protected.
92
109
  def from_qa_result(qa_result:)
93
110
  qa_result.dup
94
- model = new(qa_result.delete(:id))
111
+ model = new(qa_result.delete(:id.to_s))
95
112
  model.set_value(model.send(:default_labels).first,
96
- qa_result.delete(:label))
113
+ qa_result.delete(:label.to_s))
97
114
 
98
115
  model
99
116
  end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+ module Qa
3
+ module LDF
4
+ ##
5
+ # A search service that wraps another SearchService and casts its ids to a
6
+ # namespace.
7
+ #
8
+ # @example
9
+ # nsify = Qa::LDF::NamespacedSearchService.new do |service|
10
+ # service.namespace = 'http://example.com/ns/'
11
+ # service.parent_service = a_search_service_instance
12
+ # end
13
+ #
14
+ # # when
15
+ # a_search_service_endpoint.search('moomin') # => { 'id' => 'blah' }
16
+ #
17
+ # # then
18
+ # nsify.search('moomin') # => { 'id' => 'http://example.com/ns/blah' }
19
+ #
20
+ class NamespacedSearchService
21
+ ##
22
+ # @!attribute [rw] namespace
23
+ # @return [String]
24
+ # @!attribute [rw] parent_service
25
+ # @return [#search]
26
+ attr_accessor :namespace, :parent_service
27
+
28
+ ##
29
+ # @yieldparam service [NamespacedSearchService] yields self
30
+ def initialize
31
+ yield self
32
+ end
33
+
34
+ ##
35
+ # @see Qa::Authority::Base#search
36
+ def search(query)
37
+ responses = parent_service.search(query)
38
+
39
+ responses.map do |result|
40
+ result['id'] = apply_namespace(result['id'])
41
+ result
42
+ end
43
+ end
44
+
45
+ def apply_namespace(id)
46
+ return id if RDF::URI(id).valid?
47
+ (RDF::URI(namespace) / id).to_s
48
+ end
49
+ end
50
+ end
51
+ end
@@ -113,7 +113,7 @@ shared_examples 'an ldf authority' do
113
113
 
114
114
  describe '#search' do
115
115
  let(:query) { 'My Fake Query' }
116
- let(:response) { { my: 'response' } }
116
+ let(:response) { [{ my: 'response' }] }
117
117
 
118
118
  it 'gives an array' do
119
119
  expect(authority.search('tove')).to respond_to :to_ary
@@ -49,7 +49,7 @@ shared_examples 'an ldf model' do
49
49
  end
50
50
 
51
51
  describe '.from_qa_result' do
52
- let(:json_hash) { { id: id, label: label, papa: 'moomin papa' } }
52
+ let(:json_hash) { { 'id' => id, 'label' => label, 'snork' => 'snork' } }
53
53
 
54
54
  it 'builds a model with the id as uri' do
55
55
  expect(described_class.from_qa_result(qa_result: json_hash))
data/qa-ldf.gemspec CHANGED
@@ -22,7 +22,8 @@ Gem::Specification.new do |spec|
22
22
 
23
23
  spec.required_ruby_version = '>= 2.2.2'
24
24
 
25
- spec.add_dependency 'qa', '~> 0.11.0'
25
+ spec.add_dependency 'rdf', '>= 2.0', '< 2.2.4'
26
+ spec.add_dependency 'qa', '~> 0.11.0'
26
27
 
27
28
  spec.add_development_dependency 'active-fedora', '~> 11.0'
28
29
  spec.add_development_dependency 'guard', '~> 2.14'
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+
3
+ describe Qa::LDF::FAST do
4
+ it_behaves_like 'an ldf authority'
5
+
6
+ subject(:authority) do
7
+ auth = described_class.new
8
+
9
+ auth.client = FakeClient.new do |client|
10
+ client.graph = RDF::Graph.new
11
+ client.graph.insert(*statements)
12
+ client.label = ldf_label
13
+ end
14
+
15
+ auth
16
+ end
17
+
18
+ let(:statements) do
19
+ [RDF::Statement(RDF::URI(ldf_uri),
20
+ RDF::Vocab::SKOS.prefLabel,
21
+ RDF::Literal(ldf_label))]
22
+ end
23
+
24
+ let(:ldf_label) { 'Jansson, Tove' }
25
+ let(:ldf_uri) { 'http://id.worldcat.org/fast/34647' }
26
+
27
+ before do
28
+ # mock empty responses for all queries;
29
+ # @todo contract test AssignFast authority
30
+ body = <<-eos
31
+ {
32
+ "responseHeader":{
33
+ "status":0,
34
+ "QTime":468,
35
+ "params":{
36
+ "fl":"suggestall,idroot,auth,type",
37
+ "q":"suggestall:nothing_to_see_here",
38
+ "rows":"20"
39
+ }
40
+ },
41
+ "response":{"numFound":0,"start":0,"docs":[]}
42
+ }
43
+ eos
44
+
45
+ stub_request(:get, 'http://fast.oclc.org/searchfast/fastsuggest?' \
46
+ 'query=tove&queryIndex=suggestall&' \
47
+ 'queryReturn=suggestall,idroot,auth,type&' \
48
+ 'rows=20&suggest=autoSubject')
49
+ .with(headers: { 'Accept' => 'application/json' })
50
+ .to_return(status: 200, body: body, headers: {})
51
+ end
52
+
53
+ describe '#dataset' do
54
+ it 'defaults to :fast' do
55
+ expect(described_class.new.dataset).to eq :fast
56
+ end
57
+ end
58
+ end
@@ -27,20 +27,38 @@ describe Qa::LDF::LCNames do
27
27
  before do
28
28
  # mock empty responses for all queries;
29
29
  # see spec/contracts/qa_loc_as_search_service.rb for lc search service tests
30
+ # @todo: test that correct handling of namespacing
30
31
  stub_request(:get, 'http://id.loc.gov/search/?format=json&' \
31
32
  'q=cs:http://id.loc.gov/authorities/names')
32
33
  .with(headers: { 'Accept' => 'application/json' })
33
34
  .to_return(status: 200, body: '[]', headers: {})
34
35
  end
35
36
 
36
- describe '#search_service' do
37
- it 'hits the upstream loc endpoint by default' do
38
- query = 'a query'
39
- url = 'http://id.loc.gov/search/?format=json' \
40
- "&q=#{query}&q=cs:http://id.loc.gov/authorities/names"
41
- expect(a_request(:get, url))
37
+ describe '#search' do
38
+ context 'with real search service' do
39
+ it 'hits the upstream loc endpoint' do
40
+ query = 'a query'
41
+ url = 'http://id.loc.gov/search/?format=json' \
42
+ "&q=#{query}&q=cs:http://id.loc.gov/authorities/names"
43
+ expect(a_request(:get, url))
42
44
 
43
- authority.search_service.search(query)
45
+ authority.search(query)
46
+ end
47
+ end
48
+
49
+ context 'with fake search service' do
50
+ let(:search_service) do
51
+ FakeSearchService.new { |service| service.queries = searches }
52
+ end
53
+
54
+ let(:searches) { { 'moomin' => results } }
55
+ let(:results) { [{ id: 'moomin123', name: 'Moomin' }] }
56
+
57
+ before { authority.search_service = search_service }
58
+
59
+ it 'returns the values' do
60
+ expect(authority.search('moomin')).to contain_exactly(*results)
61
+ end
44
62
  end
45
63
  end
46
64
 
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ require 'qa/ldf/namespaced_search_service'
4
+
5
+ describe Qa::LDF::NamespacedSearchService do
6
+ it_behaves_like 'an ldf search service'
7
+
8
+ subject(:search_service) do
9
+ described_class.new do |service|
10
+ service.namespace = namespace
11
+ service.parent_service = fake_service
12
+ end
13
+ end
14
+
15
+ let(:namespace) { 'http://example.com/ns/' }
16
+ let(:parent_searches) { { query => parent_response } }
17
+ let(:parent_response) { [{ 'id' => 'id', 'key' => 'value' }] }
18
+ let(:searches) { { query => response } }
19
+ let(:query) { 'tove' }
20
+ let(:response) { [{ 'id' => namespace + 'id', 'key' => 'value' }] }
21
+
22
+ let(:fake_service) do
23
+ FakeSearchService.new { |service| service.queries = parent_searches }
24
+ end
25
+ end
metadata CHANGED
@@ -1,15 +1,35 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: qa-ldf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tom Johnson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-03-02 00:00:00.000000000 Z
11
+ date: 2017-03-16 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rdf
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: 2.2.4
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '2.0'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: 2.2.4
13
33
  - !ruby/object:Gem::Dependency
14
34
  name: qa
15
35
  requirement: !ruby/object:Gem::Requirement
@@ -177,6 +197,7 @@ files:
177
197
  - config/ldf.yml
178
198
  - lib/qa/ldf.rb
179
199
  - lib/qa/ldf/authorities.rb
200
+ - lib/qa/ldf/authorities/fast.rb
180
201
  - lib/qa/ldf/authorities/lc_names.rb
181
202
  - lib/qa/ldf/authority.rb
182
203
  - lib/qa/ldf/client.rb
@@ -184,6 +205,7 @@ files:
184
205
  - lib/qa/ldf/empty_search_service.rb
185
206
  - lib/qa/ldf/json_mapper.rb
186
207
  - lib/qa/ldf/model.rb
208
+ - lib/qa/ldf/namespaced_search_service.rb
187
209
  - lib/qa/ldf/spec.rb
188
210
  - lib/qa/ldf/spec/authority.rb
189
211
  - lib/qa/ldf/spec/model.rb
@@ -192,6 +214,7 @@ files:
192
214
  - qa-ldf.gemspec
193
215
  - spec/contracts/qa_loc_as_search_service_spec.rb
194
216
  - spec/integration/active_fedora_model_spec.rb
217
+ - spec/qa/ldf/authorities/fast_spec.rb
195
218
  - spec/qa/ldf/authorities/lc_names_spec.rb
196
219
  - spec/qa/ldf/authority_spec.rb
197
220
  - spec/qa/ldf/client_spec.rb
@@ -199,6 +222,7 @@ files:
199
222
  - spec/qa/ldf/empty_search_service_spec.rb
200
223
  - spec/qa/ldf/json_mapper_spec.rb
201
224
  - spec/qa/ldf/model_spec.rb
225
+ - spec/qa/ldf/namespaced_search_service_spec.rb
202
226
  - spec/qa/ldf_spec.rb
203
227
  - spec/spec_helper.rb
204
228
  - spec/support/cache_server.rb
@@ -233,6 +257,7 @@ summary: Provides a bridge between questioning authority and a caching linked da
233
257
  test_files:
234
258
  - spec/contracts/qa_loc_as_search_service_spec.rb
235
259
  - spec/integration/active_fedora_model_spec.rb
260
+ - spec/qa/ldf/authorities/fast_spec.rb
236
261
  - spec/qa/ldf/authorities/lc_names_spec.rb
237
262
  - spec/qa/ldf/authority_spec.rb
238
263
  - spec/qa/ldf/client_spec.rb
@@ -240,6 +265,7 @@ test_files:
240
265
  - spec/qa/ldf/empty_search_service_spec.rb
241
266
  - spec/qa/ldf/json_mapper_spec.rb
242
267
  - spec/qa/ldf/model_spec.rb
268
+ - spec/qa/ldf/namespaced_search_service_spec.rb
243
269
  - spec/qa/ldf_spec.rb
244
270
  - spec/spec_helper.rb
245
271
  - spec/support/cache_server.rb