tripod 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|