gearbox 0.1.0 → 0.1.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/Gemfile +5 -3
  2. data/Rakefile +1 -0
  3. data/VERSION +1 -1
  4. data/bin/gearbox +9 -0
  5. data/gearbox.gemspec +134 -0
  6. data/lib/gearbox.rb +124 -5
  7. data/lib/gearbox/attribute.rb +128 -0
  8. data/lib/gearbox/mixins/active_model_implementation.rb +27 -0
  9. data/lib/gearbox/mixins/resource.rb +20 -4
  10. data/lib/gearbox/mixins/semantic_accessors.rb +128 -89
  11. data/lib/gearbox/mixins/subject_methods.rb +88 -0
  12. data/lib/gearbox/rdf_collection.rb +22 -8
  13. data/lib/gearbox/types.rb +9 -8
  14. data/lib/gearbox/vocabulary.rb +149 -0
  15. data/lib/pry_utilities.rb +71 -0
  16. data/scratch/4s.rb +335 -0
  17. data/scratch/DEVELOPMENT_NOTES.md +85 -0
  18. data/scratch/actionable.md +34 -0
  19. data/scratch/ccrdf.html-rdfa.nq +100 -0
  20. data/scratch/foo.rb +17 -0
  21. data/scratch/index.rdf +7932 -0
  22. data/scratch/j2.rb +10 -0
  23. data/scratch/junk.rb +16 -0
  24. data/scratch/out.rb +67 -0
  25. data/spec/gearbox/attribute_spec.rb +455 -0
  26. data/spec/gearbox/mixins/active_model_implementation_spec.rb +18 -0
  27. data/spec/gearbox/mixins/ad_hoc_properties_spec.rb +44 -44
  28. data/spec/gearbox/mixins/resource_spec.rb +47 -8
  29. data/spec/gearbox/mixins/semantic_accessors_spec.rb +72 -43
  30. data/spec/gearbox/mixins/subject_methods_spec.rb +126 -0
  31. data/spec/gearbox/rdf_collection_spec.rb +28 -2
  32. data/spec/gearbox_spec.rb +6 -2
  33. data/spec/spec_helper.rb +1 -0
  34. metadata +150 -42
  35. data/Gemfile.lock +0 -138
  36. data/lib/examples/audience.rb +0 -24
  37. data/lib/examples/person.rb +0 -29
  38. data/lib/examples/reference.rb +0 -38
  39. data/lib/examples/theme.rb +0 -8
  40. data/spec/examples/audience_spec.rb +0 -28
  41. data/spec/examples/person_spec.rb +0 -45
  42. data/spec/examples/reference_spec.rb +0 -43
  43. data/spec/examples/theme_spec.rb +0 -137
@@ -14,14 +14,15 @@ module Gearbox
14
14
  # No autoloading here--the associations to XSD types are made by the
15
15
  # classes themselves, so we need to explicitly require them or XSD types
16
16
  # will show up as not found.
17
- require 'gearbox/types/integer'
18
- require 'gearbox/types/boolean'
19
- require 'gearbox/types/any'
20
- require 'gearbox/types/string'
21
- require 'gearbox/types/float'
22
- require 'gearbox/types/uri'
23
- require 'gearbox/types/decimal'
24
- require 'gearbox/types/native'
17
+ require_relative 'types/integer'
18
+ require_relative 'types/boolean'
19
+ require_relative 'types/any'
20
+ require_relative 'types/string'
21
+ require_relative 'types/float'
22
+ require_relative 'types/uri'
23
+ require_relative 'types/decimal'
24
+ require_relative 'types/native'
25
+ require_relative 'types/date'
25
26
 
26
27
 
27
28
  end
@@ -0,0 +1,149 @@
1
+
2
+
3
+
4
+
5
+ module Gearbox
6
+ =begin
7
+ ##
8
+ # Derived from RDF::Vocabulary
9
+ # However, I have two new use cases for Gearbox::Vocabulary
10
+ # * as a single source for an ontology
11
+ # * as a base_uri in a model
12
+
13
+ ## Single source Ontology
14
+ Ontologies have classes and attributes. That means there is some nesting in the vocabulary.
15
+ We have this
16
+
17
+ * /:collection/:item/:sub-collection/:item
18
+ * concatenating the existing identifier or key with a suitable base URI.
19
+ * /:collection/:id
20
+
21
+ http://www.bbc.co.uk/music/artists/a74b1b7f-71a5-4011-9441-d0b5e4122711
22
+ http://musicbrainz.org/artist/a74b1b7f-71a5-4011-9441-d0b5e4122711
23
+
24
+ <http:www.example.org/category/heavy-metal>
25
+ rdfs:label "Heavy Metal"
26
+
27
+ =end
28
+
29
+
30
+ # But with some patterns in mind from http://patterns.dataincubator.org/book/
31
+
32
+
33
+ class Vocabulary < RDF::Vocabulary
34
+
35
+ # =================
36
+ # = Class Methods =
37
+ # =================
38
+
39
+ class << self
40
+
41
+ ##
42
+ # Defines a vocabulary term called `property`.
43
+ #
44
+ # @param [Symbol]
45
+ # @return [void]
46
+ def property(property, opts={})
47
+ metaclass = class << self; self; end
48
+ metaclass.send(:define_method, property) { self.lookup(property) } # class method
49
+ end
50
+
51
+ ##
52
+ # Returns the URI for the term `property` in this vocabulary.
53
+ #
54
+ # @param [#to_s] property
55
+ # @return [RDF::URI]
56
+ def [](property)
57
+ RDF::URI.intern([to_s, property.to_s].join(''))
58
+ end
59
+
60
+ def lookup(property)
61
+
62
+ end
63
+
64
+
65
+ protected
66
+ def create(uri) # @private
67
+ @@uri = uri
68
+ self
69
+ end
70
+
71
+ def inherited(subclass) # @private
72
+ @@subclasses << subclass
73
+ unless @@uri.nil?
74
+ subclass.send(:private_class_method, :new)
75
+ @@uris[subclass] = @@uri
76
+ @@uri = nil
77
+ end
78
+ super
79
+ end
80
+
81
+ def method_missing(property, *args, &block)
82
+ if args.empty? && @@uris.has_key?(self)
83
+ self[property]
84
+ else
85
+ super
86
+ end
87
+ end
88
+
89
+ end
90
+
91
+ ##
92
+ # @param [RDF::URI, String, #to_s]
93
+ def initialize(uri)
94
+ @uri = case uri
95
+ when RDF::URI then uri.to_s
96
+ else RDF::URI.parse(uri.to_s) ? uri.to_s : nil
97
+ end
98
+ end
99
+
100
+ ##
101
+ # Returns the URI for the term `property` in this vocabulary.
102
+ #
103
+ # @param [#to_s] property
104
+ # @return [URI]
105
+ def [](property)
106
+ RDF::URI.intern([to_s, property.to_s].join(''))
107
+ end
108
+
109
+ ##
110
+ # Returns the base URI for this vocabulary.
111
+ #
112
+ # @return [URI]
113
+ def to_uri
114
+ RDF::URI.intern(to_s)
115
+ end
116
+
117
+ ##
118
+ # Returns a string representation of this vocabulary.
119
+ #
120
+ # @return [String]
121
+ def to_s
122
+ @uri.to_s
123
+ end
124
+
125
+ ##
126
+ # Returns a developer-friendly representation of this vocabulary.
127
+ #
128
+ # @return [String]
129
+ def inspect
130
+ sprintf("#<%s:%#0x(%s)>", self.class.name, __id__, to_s)
131
+ end
132
+
133
+ protected
134
+
135
+ def method_missing(property, *args, &block)
136
+ if args.empty?
137
+ self[property]
138
+ else
139
+ raise ArgumentError.new("wrong number of arguments (#{args.size} for 0)")
140
+ end
141
+ end
142
+
143
+ private
144
+
145
+ @@subclasses = [::RDF] # @private
146
+ @@uris = {} # @private
147
+ @@uri = nil # @private
148
+ end # Vocabulary
149
+ end # RDF
@@ -0,0 +1,71 @@
1
+ # Useful for a Pry session.
2
+
3
+ require 'forwardable'
4
+
5
+ include Gearbox
6
+
7
+ class Utilities
8
+ # Great for writing ad hoc models.
9
+ # TODO: Make this work for several sessions. (Thread it?)
10
+ def write_model(name)
11
+ raise "Directory does not exist" unless File.exist?(model_directory)
12
+ filename = File.join(model_directory, "#{name}.rb")
13
+ raise "ENV['EDITOR'] not set" unless ENV['EDITOR']
14
+ `#{ENV['EDITOR']} #{filename}`
15
+ load filename
16
+ end
17
+ alias :update_model :write_model
18
+ alias :build_model :write_model
19
+
20
+ def load_model(name)
21
+ raise "Directory does not exist" unless File.exist?(model_directory)
22
+ filename = File.join(model_directory, "#{name}.rb")
23
+ load filename
24
+ end
25
+
26
+ def model_directory
27
+ @model_directory ||= "/tmp"
28
+ end
29
+ attr_writer :model_directory
30
+
31
+ def tmp_directory
32
+ @tmp_directory ||= "/tmp"
33
+ end
34
+ attr_writer :tmp_directory
35
+
36
+ require 'fileutils'
37
+ # Great for writing descriptions without messing around with quotes and escapes and things
38
+ # TODO: Make this work for several sessions. (Thread it?)
39
+ def get_note(type="md")
40
+ contents = nil
41
+ begin
42
+ filename = File.join(tmp_directory, "#{self.object_id}.#{type}")
43
+ i = 0
44
+ while File.exist?(filename)
45
+ filename = File.join(tmp_directory, "#{self.object_id}#{i}.#{type}")
46
+ i += 1
47
+ end
48
+ raise "ENV['EDITOR'] not set" unless ENV['EDITOR']
49
+ `#{ENV['EDITOR']} #{filename}`
50
+ contents = File.read(filename)
51
+ ensure
52
+ puts "Cleaning up temp file and exiting ..."
53
+ FileUtils.rm_f(filename)
54
+ end
55
+ end
56
+
57
+ end
58
+
59
+ @utilities = Utilities.new
60
+ extend Forwardable
61
+ def_delegators :@utilities,
62
+ :write_model,
63
+ :update_model,
64
+ :build_model,
65
+ :model_directory,
66
+ :model_directory=,
67
+ :tmp_directory,
68
+ :tmp_directory=,
69
+ :get_note,
70
+ :load_model
71
+
@@ -0,0 +1,335 @@
1
+ require 'linkeddata'
2
+ require 'rest_client'
3
+ require 'nokogiri'
4
+ require 'pry'
5
+
6
+ # endpoint = "http://localhost:8000/data/"
7
+ # stmt = <<-END
8
+ # INSERT DATA
9
+ # {
10
+ # <http://example.org/subject> <http://example.org/predicate> <http://example.org/object>
11
+ # }
12
+ # END
13
+ # response = RestClient.put endpoint, stmt, :content_type => "application/rdf+xml"
14
+ # graph = 'http://source.data.gov.uk/data/reference/organogram-co/2010-06-30'
15
+ # binding.pry
16
+ # response = RestClient.put endpoint + graph, stmt, :content_type => "application/rdf+xml"
17
+
18
+ def load_graph
19
+
20
+ endpoint = "http://localhost:8000/data/"
21
+ filename = File.expand_path("../index.rdf", __FILE__)
22
+ graph = 'http://source.data.gov.uk/data/reference/organogram-co/2010-06-30'
23
+ response = RestClient.put endpoint + graph, File.read(filename), :content_type => "application/rdf+xml"
24
+ # response = RestClient.put endpoint, File.read(filename), :content_type => "application/rdf+xml"
25
+ puts "Response: #{response.code}", response.to_str
26
+ end
27
+
28
+ def count_triples
29
+ endpoint = "http://localhost:8000/sparql/"
30
+ # sparql = "SELECT (COUNT(DISTINCT ?s) AS ?count) WHERE { ?s ?p ?o } LIMIT 10"
31
+ sparql = "SELECT * WHERE { ?s ?p ?o } LIMIT 10"
32
+ response = RestClient.post endpoint, :query => sparql
33
+ xml = Nokogiri::XML(response.to_str)
34
+ end
35
+
36
+ def ordered_variables
37
+ endpoint = "http://localhost:8000/sparql/"
38
+ sparql = "SELECT (COUNT(DISTINCT ?s) AS ?count) WHERE { ?s ?p ?o } LIMIT 10"
39
+ # sparql = "SELECT * WHERE { ?s ?p ?o } LIMIT 10"
40
+ response = RestClient.post endpoint, :query => sparql
41
+ xml = Nokogiri::XML(response.to_str)
42
+ found = xml.xpath("//sparql:head/sparql:variable/@name", "sparql" => "http://www.w3.org/2005/sparql-results#").map(&:value)
43
+ found
44
+ # xml.xpath("//sparql:head/sparql:variable", "sparql" => "http://www.w3.org/2005/sparql-results#").map {|e| e.attr('name')}
45
+ end
46
+
47
+ # result = ordered_variables
48
+
49
+
50
+ # Examples of SPARQL Update 1.1 for 4Store:
51
+ # http://4store.org/trac/wiki/SparqlServer
52
+
53
+ # endpoint = "http://localhost:8000/sparql/"
54
+ # You can use sparql-query to query the SPARQL server on the command line.
55
+
56
+
57
+ # Soft limit of about 1000, what is that? triples? seconds?
58
+
59
+ class NotImplemented < StandardError; end
60
+
61
+ # Geared towards interpreting whatever results we get back from a SPARQL result
62
+ class SPARQLResult
63
+
64
+ SPARQL_NAMESPACE = {'sparql' => 'http://www.w3.org/2005/sparql-results#'} unless defined?(SPARQL_NAMESPACE)
65
+ attr_reader :result_string
66
+
67
+ def initialize(result_string)
68
+ @result_string = result_string
69
+ end
70
+
71
+ def ask?
72
+ not xml.xpath('//sparql:boolean', SPARQL_NAMESPACE).empty?
73
+ end
74
+
75
+ def ask_value
76
+ return nil unless ask?
77
+ xml.xpath('//sparql:boolean', SPARQL_NAMESPACE).text == 'true'
78
+ end
79
+
80
+ def results
81
+ return ask_value if ask?
82
+ @results = Hash.new {|h, k| h[k] = []}
83
+ xml.xpath('//sparql:result', SPARQL_NAMESPACE).each do |result|
84
+ result.xpath('./sparql:binding', SPARQL_NAMESPACE).each do |variable_binding|
85
+ name = variable_binding.attr('name')
86
+ value = extract_value(variable_binding)
87
+ @results[name] << value
88
+ end
89
+ end
90
+ @results
91
+ end
92
+
93
+ def result_table
94
+ return ask_value if ask?
95
+ keys = head_node.xpath(".//sparql:variable/@name", SPARQL_NAMESPACE).map(&:value)
96
+ values = []
97
+ xml.xpath('//sparql:result', SPARQL_NAMESPACE).each do |result|
98
+ record = []
99
+ result.xpath('./sparql:binding', SPARQL_NAMESPACE).each do |variable_binding|
100
+ value = extract_value(variable_binding)
101
+ record << value
102
+ end
103
+ values << record
104
+ end
105
+ # puts keys.inspect
106
+ # values.each {|v| puts v.inspect}
107
+ values.unshift keys
108
+ values
109
+ end
110
+
111
+ def inspect
112
+ # "SPARQLResult: #{result_string[0..50].split(/\n/)[0]}"
113
+ "SPARQLResult: #{results.keys}"
114
+ end
115
+
116
+ private
117
+
118
+ def extract_value(variable_binding_node)
119
+ # Not Implemented: literals, sequences, indices, data types, languages
120
+ # Need to deal with bnode, uri, or literal. Ignoring all of that for now...
121
+ variable_binding_node.text
122
+ end
123
+
124
+ def head_node
125
+ @head_node ||= xml.xpath('//sparql:head', SPARQL_NAMESPACE)
126
+ end
127
+
128
+ def variable_nodes
129
+ @variable_nodes ||= head_node.xpath('//sparql::variable', SPARQL_NAMESPACE)
130
+ end
131
+
132
+ def xml
133
+ @xml ||= Nokogiri::XML(result_string)
134
+ end
135
+ end
136
+
137
+
138
+ # response = RestClient.put endpoint + graph, File.read(filename), :content_type => "application/rdf+xml"
139
+
140
+ def exercise_sparql_result
141
+ endpoint = "http://localhost:8000/sparql/"
142
+ sparql = "SELECT * WHERE { ?s ?p ?o } LIMIT 10"
143
+ # sparql = "ASK { ?s ?p ?o }"
144
+ response = RestClient.post endpoint, :query => sparql
145
+ s = SPARQLResult.new(response)
146
+ s.results
147
+ end
148
+
149
+ # puts exercise_sparql_result.inspect
150
+
151
+ # TODO: Not dealing with the soft limit yet
152
+
153
+ # Need to figure out how to write a finder for a model...
154
+ # 1) load the data in the graph
155
+ # 2) write the query
156
+ # 3) start to figure out the different ways to construct this...
157
+ def constructing_model_sparql
158
+ end
159
+
160
+ class SPARQLEndpoint
161
+
162
+ attr_reader :base_uri
163
+
164
+ def initialize(uri="http://localhost:8000")
165
+ @base_uri = uri
166
+ end
167
+
168
+ attr_writer :select_uri
169
+ def select_uri
170
+ @select_uri ||= File.join(base_uri, 'sparql/')
171
+ end
172
+
173
+ attr_writer :update_uri
174
+ def update_uri
175
+ @update_uri ||= File.join(base_uri, 'data/')
176
+ end
177
+
178
+ def query(sparql)
179
+ response = RestClient.post select_uri, :query => sparql
180
+ SPARQLResult.new(response)
181
+ end
182
+
183
+ def queries
184
+ registered.keys
185
+ end
186
+
187
+ def memoize_query(name, sparql=nil)
188
+ return memoized[name] if memoized[name]
189
+ register_query(name, sparql)
190
+ memoized[name] ||= query(sparql)
191
+ end
192
+ alias :memoize :memoize_query
193
+
194
+ def register_query(name, sparql)
195
+ registered[name] = sparql
196
+ memoized[name] = nil
197
+ self.class.send(:define_method, name) do
198
+ memoized[name] ||= query(registered[name])
199
+ end
200
+ true
201
+ end
202
+ alias :register :register_query
203
+
204
+ def registered
205
+ @registered ||= {}
206
+ end
207
+
208
+ def memoized
209
+ @memoized ||= {}
210
+ end
211
+
212
+ def inspect
213
+ "SPARQLEndpoint: #{base_uri}"
214
+ end
215
+
216
+ end
217
+
218
+ # s = SPARQLEndpoint.new
219
+ # s.register('basic', "SELECT * WHERE { ?s ?p ?o } LIMIT 10")
220
+ # s.register 'predicates', 'select distinct ?predicate where {?s ?predicate ?o}'
221
+ # s.register 'notations', 'select distinct ?subject ?notations where {?subject <http://www.w3.org/2004/02/skos/core#notation}'
222
+ # s.register 'phones', 'select distinct ?phone where {?s <http://xmlns.com/foaf/0.1/phone> ?phone}'
223
+ # binding.pry
224
+
225
+ require 'fileutils'
226
+ # Note, I haven't come up with a good fallback if ENV['EDITOR'] hasn't been defined yet.
227
+ # I tried to open vim as a fallback, I ended up in non-terminal mode, had to kill processes manually.
228
+ # Will need to fork or something: http://workingwithunixprocesses.com/
229
+ def get_note(type="md")
230
+ contents = nil
231
+ begin
232
+ filename = "/tmp/#{self.object_id}.#{type}"
233
+ while File.exist?(filename)
234
+ i ||= 0
235
+ filename = "/tmp/#{self.object_id}#{i}.#{type}"
236
+ end
237
+ `#{ENV['EDITOR']} #{filename}`
238
+ contents = File.read(filename)
239
+ ensure
240
+ puts "Exiting ..."
241
+ FileUtils.rm_f(filename)
242
+ end
243
+ # contents
244
+ end
245
+
246
+ def get_model_contents(subject='http://reference.data.gov.uk/id/department/co/post/44')
247
+ s = SPARQLEndpoint.new
248
+ s.register 'model', <<-END
249
+ SELECT DISTINCT ?predicate ?value
250
+ WHERE {
251
+ {<#{subject}> ?predicate ?value}
252
+ UNION
253
+ {?value ?predicate <#{subject}>}
254
+ }
255
+ END
256
+ s.model
257
+ end
258
+
259
+ # get_model_contents
260
+
261
+ def write_model(name)
262
+ filename = "/tmp/#{name}.rb"
263
+ `#{ENV['EDITOR']} #{filename}`
264
+ load filename
265
+ end
266
+ alias :update_model :write_model
267
+
268
+ =begin
269
+
270
+ The task list says to play with more semantic models: exploratory SPARQL, SPARQL for a single model, discover data structures in other LOD. Basically, I'm trying to figure out how to be familiar with this stuff.
271
+ =end
272
+
273
+ require 'forwardable'
274
+
275
+ class Person
276
+
277
+ extend Forwardable
278
+ def_delegators :@endpoint, :white_list, :direct_attributes
279
+
280
+ attr_reader :endpoint, :id
281
+
282
+ def initialize(id="person178")
283
+ @id = id
284
+ @endpoint = SPARQLEndpoint.new
285
+ @endpoint.register :white_list, white_list_sparql
286
+ @endpoint.register :direct_attributes, direct_attributes_sparql
287
+ end
288
+
289
+ def inspect
290
+ "Person: #{URI.split(subject)[-1]}"
291
+ end
292
+
293
+ def predicate_hash
294
+ @predicate_hash ||= {
295
+ "?email" => "http://xmlns.com/foaf/0.1/mbox",
296
+ "?page" => "http://xmlns.com/foaf/0.1/page",
297
+ "?phone" => "http://xmlns.com/foaf/0.1/phone",
298
+ "?name" => "http://xmlns.com/foaf/0.1/name"
299
+ }
300
+ end
301
+
302
+ def base_uri
303
+ "http://source.data.gov.uk/data/reference/organogram-co/2010-10-31#"
304
+ end
305
+
306
+ def subject
307
+ "#{base_uri}#{id}"
308
+ end
309
+
310
+ def white_list_sparql
311
+ <<-END
312
+ SELECT *
313
+ #{white_list_where_clause}
314
+ END
315
+ end
316
+
317
+ def white_list_where_clause
318
+ @white_list_where_clause ||= <<-END
319
+ WHERE {
320
+ #{predicate_hash.map {|variable, predicate| "<#{subject}> <#{predicate}> #{variable}"}.join(" .\n")}
321
+ }
322
+ END
323
+ end
324
+
325
+ def direct_attributes_sparql
326
+ @direct_attributes_sparql ||=<<-END
327
+ SELECT DISTINCT ?predicate ?value
328
+ WHERE {
329
+ {<#{subject}> ?predicate ?value}
330
+ UNION
331
+ {?value ?predicate <#{subject}>}
332
+ }
333
+ END
334
+ end
335
+ end