tripod 0.2.3 → 0.3.0

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