tripod 0.0.10 → 0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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/README.md
CHANGED
@@ -6,10 +6,12 @@ ActiveModel-style Ruby ORM for RDF Linked Data. Works with SPARQL 1.1 HTTP endpo
|
|
6
6
|
* Inspired by [Durran Jordan's](https://github.com/durran) [Mongoid](http://mongoid.org/en/mongoid/) ORM for [MongoDB](http://www.mongodb.org/), and [Ben Lavender's](https://github.com/bhuga) RDF ORM, [Spira](https://github.com/ruby-rdf/spira).
|
7
7
|
* Uses [Ruby-RDF](https://github.com/ruby-rdf/rdf) to manage the data internally.
|
8
8
|
|
9
|
-
__Warning:
|
9
|
+
__Warning: Some features are still experimental.__
|
10
10
|
|
11
11
|
## Quick start, for using in a rails app.
|
12
12
|
|
13
|
+
Note: Tripod doesn't supply a database. You need to install one. I recommend [Fuseki](http://jena.apache.org/documentation/serving_data/index.html), which runs on port 3030 by default.
|
14
|
+
|
13
15
|
1. Install the gem:
|
14
16
|
|
15
17
|
gem install tripod
|
@@ -32,7 +34,11 @@ __Warning: Work still in progress / experimental. Not production ready!__
|
|
32
34
|
class Person
|
33
35
|
include Tripod::Resource
|
34
36
|
|
37
|
+
rdf_type 'http://person'
|
38
|
+
graph_uri 'http://people'
|
39
|
+
|
35
40
|
field :name, 'http://name'
|
41
|
+
field :knows, 'http://knows', :multivalued => true
|
36
42
|
field :aliases, 'http://alias', :multivalued => true
|
37
43
|
field :age, 'http://age', :datatype => RDF::XSD.integer
|
38
44
|
field :important_dates, 'http://importantdates', :datatype => RDF::XSD.date, :multivalued => true
|
@@ -43,15 +49,15 @@ __Warning: Work still in progress / experimental. Not production ready!__
|
|
43
49
|
5. Use it
|
44
50
|
|
45
51
|
uri = 'http://ric'
|
46
|
-
|
47
|
-
p = Person.new(uri, graph)
|
52
|
+
p = Person.new(uri)
|
48
53
|
p.name = 'Ric'
|
49
54
|
p.age = 31
|
50
55
|
p.aliases = ['Rich', 'Richard']
|
51
56
|
p.important_dates = [Date.new(2011,1,1)]
|
52
|
-
p[RDF::type] = RDF::URI('http://person')
|
53
57
|
p.save!
|
54
58
|
|
59
|
+
# Note: queries supplied to the where method should return the uris of the resource,
|
60
|
+
# and what graph they're in.
|
55
61
|
people = Person.where("
|
56
62
|
SELECT ?person ?graph
|
57
63
|
WHERE {
|
@@ -60,7 +66,7 @@ __Warning: Work still in progress / experimental. Not production ready!__
|
|
60
66
|
?person a <http://person> .
|
61
67
|
}
|
62
68
|
}",
|
63
|
-
:uri_variable => 'person' )
|
69
|
+
:uri_variable => 'person' ) # optionally, set a different name for the uri parameter (default: uri)
|
64
70
|
# => returns an array of Person objects, containing all data we know about them.
|
65
71
|
|
66
72
|
ric = Person.find('http://ric')
|
data/lib/tripod.rb
CHANGED
@@ -33,6 +33,7 @@ require 'rdf'
|
|
33
33
|
require 'rdf/rdfxml'
|
34
34
|
require 'rdf/n3'
|
35
35
|
require 'rdf/json'
|
36
|
+
require 'json/ld'
|
36
37
|
|
37
38
|
require 'rest_client'
|
38
39
|
|
@@ -64,12 +65,14 @@ end
|
|
64
65
|
require "tripod/extensions"
|
65
66
|
require "tripod/sparql_client"
|
66
67
|
|
68
|
+
require "tripod/predicates"
|
67
69
|
require "tripod/attributes"
|
68
70
|
require "tripod/errors"
|
69
71
|
require "tripod/fields"
|
70
72
|
require "tripod/finders"
|
71
73
|
require "tripod/persistence"
|
72
74
|
require "tripod/repository"
|
75
|
+
require "tripod/serialization"
|
73
76
|
require "tripod/state"
|
74
77
|
require "tripod/version"
|
75
78
|
|
data/lib/tripod/attributes.rb
CHANGED
@@ -5,77 +5,69 @@ module Tripod::Attributes
|
|
5
5
|
|
6
6
|
extend ActiveSupport::Concern
|
7
7
|
|
8
|
-
# Reads
|
9
|
-
# Returns
|
8
|
+
# Reads an attribute from this resource, based on a defined field
|
9
|
+
# Returns the value(s) for the named (or given) field
|
10
10
|
#
|
11
|
-
# @example Read
|
12
|
-
#
|
13
|
-
#
|
11
|
+
# @example Read the value associated with a predicate.
|
12
|
+
# class Person
|
13
|
+
# field :name, 'http://name'
|
14
|
+
# end
|
14
15
|
#
|
15
|
-
#
|
16
|
-
# person['http://foo']
|
17
|
-
# person[RDF::URI.new('http://foo')]
|
16
|
+
# person.read_attribute(:name)
|
18
17
|
#
|
19
|
-
# @param [ String
|
18
|
+
# @param [ String ] name The name of the field for which to get the value.
|
19
|
+
# @param [ Field ] field An optional Field object
|
20
20
|
#
|
21
|
-
# @return
|
22
|
-
def read_attribute(
|
23
|
-
|
24
|
-
|
25
|
-
|
21
|
+
# @return Either a string or an Array of strings, depending on whether the field is multivalued or not
|
22
|
+
def read_attribute(name, field=nil)
|
23
|
+
field ||= self.fields[name]
|
24
|
+
raise Tripod::Errors::FieldNotPresent.new unless field
|
25
|
+
|
26
|
+
attr_values = read_predicate(field.predicate)
|
27
|
+
# We always return strings on way out.
|
28
|
+
# If the field is multivalued, return an array of the results
|
29
|
+
# If it's not multivalued, return the first (should be only) result.
|
30
|
+
if field.multivalued
|
31
|
+
attr_values.map do |v|
|
32
|
+
v.nil? ? nil : v.to_s
|
33
|
+
end
|
34
|
+
else
|
35
|
+
first_val = attr_values.first
|
36
|
+
first_val.nil? ? nil : first_val.to_s
|
26
37
|
end
|
27
|
-
values
|
28
38
|
end
|
29
39
|
alias :[] :read_attribute
|
30
40
|
|
31
|
-
#
|
41
|
+
# Writes an attribute to the resource, based on a defined field
|
32
42
|
#
|
33
|
-
# @example Write the
|
34
|
-
#
|
35
|
-
#
|
43
|
+
# @example Write the value associated with a predicate.
|
44
|
+
# class Person
|
45
|
+
# field :name, 'http://name'
|
46
|
+
# end
|
36
47
|
#
|
37
|
-
#
|
38
|
-
# person['http://title'] = "Mr."
|
39
|
-
# person['http://title'] = ["Mrs.", "Ms."]
|
48
|
+
# person.write_attribute(:name, 'Bob')
|
40
49
|
#
|
41
|
-
# @param [ String
|
42
|
-
# @param [
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
remove_attribute(predicate_uri)
|
50
|
+
# @param [ String ] name The name of the field for which to set the value.
|
51
|
+
# @param [ String ] value The value to set it to
|
52
|
+
# @param [ Field ] field An optional Field object
|
53
|
+
def write_attribute(name, value, field=nil)
|
54
|
+
field ||= self.fields[name]
|
55
|
+
raise Tripod::Errors::FieldNotPresent.new unless field
|
48
56
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
57
|
+
if value.kind_of?(Array)
|
58
|
+
if field.multivalued
|
59
|
+
new_val = []
|
60
|
+
value.each do |v|
|
61
|
+
new_val << self.class.new_value_for_field(v, field)
|
62
|
+
end
|
63
|
+
else
|
64
|
+
new_val = self.class.new_value_for_field(value.first, field)
|
65
|
+
end
|
66
|
+
else
|
67
|
+
new_val = self.class.new_value_for_field(value, field)
|
53
68
|
end
|
54
69
|
|
55
|
-
|
56
|
-
read_attribute(predicate_uri)
|
70
|
+
write_predicate(field.predicate, new_val)
|
57
71
|
end
|
58
72
|
alias :[]= :write_attribute
|
59
|
-
|
60
|
-
# Append the statement-values for a single predicate in this resource's in-memory repository. Basically just adds a new statement for this ((resource's uri)+predicate)
|
61
|
-
#
|
62
|
-
# @example Write the attribute.
|
63
|
-
# person.append_to_attribute('http://title', "Mrs.")
|
64
|
-
# person.append_to_attribute('http://title', "Ms.")
|
65
|
-
#
|
66
|
-
# @param [ String, RDF::URI ] predicate_uri The uri of the attribute to update.
|
67
|
-
# @param [ Object ] value The values to append for the attribute. Should compatible with RDF::Terms
|
68
|
-
def append_to_attribute(predicate_uri, object )
|
69
|
-
raise Tripod::Errors::UriNotSet.new() unless @uri
|
70
|
-
|
71
|
-
@repository << RDF::Statement.new(@uri, RDF::URI.new(predicate_uri.to_s), object)
|
72
|
-
end
|
73
|
-
|
74
|
-
def remove_attribute(predicate_uri)
|
75
|
-
@repository.query( [:subject, RDF::URI.new(predicate_uri.to_s), :object] ) do |statement|
|
76
|
-
@repository.delete( statement )
|
77
|
-
end
|
78
|
-
end
|
79
|
-
alias :delete :remove_attribute
|
80
|
-
|
81
73
|
end
|
data/lib/tripod/components.rb
CHANGED
@@ -13,11 +13,13 @@ module Tripod::Components
|
|
13
13
|
include ActiveModel::Naming
|
14
14
|
include ActiveModel::Validations
|
15
15
|
|
16
|
+
include Tripod::Predicates
|
16
17
|
include Tripod::Attributes
|
17
18
|
include Tripod::Persistence
|
18
19
|
include Tripod::Fields
|
19
20
|
include Tripod::Finders
|
20
21
|
include Tripod::Repository
|
22
|
+
include Tripod::Serialization
|
21
23
|
include Tripod::State
|
22
24
|
|
23
25
|
end
|
data/lib/tripod/errors.rb
CHANGED
data/lib/tripod/fields.rb
CHANGED
@@ -28,9 +28,8 @@ module Tripod::Fields
|
|
28
28
|
#
|
29
29
|
# @return [ Field ] The generated field
|
30
30
|
def field(name, predicate, options = {})
|
31
|
-
named = name.to_s
|
32
31
|
# TODO: validate the field params/options here..
|
33
|
-
add_field(
|
32
|
+
add_field(name, predicate, options)
|
34
33
|
end
|
35
34
|
|
36
35
|
|
@@ -92,20 +91,7 @@ module Tripod::Fields
|
|
92
91
|
def create_field_getter(name, meth, field)
|
93
92
|
generated_methods.module_eval do
|
94
93
|
re_define_method(meth) do
|
95
|
-
|
96
|
-
attr_values = read_attribute(field.predicate)
|
97
|
-
|
98
|
-
# We always return strings on way out.
|
99
|
-
# If the field is multivalued, return an array of the results
|
100
|
-
# If it's not multivalued, return the first (should be only) result.
|
101
|
-
if field.multivalued
|
102
|
-
attr_values.map do |v|
|
103
|
-
v.nil? ? nil : v.to_s
|
104
|
-
end
|
105
|
-
else
|
106
|
-
first_val = attr_values.first
|
107
|
-
first_val.nil? ? nil : first_val.to_s
|
108
|
-
end
|
94
|
+
read_attribute(name, field)
|
109
95
|
end
|
110
96
|
end
|
111
97
|
end
|
@@ -121,20 +107,7 @@ module Tripod::Fields
|
|
121
107
|
def create_field_setter(name, meth, field)
|
122
108
|
generated_methods.module_eval do
|
123
109
|
re_define_method("#{meth}=") do |value|
|
124
|
-
|
125
|
-
if field.multivalued
|
126
|
-
new_val = []
|
127
|
-
value.each do |v|
|
128
|
-
new_val << self.class.new_value_for_field(v, field)
|
129
|
-
end
|
130
|
-
else
|
131
|
-
new_val = self.class.new_value_for_field(value.first, field)
|
132
|
-
end
|
133
|
-
else
|
134
|
-
new_val = self.class.new_value_for_field(value, field)
|
135
|
-
end
|
136
|
-
|
137
|
-
write_attribute(field.predicate, new_val)
|
110
|
+
write_attribute(name, value, field)
|
138
111
|
end
|
139
112
|
end
|
140
113
|
end
|
@@ -149,7 +122,7 @@ module Tripod::Fields
|
|
149
122
|
def create_field_check(name, meth, field)
|
150
123
|
generated_methods.module_eval do
|
151
124
|
re_define_method("#{meth}?") do
|
152
|
-
attr = read_attribute(field
|
125
|
+
attr = read_attribute(name, field)
|
153
126
|
attr == true || attr.present?
|
154
127
|
end
|
155
128
|
end
|
data/lib/tripod/finders.rb
CHANGED
@@ -6,30 +6,37 @@ module Tripod::Finders
|
|
6
6
|
|
7
7
|
module ClassMethods
|
8
8
|
|
9
|
-
# Find a +Resource+ by its uri.
|
9
|
+
# Find a +Resource+ by its uri (and, optionally, by its graph if there are more than one).
|
10
10
|
#
|
11
11
|
# @example Find a single resource by a uri.
|
12
12
|
# Person.find('http://ric')
|
13
13
|
# Person.find(RDF::URI('http://ric'))
|
14
|
+
# @example Find a single resource by uri and graph
|
15
|
+
# Person.find('http://ric', 'http://example.com/people')
|
16
|
+
# Person.find(RDF::URI('http://ric'), Person.find(RDF::URI('http://example.com/people')))
|
14
17
|
#
|
15
18
|
# @param [ String, RDF::URI ] uri The uri of the resource to find
|
19
|
+
# @param [ String, RDF::URI ] graph_uri The uri of the graph from which to get the resource
|
16
20
|
#
|
17
21
|
# @raise [ Tripod::Errors::ResourceNotFound ] If no resource found.
|
18
22
|
#
|
19
23
|
# @return [ Resource ] A single resource
|
20
|
-
def find(uri)
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
24
|
+
def find(uri, graph_uri=nil)
|
25
|
+
|
26
|
+
unless graph_uri
|
27
|
+
# do a quick select to see what graph to use.
|
28
|
+
select_query = "SELECT ?g WHERE { GRAPH ?g {<#{uri.to_s}> ?p ?o } } LIMIT 1"
|
29
|
+
result = Tripod::SparqlClient::Query.select(select_query)
|
30
|
+
if result.length > 0
|
31
|
+
graph_uri = result[0]["g"]["value"]
|
32
|
+
else
|
33
|
+
raise Tripod::Errors::ResourceNotFound.new
|
34
|
+
end
|
29
35
|
end
|
30
36
|
|
31
37
|
# instantiate and hydrate the resource
|
32
|
-
resource = self.new(uri,
|
38
|
+
resource = self.new(uri, graph_uri.to_s)
|
39
|
+
|
33
40
|
resource.hydrate!
|
34
41
|
resource.new_record = false
|
35
42
|
|
@@ -90,7 +97,7 @@ module Tripod::Finders
|
|
90
97
|
triples_repository.query( [RDF::URI.new(u), :predicate, :object] ) do |statement|
|
91
98
|
data_graph << statement
|
92
99
|
end
|
93
|
-
r.hydrate!(data_graph)
|
100
|
+
r.hydrate!(:graph => data_graph)
|
94
101
|
r.new_record = false
|
95
102
|
resources << r
|
96
103
|
end
|
data/lib/tripod/persistence.rb
CHANGED
@@ -144,6 +144,18 @@ module Tripod::Persistence
|
|
144
144
|
true
|
145
145
|
end
|
146
146
|
|
147
|
+
def update_attribute(name, value)
|
148
|
+
write_attribute(name, value)
|
149
|
+
save
|
150
|
+
end
|
151
|
+
|
152
|
+
def update_attributes(attributes={})
|
153
|
+
attributes.each_pair do |name, value|
|
154
|
+
send "#{name}=", value
|
155
|
+
end
|
156
|
+
save
|
157
|
+
end
|
158
|
+
|
147
159
|
module ClassMethods #:nodoc:
|
148
160
|
|
149
161
|
# Raise an error if validation failed.
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# This module defines behaviour for predicates.
|
4
|
+
module Tripod::Predicates
|
5
|
+
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
# returns a list of predicates (as RDF::URIs) for this resource
|
9
|
+
def predicates
|
10
|
+
pred_uris = []
|
11
|
+
@repository.statements.each do |s|
|
12
|
+
pred_uris << s.predicate unless pred_uris.include?(s.predicate)
|
13
|
+
end
|
14
|
+
pred_uris
|
15
|
+
end
|
16
|
+
|
17
|
+
# Reads values from this resource's in-memory statement repository, where the predicate matches that of the uri passed in.
|
18
|
+
# Returns an Array of RDF::Terms object.
|
19
|
+
#
|
20
|
+
# @example Read the value associated with a predicate.
|
21
|
+
# person.read_predicate('http://foo')
|
22
|
+
# person.read_predicate(RDF::URI.new('http://foo'))
|
23
|
+
#
|
24
|
+
# @param [ String, RDF::URI ] uri The uri of the predicate to get.
|
25
|
+
#
|
26
|
+
# @return [ Array ] An array of RDF::Terms.
|
27
|
+
def read_predicate(predicate_uri)
|
28
|
+
values = []
|
29
|
+
@repository.query( [:subject, RDF::URI.new(predicate_uri.to_s), :object] ) do |statement|
|
30
|
+
values << statement.object
|
31
|
+
end
|
32
|
+
values
|
33
|
+
end
|
34
|
+
|
35
|
+
# Replace the statement-values for a single predicate in this resource's in-memory repository.
|
36
|
+
#
|
37
|
+
# @example Write the predicate.
|
38
|
+
# person.write_predicate('http://title', "Mr.")
|
39
|
+
# person.write_predicate('http://title', ["Mrs.", "Ms."])
|
40
|
+
#
|
41
|
+
# @param [ String, RDF::URI ] predicate_uri The name of the attribute to update.
|
42
|
+
# @param [ Object, Array ] value The values to set for the attribute. Can be an array, or single item. They should be compatible with RDF::Terms
|
43
|
+
def write_predicate(predicate_uri, objects)
|
44
|
+
# remove existing
|
45
|
+
remove_predicate(predicate_uri)
|
46
|
+
|
47
|
+
# ... and replace
|
48
|
+
objects = [objects] unless objects.kind_of?(Array)
|
49
|
+
objects.each do |object|
|
50
|
+
@repository << RDF::Statement.new( @uri, RDF::URI.new(predicate_uri.to_s), object )
|
51
|
+
end
|
52
|
+
|
53
|
+
# returns the new values
|
54
|
+
read_predicate(predicate_uri)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Append the statement-values for a single predicate in this resource's in-memory repository. Basically just adds a new statement for this ((resource's uri)+predicate)
|
58
|
+
#
|
59
|
+
# @example Write the attribute.
|
60
|
+
# person.append_to_predicate('http://title', "Mrs.")
|
61
|
+
# person.append_to_predicate('http://title', "Ms.")
|
62
|
+
#
|
63
|
+
# @param [ String, RDF::URI ] predicate_uri The uri of the attribute to update.
|
64
|
+
# @param [ Object ] value The values to append for the attribute. Should compatible with RDF::Terms
|
65
|
+
def append_to_predicate(predicate_uri, object )
|
66
|
+
raise Tripod::Errors::UriNotSet.new() unless @uri
|
67
|
+
|
68
|
+
@repository << RDF::Statement.new(@uri, RDF::URI.new(predicate_uri.to_s), object)
|
69
|
+
end
|
70
|
+
|
71
|
+
def remove_predicate(predicate_uri)
|
72
|
+
@repository.query( [:subject, RDF::URI.new(predicate_uri.to_s), :object] ) do |statement|
|
73
|
+
@repository.delete( statement )
|
74
|
+
end
|
75
|
+
end
|
76
|
+
alias :delete :remove_predicate
|
77
|
+
|
78
|
+
end
|