roadforest 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/examples/file-management.rb +2 -1
  2. data/lib/roadforest/application/dispatcher.rb +12 -2
  3. data/lib/roadforest/application/services-host.rb +1 -1
  4. data/lib/roadforest/application.rb +12 -5
  5. data/lib/roadforest/blob-model.rb +7 -1
  6. data/lib/roadforest/content-handling/engine.rb +4 -1
  7. data/lib/roadforest/content-handling/type-handlers/jsonld.rb +25 -17
  8. data/lib/roadforest/http/adapters/excon.rb +1 -1
  9. data/lib/roadforest/http/graph-response.rb +18 -4
  10. data/lib/roadforest/http/graph-transfer.rb +52 -8
  11. data/lib/roadforest/http/message.rb +10 -1
  12. data/lib/roadforest/model.rb +17 -4
  13. data/lib/roadforest/rdf/context-fascade.rb +80 -5
  14. data/lib/roadforest/rdf/etagging.rb +53 -0
  15. data/lib/roadforest/rdf/focus-list.rb +47 -5
  16. data/lib/roadforest/rdf/graph-copier.rb +4 -5
  17. data/lib/roadforest/rdf/graph-focus.rb +38 -14
  18. data/lib/roadforest/rdf/graph-reading.rb +102 -47
  19. data/lib/roadforest/rdf/graph-store.rb +12 -8
  20. data/lib/roadforest/rdf/investigation.rb +0 -1
  21. data/lib/roadforest/rdf/normalization.rb +37 -4
  22. data/lib/roadforest/rdf/resource-pattern.rb +1 -0
  23. data/lib/roadforest/rdf/source-rigor/credence-annealer.rb +2 -0
  24. data/lib/roadforest/rdf/source-rigor/http-investigator.rb +12 -14
  25. data/lib/roadforest/rdf/update-focus.rb +7 -62
  26. data/lib/roadforest/remote-host.rb +21 -10
  27. data/lib/roadforest/resource/rdf/read-only.rb +4 -0
  28. data/lib/roadforest/server.rb +22 -0
  29. data/lib/roadforest/test-support/dispatcher-facade.rb +1 -1
  30. data/lib/roadforest/test-support/http-client.rb +8 -0
  31. data/spec/client.rb +20 -2
  32. data/spec/credence-annealer.rb +1 -1
  33. data/spec/excon-adapater.rb +30 -0
  34. data/spec/focus-list.rb +34 -0
  35. data/spec/form-parsing.rb +1 -0
  36. data/spec/full-integration.rb +193 -0
  37. data/spec/rdf-normalization.rb +17 -0
  38. data/spec/update-focus.rb +37 -0
  39. metadata +22 -17
  40. data/lib/roadforest/rdf/focus-wrapping.rb +0 -30
@@ -56,6 +56,7 @@ module FileManagementExample
56
56
  end
57
57
 
58
58
  def add_child(graph)
59
+ services.logger.debug(graph.source_graph.dump(:nquads))
59
60
  new_file = FileRecord.new(graph.first(:lc, "name"), false)
60
61
  services.file_records << new_file
61
62
  end
@@ -83,7 +84,7 @@ module FileManagementExample
83
84
  end
84
85
 
85
86
  def graph_update(graph)
86
- data.resolved = graph[[:lc, "resolved"]]
87
+ data.resolved = graph[:lc, "resolved"]
87
88
  new_graph
88
89
  end
89
90
 
@@ -7,8 +7,9 @@ module RoadForest
7
7
  super(method(:create_resource))
8
8
  @services = services
9
9
  @route_names = {}
10
+ @trace_by_default = false
10
11
  end
11
- attr_accessor :services
12
+ attr_accessor :services, :trace_by_default
12
13
 
13
14
  def resource_route(resource, name, path_spec, bindings)
14
15
  route = Route.new(path_spec, resource, bindings || {})
@@ -19,10 +20,19 @@ module RoadForest
19
20
  end
20
21
 
21
22
  def add_route(name, path_spec, resource_type, model_class, bindings = nil, &block)
23
+ if trace_by_default
24
+ return add_traced_route(name, path_spec, resource_type, model_class, bindings, &block)
25
+ else
26
+ return add_untraced_route(name, path_spec, resource_type, model_class, bindings, &block)
27
+ end
28
+ end
29
+ alias add add_route
30
+
31
+ def add_untraced_route(name, path_spec, resource_type, model_class, bindings = nil, &block)
22
32
  resource = bundle_typed_resource(resource_type, model_class, name)
23
33
  resource_route(resource, name, path_spec, bindings, &block)
24
34
  end
25
- alias add add_route
35
+ alias add_untraced add_untraced_route
26
36
 
27
37
  def add_traced_route(name, path_spec, resource_type, model_class, bindings = nil, &block)
28
38
  resource = bundle_traced_resource(resource_type, model_class, name)
@@ -5,6 +5,6 @@ class RoadForest::Application
5
5
  def initialize
6
6
  end
7
7
 
8
- attr_accessor :router, :canonical_host, :type_handling
8
+ attr_accessor :router, :canonical_host, :type_handling, :logger
9
9
  end
10
10
  end
@@ -9,17 +9,19 @@ require 'roadforest/application/path-provider'
9
9
  require 'roadforest/application/services-host'
10
10
  require 'roadforest/resource/rdf'
11
11
  require 'roadforest/content-handling/engine'
12
+ require 'roadforest/rdf/normalization'
12
13
 
13
14
  module RoadForest
14
15
  class Application
16
+ include RDF::Normalization
15
17
  include Resource::Handlers
16
18
 
17
- def initialize(canonical_host, services, configuration = nil, dispatcher = nil)
18
- @canonical_host = ::RDF::URI.parse(canonical_host)
19
+ def initialize(canonical_host, services = nil, configuration = nil, dispatcher = nil)
20
+ @canonical_host = normalize_resource(canonical_host)
19
21
  configuration ||= Webmachine::Configuration.default
20
22
  dispatcher ||= Dispatcher.new(services)
21
23
  super(configuration, dispatcher)
22
- self.services = services
24
+ self.services = services unless services.nil?
23
25
 
24
26
  setup
25
27
  end
@@ -27,16 +29,21 @@ module RoadForest
27
29
  def setup
28
30
  end
29
31
 
30
- attr_reader :services, :canonical_host
32
+ attr_accessor :services, :canonical_host
31
33
 
32
34
  alias router dispatcher
33
35
 
34
36
  def services=(service_host)
35
37
  router.services = service_host
36
38
  @services = service_host
37
- @services.canonical_host = @canonical_host
39
+ @services.canonical_host = canonical_host
38
40
  @services.router = PathProvider.new(@dispatcher)
39
41
  @services.type_handling ||= ContentHandling::Engine.default
42
+ @services.logger ||=
43
+ begin
44
+ require 'logger'
45
+ Logger.new("roadforest.log")
46
+ end
40
47
  end
41
48
  end
42
49
  end
@@ -44,12 +44,18 @@ module RoadForest
44
44
  File::open(path)
45
45
  end
46
46
 
47
+ def incomplete_path
48
+ [path,"incomplete"].join(".")
49
+ end
50
+
47
51
  def update(incoming)
48
- File::open(path, "w") do |file|
52
+ File::open(incomplete_path, "w") do |file|
49
53
  incoming.each do |chunk|
50
54
  file.write(chunk)
51
55
  end
52
56
  end
57
+ Pathname.new(incomplete_path).rename(path)
58
+
53
59
  return nil
54
60
  end
55
61
  end
@@ -2,6 +2,8 @@ require 'roadforest/content-handling/media-type'
2
2
 
3
3
  module RoadForest
4
4
  module ContentHandling
5
+ class UnrecognizedType < ::StandardError; end
6
+
5
7
  class Engine
6
8
  class TypeHandlerList
7
9
  def initialize(prefix)
@@ -39,7 +41,7 @@ module RoadForest
39
41
  type = MediaType.parse(type)
40
42
  @handlers.fetch(type)
41
43
  rescue KeyError
42
- raise "No Content-Type handler for #{content_type}"
44
+ raise UnrecognizedType, "No Content-Type handler for #{type}"
43
45
  end
44
46
  end
45
47
 
@@ -105,6 +107,7 @@ module RoadForest
105
107
  # Given the 'Accept' header and provided types, chooses an
106
108
  # appropriate media type.
107
109
  def choose_media_type(provided, header)
110
+ return "*/*" if header.nil?
108
111
  requested = MediaTypeList.build(header.split(/\s*,\s*/))
109
112
  requested.best_match_from(provided)
110
113
  end
@@ -1,6 +1,7 @@
1
1
  #@require 'rdf/rdfa' #XXX Otherwise json-ld grabs RDFa documents. Awaiting fix
2
2
  #upstream
3
3
  require 'json/ld'
4
+ require 'roadforest/rdf/normalization'
4
5
 
5
6
  module RoadForest
6
7
  module MediaType
@@ -13,13 +14,13 @@ module RoadForest
13
14
  end
14
15
  attr_reader :type, :handler
15
16
 
16
- def local_to_network(network)
17
- @handler.local_to_network(network)
17
+ def local_to_network(base_uri, network)
18
+ @handler.local_to_network(base_uri, network)
18
19
  end
19
20
  alias from_graph local_to_network
20
21
 
21
- def network_to_local(source)
22
- @handler.network_to_local(source)
22
+ def network_to_local(base_uri, source)
23
+ @handler.network_to_local(base_uri, source)
23
24
  end
24
25
  alias to_graph network_to_local
25
26
  end
@@ -46,23 +47,23 @@ module RoadForest
46
47
  end
47
48
 
48
49
  class Handler
49
- def network_to_local(network)
50
+ def network_to_local(base_uri, network)
50
51
  return network
51
52
  end
52
53
 
53
- def local_to_network(local)
54
+ def local_to_network(base_uri, local)
54
55
  return local
55
56
  end
56
57
 
57
58
  def parse_for(resource)
58
59
  source = resource.request_body
59
- input_data = network_to_local(source)
60
60
  model = resource.model
61
+ input_data = network_to_local(model.my_url, source)
61
62
 
62
63
  update_model(model, input_data)
63
64
 
64
65
  renderer = model.type_handling.choose_renderer(resource.request_accept_header)
65
- body = renderer.local_to_network(model.response_data)
66
+ body = renderer.local_to_network(model.my_url, model.response_data)
66
67
 
67
68
  build_response(resource)
68
69
  end
@@ -70,13 +71,13 @@ module RoadForest
70
71
  def render_for(resource)
71
72
  model = resource.model
72
73
  output_data = get_output(model)
73
- local_to_network(output_data)
74
+ local_to_network(model.my_url, output_data)
74
75
  end
75
76
 
76
77
  def add_child_to(resource)
77
78
  model = resource.model
78
79
  source = resource.request_body
79
- input_data = network_to_local(source)
80
+ input_data = network_to_local(model.my_url, source)
80
81
 
81
82
  child_for_model(resource.model, input_data)
82
83
 
@@ -87,7 +88,7 @@ module RoadForest
87
88
  model = resource.model
88
89
 
89
90
  renderer = model.type_handling.choose_renderer(resource.request_accept_header)
90
- body = renderer.local_to_network(model.response_data)
91
+ body = renderer.local_to_network(model.my_url, model.response_data)
91
92
 
92
93
  resource.response_content_type = renderer.content_type_header
93
94
  resource.response_body = body
@@ -115,8 +116,6 @@ module RoadForest
115
116
  result
116
117
  end
117
118
  end
118
-
119
- require 'roadforest/rdf/normalization'
120
119
  class RDFHandler < Handler
121
120
  include RDF::Normalization
122
121
 
@@ -150,17 +149,26 @@ require 'roadforest/rdf/normalization'
150
149
 
151
150
  #application/ld+json
152
151
  class JSONLD < RDFHandler
153
- def local_to_network(rdf)
154
- JSON::LD::Writer.buffer do |writer|
152
+ include RDF::Normalization
153
+
154
+ def local_to_network(base_uri, rdf)
155
+ raise "Invalid base uri: #{base_uri}" if base_uri.nil?
156
+ prefixes = relevant_prefixes_for_graph(rdf)
157
+ prefixes.keys.each do |prefix|
158
+ prefixes[prefix.to_sym] = prefixes[prefix]
159
+ end
160
+ JSON::LD::Writer.buffer(:base_uri => base_uri.to_s,
161
+ :prefixes => prefixes) do |writer|
155
162
  rdf.each_statement do |statement|
156
163
  writer << statement
157
164
  end
158
165
  end
159
166
  end
160
167
 
161
- def network_to_local(source)
168
+ def network_to_local(base_uri, source)
169
+ raise "Invalid base uri: #{base_uri.inspect}" if base_uri.nil?
162
170
  graph = ::RDF::Graph.new
163
- reader = JSON::LD::Reader.new(source.to_s)
171
+ reader = JSON::LD::Reader.new(source.to_s, :base_uri => base_uri.to_s)
164
172
  reader.each_statement do |statement|
165
173
  graph.insert(statement)
166
174
  end
@@ -27,7 +27,7 @@ module RoadForest
27
27
  excon_response = connection.request(
28
28
  :method => request.method,
29
29
  :path => uri.path,
30
- :headers => request.headers,
30
+ :headers => {"Host" => uri.host}.merge(request.headers),
31
31
  :body => request.body
32
32
  )
33
33
 
@@ -1,20 +1,34 @@
1
1
  module RoadForest
2
2
  module HTTP
3
- class GraphResponse
4
- attr_accessor :graph
3
+ class BaseResponse
5
4
  attr_reader :request, :response
6
5
 
7
- def initialize(request, response, graph)
8
- @request, @response, @graph = request, response, graph
6
+ def initialize(request, response)
7
+ @request, @response = request, response
9
8
  end
10
9
 
11
10
  def url
12
11
  request.url
13
12
  end
14
13
 
14
+ def etag
15
+ response.etag
16
+ end
17
+
15
18
  def status
16
19
  response.status
17
20
  end
21
+
22
+ def raw_body
23
+ response.body
24
+ end
25
+ end
26
+
27
+ class UnparseableResponse < BaseResponse
28
+ end
29
+
30
+ class GraphResponse < BaseResponse
31
+ attr_accessor :graph
18
32
  end
19
33
  end
20
34
  end
@@ -5,11 +5,18 @@ require 'roadforest/content-handling/engine'
5
5
  module RoadForest
6
6
  module HTTP
7
7
  class GraphTransfer
8
- attr_accessor :http_client
8
+ class Retryable < StandardError; end
9
+
10
+ attr_accessor :http_client, :trace
9
11
  attr_writer :type_handling
12
+ attr_reader :graph_cache
10
13
 
11
14
  def initialize
15
+ @trace = nil
12
16
  @type_preferences = Hash.new{|h,k| k.nil? ? "*/*" : h[nil]}
17
+ @graph_cache = Hash.new do |cache, url|
18
+ cache[url] = {}
19
+ end
13
20
  end
14
21
 
15
22
  def type_handling
@@ -37,7 +44,20 @@ module RoadForest
37
44
 
38
45
  response = send_request(request, graph)
39
46
 
40
- return build_response(request, response)
47
+ case response
48
+ when HTTP::Response
49
+ response = build_response(request, response)
50
+ cache_response(response)
51
+ return response
52
+ when GraphResponse
53
+ return response
54
+ end
55
+ end
56
+
57
+ def cache_response(response)
58
+ return if response.etag.nil?
59
+ return if response.etag.empty?
60
+ graph_cache[response.url][response.etag] = response
41
61
  end
42
62
 
43
63
  def validate(method, url, graph)
@@ -59,9 +79,18 @@ module RoadForest
59
79
  def setup_request(method, url)
60
80
  request = Request.new(method, url)
61
81
  request.headers["Accept"] = type_handling.parsers.types.accept_header
82
+ add_cache_headers(request)
62
83
  request
63
84
  end
64
85
 
86
+ def add_cache_headers(request)
87
+ return unless request.method == "GET"
88
+ return unless graph_cache.has_key?(request.url)
89
+ cached = graph_cache[request.url]
90
+ return if cached.empty?
91
+ request.headers["If-None-Match"] = cached.keys.join(", ")
92
+ end
93
+
65
94
  def select_renderer(url)
66
95
  end
67
96
 
@@ -77,17 +106,28 @@ module RoadForest
77
106
  content_type = best_type_for(request.url)
78
107
  renderer = type_handling.choose_renderer(content_type)
79
108
  request.headers["Content-Type"] = renderer.content_type_header
80
- request.body_string = renderer.from_graph(graph)
109
+ request.body_string = renderer.from_graph(request.url, graph)
81
110
  end
82
111
 
83
- class Retryable < StandardError; end
112
+ def trace_message(message)
113
+ return unless @trace
114
+ @trace = $stdout unless IO === @trace
115
+ @trace.puts message.inspect
116
+ end
84
117
 
85
118
  def send_request(request, graph)
86
119
  retry_limit ||= 5
120
+ #Check expires headers on received
87
121
  render_graph(graph, request)
88
122
 
123
+ trace_message(request)
89
124
  response = http_client.do_request(request)
125
+ trace_message(response)
90
126
  case response.status
127
+ when 304 #Not Modified
128
+ response = graph_cache.fetch(request.url).fetch(response.etag)
129
+ trace_message(response)
130
+ return response
91
131
  when 415 #Type not accepted
92
132
  record_accept_header(request.url, response.headers["Accept"])
93
133
  raise Retryable
@@ -98,14 +138,18 @@ module RoadForest
98
138
  retry
99
139
  end
100
140
 
101
- def parse_response(response)
141
+ def parse_response(base_uri, response)
102
142
  parser = type_handling.choose_parser(response.headers["Content-Type"])
103
- parser.to_graph(response.body_string)
143
+ parser.to_graph(base_uri, response.body_string)
104
144
  end
105
145
 
106
146
  def build_response(request, response)
107
- graph = parse_response(response)
108
- return GraphResponse.new(request, response, graph)
147
+ graph = parse_response(request.url, response)
148
+ response = GraphResponse.new(request, response)
149
+ response.graph = graph
150
+ return response
151
+ rescue ContentHandling::UnrecognizedType
152
+ return UnparseableResponse.new(request, response)
109
153
  end
110
154
  end
111
155
  end
@@ -69,6 +69,11 @@ module RoadForest
69
69
  def initialize(method, url)
70
70
  super()
71
71
  @method, @url = method, url
72
+ headers["Host"] = Addressable::URI.parse(url).host
73
+ end
74
+
75
+ def inspect
76
+ "\n" + super
72
77
  end
73
78
 
74
79
  def needs_body?
@@ -76,13 +81,17 @@ module RoadForest
76
81
  end
77
82
 
78
83
  def inspection_payload
79
- ["#{method} #{url}"] + super
84
+ [url, "#{method} #{url.path}"] + super
80
85
  end
81
86
  end
82
87
 
83
88
  class Response < Message
84
89
  attr_accessor :status
85
90
 
91
+ def etag
92
+ headers["ETag"]
93
+ end
94
+
86
95
  def inspection_payload
87
96
  [status] + super
88
97
  end
@@ -12,6 +12,7 @@ module RoadForest
12
12
  @response_values = {}
13
13
  end
14
14
  attr_reader :route_name, :params, :services, :data
15
+ attr_reader :response_values
15
16
 
16
17
  def path_for(route_name = nil, params = nil)
17
18
  services.router.path_for(route_name, (params || self.params).to_hash)
@@ -103,7 +104,10 @@ module RoadForest
103
104
 
104
105
  end
105
106
 
107
+ require 'roadforest/rdf/etagging'
106
108
  class RDFModel < Model
109
+ include RDF::Etagging
110
+
107
111
  def update(graph)
108
112
  graph_update(start_focus(graph))
109
113
  end
@@ -137,13 +141,22 @@ module RoadForest
137
141
  return focus
138
142
  end
139
143
 
140
- def start_graph(resource_url=nil, &block)
141
- graph = ::RDF::Graph.new
142
- start_focus(graph, resource_url, &block)
144
+ def etag
145
+ @etag ||= etag_from(etag_graph)
146
+ end
147
+
148
+ def etag_graph
149
+ current_graph
150
+ end
151
+
152
+ def current_graph
153
+ return response_data if response_values.has_key?(:data)
154
+ new_graph
143
155
  end
144
156
 
145
157
  def new_graph
146
- focus = start_graph(my_url)
158
+ graph = ::RDF::Graph.new
159
+ focus = start_focus(graph, my_url)
147
160
  fill_graph(focus)
148
161
  self.response_data = focus.graph
149
162
  end
@@ -1,25 +1,100 @@
1
1
  require 'rdf'
2
+ require 'roadforest/rdf/resource-query'
3
+ require 'roadforest/rdf/resource-pattern'
4
+ require 'roadforest/rdf/normalization'
2
5
 
3
6
  module RoadForest::RDF
4
7
  class ContextFascade
5
8
  include ::RDF::Countable
6
9
  include ::RDF::Enumerable
7
10
  include ::RDF::Queryable
11
+ include Normalization
8
12
 
9
- def initialize(store, resource, rigor)
10
- @store, @resource, @rigor = store, resource, rigor
13
+ attr_accessor :resource, :rigor, :source_graph, :target_graph, :copied_contexts
14
+
15
+ def initialize
16
+ @copied_contexts = {}
17
+ end
18
+
19
+ def resource=(resource)
20
+ @resource = normalize_context(resource)
21
+ end
22
+
23
+ def dup
24
+ other = self.class.allocate
25
+ other.resource = self.resource
26
+ other.rigor = self.rigor
27
+
28
+ other.copied_contexts = self.copied_contexts
29
+ other.source_graph = self.source_graph
30
+ other.target_graph = self.target_graph
31
+ return other
32
+ end
33
+
34
+ def parceller
35
+ @parceller ||=
36
+ begin
37
+ parceller = Parcel.new
38
+ parceller.graph = source_graph
39
+ parceller
40
+ end
41
+ end
42
+
43
+ def copy_context
44
+ return if copied_contexts[resource]
45
+ return if target_graph.nil? or source_graph == target_graph
46
+ parceller.graph_for(resource).each_statement do |statement|
47
+ statement.context = resource
48
+ target_graph << statement
49
+ end
50
+ copied_contexts[resource] = true
51
+ end
52
+
53
+ #superfluous?
54
+ def build_query
55
+ ResourceQuery.new([], {}) do |query|
56
+ query.subject_context = resource
57
+ query.source_rigor = rigor
58
+ yield query
59
+ end
11
60
  end
12
61
 
13
62
  def query_execute(query, &block)
14
- ResourceQuery.from(query, @resource, @rigor).execute(@store, &block)
63
+ query = ResourceQuery.from(query, resource, rigor)
64
+ execute_search(query, &block)
15
65
  end
16
66
 
17
67
  def query_pattern(pattern, &block)
18
- ResourcePattern.from(pattern, {:context_roles => {:subject => @resource}, :source_rigor => @rigor}).execute(@store, &block)
68
+ pattern = ResourcePattern.from(pattern, {:context_roles => {:subject => resource}, :source_rigor => rigor})
69
+ execute_search(pattern, &block)
70
+ end
71
+
72
+ def execute_search(search, &block)
73
+ if target_graph != source_graph and not target_graph.nil?
74
+ enum = search.execute(target_graph)
75
+ if enum.any?{ true }
76
+ enum.each(&block)
77
+ return enum
78
+ end
79
+ end
80
+ search.execute(source_graph, &block)
19
81
  end
20
82
 
21
83
  def each(&block)
22
- @store.each(&block)
84
+ source_graph.each(&block)
85
+ end
86
+
87
+ def insert(statement)
88
+ copy_context
89
+ statement[3] = resource
90
+ target_graph.insert(statement)
91
+ end
92
+
93
+ def delete(statement)
94
+ statement = RDF::Query::Pattern.from(statement)
95
+ statement.context = resource
96
+ copy_context
97
+ target_graph.delete(statement)
23
98
  end
24
99
  end
25
100
  end
@@ -0,0 +1,53 @@
1
+ require 'roadforest/rdf'
2
+
3
+ module RoadForest::RDF
4
+ module Etagging
5
+ def etag_from(graph)
6
+ require 'openssl'
7
+ quads = sorted_quads(graph)
8
+ mapped = blank_mapped(quads)
9
+ strings = mapped.map(&:inspect)
10
+
11
+ ripe = OpenSSL::Digest::RIPEMD160.new
12
+ mapped.each do |quad|
13
+ ripe << quad.inspect
14
+ end
15
+ "W/\"#{ripe.base64digest}\""
16
+ end
17
+
18
+ def blank_mapped(quads)
19
+ sequence = 0
20
+ mapping = Hash.new do |h,k|
21
+ h[k] = RDF::Node.new(sequence+=1)
22
+ end
23
+
24
+ quads.map do |quad|
25
+ quad.map do |term|
26
+ case term
27
+ when RDF::Node
28
+ mapping[term].to_s
29
+ when nil
30
+ nil
31
+ else
32
+ term.to_s
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ def sorted_quads(graph)
39
+ graph.statements.map do |statement|
40
+ [statement.subject, statement.predicate, statement.object, statement.context]
41
+ end.sort_by do |quad|
42
+ quad.map do |term|
43
+ case term
44
+ when RDF::Node
45
+ nil
46
+ else
47
+ term
48
+ end
49
+ end.join("/")
50
+ end
51
+ end
52
+ end
53
+ end