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/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
|