tripod 0.2.3 → 0.3.0

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/.autotest ADDED
@@ -0,0 +1 @@
1
+ require "autotest/bundler"
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile CHANGED
@@ -4,5 +4,6 @@ source 'https://rubygems.org'
4
4
  gemspec
5
5
 
6
6
  group :test do
7
- gem "rspec", "~> 2.11"
7
+ gem "rspec", "~> 2.12"
8
+ gem "webmock"
8
9
  end
data/README.md CHANGED
@@ -55,21 +55,10 @@ Note: Tripod doesn't supply a database. You need to install one. I recommend [Fu
55
55
  p.important_dates = [Date.new(2011,1,1)]
56
56
  p.save!
57
57
 
58
- # Note: queries supplied to the where method should return the uris of the resource,
59
- # and what graph they're in.
60
- people = Person.where("
61
- SELECT ?person ?graph
62
- WHERE {
63
- GRAPH ?graph {
64
- ?person ?p ?o .
65
- ?person a <http://person> .
66
- }
67
- }",
68
- :uri_variable => 'person' ) # optionally, set a different name for the uri parameter (default: uri)
69
- # => returns an array of Person objects, containing all data we know about them.
58
+ people = Person.all.resources #=> returns all people as an array
59
+
60
+ ric = Person.find('http://ric') #=> returns a single Person object.
70
61
 
71
- ric = Person.find('http://ric')
72
- # => returns a single Person object.
73
62
 
74
63
  ## Some Other interesting features
75
64
 
@@ -89,20 +78,42 @@ Note: Tripod doesn't supply a database. You need to install one. I recommend [Fu
89
78
  ## Defining a graph at instantiation-time
90
79
 
91
80
  class Resource
92
- field :label RDF::RDFS.label
81
+ include Tripod::Resource
82
+ field :label, RDF::RDFS.label
93
83
 
94
84
  # notice also that you don't need to supply an rdf type or graph here!
95
85
  end
96
86
 
97
87
  r = Resource.new('http://foo', 'http://mygraph')
88
+ r.label = "example"
89
+ r.save
98
90
 
99
- # if you don't supply a graph at any point, you will get an error when you try to persist the resource.
91
+ # Note: if you don't supply a graph at any point (i.e. class or instance level), you will get an error when you try to persist the resource.
100
92
 
101
93
  ## Reading and writing arbitrary predicates
102
94
 
103
95
  r.write_predicate(RDF.type, 'http://myresource/type')
104
96
  r.read_predicate(RDF.type) # => RDF::URI.new("http://myresource/type")
105
97
 
98
+ ## Finders and criteria
99
+
100
+ Person.all #=> returns a Tripod::Criteria which selets all resources of rdf_type http://person
101
+
102
+ Resource.all #=> returns all resources in the database (as no rdf_type specified at class level)
103
+
104
+ Person.all.resources #=> returns all the actual resources for the criteria, as an array
105
+
106
+ Person.first #=> returns the first person
107
+
108
+ Person.count #=> returns the count of all people
109
+
110
+ # note that you need to use ?uri as the variable for the subject.
111
+ Person.where("?uri <http://name> 'Joe'") #=> returns a Tripod::Criteria
112
+
113
+ ## Chainable criteria
114
+
115
+ Person.all.where("?uri <http://name> 'Ric'").where("?uri <http://knows> <http://asa>).first
116
+
106
117
 
107
118
 
108
119
  [Full Documentation](http://rubydoc.info/gems/tripod/frames)
@@ -0,0 +1,100 @@
1
+ # This module defines behaviour for criteria
2
+ module Tripod
3
+
4
+ # this module provides execution methods to a criteria object
5
+ module CriteriaExecution
6
+
7
+ extend ActiveSupport::Concern
8
+
9
+ # Execute the query and return an array of all hydrated resources
10
+ def resources
11
+ resources_from_sparql(build_select_query)
12
+ end
13
+
14
+ # Execute the query and return the first result as a hydrated resource
15
+ def first
16
+ sq = Tripod::SparqlQuery.new(build_select_query)
17
+ first_sparql = sq.as_first_query_str
18
+ resources_from_sparql(first_sparql).first
19
+ end
20
+
21
+ # Return how many records the current criteria would return
22
+ def count
23
+ sq = Tripod::SparqlQuery.new(build_select_query)
24
+ count_sparql = sq.as_count_query_str
25
+ result = Tripod::SparqlClient::Query.select(count_sparql)
26
+ result[0][".1"]["value"].to_i
27
+ end
28
+
29
+ # PRIVATE:
30
+
31
+ included do
32
+
33
+ private
34
+
35
+ def resources_from_sparql(sparql)
36
+ uris_and_graphs = select_uris_and_graphs(sparql)
37
+ create_and_hydrate_resources(uris_and_graphs)
38
+ end
39
+
40
+ def build_select_query
41
+ select_query = "SELECT ?uri ?graph WHERE { GRAPH ?graph { "
42
+ select_query += self.where_clauses.join(" . ")
43
+ # TODO: Deal with extras.
44
+ select_query += " } }"
45
+ end
46
+
47
+ # create and hydrate the resources identified in uris_and_graphs.
48
+ # Note: if any of the graphs are not set, those resources can still be constructed, but not persisted back to DB.
49
+ def create_and_hydrate_resources(uris_and_graphs)
50
+
51
+ graph = self.resource_class.describe_uris(uris_and_graphs.keys) #uses the resource_class on the criteria object
52
+ repo = self.resource_class.add_data_to_repository(graph)
53
+
54
+ resources = []
55
+
56
+ uris_and_graphs.each_pair do |u,g|
57
+
58
+ # instantiate a new resource
59
+ r = self.resource_class.new(u,g)
60
+
61
+ # make a graph of data for this resource's uri
62
+ data_graph = RDF::Graph.new
63
+ repo.query( [RDF::URI.new(u), :predicate, :object] ) do |statement|
64
+ data_graph << statement
65
+ end
66
+
67
+ # use it to hydrate this resource
68
+ r.hydrate!(:graph => data_graph)
69
+ r.new_record = false
70
+ resources << r
71
+ end
72
+
73
+ resources
74
+ end
75
+
76
+
77
+ # based on the query passed in, build a hash of uris->graphs
78
+ # @param [ String] sparql. The sparql query
79
+ # @param [ Hash ] opts. A hash of options.
80
+ #
81
+ # @option options [ String ] uri_variable The name of the uri variable in the query, if not 'uri'
82
+ # @option options [ String ] graph_variable The name of the uri variable in thh query, if not 'graph'
83
+ def select_uris_and_graphs(sparql, opts={})
84
+ select_results = Tripod::SparqlClient::Query.select(sparql)
85
+
86
+ uris_and_graphs = {}
87
+
88
+ select_results.each do |r|
89
+ uri_variable = opts[:uri_variable] || 'uri'
90
+ graph_variable = opts[:graph_variable] || 'graph'
91
+ uris_and_graphs[ r[uri_variable]["value"] ] = r[graph_variable]["value"]
92
+ end
93
+
94
+ uris_and_graphs
95
+ end
96
+
97
+ end
98
+
99
+ end
100
+ end
@@ -0,0 +1,56 @@
1
+ # encoding: utf-8
2
+ require "tripod/criteria/execution"
3
+
4
+ module Tripod
5
+
6
+ # This module defines behaviour for criteria
7
+ class Criteria
8
+
9
+ include Tripod::CriteriaExecution
10
+
11
+ # the resource class that this criteria is for.
12
+ attr_accessor :resource_class
13
+
14
+ # array of all the where clauses in this criteria
15
+ attr_accessor :where_clauses
16
+
17
+ # array of all the extra clauses in this criteria
18
+ attr_accessor :extra_clauses
19
+
20
+ def initialize(resource_class)
21
+ self.resource_class = resource_class
22
+ self.where_clauses = []
23
+ self.extra_clauses = []
24
+
25
+ if resource_class._RDF_TYPE
26
+ self.where("?uri a <#{resource_class._RDF_TYPE.to_s}>")
27
+ else
28
+ self.where("?uri ?p ?o")
29
+ end
30
+ end
31
+
32
+ # they're equal if they return the same query
33
+ def ==(other)
34
+ build_select_query == other.send(:build_select_query)
35
+ end
36
+
37
+ # Takes a string and adds a where clause to this criteria.
38
+ # Returns a criteria object.
39
+ # Note: the subject being returned by the query must be identified by ?uri
40
+ # e.g. my_criteria.where("?uri a <http://my-type>")
41
+ #
42
+ # TODO: make it also take a hash?
43
+ def where(sparql_snippet)
44
+ where_clauses << sparql_snippet
45
+ self
46
+ end
47
+
48
+ # takes a string and adds an extra clause to this criteria.
49
+ # TODO: make it also take a hash?
50
+ def extras(sparql_snippet)
51
+ extra_clauses << sparql_snippet
52
+ self
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+ module Tripod::Errors
3
+
4
+ # field not present error.
5
+ class BadDataRequest < StandardError
6
+
7
+ attr_accessor :parent_bad_request
8
+
9
+ def initialize(message=nil, parent_bad_request_error=nil)
10
+ super(message)
11
+ parent_bad_request = parent_bad_request_error
12
+ end
13
+ end
14
+
15
+ end
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+ module Tripod::Errors
3
+
4
+ # field not present error.
5
+ class BadSparqlRequest < StandardError
6
+
7
+ attr_accessor :parent_bad_request
8
+
9
+ def initialize(message=nil, parent_bad_request_error=nil)
10
+ super(message)
11
+ parent_bad_request = parent_bad_request_error
12
+ end
13
+ end
14
+
15
+ end
@@ -0,0 +1,7 @@
1
+ # encoding: utf-8
2
+ module Tripod::Errors
3
+
4
+ class RdfParseFailed < StandardError
5
+ end
6
+
7
+ end
@@ -0,0 +1,9 @@
1
+ # encoding: utf-8
2
+ module Tripod::Errors
3
+
4
+ # Action attempted on a resource which requires the rdf type to be set.
5
+ class RdfTypeNotSet < StandardError
6
+
7
+ end
8
+
9
+ end
data/lib/tripod/errors.rb CHANGED
@@ -3,4 +3,9 @@ require 'tripod/errors/field_not_present'
3
3
  require 'tripod/errors/resource_not_found'
4
4
  require 'tripod/errors/uri_not_set'
5
5
  require 'tripod/errors/graph_uri_not_set'
6
- require 'tripod/errors/validations'
6
+ require 'tripod/errors/rdf_type_not_set'
7
+ require 'tripod/errors/validations'
8
+ require 'tripod/errors/rdf_parse_failed'
9
+
10
+ require 'tripod/errors/bad_sparql_request'
11
+ require 'tripod/errors/bad_data_request'
@@ -48,23 +48,26 @@ module Tripod::Finders
48
48
  resource
49
49
  end
50
50
 
51
- # Find a collection of +Resource+s by a SPARQL select statement which returns their uris.
52
- # Under the hood, this only executes two queries: a select, then a describe.
53
- #
54
- # @example
55
- # Person.where('SELECT ?uri ?graph WHERE { GRAPH ?graph { ?uri ?p ?o } } LIMIT 3')
56
- #
57
- # @param [ String ] criteria. A sparql query which returns a list of uris of the objects.
58
- # @param [ Hash ] opts. A hash of options.
59
- #
60
- # @option options [ String ] uri_variable The name of the uri variable in thh query, if not 'uri'
61
- # @option options [ String ] graph_variable The name of the uri variable in thh query, if not 'graph'
62
- # @option options [ String, RDF:URI, Array] only_hydrate a single predicate or list of predicates to hydrate the returned objects with. If ommited, does a full hydrate
63
- #
64
- # @return [ Array ] An array of hydrated resources of this class's type.
65
- def where(criteria, opts={})
66
- uris_and_graphs = select_uris_and_graphs(criteria, opts)
67
- create_and_hydrate_resources(uris_and_graphs)
51
+ # execute a where clause on this resource.
52
+ # returns a criteria object
53
+ def where(sparql_snippet)
54
+ criteria = Tripod::Criteria.new(self)
55
+ criteria.where(sparql_snippet)
56
+ end
57
+
58
+ # execute a query to return all objects (restricted by this class's rdf_type if specified)
59
+ # returns a criteria object
60
+ def all
61
+ criteria = Tripod::Criteria.new(self)
62
+ criteria
63
+ end
64
+
65
+ def count
66
+ self.all.count
67
+ end
68
+
69
+ def first
70
+ self.all.first
68
71
  end
69
72
 
70
73
  # returns a graph of triples which describe the uris passed in.
@@ -75,7 +78,7 @@ module Tripod::Finders
75
78
  uris_sparql_str = uris.map{ |u| "<#{u.to_s}>" }.join(" ")
76
79
 
77
80
  # Do a big describe statement, and read the results into an in-memory repo
78
- triples_string = Tripod::SparqlClient::Query::describe("DESCRIBE #{uris_sparql_str}")
81
+ triples_string = Tripod::SparqlClient::Query.describe("DESCRIBE #{uris_sparql_str}")
79
82
 
80
83
  RDF::Reader.for(:ntriples).new(triples_string) do |reader|
81
84
  reader.each_statement do |statement|
@@ -88,63 +91,5 @@ module Tripod::Finders
88
91
  graph
89
92
  end
90
93
 
91
-
92
- end
93
-
94
- # FOLLOWING METHODS NOT PART OF THE PUBLIC API:
95
- def self.included(base)
96
-
97
- class << base
98
-
99
- private
100
-
101
- # create and hydrate the resources identified in uris_and_graphs.
102
- # Note: if any of the graphs are not set, those resources can still be constructed, but not persisted back to DB.
103
- def create_and_hydrate_resources(uris_and_graphs)
104
-
105
- graph = describe_uris(uris_and_graphs.keys)
106
- repo = add_data_to_repository(graph)
107
-
108
- resources = []
109
-
110
- uris_and_graphs.each_pair do |u,g|
111
-
112
- # instantiate a new resource
113
- r = self.new(u,g)
114
-
115
- # make a graph of data for this resource's uri
116
- data_graph = RDF::Graph.new
117
- repo.query( [RDF::URI.new(u), :predicate, :object] ) do |statement|
118
- data_graph << statement
119
- end
120
-
121
- # use it to hydrate this resource
122
- r.hydrate!(:graph => data_graph)
123
- r.new_record = false
124
- resources << r
125
- end
126
-
127
- resources
128
- end
129
-
130
-
131
- # based on the query passed in, build a hash of uris->graphs
132
- def select_uris_and_graphs(criteria, opts)
133
- select_results = Tripod::SparqlClient::Query.select(criteria)
134
-
135
- uris_and_graphs = {}
136
-
137
- select_results.each do |r|
138
- uri_variable = opts[:uri_variable] || 'uri'
139
- graph_variable = opts[:graph_variable] || 'graph'
140
- uris_and_graphs[ r[uri_variable]["value"] ] = r[graph_variable]["value"]
141
- end
142
-
143
- uris_and_graphs
144
- end
145
-
146
-
147
- end
148
-
149
94
  end
150
95
  end
@@ -32,7 +32,7 @@ module Tripod::Persistence
32
32
  transaction && transaction.class == Tripod::Persistence::Transaction
33
33
  end
34
34
 
35
- def self.get_transcation(trans)
35
+ def self.get_transaction(trans)
36
36
  transaction = nil
37
37
 
38
38
  if Tripod::Persistence::Transaction.valid_transaction(trans)
@@ -70,7 +70,7 @@ module Tripod::Persistence
70
70
 
71
71
  raise Tripod::Errors::GraphUriNotSet.new() unless @graph_uri
72
72
 
73
- transaction = Tripod::Persistence::Transaction.get_transcation(opts[:transaction])
73
+ transaction = Tripod::Persistence::Transaction.get_transaction(opts[:transaction])
74
74
 
75
75
  if self.valid?
76
76
 
@@ -114,7 +114,7 @@ module Tripod::Persistence
114
114
  # if we get in here, save failed.
115
115
 
116
116
  # abort the transaction
117
- transaction = Tripod::Persistence::Transaction.get_transcation(opts[:transaction])
117
+ transaction = Tripod::Persistence::Transaction.get_transaction(opts[:transaction])
118
118
  transaction.abort() if transaction
119
119
 
120
120
  self.class.fail_validate!(self) # throw an exception
@@ -126,7 +126,7 @@ module Tripod::Persistence
126
126
 
127
127
  def destroy(opts={})
128
128
 
129
- transaction = Tripod::Persistence::Transaction.get_transcation(opts[:transaction])
129
+ transaction = Tripod::Persistence::Transaction.get_transaction(opts[:transaction])
130
130
 
131
131
  query = "
132
132
  # delete from default graph:
@@ -92,9 +92,11 @@ module Tripod::Resource
92
92
  other.class == Class ? self <= other : other.is_a?(self)
93
93
  end
94
94
 
95
+ # makes a "field" on this model called rdf_type
96
+ # and sets a class level _RDF_TYPE variable with the rdf_type passed in.
95
97
  def rdf_type(new_rdf_type)
96
98
  field :rdf_type, RDF.type
97
- self._RDF_TYPE = new_rdf_type
99
+ self._RDF_TYPE = RDF::URI.new(new_rdf_type.to_s)
98
100
  end
99
101
 
100
102
  def graph_uri(new_graph_uri)
@@ -23,13 +23,8 @@ module Tripod::SparqlClient
23
23
  :timeout => Tripod.timeout_seconds,
24
24
  )
25
25
  rescue RestClient::BadRequest => e
26
- body = e.http_body
27
- if body.start_with?('Error 400: Parse error:')
28
- # TODO: this is a SPARQL parsing exception. Do something different.
29
- raise e
30
- else
31
- raise e
32
- end
26
+ # just re-raise as a BadSparqlRequest Exception
27
+ raise Tripod::Errors::BadSparqlRequest.new(e.http_body, e)
33
28
  end
34
29
  end
35
30
 
@@ -67,6 +62,21 @@ module Tripod::SparqlClient
67
62
  return response.body
68
63
  end
69
64
 
65
+ # Executes an ASK +query+ against the SPARQL endpoint.
66
+ # Executes the +query+ and returns text by default
67
+ #
68
+ # @example Run a ASK query
69
+ # Tripod::SparqlClient::Query.select('ASK <http://foo>')
70
+ #
71
+ # @param [ String ] query The query to run
72
+ # @param [ String ] accept_header The format parameter to send to the database. Valud valid formats are text, xml, json
73
+
74
+ # @return [ String ] the raw response from the endpoint
75
+ def self.ask(query, format='text')
76
+ response = self.query(query, format)
77
+ return response.body
78
+ end
79
+
70
80
  # Executes a CONSTRUCT +query+ against the SPARQL endpoint.
71
81
  # Executes the +query+ and returns ntriples by default
72
82
  #
@@ -102,15 +112,37 @@ module Tripod::SparqlClient
102
112
  )
103
113
  true
104
114
  rescue RestClient::BadRequest => e
105
- body = e.http_body
106
- if body.start_with?('Error 400: Parse error:')
107
- # TODO: this is a SPARQL parsing exception. Do something different.
108
- raise e
109
- else
110
- raise e
115
+ # just re-raise as a BadSparqlRequest Exception
116
+ raise Tripod::Errors::BadSparqlRequest.new(e.http_body, e)
117
+ end
118
+ end
119
+
120
+ end
121
+
122
+ module Data
123
+ class DataClient
124
+ def self.submit(graph_uri, data, method)
125
+ url = "#{Tripod.data_endpoint}?graph=#{graph_uri}"
126
+ begin
127
+ RestClient::Request.execute(
128
+ :method => method,
129
+ :url => url,
130
+ :timeout => Tripod.timeout_seconds,
131
+ :payload => data
132
+ )
133
+ true
134
+ rescue RestClient::BadRequest => e
135
+ raise Tripod::Errors::BadDataRequest.new(e.http_body, e)
111
136
  end
112
137
  end
113
138
  end
114
139
 
140
+ def self.append(graph_uri, data)
141
+ DataClient.submit(graph_uri, data, :post)
142
+ end
143
+
144
+ def self.replace(graph_uri, data)
145
+ DataClient.submit(graph_uri, data, :put)
146
+ end
115
147
  end
116
148
  end
@@ -0,0 +1,86 @@
1
+ # encoding: utf-8
2
+ module Tripod
3
+
4
+ class SparqlQueryError < StandardError; end
5
+
6
+ class SparqlQuery
7
+
8
+ attr_reader :query # the original query string
9
+ attr_reader :query_type # symbol representing the type (:select, :ask etc)
10
+ attr_reader :body # the body of the query
11
+ attr_reader :prefixes # any prefixes the query may have
12
+
13
+ cattr_accessor :PREFIX_KEYWORDS
14
+ @@PREFIX_KEYWORDS = %w(BASE PREFIX)
15
+ cattr_accessor :KEYWORDS
16
+ @@KEYWORDS = %w(CONSTRUCT ASK DESCRIBE SELECT)
17
+
18
+ def initialize(query_string, parent_query=nil)
19
+ @query = query_string
20
+ @parent_query = parent_query
21
+
22
+ if self.has_prefixes?
23
+ @prefixes, @body = self.extract_prefixes
24
+ else
25
+ @body = self.query
26
+ end
27
+
28
+ @query_type = get_query_type
29
+ end
30
+
31
+ def has_prefixes?
32
+ self.class.PREFIX_KEYWORDS.each do |k|
33
+ return true if /^#{k}/i.match(query)
34
+ end
35
+ return false
36
+ end
37
+
38
+ def extract_prefixes
39
+ i = self.class.KEYWORDS.map {|k| self.query.index(/#{k}/i) || self.query.size+1 }.min
40
+ p = query[0..i-1]
41
+ b = query[i..-1]
42
+ return p.strip, b.strip
43
+ end
44
+
45
+ def as_count_query_str
46
+ # only allow for selects
47
+ raise SparqlQueryError.new("Can't turn this into a subquery") unless self.query_type == :select
48
+
49
+ count_query = "SELECT COUNT(*) { #{self.body} }"
50
+ count_query = "#{self.prefixes} #{count_query}" if self.prefixes
51
+
52
+ # just returns the string representing the count query for this query.
53
+ count_query
54
+ end
55
+
56
+ def as_first_query_str
57
+ # only allow for selects
58
+ raise SparqlQueryError.new("Can't turn this into a subquery") unless self.query_type == :select
59
+
60
+ first_query = "SELECT * { #{self.body} } LIMIT 1"
61
+ first_query = "#{self.prefixes} #{first_query}" if self.prefixes
62
+
63
+ # just returns the string representing the 'first' query for this query.
64
+ first_query
65
+ end
66
+
67
+ private
68
+
69
+ def get_query_type
70
+ if /^CONSTRUCT/i.match(self.body)
71
+ :construct
72
+ elsif /^ASK/i.match(self.body)
73
+ :ask
74
+ elsif /^DESCRIBE/i.match(self.body)
75
+ :describe
76
+ elsif /^SELECT/i.match(self.body)
77
+ :select
78
+ else
79
+ :unknown
80
+ end
81
+ end
82
+
83
+
84
+ end
85
+
86
+ end
@@ -1,3 +1,3 @@
1
1
  module Tripod
2
- VERSION = "0.2.3"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/tripod.rb CHANGED
@@ -45,6 +45,9 @@ module Tripod
45
45
  mattr_accessor :query_endpoint
46
46
  @@query_endpoint = 'http://127.0.0.1:3030/tripod/sparql'
47
47
 
48
+ mattr_accessor :data_endpoint
49
+ @@data_endpoint = 'http://127.0.0.1:3030/tripod/data'
50
+
48
51
  mattr_accessor :timeout_seconds
49
52
  @@timeout_seconds = 30
50
53
 
@@ -64,12 +67,14 @@ end
64
67
 
65
68
  require "tripod/extensions"
66
69
  require "tripod/sparql_client"
70
+ require "tripod/sparql_query"
67
71
 
68
72
  require "tripod/predicates"
69
73
  require "tripod/attributes"
70
74
  require "tripod/errors"
71
75
  require "tripod/repository"
72
76
  require "tripod/fields"
77
+ require "tripod/criteria"
73
78
  require "tripod/finders"
74
79
  require "tripod/persistence"
75
80
  require "tripod/eager_loading"