tripod 0.0.10 → 0.2
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.
- data/README.md +11 -5
- data/lib/tripod.rb +3 -0
- data/lib/tripod/attributes.rb +49 -57
- data/lib/tripod/components.rb +2 -0
- data/lib/tripod/errors.rb +1 -0
- data/lib/tripod/errors/field_not_present.rb +8 -0
- data/lib/tripod/fields.rb +4 -31
- data/lib/tripod/finders.rb +19 -12
- data/lib/tripod/persistence.rb +12 -0
- data/lib/tripod/predicates.rb +78 -0
- data/lib/tripod/repository.rb +20 -3
- data/lib/tripod/resource.rb +23 -24
- data/lib/tripod/serialization.rb +27 -0
- data/lib/tripod/sparql_client.rb +15 -4
- data/lib/tripod/version.rb +1 -1
- data/spec/app/models/person.rb +4 -0
- data/spec/tripod/attributes_spec.rb +79 -70
- data/spec/tripod/fields_spec.rb +15 -65
- data/spec/tripod/finders_spec.rb +14 -19
- data/spec/tripod/persistence_spec.rb +62 -58
- data/spec/tripod/predicates_spec.rb +82 -0
- data/spec/tripod/repository_spec.rb +53 -26
- data/spec/tripod/resource_spec.rb +20 -40
- data/spec/tripod/serialization_spec.rb +36 -0
- data/spec/tripod/state_spec.rb +2 -2
- data/tripod.gemspec +3 -2
- metadata +38 -20
data/lib/tripod/repository.rb
CHANGED
@@ -13,10 +13,18 @@ module Tripod::Repository
|
|
13
13
|
# person.hydrate!
|
14
14
|
#
|
15
15
|
# @example Hydrate the resource from a passed in graph
|
16
|
-
# person.hydrate!(my_graph)
|
16
|
+
# person.hydrate!(:graph => my_graph)
|
17
|
+
#
|
18
|
+
# @example Only hydrate certain predicates (ignored if a graph is passde in)
|
19
|
+
# person.hydrate!(:only => ["http://foo", "http://bar"])
|
20
|
+
# person.hydrate!(:only => "http://foo")
|
21
|
+
#
|
17
22
|
#
|
18
23
|
# @return [ RDF::Repository ] A reference to the repository for this instance.
|
19
|
-
def hydrate!(
|
24
|
+
def hydrate!(opts = {})
|
25
|
+
|
26
|
+
graph = opts[:graph]
|
27
|
+
only_hydrate_predicates = [opts[:only]].flatten # allow
|
20
28
|
|
21
29
|
# we require that the uri is set.
|
22
30
|
raise Tripod::Errors::UriNotSet.new() unless @uri
|
@@ -31,7 +39,16 @@ module Tripod::Repository
|
|
31
39
|
end
|
32
40
|
end
|
33
41
|
else
|
34
|
-
|
42
|
+
|
43
|
+
unless only_hydrate_predicates && only_hydrate_predicates.any?
|
44
|
+
triples = Tripod::SparqlClient::Query::describe("DESCRIBE <#{uri}>")
|
45
|
+
else
|
46
|
+
query = "CONSTRUCT { <#{uri}> ?p ?o } WHERE { <#{uri}> ?p ?o . FILTER ("
|
47
|
+
query += only_hydrate_predicates.map { |p| "?p = <#{p.to_s}>" }.join(" || ")
|
48
|
+
query += ")}"
|
49
|
+
triples = Tripod::SparqlClient::Query::construct(query)
|
50
|
+
end
|
51
|
+
|
35
52
|
@repository = RDF::Repository.new
|
36
53
|
RDF::Reader.for(:ntriples).new(triples) do |reader|
|
37
54
|
reader.each_statement do |statement|
|
data/lib/tripod/resource.rb
CHANGED
@@ -9,8 +9,12 @@ module Tripod::Resource
|
|
9
9
|
include Tripod::Components
|
10
10
|
|
11
11
|
included do
|
12
|
-
# every resource needs a graph
|
13
|
-
validates_presence_of :
|
12
|
+
# every resource needs a graph set.
|
13
|
+
validates_presence_of :graph_uri
|
14
|
+
# every instance of a resource has an rdf type field, which is set at the class level
|
15
|
+
class_attribute :_RDF_TYPE
|
16
|
+
# the Graph URI is set at the class level by default also, although this can be overridden in the constructor
|
17
|
+
class_attribute :_GRAPH_URI
|
14
18
|
end
|
15
19
|
|
16
20
|
attr_reader :new_record
|
@@ -18,7 +22,6 @@ module Tripod::Resource
|
|
18
22
|
attr_reader :uri
|
19
23
|
|
20
24
|
# Instantiate a +Resource+.
|
21
|
-
# Optionsally pass a uri
|
22
25
|
#
|
23
26
|
# @example Instantiate a new Resource
|
24
27
|
# Person.new('http://swirrl.com/ric.rdf#me')
|
@@ -26,29 +29,17 @@ module Tripod::Resource
|
|
26
29
|
# @param [ String, RDF::URI ] uri The uri of the resource.
|
27
30
|
#
|
28
31
|
# @return [ Resource ] A new +Resource+
|
29
|
-
def initialize(uri
|
30
|
-
|
31
|
-
@uri = RDF::URI(uri.to_s)
|
32
|
-
@graph_uri = RDF::URI(graph_uri.to_s) if graph_uri
|
33
|
-
@repository = RDF::Repository.new
|
34
|
-
end
|
32
|
+
def initialize(uri, graph_uri=nil)
|
33
|
+
raise Tripod::Errors::UriNotSet.new('uri missing') unless uri
|
34
|
+
@uri = RDF::URI(uri.to_s)
|
35
35
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
@uri = RDF::URI(new_uri.to_s)
|
40
|
-
else
|
41
|
-
@uri = nil
|
42
|
-
end
|
43
|
-
end
|
36
|
+
graph_uri ||= self.class._GRAPH_URI if self.class._GRAPH_URI
|
37
|
+
raise Tripod::Errors::UriNotSet.new('graph_uri missing') unless graph_uri
|
38
|
+
@graph_uri = RDF::URI(graph_uri)
|
44
39
|
|
45
|
-
|
46
|
-
|
47
|
-
if
|
48
|
-
@graph_uri = RDF::URI(new_graph_uri.to_s)
|
49
|
-
else
|
50
|
-
@graph_uri = nil
|
51
|
-
end
|
40
|
+
@repository = RDF::Repository.new
|
41
|
+
@new_record = true
|
42
|
+
self.rdf_type = self.class._RDF_TYPE if respond_to?(:rdf_type=)
|
52
43
|
end
|
53
44
|
|
54
45
|
# default comparison is via the uri
|
@@ -102,6 +93,14 @@ module Tripod::Resource
|
|
102
93
|
other.class == Class ? self <= other : other.is_a?(self)
|
103
94
|
end
|
104
95
|
|
96
|
+
def rdf_type(new_rdf_type)
|
97
|
+
field :rdf_type, RDF.type
|
98
|
+
self._RDF_TYPE = new_rdf_type
|
99
|
+
end
|
100
|
+
|
101
|
+
def graph_uri(new_graph_uri)
|
102
|
+
self._GRAPH_URI = new_graph_uri
|
103
|
+
end
|
105
104
|
end
|
106
105
|
|
107
106
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# This module defines behaviour for finders.
|
4
|
+
module Tripod::Serialization
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
# Serialises this resource's triples to rdf/xml
|
8
|
+
def to_rdf
|
9
|
+
@repository.dump(:rdfxml)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Serialises this resource's triples to turtle
|
13
|
+
def to_ttl
|
14
|
+
@repository.dump(:n3)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Serialises this resource's triples to n-triples
|
18
|
+
def to_nt
|
19
|
+
@repository.dump(:ntriples)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Serialises this resource's triples to JSON-LD
|
23
|
+
def to_json(opts={})
|
24
|
+
@repository.dump(:jsonld)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
data/lib/tripod/sparql_client.rb
CHANGED
@@ -26,10 +26,8 @@ module Tripod::SparqlClient
|
|
26
26
|
body = e.http_body
|
27
27
|
if body.start_with?('Error 400: Parse error:')
|
28
28
|
# TODO: this is a SPARQL parsing exception. Do something different.
|
29
|
-
puts body.inspect
|
30
29
|
raise e
|
31
30
|
else
|
32
|
-
puts body.inspect
|
33
31
|
raise e
|
34
32
|
end
|
35
33
|
end
|
@@ -68,6 +66,21 @@ module Tripod::SparqlClient
|
|
68
66
|
response = self.query(query, nil, {:accept=>accept_header})
|
69
67
|
return response.body
|
70
68
|
end
|
69
|
+
|
70
|
+
# Executes a CONSTRUCT +query+ against the SPARQL endpoint.
|
71
|
+
# Executes the +query+ and returns ntriples by default
|
72
|
+
#
|
73
|
+
# @example Run a CONSTRUCT query
|
74
|
+
# Tripod::SparqlClient::Query.select('CONSTRUCT <http://foo>')
|
75
|
+
#
|
76
|
+
# @param [ String ] query The query to run
|
77
|
+
# @param [ String ] accept_header The header to pass to the database.
|
78
|
+
#
|
79
|
+
# @return [ String ] the raw response from the endpoint
|
80
|
+
def self.construct(query, accept_header='application/n-triples')
|
81
|
+
response = self.query(query, nil, {:accept=>accept_header})
|
82
|
+
return response.body
|
83
|
+
end
|
71
84
|
end
|
72
85
|
|
73
86
|
module Update
|
@@ -92,10 +105,8 @@ module Tripod::SparqlClient
|
|
92
105
|
body = e.http_body
|
93
106
|
if body.start_with?('Error 400: Parse error:')
|
94
107
|
# TODO: this is a SPARQL parsing exception. Do something different.
|
95
|
-
puts body.inspect
|
96
108
|
raise e
|
97
109
|
else
|
98
|
-
puts body.inspect
|
99
110
|
raise e
|
100
111
|
end
|
101
112
|
end
|
data/lib/tripod/version.rb
CHANGED
data/spec/app/models/person.rb
CHANGED
@@ -2,7 +2,11 @@ class Person
|
|
2
2
|
|
3
3
|
include Tripod::Resource
|
4
4
|
|
5
|
+
rdf_type 'http://person'
|
6
|
+
graph_uri 'http://graph'
|
7
|
+
|
5
8
|
field :name, 'http://name'
|
9
|
+
field :knows, 'http://knows', :multivalued => true
|
6
10
|
field :aliases, 'http://alias', :multivalued => true
|
7
11
|
field :age, 'http://age', :datatype => RDF::XSD.integer
|
8
12
|
field :important_dates, 'http://importantdates', :datatype => RDF::XSD.date, :multivalued => true
|
@@ -1,103 +1,112 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
3
|
describe Tripod::Attributes do
|
4
|
+
describe ".read_attribute" do
|
5
|
+
let(:person) do
|
6
|
+
p = Person.new('http://barry')
|
7
|
+
p.name = 'Barry'
|
8
|
+
p
|
9
|
+
end
|
4
10
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
stmt = RDF::Statement.new
|
10
|
-
stmt.subject = RDF::URI.new(@uri)
|
11
|
-
stmt.predicate = RDF::URI.new('http://blog')
|
12
|
-
stmt.object = RDF::URI.new('http://blog1')
|
13
|
-
@graph << stmt
|
14
|
-
|
15
|
-
stmt2 = RDF::Statement.new
|
16
|
-
stmt2.subject = RDF::URI.new(@uri)
|
17
|
-
stmt2.predicate = RDF::URI.new('http://blog')
|
18
|
-
stmt2.object = RDF::URI.new('http://blog2')
|
19
|
-
@graph << stmt2
|
20
|
-
|
21
|
-
stmt3 = RDF::Statement.new
|
22
|
-
stmt3.subject = RDF::URI.new(@uri)
|
23
|
-
stmt3.predicate = RDF::URI.new('http://name')
|
24
|
-
stmt3.object = "ric"
|
25
|
-
@graph << stmt3
|
26
|
-
end
|
11
|
+
it "should read the given attribute" do
|
12
|
+
person[:name].should == 'Barry'
|
13
|
+
end
|
27
14
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
end
|
15
|
+
context "where the attribute is multi-valued" do
|
16
|
+
before do
|
17
|
+
person.aliases = ['Boz', 'Baz', 'Bez']
|
18
|
+
end
|
33
19
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
values.length.should == 2
|
38
|
-
values.first.should == RDF::URI('http://blog1')
|
39
|
-
values[1].should == RDF::URI('http://blog2')
|
20
|
+
it "should return an array" do
|
21
|
+
person[:aliases].should == ['Boz', 'Baz', 'Bez']
|
22
|
+
end
|
40
23
|
end
|
41
|
-
end
|
42
24
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
25
|
+
context "where field is given and single-valued" do
|
26
|
+
let(:field) { Person.send(:field_for, :hat_type, 'http://hat', {}) }
|
27
|
+
before do
|
28
|
+
person.stub(:read_predicate).with('http://hat').and_return(['fez'])
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should use the predicate name from the given field" do
|
32
|
+
person.should_receive(:read_predicate).with('http://hat').and_return(['fez'])
|
33
|
+
person.read_attribute(:hat_type, field)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should return a single value" do
|
37
|
+
person.read_attribute(:hat_type, field).should == 'fez'
|
38
|
+
end
|
49
39
|
end
|
50
|
-
end
|
51
40
|
|
52
|
-
|
41
|
+
context "where field is given and is multi-valued" do
|
42
|
+
let(:field) { Person.send(:field_for, :hat_types, 'http://hat', {multivalued: true}) }
|
43
|
+
before do
|
44
|
+
person.stub(:read_predicate).with('http://hat').and_return(['fez', 'bowler'])
|
45
|
+
end
|
53
46
|
|
54
|
-
|
55
|
-
|
56
|
-
person['http://name'] = 'richard'
|
57
|
-
person['http://name'].should == [RDF::Literal.new('richard')]
|
47
|
+
it "should return an array of values" do
|
48
|
+
person.read_attribute(:hat_types, field).should == ['fez', 'bowler']
|
58
49
|
end
|
59
50
|
end
|
60
51
|
|
61
|
-
context
|
62
|
-
it
|
63
|
-
|
64
|
-
person['http://name'].should == [RDF::Literal.new('richard'), RDF::Literal.new('ric'), RDF::Literal.new('ricardo')]
|
52
|
+
context "where there is no field with the given name" do
|
53
|
+
it "should raise a 'field not present' error" do
|
54
|
+
lambda { person.read_attribute(:hoof_size) }.should raise_error(Tripod::Errors::FieldNotPresent)
|
65
55
|
end
|
66
56
|
end
|
67
57
|
end
|
68
58
|
|
69
|
-
describe
|
59
|
+
describe ".write_attribute" do
|
60
|
+
let(:person) { Person.new('http://barry') }
|
61
|
+
|
62
|
+
it "should write the given attribute" do
|
63
|
+
person[:name] = 'Barry'
|
64
|
+
person.name.should == 'Barry'
|
65
|
+
end
|
70
66
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
67
|
+
it "should co-erce the value given to the correct datatype" do
|
68
|
+
person[:age] = 34
|
69
|
+
person.read_predicate('http://age').first.datatype.should == RDF::XSD.integer
|
70
|
+
end
|
71
|
+
|
72
|
+
context "where the attribute is multi-valued" do
|
73
|
+
it "should co-erce all the values to the correct datatype" do
|
74
|
+
person[:important_dates] = [Date.today]
|
75
|
+
person.read_predicate('http://importantdates').first.datatype.should == RDF::XSD.date
|
75
76
|
end
|
76
77
|
end
|
77
78
|
|
78
|
-
context
|
79
|
-
|
80
|
-
|
81
|
-
|
79
|
+
context "where field is given" do
|
80
|
+
let(:field) { Person.send(:field_for, :hat_type, 'http://hat', {}) }
|
81
|
+
|
82
|
+
it "should derive the predicate name from the given field" do
|
83
|
+
person.write_attribute(:hat_type, 'http://bowlerhat', field)
|
84
|
+
person.read_predicate('http://hat').first.to_s.should == 'http://bowlerhat'
|
82
85
|
end
|
83
86
|
end
|
84
87
|
|
85
|
-
|
88
|
+
context "where a field of a particular datatype is given" do
|
89
|
+
let(:field) { Person.send(:field_for, :hat_size, 'http://hatsize', {datatype: RDF::XSD.integer}) }
|
86
90
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
+
it "should derive the datatype from the given field" do
|
92
|
+
person.write_attribute(:hat_size, 10, field)
|
93
|
+
person.read_predicate('http://hatsize').first.datatype.should == RDF::XSD.integer
|
94
|
+
end
|
91
95
|
end
|
92
|
-
end
|
93
96
|
|
94
|
-
|
97
|
+
context "where a multi-valued field of a given datatype is given" do
|
98
|
+
let(:field) { Person.send(:field_for, :hat_heights, 'http://hatheight', {datatype: RDF::XSD.integer, multivalued: true}) }
|
95
99
|
|
96
|
-
|
97
|
-
|
98
|
-
|
100
|
+
it "should co-erce the values passed" do
|
101
|
+
person.write_attribute(:hat_heights, [5, 10, 15], field)
|
102
|
+
person.read_predicate('http://hatheight').first.datatype.should == RDF::XSD.integer
|
103
|
+
end
|
99
104
|
end
|
100
105
|
|
106
|
+
context "where there is no field with the given name" do
|
107
|
+
it "should raise a 'field not present' error" do
|
108
|
+
lambda { person.write_attribute(:hoof_size, 'A1') }.should raise_error(Tripod::Errors::FieldNotPresent)
|
109
|
+
end
|
110
|
+
end
|
101
111
|
end
|
102
|
-
|
103
112
|
end
|
data/spec/tripod/fields_spec.rb
CHANGED
@@ -6,82 +6,32 @@ describe Tripod::Fields do
|
|
6
6
|
|
7
7
|
let(:barry) do
|
8
8
|
b = Person.new('http://barry')
|
9
|
-
b
|
10
|
-
b
|
11
|
-
b['http://age'] = 54
|
12
|
-
b['http://importantdates'] = [Date.new(2010,01,01), Date.new(2012,01,01)]
|
9
|
+
b.name = 'Barry'
|
10
|
+
b
|
13
11
|
b
|
14
12
|
end
|
15
13
|
|
16
|
-
|
17
|
-
|
18
|
-
it "creates a getter for the field, which accesses data for the predicate, returning a single String" do
|
19
|
-
barry.name.should == "Barry"
|
20
|
-
end
|
21
|
-
|
22
|
-
it "creates a setter for the field, which sets data for the predicate" do
|
23
|
-
barry.name = "Basildon"
|
24
|
-
barry.name.should == "Basildon"
|
25
|
-
barry['http://name'].should == [RDF::Literal.new("Basildon")]
|
26
|
-
end
|
27
|
-
|
14
|
+
it "creates a getter for the field, which accesses data for the predicate, returning a single String" do
|
15
|
+
barry.name.should == "Barry"
|
28
16
|
end
|
29
17
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
barry.aliases.should == ['Basildon', 'Baz']
|
34
|
-
end
|
35
|
-
|
36
|
-
it "creates a setter for the field, which sets data for the predicate" do
|
37
|
-
barry.aliases = ['Basildon', 'Baz', 'B-man']
|
38
|
-
barry.aliases.should == ['Basildon', 'Baz', 'B-man']
|
39
|
-
barry['http://alias'].should == [RDF::Literal.new("Basildon"),RDF::Literal.new("Baz"),RDF::Literal.new("B-man")]
|
40
|
-
end
|
41
|
-
|
42
|
-
context 'with data type' do
|
43
|
-
|
44
|
-
it "creates a getter for the field, which accesses data for the predicate, returning an array of Strings" do
|
45
|
-
barry.important_dates.class.should == Array
|
46
|
-
barry.important_dates.should == ["2010-01-01Z", "2012-01-01Z"]
|
47
|
-
end
|
48
|
-
|
49
|
-
it "creates a setter for the field, which sets data for the predicate, using the right data type" do
|
50
|
-
barry.important_dates = [Date.new(2010,01,02),Date.new(2010,01,03)]
|
51
|
-
barry['http://importantdates'].should == [ RDF::Literal.new(Date.new(2010,01,02)), RDF::Literal.new(Date.new(2010,01,03)) ]
|
52
|
-
barry['http://importantdates'].map(&:datatype).should == [RDF::XSD.date,RDF::XSD.date]
|
53
|
-
end
|
54
|
-
|
55
|
-
end
|
56
|
-
|
18
|
+
it "creates a setter for the field, which sets data for the predicate" do
|
19
|
+
barry.name = "Basildon"
|
20
|
+
barry.name.should == "Basildon"
|
57
21
|
end
|
58
22
|
|
59
|
-
|
60
|
-
|
61
|
-
it "creates a getter for the field, which accesses data for the predicate, returning a single String" do
|
62
|
-
barry.age.should == "54"
|
63
|
-
end
|
64
|
-
|
65
|
-
it "creates a setter for the field, which sets data for the predicate, using the right data type" do
|
66
|
-
barry.age = 57
|
67
|
-
barry.age.should == '57'
|
68
|
-
barry['http://age'] = [ RDF::Literal.new(57) ]
|
69
|
-
barry['http://age'].first.datatype = RDF::XSD.integer
|
70
|
-
end
|
71
|
-
|
23
|
+
it "creates a check? method, which returns true when the value is present" do
|
24
|
+
barry.name?.should == true
|
72
25
|
end
|
73
26
|
|
74
|
-
context
|
75
|
-
|
76
|
-
|
77
|
-
barry.name == 100 # set an integer
|
78
|
-
barry['http://name'] = [ RDF::Literal.new(100) ]
|
79
|
-
barry['http://name'].first.datatype = RDF::XSD.integer
|
27
|
+
context "when the value is not set" do
|
28
|
+
before do
|
29
|
+
barry.name = nil
|
80
30
|
end
|
81
31
|
|
82
|
-
|
32
|
+
it "should have a check? method which returns false" do
|
33
|
+
barry.name?.should == false
|
34
|
+
end
|
83
35
|
end
|
84
|
-
|
85
36
|
end
|
86
|
-
|
87
37
|
end
|