linked_vocabs 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,53 @@
1
+ module OregonDigital::RDF
2
+ module DeepFetch
3
+ extend ActiveSupport::Concern
4
+ def fetch_external
5
+ controlled_properties.each do |property|
6
+ get_values(property).each do |value|
7
+ resource = value.respond_to?(:resource) ? value.resource : value
8
+ next unless resource.kind_of?(ActiveFedora::Rdf::Resource)
9
+ fetch_value(resource) if resource.kind_of? ActiveFedora::Rdf::Resource
10
+ resource.persist! unless value.kind_of?(ActiveFedora::Base)
11
+ fix_fedora_index(property, resource)
12
+ end
13
+ end
14
+ end
15
+
16
+ def fix_fedora_index(property, resource)
17
+ # Get assets which have this property set, but don't have the right label.
18
+ if resource.rdf_label.first.blank? || resource.rdf_label.first.to_s == resource.rdf_subject.to_s
19
+ assets = ActiveFedora::Base.where("#{Solrizer.solr_name(apply_prefix(property), :facetable)}:#{RSolr.escape(resource.rdf_subject.to_s)} AND #{Solrizer.solr_name(apply_prefix("#{property}_label"), :facetable)}:[\"\" TO *]")
20
+ else
21
+ assets = ActiveFedora::Base.where(
22
+ Solrizer.solr_name(apply_prefix(property), :facetable) => resource.rdf_subject.to_s,
23
+ "-#{Solrizer.solr_name(apply_prefix("#{property}_label"), :facetable)}" => "#{resource.rdf_label.first}$#{resource.rdf_subject.to_s}"
24
+ )
25
+ end
26
+ assets.each do |a|
27
+ a.skip_queue = 1 if a.respond_to?(:skip_queue=)
28
+ a.update_index
29
+ end
30
+ end
31
+
32
+ protected
33
+
34
+ def controlled_properties
35
+ @controlled_properties ||= self.class.properties.each_with_object([]) do |(key, value), arr|
36
+ if value["class_name"] && (value["class_name"] < ActiveFedora::Rdf::Resource || value["class_name"].new.resource.class < ActiveFedora::Rdf::Resource)
37
+ arr << key
38
+ end
39
+ end
40
+ end
41
+
42
+ def fetch_value(value)
43
+ redis_connection.cache("fetch-cache:#{value.rdf_subject.to_s}", 7.days) do
44
+ value.fetch
45
+ Time.current.to_s
46
+ end
47
+ end
48
+
49
+ def redis_connection
50
+ @redis ||= Redis.new
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,7 @@
1
+ require 'linked_vocabs/validators/authority_validator'
2
+ require 'linked_vocabs/validators/property_validator'
3
+
4
+ module LinkedVocabs::Validators
5
+ # autoload :AuthorityValidator, 'linked_vocabs/validators/authority_validator'
6
+ # autoload :PropertyValidator, 'linked_vocabs/validators/property_validator'
7
+ end
@@ -0,0 +1,11 @@
1
+ require 'active_model'
2
+
3
+ module LinkedVocabs::Validators
4
+ class AuthorityValidator < ActiveModel::Validator
5
+ def validate(record)
6
+ unless record.in_vocab?
7
+ record.errors.add :base, "#{record.rdf_subject.to_s} is not a term in a controlled vocabulary #{record.class.vocabularies}"
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,21 @@
1
+ require 'active_model'
2
+
3
+ module LinkedVocabs::Validators
4
+ class PropertyValidator < ActiveModel::EachValidator
5
+ def validate_each(record, attribute, values)
6
+ values.each do |v|
7
+ unless v.try(:in_vocab?)
8
+ term = v.try(:rdf_subject) || v
9
+ vocabularies = record.class.properties[attribute].class_name.vocabularies.keys
10
+ record.errors.add :base, "value `#{term} for `#{attribute}` property is not a term in a controlled vocabulary #{vocabularies.join(', ')}"
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ module ActiveModel::Validations::HelperMethods
18
+ def validates_vocabulary_of(*attr_names)
19
+ validates_with LinkedVocabs::Validators::PropertyValidator, :attributes => attr_names
20
+ end
21
+ end
@@ -0,0 +1,3 @@
1
+ module LinkedVocabs
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,6 @@
1
+ module LinkedVocabs::Vocabularies
2
+ autoload :DCMITYPE, 'linked_vocabs/vocabularies/dcmitype'
3
+ autoload :GEONAMES, 'linked_vocabs/vocabularies/geonames'
4
+ autoload :LCSH, 'linked_vocabs/vocabularies/lcsh'
5
+ autoload :ISO_630_2, 'linked_vocabs/vocabularies/iso_639_2'
6
+ end
@@ -0,0 +1,138 @@
1
+ # -*- encoding: utf-8 -*-
2
+ # This file generated automatically using vocab-fetch from http://dublincore.org/2012/06/14/dctype.rdf
3
+ require 'rdf'
4
+ module RDF
5
+ class DCMITYPE < RDF::StrictVocabulary("http://purl.org/dc/dcmitype/")
6
+
7
+ # Class definitions
8
+ term :Collection,
9
+ comment: %(An aggregation of resources.).freeze,
10
+ "dc:description" => %(A collection is described as a group; its parts may also be separately described.).freeze,
11
+ "dc:hasVersion" => %(http://dublincore.org/usage/terms/history/#Collection-003).freeze,
12
+ "dc:issued" => %(2000-07-11).freeze,
13
+ "dc:modified" => %(2008-01-14).freeze,
14
+ "http://purl.org/dc/dcam/memberOf" => %(dc:DCMIType).freeze,
15
+ label: "Collection".freeze,
16
+ "rdfs:isDefinedBy" => %(dcmitype:).freeze,
17
+ type: "rdfs:Class".freeze
18
+ term :Dataset,
19
+ comment: %(Data encoded in a defined structure.).freeze,
20
+ "dc:description" => %(Examples include lists, tables, and databases. A dataset may be useful for direct machine processing.).freeze,
21
+ "dc:hasVersion" => %(http://dublincore.org/usage/terms/history/#Dataset-003).freeze,
22
+ "dc:issued" => %(2000-07-11).freeze,
23
+ "dc:modified" => %(2008-01-14).freeze,
24
+ "http://purl.org/dc/dcam/memberOf" => %(dc:DCMIType).freeze,
25
+ label: "Dataset".freeze,
26
+ "rdfs:isDefinedBy" => %(dcmitype:).freeze,
27
+ type: "rdfs:Class".freeze
28
+ term :Event,
29
+ comment: %(A non-persistent, time-based occurrence.).freeze,
30
+ "dc:description" => %(Metadata for an event provides descriptive information that is the basis for discovery of the purpose, location, duration, and responsible agents associated with an event. Examples include an exhibition, webcast, conference, workshop, open day, performance, battle, trial, wedding, tea party, conflagration.).freeze,
31
+ "dc:hasVersion" => %(http://dublincore.org/usage/terms/history/#Event-003).freeze,
32
+ "dc:issued" => %(2000-07-11).freeze,
33
+ "dc:modified" => %(2008-01-14).freeze,
34
+ "http://purl.org/dc/dcam/memberOf" => %(dc:DCMIType).freeze,
35
+ label: "Event".freeze,
36
+ "rdfs:isDefinedBy" => %(dcmitype:).freeze,
37
+ type: "rdfs:Class".freeze
38
+ term :Image,
39
+ comment: %(A visual representation other than text.).freeze,
40
+ "dc:description" => %(Examples include images and photographs of physical objects, paintings, prints, drawings, other images and graphics, animations and moving pictures, film, diagrams, maps, musical notation. Note that Image may include both electronic and physical representations.).freeze,
41
+ "dc:hasVersion" => %(http://dublincore.org/usage/terms/history/#Image-004).freeze,
42
+ "dc:issued" => %(2000-07-11).freeze,
43
+ "dc:modified" => %(2008-01-14).freeze,
44
+ "http://purl.org/dc/dcam/memberOf" => %(dc:DCMIType).freeze,
45
+ label: "Image".freeze,
46
+ "rdfs:isDefinedBy" => %(dcmitype:).freeze,
47
+ type: "rdfs:Class".freeze
48
+ term :InteractiveResource,
49
+ comment: %(A resource requiring interaction from the user to be understood, executed, or experienced.).freeze,
50
+ "dc:description" => %(Examples include forms on Web pages, applets, multimedia learning objects, chat services, or virtual reality environments.).freeze,
51
+ "dc:hasVersion" => %(http://dublincore.org/usage/terms/history/#InteractiveResource-003).freeze,
52
+ "dc:issued" => %(2000-07-11).freeze,
53
+ "dc:modified" => %(2008-01-14).freeze,
54
+ "http://purl.org/dc/dcam/memberOf" => %(dc:DCMIType).freeze,
55
+ label: "Interactive Resource".freeze,
56
+ "rdfs:isDefinedBy" => %(dcmitype:).freeze,
57
+ type: "rdfs:Class".freeze
58
+ term :MovingImage,
59
+ comment: %(A series of visual representations imparting an impression of motion when shown in succession.).freeze,
60
+ "dc:description" => %(Examples include animations, movies, television programs, videos, zoetropes, or visual output from a simulation. Instances of the type Moving Image must also be describable as instances of the broader type Image.).freeze,
61
+ "dc:hasVersion" => %(http://dublincore.org/usage/terms/history/#MovingImage-003).freeze,
62
+ "dc:issued" => %(2003-11-18).freeze,
63
+ "dc:modified" => %(2008-01-14).freeze,
64
+ "http://purl.org/dc/dcam/memberOf" => %(dc:DCMIType).freeze,
65
+ label: "Moving Image".freeze,
66
+ "rdfs:isDefinedBy" => %(dcmitype:).freeze,
67
+ subClassOf: "dcmitype:Image".freeze,
68
+ type: "rdfs:Class".freeze
69
+ term :PhysicalObject,
70
+ comment: %(An inanimate, three-dimensional object or substance.).freeze,
71
+ "dc:description" => %(Note that digital representations of, or surrogates for, these objects should use Image, Text or one of the other types.).freeze,
72
+ "dc:hasVersion" => %(http://dublincore.org/usage/terms/history/#PhysicalObject-003).freeze,
73
+ "dc:issued" => %(2002-07-13).freeze,
74
+ "dc:modified" => %(2008-01-14).freeze,
75
+ "http://purl.org/dc/dcam/memberOf" => %(dc:DCMIType).freeze,
76
+ label: "Physical Object".freeze,
77
+ "rdfs:isDefinedBy" => %(dcmitype:).freeze,
78
+ type: "rdfs:Class".freeze
79
+ term :Service,
80
+ comment: %(A system that provides one or more functions.).freeze,
81
+ "dc:description" => %(Examples include a photocopying service, a banking service, an authentication service, interlibrary loans, a Z39.50 or Web server.).freeze,
82
+ "dc:hasVersion" => %(http://dublincore.org/usage/terms/history/#Service-003).freeze,
83
+ "dc:issued" => %(2000-07-11).freeze,
84
+ "dc:modified" => %(2008-01-14).freeze,
85
+ "http://purl.org/dc/dcam/memberOf" => %(dc:DCMIType).freeze,
86
+ label: "Service".freeze,
87
+ "rdfs:isDefinedBy" => %(dcmitype:).freeze,
88
+ type: "rdfs:Class".freeze
89
+ term :Software,
90
+ comment: %(A computer program in source or compiled form.).freeze,
91
+ "dc:description" => %(Examples include a C source file, MS-Windows .exe executable, or Perl script.).freeze,
92
+ "dc:hasVersion" => %(http://dublincore.org/usage/terms/history/#Software-003).freeze,
93
+ "dc:issued" => %(2000-07-11).freeze,
94
+ "dc:modified" => %(2008-01-14).freeze,
95
+ "http://purl.org/dc/dcam/memberOf" => %(dc:DCMIType).freeze,
96
+ label: "Software".freeze,
97
+ "rdfs:isDefinedBy" => %(dcmitype:).freeze,
98
+ type: "rdfs:Class".freeze
99
+ term :Sound,
100
+ comment: %(A resource primarily intended to be heard.).freeze,
101
+ "dc:description" => %(Examples include a music playback file format, an audio compact disc, and recorded speech or sounds.).freeze,
102
+ "dc:hasVersion" => %(http://dublincore.org/usage/terms/history/#Sound-003).freeze,
103
+ "dc:issued" => %(2000-07-11).freeze,
104
+ "dc:modified" => %(2008-01-14).freeze,
105
+ "http://purl.org/dc/dcam/memberOf" => %(dc:DCMIType).freeze,
106
+ label: "Sound".freeze,
107
+ "rdfs:isDefinedBy" => %(dcmitype:).freeze,
108
+ type: "rdfs:Class".freeze
109
+ term :StillImage,
110
+ comment: %(A static visual representation.).freeze,
111
+ "dc:description" => %(Examples include paintings, drawings, graphic designs, plans and maps. Recommended best practice is to assign the type Text to images of textual materials. Instances of the type Still Image must also be describable as instances of the broader type Image.).freeze,
112
+ "dc:hasVersion" => %(http://dublincore.org/usage/terms/history/#StillImage-003).freeze,
113
+ "dc:issued" => %(2003-11-18).freeze,
114
+ "dc:modified" => %(2008-01-14).freeze,
115
+ "http://purl.org/dc/dcam/memberOf" => %(dc:DCMIType).freeze,
116
+ label: "Still Image".freeze,
117
+ "rdfs:isDefinedBy" => %(dcmitype:).freeze,
118
+ subClassOf: "dcmitype:Image".freeze,
119
+ type: "rdfs:Class".freeze
120
+ term :Text,
121
+ comment: %(A resource consisting primarily of words for reading.).freeze,
122
+ "dc:description" => %(Examples include books, letters, dissertations, poems, newspapers, articles, archives of mailing lists. Note that facsimiles or images of texts are still of the genre Text.).freeze,
123
+ "dc:hasVersion" => %(http://dublincore.org/usage/terms/history/#Text-003).freeze,
124
+ "dc:issued" => %(2000-07-11).freeze,
125
+ "dc:modified" => %(2008-01-14).freeze,
126
+ "http://purl.org/dc/dcam/memberOf" => %(dc:DCMIType).freeze,
127
+ label: "Text".freeze,
128
+ "rdfs:isDefinedBy" => %(dcmitype:).freeze,
129
+ type: "rdfs:Class".freeze
130
+
131
+ # Extra definitions
132
+ term :"",
133
+ "dc:modified" => %(2012-06-14).freeze,
134
+ "dc:publisher" => %(http://purl.org/dc/aboutdcmi#DCMI).freeze,
135
+ "dc:title" => %(DCMI Type Vocabulary).freeze,
136
+ label: "".freeze
137
+ end
138
+ end
@@ -0,0 +1,8 @@
1
+ # -*- encoding: utf-8 -*-
2
+ # This file generated automatically using vocab-fetch from http://sws.geonames.org/
3
+ require 'rdf'
4
+ module RDF
5
+ class GEONAMES < RDF::Vocabulary("http://sws.geonames.org/")
6
+ # terms not fetched by vocab-fetch
7
+ end
8
+ end
data/lib/rdf/lcsh.rb ADDED
@@ -0,0 +1,8 @@
1
+ # -*- encoding: utf-8 -*-
2
+ # This file generated automatically using vocab-fetch from http://id.loc.gov/authorities/subjects/
3
+ require 'rdf'
4
+ module RDF
5
+ class LCSH < RDF::Vocabulary("http://id.loc.gov/authorities/subjects/")
6
+ # terms not fetched by vocab-fetch
7
+ end
8
+ end
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'linked_vocabs/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "linked_vocabs"
8
+ spec.version = LinkedVocabs::VERSION
9
+ spec.authors = ["Tom Johnson"]
10
+ spec.email = ["johnson.tom@gmail.com"]
11
+ spec.description = 'Linked Data Controlled Vocabularies for ActiveFedora::Rdf.'
12
+ spec.summary = 'Linked Data Controlled Vocabularies for ActiveFedora::Rdf.'
13
+ spec.license = "APACHE2"
14
+
15
+ spec.files = `git ls-files`.split($/)
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler"
21
+ spec.add_development_dependency "rspec"
22
+ spec.add_development_dependency "guard"
23
+ spec.add_development_dependency "guard-rspec"
24
+
25
+ spec.add_dependency 'rake'
26
+ spec.add_dependency 'active-triples', '>=0.1.0'
27
+ spec.add_dependency 'rdf', '>=1.1.2.1'
28
+ spec.add_dependency 'sparql'
29
+ spec.add_dependency 'sparql-client'
30
+
31
+ end
@@ -0,0 +1,170 @@
1
+ require 'spec_helper'
2
+
3
+ describe LinkedVocabs::Controlled do
4
+ before(:each) do
5
+ ActiveTriples::Repositories.add_repository :default, RDF::Repository.new
6
+ class DummyAuthority < ActiveTriples::Resource
7
+ include LinkedVocabs::Controlled
8
+ configure :repository => :default
9
+ use_vocabulary :dcmitype
10
+ property :title, :predicate => RDF::DC.title
11
+ end
12
+ end
13
+
14
+ after(:each) do
15
+ Object.send(:remove_const, 'DummyAuthority') if Object
16
+ ActiveTriples::Repositories.clear_repositories!
17
+ end
18
+
19
+ subject { DummyAuthority }
20
+
21
+ describe 'vocabulary registration' do
22
+ it 'should add vocabulary' do
23
+ expect(subject.vocabularies).to include :dcmitype
24
+ end
25
+ it 'should find its vocabulary class' do
26
+ expect(subject.vocabularies[:dcmitype][:class]).to eq RDF::DCMITYPE
27
+ end
28
+ it 'should allow multiple vocabularies' do
29
+ subject.use_vocabulary :lcsh
30
+ expect(subject.vocabularies).to include :dcmitype, :lcsh
31
+ end
32
+ end
33
+
34
+ describe '#list_terms' do
35
+ it 'should list terms from registered StrictVocabs' do
36
+ subject.vocabularies.each do |name, vocab|
37
+ expect([vocab[:class].Image] - subject.list_terms).to be_empty
38
+ end
39
+ end
40
+ it 'should list only terms from registered StrictVocabs' do
41
+ terms = []
42
+ subject.vocabularies.each do |name, vocab|
43
+ terms += vocab[:class].properties
44
+ end
45
+ expect(subject.list_terms - terms).to be_empty
46
+ end
47
+ end
48
+
49
+ describe '#rdf_label' do
50
+ subject {DummyAuthority.new}
51
+ context "when there are only plain labels" do
52
+ before do
53
+ subject.title = ["English", "French"]
54
+ end
55
+ it "should return both" do
56
+ expect(subject.title).to eq ["English", "French"]
57
+ end
58
+ end
59
+ # context "when there are english labels" do
60
+ # before do
61
+ # subject.title = RDF::Literal.new("English", :langauge => :en)
62
+ # end
63
+ # context "and plain labels" do
64
+ # before do
65
+ # subject.title = ["Plain", RDF::Literal.new("English", :language => :en)]
66
+ # end
67
+ # it "should return the english label" do
68
+ # expect(subject.rdf_label).to eq ["English"]
69
+ # end
70
+ # end
71
+ # context "and other language labels" do
72
+ # before do
73
+ # subject.title = [RDF::Literal.new("French", :language => :fr), RDF::Literal.new("English", :language => :en)]
74
+ # end
75
+ # it "should return the english label" do
76
+ # expect(subject.rdf_label).to eq ["English"]
77
+ # end
78
+ # end
79
+ # end
80
+ end
81
+
82
+ describe '#load_vocabularies' do
83
+ it 'should load data' do
84
+ subject.load_vocabularies
85
+ expect(subject.new('Image').has_subject?(RDF::URI('http://purl.org/dc/dcmitype/Image'))).to be_true
86
+ end
87
+ end
88
+
89
+ describe '#search' do
90
+ before do
91
+ image = subject.new('Image')
92
+ image << RDF::Statement(image.rdf_subject, RDF::SKOS.prefLabel, "Image")
93
+ image.persist!
94
+ end
95
+
96
+ it 'should return matches' do
97
+ expect(subject.new.search('Image').first[:id]).to eq RDF::URI('http://purl.org/dc/dcmitype/Image')
98
+ end
99
+ it 'should search case insensitively' do
100
+ expect(subject.new.search('ima').first[:id]).to eq RDF::URI('http://purl.org/dc/dcmitype/Image')
101
+ end
102
+ describe 'non-label matches' do
103
+ before do
104
+ doc = subject.new('Text')
105
+ doc << RDF::Statement(doc.rdf_subject, RDF::DC.description, "This is not an image!")
106
+ doc.persist!
107
+ end
108
+
109
+ it 'should return non-label matches if no label matches exist' do
110
+ image = subject.new('Image')
111
+ image.clear
112
+ image.persist!
113
+ expect(subject.new.search('ima').map { |result| result[:id] } ).to include subject.new('Text').rdf_subject
114
+ end
115
+ it 'should not return non-label matches if label matches exist' do
116
+ expect(subject.new.search('ima').map { |result| result[:id] } ).not_to include subject.new('Text').rdf_subject
117
+ end
118
+ end
119
+ end
120
+
121
+ describe 'uris' do
122
+ it 'should use a vocabulary uri' do
123
+ dummy = DummyAuthority.new('Image')
124
+ expect(dummy.rdf_subject).to eq RDF::DCMITYPE.Image
125
+ end
126
+ it 'should accept a full uri' do
127
+ dummy = DummyAuthority.new(RDF::DCMITYPE.Image)
128
+ expect(dummy.rdf_subject).to eq RDF::DCMITYPE.Image
129
+ end
130
+ it 'should accept a string for a full uri' do
131
+ dummy = DummyAuthority.new(RDF::DCMITYPE.Image.to_s)
132
+ expect(dummy.rdf_subject).to eq RDF::DCMITYPE.Image
133
+ end
134
+ it 'raises an error if the term is not in the vocabulary' do
135
+ expect{ DummyAuthority.new('FakeTerm') }.to raise_error
136
+ end
137
+ it 'is invalid if the uri is not in the vocabulary' do
138
+ d = DummyAuthority.new(RDF::URI('http://example.org/blah'))
139
+ expect(d).not_to be_valid
140
+ end
141
+ it 'is invalid if the uri string is not in the vocabulary' do
142
+ d = DummyAuthority.new('http://example.org/blah')
143
+ expect(d).not_to be_valid
144
+ end
145
+ it 'is invalid if the uri string is not in the strict vocabulary but has vocab prefix' do
146
+ d = DummyAuthority.new(subject.vocabularies[:dcmitype][:prefix] + 'FakeTerm')
147
+ expect(d).not_to be_valid
148
+ end
149
+ it 'should raise an error if the uri string just the prefix' do
150
+ d = DummyAuthority.new(subject.vocabularies[:dcmitype][:prefix])
151
+ expect(d).not_to be_valid
152
+ end
153
+
154
+ context 'with non-strict vocabularies' do
155
+ before(:each) do
156
+ DummyAuthority.use_vocabulary :geonames
157
+ end
158
+ it 'should make uri for terms not defined' do
159
+ expect(DummyAuthority.new('http://sws.geonames.org/FakeTerm').rdf_subject).to eq RDF::GEONAMES.FakeTerm
160
+ p end
161
+ it 'should use strict uri when one is available' do
162
+ expect(DummyAuthority.new('Image').rdf_subject).to eq RDF::DCMITYPE.Image
163
+ end
164
+ it 'should raise error for terms that are not clear' do
165
+ DummyAuthority.use_vocabulary :lcsh
166
+ expect{ DummyAuthority.new('FakeTerm').rdf_subject }.to raise_error
167
+ end
168
+ end
169
+ end
170
+ end