tripod 0.0.1 → 0.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/.gitignore +1 -0
- data/Gemfile +4 -0
- data/LICENSE +1 -1
- data/README.md +5 -1
- data/Rakefile +13 -0
- data/lib/tripod.rb +72 -1
- data/lib/tripod/attributes.rb +80 -0
- data/lib/tripod/components.rb +23 -0
- data/lib/tripod/errors.rb +4 -0
- data/lib/tripod/errors/resource_not_found.rb +8 -0
- data/lib/tripod/errors/uri_not_set.rb +9 -0
- data/lib/tripod/errors/validations.rb +18 -0
- data/lib/tripod/fields.rb +7 -0
- data/lib/tripod/finders.rb +44 -0
- data/lib/tripod/persistence.rb +68 -0
- data/lib/tripod/repository.rb +43 -0
- data/lib/tripod/resource.rb +110 -0
- data/lib/tripod/sparql_client.rb +101 -0
- data/lib/tripod/state.rb +47 -0
- data/lib/tripod/version.rb +1 -1
- data/spec/app/models/person.rb +5 -0
- data/spec/spec_helper.rb +35 -0
- data/spec/tripod/attributes_spec.rb +103 -0
- data/spec/tripod/finders_spec.rb +71 -0
- data/spec/tripod/persistence_spec.rb +103 -0
- data/spec/tripod/repository_spec.rb +60 -0
- data/spec/tripod/resource_spec.rb +63 -0
- data/spec/tripod/state_spec.rb +93 -0
- data/tripod.gemspec +11 -0
- metadata +112 -6
data/.gitignore
CHANGED
data/Gemfile
CHANGED
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -1,3 +1,7 @@
|
|
1
1
|
# Tripod
|
2
2
|
|
3
|
-
|
3
|
+
Active Model style Ruby ORM for RDF data, inspired by http://github.com/mongoid/mongoid
|
4
|
+
|
5
|
+
Warning: Work still in progress / experimental. Not production ready!
|
6
|
+
|
7
|
+
Copyright (c) 2012 Swirrl IT Limited. http://swirrl.com. Released under MIT License
|
data/Rakefile
CHANGED
@@ -1,2 +1,15 @@
|
|
1
1
|
#!/usr/bin/env rake
|
2
2
|
require "bundler/gem_tasks"
|
3
|
+
require "rspec/core/rake_task"
|
4
|
+
|
5
|
+
RSpec::Core::RakeTask.new("spec") do |spec|
|
6
|
+
spec.pattern = "spec/**/*_spec.rb"
|
7
|
+
end
|
8
|
+
|
9
|
+
RSpec::Core::RakeTask.new('spec:progress') do |spec|
|
10
|
+
spec.rspec_opts = %w(--format progress)
|
11
|
+
spec.pattern = "spec/**/*_spec.rb"
|
12
|
+
end
|
13
|
+
|
14
|
+
task :default => :spec
|
15
|
+
|
data/lib/tripod.rb
CHANGED
@@ -1,5 +1,76 @@
|
|
1
|
+
# Copyright (c) 2012 Swirrl IT Limited. http://swirrl.com
|
2
|
+
|
3
|
+
# MIT License
|
4
|
+
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
# a copy of this software and associated documentation files (the
|
7
|
+
# "Software"), to deal in the Software without restriction, including
|
8
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
# the following conditions:
|
12
|
+
|
13
|
+
# The above copyright notice and this permission notice shall be
|
14
|
+
# included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
23
|
+
|
1
24
|
require "tripod/version"
|
2
25
|
|
26
|
+
require "active_support/core_ext"
|
27
|
+
require 'active_support/json'
|
28
|
+
require "active_support/inflector"
|
29
|
+
require "active_model"
|
30
|
+
|
31
|
+
require 'rdf'
|
32
|
+
require 'rdf/rdfxml'
|
33
|
+
require 'rdf/n3'
|
34
|
+
require 'rdf/json'
|
35
|
+
|
36
|
+
require 'rest_client'
|
37
|
+
|
3
38
|
module Tripod
|
4
|
-
|
39
|
+
|
40
|
+
mattr_accessor :update_endpoint
|
41
|
+
@@update_endpoint = 'http://127.0.0.1:3030/tripod/update'
|
42
|
+
|
43
|
+
mattr_accessor :query_endpoint
|
44
|
+
@@query_endpoint = 'http://127.0.0.1:3030/tripod/sparql'
|
45
|
+
|
46
|
+
mattr_accessor :timeout_seconds
|
47
|
+
@@timeout_seconds = 30
|
48
|
+
|
49
|
+
# Use +configure+ to override configuration in an app, e.g.:
|
50
|
+
#
|
51
|
+
# Tripod.configure do |config|
|
52
|
+
# config.update_endpoint = 'http://127.0.0.1:3030/tripod/update'
|
53
|
+
# config.query_endpoint = 'http://127.0.0.1:3030/tripod/sparql'
|
54
|
+
# config.timeout_seconds = 30
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
def self.configure
|
58
|
+
yield self
|
59
|
+
end
|
60
|
+
|
5
61
|
end
|
62
|
+
|
63
|
+
require "tripod/sparql_client"
|
64
|
+
|
65
|
+
require "tripod/attributes"
|
66
|
+
require "tripod/errors"
|
67
|
+
require "tripod/fields"
|
68
|
+
require "tripod/finders"
|
69
|
+
require "tripod/persistence"
|
70
|
+
require "tripod/repository"
|
71
|
+
require "tripod/state"
|
72
|
+
require "tripod/version"
|
73
|
+
|
74
|
+
# these need to be at the end
|
75
|
+
require "tripod/components"
|
76
|
+
require "tripod/resource"
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# This module defines behaviour for attributes.
|
4
|
+
module Tripod::Attributes
|
5
|
+
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
# Reads values from this respource's in-memory statement repository, where the predicate matches that of the uri passed in.
|
9
|
+
# Returns an Array of RDF::Terms object.
|
10
|
+
#
|
11
|
+
# @example Read an attribute.
|
12
|
+
# person.read_attribute('http://foo')
|
13
|
+
# person.read_attribute(RDF::URI.new('http://foo'))
|
14
|
+
#
|
15
|
+
# @example Read an attribute (alternate syntax.)
|
16
|
+
# person['http://foo']
|
17
|
+
# person[RDF::URI.new('http://foo')]
|
18
|
+
#
|
19
|
+
# @param [ String, RDF::URI ] uri The uri of the attribute to get.
|
20
|
+
#
|
21
|
+
# @return [ Array ] An array of RDF::Terms.
|
22
|
+
def read_attribute(predicate_uri)
|
23
|
+
values = []
|
24
|
+
@repository.query( [:subject, RDF::URI.new(predicate_uri.to_s), :object] ) do |statement|
|
25
|
+
values << statement.object
|
26
|
+
end
|
27
|
+
values
|
28
|
+
end
|
29
|
+
alias :[] :read_attribute
|
30
|
+
|
31
|
+
# Replace the statement-values for a single predicate in this resource's in-memory repository.
|
32
|
+
#
|
33
|
+
# @example Write the attribute.
|
34
|
+
# person.write_attribute('http://title', "Mr.")
|
35
|
+
# person.write_attribute('http://title', ["Mrs.", "Ms."])
|
36
|
+
#
|
37
|
+
# @example Write the attribute (alternate syntax.)
|
38
|
+
# person['http://title'] = "Mr."
|
39
|
+
# person['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 compatible with RDF::Terms
|
43
|
+
def write_attribute(predicate_uri, objects)
|
44
|
+
# remove existing
|
45
|
+
remove_attribute(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_attribute(predicate_uri)
|
55
|
+
end
|
56
|
+
alias :[]= :write_attribute
|
57
|
+
|
58
|
+
# 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)
|
59
|
+
#
|
60
|
+
# @example Write the attribute.
|
61
|
+
# person.append_attribute('http://title', "Mrs.")
|
62
|
+
# person.append_attribute('http://title', "Ms.")
|
63
|
+
#
|
64
|
+
# @example Write the attribute (alternate syntax.)
|
65
|
+
# person['http://title'] << "Mrs."
|
66
|
+
# person['http://title'] << "Ms."
|
67
|
+
#
|
68
|
+
# @param [ String, RDF::URI ] predicate_uri The uri of the attribute to update.
|
69
|
+
# @param [ Object ] value The values to append for the attribute. Should compatible with RDF::Terms
|
70
|
+
def append_to_attribute(predicate_uri, object )
|
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
|
+
|
80
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# All modules that a +Resource+ is composed of are defined in this
|
4
|
+
# module, to keep the resource module from getting too cluttered.
|
5
|
+
module Tripod::Components
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
end
|
10
|
+
|
11
|
+
include ActiveModel::Conversion # to_param, to_key etc.
|
12
|
+
# include ActiveModel::MassAssignmentSecurity
|
13
|
+
include ActiveModel::Naming
|
14
|
+
include ActiveModel::Validations
|
15
|
+
|
16
|
+
include Tripod::Attributes
|
17
|
+
include Tripod::Persistence
|
18
|
+
include Tripod::Fields
|
19
|
+
include Tripod::Finders
|
20
|
+
include Tripod::Repository
|
21
|
+
include Tripod::State
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Tripod::Errors
|
3
|
+
|
4
|
+
# Raised when a persistence method ending in ! fails validation. The message
|
5
|
+
# will contain the full error messages from the +Resource+ in question.
|
6
|
+
#
|
7
|
+
# @example Create the error.
|
8
|
+
# Validations.new(person.errors)
|
9
|
+
class Validations < StandardError
|
10
|
+
attr_reader :resource
|
11
|
+
alias :record :resource
|
12
|
+
|
13
|
+
def initialize(resource)
|
14
|
+
@resource = resource
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# This module defines behaviour for finders.
|
4
|
+
module Tripod::Finders
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
|
9
|
+
# Find a +Resource+ by its uri.
|
10
|
+
#
|
11
|
+
# @example Find a single resource by a uri.
|
12
|
+
# Person.find('http://ric')
|
13
|
+
# Person.find(RDF::URI('http://ric'))
|
14
|
+
#
|
15
|
+
# @param [ String, RDF::URI ] uri The uri of the resource to find
|
16
|
+
#
|
17
|
+
# @raise [ Errors::DocumentNotFound ] If no document found.
|
18
|
+
#
|
19
|
+
# @return [ Resource ] A single resource
|
20
|
+
def find(uri)
|
21
|
+
|
22
|
+
# do a quick select to see what graph to use.
|
23
|
+
select_query = "SELECT ?g WHERE { GRAPH ?g {<#{uri.to_s}> ?p ?o } } LIMIT 1"
|
24
|
+
result = Tripod::SparqlClient::Query.select(select_query)
|
25
|
+
if result.length > 0
|
26
|
+
graph_uri_str = result[0]["g"]["value"]
|
27
|
+
else
|
28
|
+
raise Tripod::Errors::ResourceNotFound.new
|
29
|
+
end
|
30
|
+
|
31
|
+
# instantiate and hydrate the resource
|
32
|
+
resource = self.new(uri, graph_uri_str)
|
33
|
+
resource.hydrate!
|
34
|
+
|
35
|
+
# check that there are triples for the resource (catches case when someone has deleted data
|
36
|
+
# between our original check for the graph and hydrating the object.
|
37
|
+
raise Tripod::Errors::ResourceNotFound.new if resource.repository.empty?
|
38
|
+
|
39
|
+
# return the instantiated, hydrated resource
|
40
|
+
resource
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# This module defines behaviour for persisting to the database.
|
4
|
+
module Tripod::Persistence
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
# Save the resource.
|
7
|
+
# Note: regardless of whether it's a new_record or not, we always make the
|
8
|
+
# db match the contents of this resource's statements.
|
9
|
+
#
|
10
|
+
# @example Save the resource.
|
11
|
+
# resource.save
|
12
|
+
#
|
13
|
+
# @return [ true, false ] True is success, false if not.
|
14
|
+
def save()
|
15
|
+
if self.valid?
|
16
|
+
query = "
|
17
|
+
DELETE {GRAPH ?g {<#{@uri.to_s}> ?p ?o}} WHERE {GRAPH ?g {<#{@uri.to_s}> ?p ?o}};
|
18
|
+
INSERT DATA {
|
19
|
+
GRAPH <#{@graph_uri}> {
|
20
|
+
#{ @repository.dump(:ntriples) }
|
21
|
+
}
|
22
|
+
}
|
23
|
+
"
|
24
|
+
success = Tripod::SparqlClient::Update::update(query)
|
25
|
+
@new_record = false if success
|
26
|
+
success
|
27
|
+
else
|
28
|
+
false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def save!()
|
33
|
+
# try to save
|
34
|
+
unless self.save()
|
35
|
+
# if we get in here, save failed.
|
36
|
+
self.class.fail_validate!(self)
|
37
|
+
# TODO: similar stuff for callbacks?
|
38
|
+
end
|
39
|
+
return true
|
40
|
+
end
|
41
|
+
|
42
|
+
def destroy()
|
43
|
+
query = "
|
44
|
+
# delete from default graph:
|
45
|
+
DELETE {<#{@uri.to_s}> ?p ?o} WHERE {<#{@uri.to_s}> ?p ?o};
|
46
|
+
# delete from named graphs:
|
47
|
+
DELETE {GRAPH ?g {<#{@uri.to_s}> ?p ?o}} WHERE {GRAPH ?g {<#{@uri.to_s}> ?p ?o}};
|
48
|
+
"
|
49
|
+
success = Tripod::SparqlClient::Update::update(query)
|
50
|
+
@destroyed = true if success
|
51
|
+
success
|
52
|
+
end
|
53
|
+
|
54
|
+
module ClassMethods #:nodoc:
|
55
|
+
|
56
|
+
# Raise an error if validation failed.
|
57
|
+
#
|
58
|
+
# @example Raise the validation error.
|
59
|
+
# Person.fail_validate!(person)
|
60
|
+
#
|
61
|
+
# @param [ Resource ] resource The resource to fail.
|
62
|
+
def fail_validate!(resource)
|
63
|
+
raise Tripod::Errors::Validations.new(resource)
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# This module wraps access to an RDF::Repository
|
4
|
+
module Tripod::Repository
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
attr_reader :repository
|
8
|
+
|
9
|
+
# hydrates the resource's repo with statements from the db or passed in graph of statements.
|
10
|
+
# where the subject is the uri of this resource.
|
11
|
+
#
|
12
|
+
# @example Hydrate the resource from the db
|
13
|
+
# person.hydrate!
|
14
|
+
#
|
15
|
+
# @example Hydrate the resource from a passed in graph
|
16
|
+
# person.hydrate!(my_graph)
|
17
|
+
#
|
18
|
+
# @return [ RDF::Repository ] A reference to the repository for this instance.
|
19
|
+
def hydrate!(graph = nil)
|
20
|
+
|
21
|
+
# we require that the uri is set.
|
22
|
+
raise Tripod::Errors::UriNotSet.new() unless @uri
|
23
|
+
|
24
|
+
if graph
|
25
|
+
graph.each_statement do |statement|
|
26
|
+
# only use statements about this resource!
|
27
|
+
if statement.subject.to_s == @uri.to_s
|
28
|
+
@repository << statement
|
29
|
+
end
|
30
|
+
end
|
31
|
+
else
|
32
|
+
triples = Tripod::SparqlClient::Query::describe("DESCRIBE <#{uri}>")
|
33
|
+
@repository = RDF::Repository.new
|
34
|
+
RDF::Reader.for(:ntriples).new(triples) do |reader|
|
35
|
+
reader.each_statement do |statement|
|
36
|
+
@repository << statement
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|