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