qa-ldf 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a949f884516d5b051b47a5b14d23c67043a0d3d3
4
- data.tar.gz: 50ca4ecf8a10304ac8a2afa38afd38264c537ebe
3
+ metadata.gz: 855a7a374246e7b3deb9e2a05f275a7e1ac4b39b
4
+ data.tar.gz: 6fceb2d6ec1511192f388a7da4edcd37b223088a
5
5
  SHA512:
6
- metadata.gz: 5ef5befc68d69cf514995bdacaea034fdbb1c03b0c4effe9898f91cd57547ae26585414b40bc158171ae7bb3850ca06c42b5157dac15bd581b2ff106e5e59c22
7
- data.tar.gz: 5a1d78db92bfc2de9d80b60292cb5dc0c1d3cc72bc210885529bcd56510ec6a455079e3a9d97df5538056d12cd6844a3d40245bba54bb4acabb10b05073a831e
6
+ metadata.gz: e3c38a54bd7ec95604666dc08113e18d8f240976b21f2d39dbb40445d954455860dbf79494dd4811083e754494864fd54cd78582f4707b53f7dfdbc23a8e976e
7
+ data.tar.gz: 951a494528c90e84f66df9747a810e3b9a0a92fb9a6643058e91de475885b3b42c8d12017295daaa918e7c3ebefc3c165776ef7ddd881370301749c49ac7392f
data/.rubocop.yml CHANGED
@@ -8,6 +8,12 @@ Metric/BlockLength:
8
8
  - 'spec/**/*'
9
9
  - 'lib/qa/ldf/spec/**/*'
10
10
 
11
+ # Class variables are fine. We want the "nasty" behavior described by rubocop
12
+ # in some cases.
13
+ # https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#no-class-vars
14
+ Style/ClassVars:
15
+ Enabled: false
16
+
11
17
  # we accept `expect(subject).to receive`
12
18
  RSpec/MessageSpies:
13
19
  Enabled: false
@@ -19,3 +25,8 @@ RSpec/FilePath:
19
25
  - 'spec/support/**/*'
20
26
  - 'spec/qa/ldf/authorities/**/*'
21
27
  - 'spec/qa/ldf/models/**/*'
28
+
29
+ # we allow non-Class describes for integration
30
+ RSpec/DescribeClass:
31
+ Exclude:
32
+ - 'spec/integration/**/*'
data/.travis.yml CHANGED
@@ -1,5 +1,6 @@
1
1
  language: ruby
2
2
  sudo: false
3
+ script: 'bundle exec rake ci environment=test'
3
4
 
4
5
  cache:
5
6
  bundler: true
data/Gemfile CHANGED
@@ -10,9 +10,6 @@ end
10
10
 
11
11
  gem 'pry' unless ENV['CI']
12
12
 
13
- # develop on qa master
14
- gem 'qa', github: 'projecthydra-labs/questioning_authority', branch: 'master'
15
-
16
13
  gem 'ld_cache_fragment',
17
14
  github: 'ActiveTriples/linked-data-fragments',
18
15
  branch: 'feature/multi-dataset'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.2.0
@@ -10,8 +10,18 @@ module Qa
10
10
  # @see LinkedDataFragments::CacheServer
11
11
  class LCNames < Authority
12
12
  DEFAULT_DATASET_NAME = :lcnames
13
+ NAMESPACE = 'http://id.loc.gov/authorities/names/'.freeze
13
14
  LC_SUBAUTHORITY = 'names'.freeze
14
15
 
16
+ register_namespace(namespace: NAMESPACE,
17
+ klass: self)
18
+
19
+ ##
20
+ # @return [String] the URI namespace associated with this authority
21
+ def self.namespace
22
+ NAMESPACE
23
+ end
24
+
15
25
  ##
16
26
  # Uses the LC names subauthority as the search provider
17
27
  def search_service
@@ -26,6 +26,52 @@ module Qa
26
26
  # The default search service
27
27
  DEFAULT_SEARCH_SERVICE = Qa::LDF::EmptySearchService
28
28
 
29
+ class << self
30
+ @@namespace_map = {}
31
+
32
+ ##
33
+ # Gives an authority class for the given namespace.
34
+ #
35
+ # @param namespace [#to_s] a URI-like namespace string
36
+ #
37
+ # @return [Authority]
38
+ def for(namespace:)
39
+ @@namespace_map.fetch(namespace.to_s) { self }.new
40
+ end
41
+
42
+ ##
43
+ # @return [String] gives an empty string for no namespace,
44
+ # {#for_namespace} returns self in this case
45
+ def namespace
46
+ ''
47
+ end
48
+
49
+ ##
50
+ # @return [Enumerable<String>]
51
+ def namespaces
52
+ @@namespace_map.keys
53
+ end
54
+
55
+ ##
56
+ # Registers a namespace/class pair.
57
+ #
58
+ # @param namespace [#to_s] a URI-like namespace string
59
+ # @param klass [Class] an authority class
60
+ #
61
+ # @return [void]
62
+ def register_namespace(namespace:, klass:)
63
+ @@namespace_map[namespace.to_s] = klass
64
+ end
65
+
66
+ ##
67
+ # Resets the namespaces
68
+ #
69
+ # @return [void]
70
+ def reset_namespaces
71
+ @@namespace_map = {}
72
+ end
73
+ end
74
+
29
75
  ##
30
76
  # @!attribute [rw] client
31
77
  # @return [Client]
@@ -60,9 +106,14 @@ module Qa
60
106
  # @see Qa::Authorities::Base#find
61
107
  # @see Qa::LDF::Client#get
62
108
  def find(id)
63
- graph = client.get(uri: id, dataset: dataset)
109
+ mapper.map_resource(id, graph(id))
110
+ end
64
111
 
65
- mapper.map_resource(id, graph)
112
+ ##
113
+ # @param uri [RDF::URI]
114
+ # @return [RDF::Enumerable]
115
+ def graph(uri)
116
+ client.get(uri: uri, dataset: dataset)
66
117
  end
67
118
 
68
119
  ##
data/lib/qa/ldf/model.rb CHANGED
@@ -7,6 +7,60 @@ module Qa
7
7
  class Model
8
8
  include ActiveTriples::RDFSource
9
9
 
10
+ ##
11
+ # @return [Qa::LDF::Authority]
12
+ #
13
+ # @example
14
+ # # Regester the namespace with the authority class.
15
+ # class MyAuthority < Authority
16
+ # register_namespace(namespace: 'http://example.com/my_authority#',
17
+ # klass: self)
18
+ # end
19
+ #
20
+ # model = Qa::LDF::Model.new('http://example.com/my_authority#moomin')
21
+ #
22
+ # model.authority # => #<MyAuthority:0xbad1dea>
23
+ #
24
+ def authority
25
+ Qa::LDF::Authority.for(namespace: authority_namespace)
26
+ end
27
+
28
+ ##
29
+ # @return [String] the namespace for the authority used by this model
30
+ # instance.
31
+ #
32
+ # @example
33
+ # # Regester the namespace with the authority class.
34
+ # class MyAuthority < Authority
35
+ # register_namespace(namespace: 'http://example.com/my_authority#',
36
+ # klass: self)
37
+ # end
38
+ #
39
+ # model = Qa::LDF::Model.new('http://example.com/my_authority#moomin')
40
+ #
41
+ # model.authority_namespace # => 'http://example.com/my_authority#'
42
+ #
43
+ def authority_namespace
44
+ return Qa::LDF::Authority.namespace if node?
45
+
46
+ Qa::LDF::Authority
47
+ .namespaces
48
+ .find { |ns| to_uri.start_with?(ns) }
49
+ end
50
+
51
+ ##
52
+ # Fetches from the cache client.
53
+ #
54
+ # @see ActiveTriples::RDFSource#fetch
55
+ def fetch
56
+ insert(authority.graph(to_uri))
57
+ rescue => e
58
+ raise e unless block_given?
59
+ yield(self)
60
+ ensure
61
+ self
62
+ end
63
+
10
64
  class << self
11
65
  ##
12
66
  # Builds a model from the graph.
@@ -25,6 +25,19 @@ shared_examples 'an ldf authority' do
25
25
  end
26
26
  end
27
27
 
28
+ it 'is registered' do
29
+ expect(Qa::LDF::Authority.for(namespace: described_class.namespace))
30
+ .to be_a described_class
31
+ end
32
+
33
+ describe '.namespace' do
34
+ it 'returns a namespace string' do
35
+ unless described_class.namespace == ''
36
+ expect(RDF::URI(described_class.namespace)).to be_valid
37
+ end
38
+ end
39
+ end
40
+
28
41
  describe '#all' do
29
42
  it 'is enumerable' do
30
43
  expect(authority.all).to respond_to :each
@@ -72,6 +85,15 @@ shared_examples 'an ldf authority' do
72
85
  it 'maps to a json-friendly hash' do
73
86
  expect { JSON.generate(authority.find(ldf_uri)) }.not_to raise_error
74
87
  end
88
+
89
+ context 'when dataset is not assigned' do
90
+ before { authority.dataset = nil }
91
+
92
+ it 'finds a uri' do
93
+ expect(authority.find(ldf_uri))
94
+ .to include id: ldf_uri.to_s, label: ldf_label
95
+ end
96
+ end
75
97
  end
76
98
 
77
99
  describe '#mapper' do
@@ -5,5 +5,111 @@ shared_examples 'an ldf model' do
5
5
  "#{model} must be defined with `let(:model)` before " \
6
6
  'using Qa::LDF::Model shared examples.'
7
7
  end
8
+
9
+ unless defined?(id)
10
+ raise ArgumentError,
11
+ "#{id} must be defined with `let(:id)` before " \
12
+ 'using Qa::LDF::Model shared examples.'
13
+ end
14
+ end
15
+
16
+ let(:label) { 'moomin' }
17
+
18
+ define :be_term do |expected|
19
+ match { |actual| expect(actual.to_term).to eq expected }
20
+ end
21
+
22
+ describe '.from_graph' do
23
+ let(:graph) do
24
+ graph = RDF::Graph.new
25
+ graph.insert(*statements)
26
+ graph
27
+ end
28
+
29
+ let(:statements) do
30
+ [RDF::Statement(RDF::URI(id), RDF::Vocab::SKOS.prefLabel, label),
31
+ RDF::Statement(RDF::URI(id), RDF::Vocab::DC.title, 'Moomin Papa'),
32
+ RDF::Statement(RDF::Node.new, RDF::Vocab::DC.title, 'Moomin Papa')]
33
+ end
34
+
35
+ it 'builds a model with the id as uri' do
36
+ expect(described_class.from_graph(uri: id, graph: graph))
37
+ .to be_term RDF::URI(id)
38
+ end
39
+
40
+ it 'builds a model with the label as label' do
41
+ expect(described_class.from_graph(uri: id, graph: graph))
42
+ .to have_attributes rdf_label: a_collection_containing_exactly(label)
43
+ end
44
+
45
+ it 'contains passed graph' do
46
+ expect(described_class.from_graph(uri: id, graph: graph).statements)
47
+ .to include(*statements)
48
+ end
49
+ end
50
+
51
+ describe '.from_qa_result' do
52
+ let(:json_hash) { { id: id, label: label, papa: 'moomin papa' } }
53
+
54
+ it 'builds a model with the id as uri' do
55
+ expect(described_class.from_qa_result(qa_result: json_hash))
56
+ .to be_term RDF::URI(id)
57
+ end
58
+
59
+ it 'builds a model with the label as label' do
60
+ expect(described_class.from_qa_result(qa_result: json_hash))
61
+ .to have_attributes rdf_label: a_collection_containing_exactly(label)
62
+ end
63
+ end
64
+
65
+ describe '#authority' do
66
+ it 'returns an authority' do
67
+ expect(model.authority).to respond_to(:find).with(1).argument
68
+ end
69
+
70
+ it 'returns the base authority for undefined namespace' do
71
+ expect(model.authority.dataset)
72
+ .to eq Qa::LDF::Authority::DEFAULT_DATASET_NAME
73
+ end
74
+
75
+ context 'with a namespaced uri' do
76
+ let(:ns) { 'http://id.loc.gov/authorities/names/' }
77
+ let(:id) { ns + 'n83175996' }
78
+ let(:klass) { Class.new(Qa::LDF::Authority) }
79
+
80
+ before do
81
+ Qa::LDF::Authority.register_namespace(namespace: ns, klass: klass)
82
+ end
83
+
84
+ after { Qa::LDF::Authority.reset_namespaces }
85
+
86
+ it 'knows its authority' do
87
+ expect(model.authority).to be_a klass
88
+ end
89
+ end
90
+ end
91
+
92
+ describe '#fetch' do
93
+ let(:ns) { 'http://example.com/moomin_auth/' }
94
+ let(:id) { ns + 'moomin' }
95
+
96
+ let(:klass) do
97
+ Class.new(Qa::LDF::Authority) do
98
+ register_namespace(namespace: 'http://example.com/moomin_auth',
99
+ klass: self)
100
+
101
+ def initialize
102
+ @client = FakeClient.new
103
+ end
104
+ end
105
+ end
106
+
107
+ before { klass }
108
+
109
+ it 'populates the graph from the client' do
110
+ expect { model.fetch }
111
+ .to change { model.statements }
112
+ .from(be_empty)
113
+ end
8
114
  end
9
115
  end
data/qa-ldf.gemspec CHANGED
@@ -24,6 +24,7 @@ Gem::Specification.new do |spec|
24
24
 
25
25
  spec.add_dependency 'qa', '~> 0.11.0'
26
26
 
27
+ spec.add_development_dependency 'active-fedora', '~> 11.0'
27
28
  spec.add_development_dependency 'guard', '~> 2.14'
28
29
  spec.add_development_dependency 'ld_cache_fragment', '~> 0.1'
29
30
  spec.add_development_dependency 'rake', '~> 12.0'
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+ require 'active_fedora'
3
+
4
+ # Tests the main boundaries to ActiveFedora. This should be considered
5
+ # incomplete, at best. We haven't exhastively analyzed relevant AF behavior.
6
+ describe 'ActiveFedora integration' do
7
+ subject(:af_model) { af_model_class.new }
8
+
9
+ let(:value_uri) { RDF::URI('http://id.loc.gov/authorities/names/n83175996') }
10
+ let(:af_model_class) do
11
+ Class.new(ActiveFedora::Base) do
12
+ property :programmer,
13
+ predicate: RDF::Vocab::MARCRelators.prg,
14
+ class_name: Qa::LDF::Model
15
+ end
16
+ end
17
+
18
+ before { af_model.programmer = [value_uri] }
19
+
20
+ define :be_term do |expected|
21
+ match { |actual| expect(actual.to_term).to eq expected }
22
+ end
23
+
24
+ it 'builds value as a model' do
25
+ expect(af_model.programmer)
26
+ .to contain_exactly(an_instance_of(Qa::LDF::Model))
27
+ end
28
+
29
+ it 'builds value with correct rdf term' do
30
+ af_model.programmer = [value_uri]
31
+ expect(af_model.programmer).to contain_exactly(be_term(value_uri))
32
+ end
33
+
34
+ describe 'retrieving from the server' do
35
+ include_context 'with cache server'
36
+
37
+ let(:statement) do
38
+ RDF::Statement(value_uri, RDF::URI('http://example.com/p'), :o)
39
+ end
40
+
41
+ let(:stub) do
42
+ stub_request(:get, value_uri.to_s)
43
+ .to_return(body: statement.to_ntriples,
44
+ headers: { 'Content-Type' => 'text/turtle' })
45
+ end
46
+
47
+ # touch the stub
48
+ before { stub }
49
+
50
+ it 'value can retrieve its graph from the cache' do
51
+ af_model.programmer = [value_uri]
52
+
53
+ af_model.programmer.first.fetch
54
+ expect(stub).to have_been_requested
55
+ end
56
+ end
57
+ end
@@ -14,6 +14,16 @@ describe Qa::LDF::Authority do
14
14
  auth
15
15
  end
16
16
 
17
+ shared_context 'with registered namespaces' do
18
+ let(:ns) { 'http://example.com/auth/' }
19
+ let(:subclass) { Class.new(described_class) }
20
+
21
+ before do
22
+ subclass.register_namespace(namespace: ns, klass: subclass)
23
+ end
24
+ after { subclass.reset_namespaces }
25
+ end
26
+
17
27
  let(:ldf_label) { 'Marble Island (Nunavut)' }
18
28
  let(:ldf_uri) { 'http://id.loc.gov/authorities/subjects/sh2004002557' }
19
29
 
@@ -21,6 +31,57 @@ describe Qa::LDF::Authority do
21
31
  [RDF::URI(ldf_uri), RDF::Vocab::SKOS.prefLabel, RDF::Literal(ldf_label)]
22
32
  end
23
33
 
34
+ describe '.for' do
35
+ let(:ns) { 'http://example.com/auth/' }
36
+ let(:subclass) { Class.new(described_class) }
37
+
38
+ before { subclass.register_namespace(namespace: ns, klass: subclass) }
39
+ after { subclass.reset_namespaces }
40
+
41
+ it 'returns registered classes from namespaces' do
42
+ expect(described_class.for(namespace: ns)).to be_a subclass
43
+ end
44
+ end
45
+
46
+ describe '.namespaces' do
47
+ it 'returns empty namespaces list' do
48
+ expect(described_class.namespaces).to be_empty
49
+ end
50
+
51
+ context 'when namespaces are registered' do
52
+ include_context 'with registered namespaces'
53
+
54
+ it 'returns populated namespaces list' do
55
+ expect(described_class.namespaces).to contain_exactly ns
56
+ end
57
+ end
58
+ end
59
+
60
+ describe '.register_namespace' do
61
+ let(:ns) { 'http://example.com/auth/' }
62
+ let(:subclass) { Class.new(described_class) }
63
+
64
+ after { subclass.reset_namespaces }
65
+
66
+ it 'adds to namespace registry' do
67
+ expect { subclass.register_namespace(namespace: ns, klass: subclass) }
68
+ .to change { described_class.for(namespace: ns) }
69
+ .from(described_class)
70
+ .to(subclass)
71
+ end
72
+ end
73
+
74
+ describe '.reset_namespaces' do
75
+ include_context 'with registered namespaces'
76
+
77
+ it 'clears the namespace list' do
78
+ expect { subclass.reset_namespaces }
79
+ .to change { described_class.for(namespace: ns) }
80
+ .from(subclass)
81
+ .to(described_class)
82
+ end
83
+ end
84
+
24
85
  describe '#dataset' do
25
86
  it 'defaults an empty string symbol' do
26
87
  expect(described_class.new.dataset).to eq :''
@@ -1,53 +1,8 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Qa::LDF::Model do
4
- define :be_term do |expected|
5
- match { |actual| expect(actual.to_term).to eq expected }
6
- end
4
+ subject(:model) { described_class.new(id) }
5
+ let(:id) { 'http://example.com/authority/moomin' }
7
6
 
8
- let(:id) { 'http://example.com/authority/moomin' }
9
- let(:label) { 'moomin' }
10
-
11
- describe '.from_graph' do
12
- let(:graph) do
13
- graph = RDF::Graph.new
14
- graph.insert(*statements)
15
- graph
16
- end
17
-
18
- let(:statements) do
19
- [RDF::Statement(RDF::URI(id), RDF::Vocab::SKOS.prefLabel, label),
20
- RDF::Statement(RDF::URI(id), RDF::Vocab::DC.title, 'Moomin Papa'),
21
- RDF::Statement(RDF::Node.new, RDF::Vocab::DC.title, 'Moomin Papa')]
22
- end
23
-
24
- it 'builds a model with the id as uri' do
25
- expect(described_class.from_graph(uri: id, graph: graph))
26
- .to be_term RDF::URI(id)
27
- end
28
-
29
- it 'builds a model with the label as label' do
30
- expect(described_class.from_graph(uri: id, graph: graph))
31
- .to have_attributes rdf_label: a_collection_containing_exactly(label)
32
- end
33
-
34
- it 'contains passed graph' do
35
- expect(described_class.from_graph(uri: id, graph: graph).statements)
36
- .to include(*statements)
37
- end
38
- end
39
-
40
- describe '.from_qa_result' do
41
- let(:json_hash) { { id: id, label: label, papa: 'moomin papa' } }
42
-
43
- it 'builds a model with the id as uri' do
44
- expect(described_class.from_qa_result(qa_result: json_hash))
45
- .to be_term RDF::URI(id)
46
- end
47
-
48
- it 'builds a model with the label as label' do
49
- expect(described_class.from_qa_result(qa_result: json_hash))
50
- .to have_attributes rdf_label: a_collection_containing_exactly(label)
51
- end
52
- end
7
+ it_behaves_like 'an ldf model'
53
8
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: qa-ldf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.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-02-23 00:00:00.000000000 Z
11
+ date: 2017-03-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: qa
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: 0.11.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: active-fedora
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '11.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '11.0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: guard
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -177,6 +191,7 @@ files:
177
191
  - lib/qa/ldf/version.rb
178
192
  - qa-ldf.gemspec
179
193
  - spec/contracts/qa_loc_as_search_service_spec.rb
194
+ - spec/integration/active_fedora_model_spec.rb
180
195
  - spec/qa/ldf/authorities/lc_names_spec.rb
181
196
  - spec/qa/ldf/authority_spec.rb
182
197
  - spec/qa/ldf/client_spec.rb
@@ -217,6 +232,7 @@ summary: Provides a bridge between questioning authority and a caching linked da
217
232
  fragment for URI authorities.
218
233
  test_files:
219
234
  - spec/contracts/qa_loc_as_search_service_spec.rb
235
+ - spec/integration/active_fedora_model_spec.rb
220
236
  - spec/qa/ldf/authorities/lc_names_spec.rb
221
237
  - spec/qa/ldf/authority_spec.rb
222
238
  - spec/qa/ldf/client_spec.rb