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 CHANGED
@@ -15,3 +15,4 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ .DS_Store
data/Gemfile CHANGED
@@ -2,3 +2,7 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in tripod.gemspec
4
4
  gemspec
5
+
6
+ group :test do
7
+ gem "rspec", "~> 2.11"
8
+ end
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012 RicSwirrl
1
+ Copyright (c) 2012 Swirrl IT Limited. http://swirrl.com
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -1,3 +1,7 @@
1
1
  # Tripod
2
2
 
3
- Coming soon: Active Model style Ruby ORM for RDF data.
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
- # Your code goes here...
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,4 @@
1
+ # encoding: utf-8
2
+ require 'tripod/errors/resource_not_found'
3
+ require 'tripod/errors/uri_not_set'
4
+ require 'tripod/errors/validations'
@@ -0,0 +1,8 @@
1
+ # encoding: utf-8
2
+ module Tripod::Errors
3
+
4
+ # not found error.
5
+ class ResourceNotFound < StandardError
6
+ end
7
+
8
+ end
@@ -0,0 +1,9 @@
1
+ # encoding: utf-8
2
+ module Tripod::Errors
3
+
4
+ # Action attempted on a resource which requires the uri to be set.
5
+ class UriNotSet < StandardError
6
+
7
+ end
8
+
9
+ 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,7 @@
1
+ # encoding: utf-8
2
+
3
+ # This module defines behaviour for fields.
4
+ module Tripod::Fields
5
+ extend ActiveSupport::Concern
6
+
7
+ 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