tripod 0.0.10 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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!(graph = nil)
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
- triples = Tripod::SparqlClient::Query::describe("DESCRIBE <#{uri}>")
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|
@@ -9,8 +9,12 @@ module Tripod::Resource
9
9
  include Tripod::Components
10
10
 
11
11
  included do
12
- # every resource needs a graph and a uri set.
13
- validates_presence_of :uri, :graph_uri
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=nil, graph_uri=nil)
30
- @new_record = true
31
- @uri = RDF::URI(uri.to_s) if uri
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
- # Set the uri for this resource
37
- def uri=(new_uri)
38
- if new_uri
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
- # Set the uri for this resource
46
- def graph_uri=(new_graph_uri)
47
- if new_graph_uri
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
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Tripod
2
- VERSION = "0.0.10"
2
+ VERSION = "0.2"
3
3
  end
@@ -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
- before do
6
- @uri = 'http://ric'
7
- @graph = RDF::Graph.new
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
- let(:person) do
29
- p = Person.new(@uri)
30
- p.hydrate!(@graph)
31
- p
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
- describe "#[]" do
35
- it 'returns the values where the predicate matches' do
36
- values = person['http://blog']
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
- describe "#read_attribute" do
44
- it 'returns the values where the predicate matches' do
45
- values = person.read_attribute('http://blog')
46
- values.length.should == 2
47
- values.first.should == RDF::URI('http://blog1')
48
- values[1].should == RDF::URI('http://blog2')
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
- describe '#[]=' do
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
- context 'single term passed' do
55
- it 'replaces the values where the predicate matches' do
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 'multiple terms passed' do
62
- it 'replaces the values where the predicate matches' do
63
- person['http://name'] = ['richard', 'ric', 'ricardo']
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 '#write_attribute' do
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
- context 'single term passed' do
72
- it 'replaces the values where the predicate matches' do
73
- person.write_attribute('http://name', 'richard')
74
- person['http://name'].should == [RDF::Literal.new('richard')]
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 'multiple terms passed' do
79
- it 'replaces the values where the predicate matches' do
80
- person.write_attribute('http://name', ['richard', 'ric', 'ricardo'])
81
- person['http://name'].should == [RDF::Literal.new('richard'), RDF::Literal.new('ric'), RDF::Literal.new('ricardo')]
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
- end
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
- describe '#remove_attribute' do
88
- it 'remnoves the values where the predicate matches' do
89
- person.remove_attribute('http://blog')
90
- person['http://blog'].should be_empty
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
- describe "append_to_attribute" do
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
- it 'appends values to the existing values for the predicate' do
97
- person.append_to_attribute('http://name', 'rico')
98
- person['http://name'].should == [RDF::Literal.new('ric'), RDF::Literal.new('rico')]
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
@@ -6,82 +6,32 @@ describe Tripod::Fields do
6
6
 
7
7
  let(:barry) do
8
8
  b = Person.new('http://barry')
9
- b['http://name'] = 'Barry'
10
- b['http://alias'] = ['Basildon', 'Baz']
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
- context "with no options" do
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
- context "with multivalued option" do
31
-
32
- it "creates a getter for the field, which accesses data for the predicate, returning an array of Strings" do
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
- context 'with data type' do
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 'with no data type specified' do
75
-
76
- it "creates the right kind of literals when setting values." do
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