Class: RDF::LDP::Resource
- Inherits:
-
Object
- Object
- RDF::LDP::Resource
- Defined in:
- lib/rdf/ldp/resource.rb
Overview
The base class for all LDP Resources.
The internal state of a Resource is specific to a given persistent datastore (an ‘RDF::Repository` passed to the initilazer) and is managed through an internal graph (`#metagraph`). A Resource has:
- a `#subject_uri` identifying the Resource.
- a `#metagraph` containing server-internal properties of the Resource.
Resources also define a basic set of CRUD operations, identity and current state, and a ‘#to_response`/`#each` method used by Rack & `Rack::LDP` to generate an appropriate HTTP response body.
‘#metagraph’ holds internal properites used by the server. It is distinct from, and may conflict with, other RDF and non-RDF information about the resource (e.g. representations suitable for a response body). Metagraph contains a canonical ‘rdf:type` statement, which specifies the resource’s interaction model and a (dcterms:modified) last-modified date. If the resource is deleted, a (prov:invalidatedAt) flag in metagraph indicates this.
The contents of ‘#metagraph` should not be confused with LDP server-managed-triples, Those triples are included in the state of the resource as represented by the response body. `#metagraph` is invisible to the client except where a subclass mirrors its contents in the body.
Rack (via ‘RDF::LDP::Rack`) uses the `#request` method to dispatch requests and interpret responses. Disallowed HTTP methods result in `RDF::LDP::MethodNotAllowed`. Individual Resources populate `Link`, `Allow`, `ETag`, `Last-Modified`, and `Accept-*` headers as required by LDP. All subclasses (MUST) return `self` as the Body, and respond to `#each`/ `#respond_to` with the intended body.
Direct Known Subclasses
Constant Summary collapse
- CONTAINS_URI =
RDF::Vocab::LDP.contains.freeze
- INVALIDATED_AT_URI =
RDF::Vocab::PROV.invalidatedAtTime.freeze
- MODIFIED_URI =
RDF::Vocab::DC.modified.freeze
Instance Attribute Summary collapse
-
#metagraph ⇒ Object
a graph representing the server-internal state of the resource.
-
#subject_uri ⇒ Object
readonly
Returns the value of attribute subject_uri.
Class Method Summary collapse
-
.find(uri, data) ⇒ RDF::LDP::Resource
Finds an existing resource and.
-
.gen_id ⇒ String
Creates an unique id (URI Slug) for a resource.
-
.interaction_model(link_header) ⇒ Class
Retrieves the correct interaction model from the Link headers.
-
.metagraph_name(uri) ⇒ Object
Build a graph name URI for the uri passed in.
-
.to_uri ⇒ RDF::URI
Uri with lexical representation ‘www.w3.org/ns/ldp#Resource’.
Instance Method Summary collapse
-
#allowed_methods ⇒ Array<Symbol>
A list of HTTP methods allowed by this resource.
-
#container? ⇒ Boolean
Whether this is an ldp:Container.
-
#containers ⇒ Array<RDF::LDP::Resource>
The container for this resource.
-
#create(_input, _content_type) {|tx| ... } ⇒ RDF::LDP::Resource
abstract
Self.
-
#destroy {|tx| ... } ⇒ RDF::LDP::Resource
Mark the resource as destroyed.
-
#destroyed? ⇒ Boolean
True if resource has been destroyed.
-
#etag ⇒ String
Returns an Etag.
-
#exists? ⇒ Boolean
Gives the status of the resource’s existance.
-
#initialize(subject_uri, data = RDF::Repository.new) {|RDF::Resource| ... } ⇒ Resource
constructor
A new instance of Resource.
-
#last_modified ⇒ DateTime
The time this resource was last modified; ‘nil` if the resource doesn’t exist and has no modified date.
-
#ldp_resource? ⇒ Boolean
Whether this is an ldp:Resource.
-
#match?(tag) ⇒ Boolean
Whether the given tag matches ‘#etag`.
-
#non_rdf_source? ⇒ Boolean
Whether this is an ldp:NonRDFSource.
-
#rdf_source? ⇒ Boolean
Whether this is an ldp:RDFSource.
-
#request(method, status, headers, env) ⇒ Array<Fixnum, Hash<String, String>, #each] a new Rack response array.
Build the response for the HTTP ‘method` given.
-
#to_response ⇒ Object
(also: #each)
Runs the request and returns the object’s desired HTTP response body, conforming to the Rack interfare.
-
#to_uri ⇒ RDF::URI
The subject URI for this resource.
-
#update(input, content_type) {|tx| ... } ⇒ RDF::LDP::Resource
abstract
Self.
Constructor Details
#initialize(subject_uri, data = RDF::Repository.new) {|RDF::Resource| ... } ⇒ Resource
Returns a new instance of Resource.
196 197 198 199 200 201 |
# File 'lib/rdf/ldp/resource.rb', line 196 def initialize(subject_uri, data = RDF::Repository.new) @subject_uri = RDF::URI.intern(subject_uri) @data = data @metagraph = RDF::Graph.new(graph_name: , data: data) yield self if block_given? end |
Instance Attribute Details
#metagraph ⇒ Object
a graph representing the server-internal state of the resource
98 99 100 |
# File 'lib/rdf/ldp/resource.rb', line 98 def @metagraph end |
#subject_uri ⇒ Object (readonly)
Returns the value of attribute subject_uri.
94 95 96 |
# File 'lib/rdf/ldp/resource.rb', line 94 def subject_uri @subject_uri end |
Class Method Details
.find(uri, data) ⇒ RDF::LDP::Resource
Finds an existing resource and
132 133 134 135 136 137 138 139 140 141 142 143 |
# File 'lib/rdf/ldp/resource.rb', line 132 def find(uri, data) graph = RDF::Graph.new(graph_name: (uri), data: data) raise NotFound if graph.empty? klass = graph.query([uri, RDF.type, :o]).find do |rdf_class| candidate = InteractionModel.for(rdf_class.object) break candidate unless candidate.nil? end klass ||= RDFSource klass.new(uri, data) end |
.gen_id ⇒ String
the current implementation uses SecureRandom#uuid.
Creates an unique id (URI Slug) for a resource.
116 117 118 |
# File 'lib/rdf/ldp/resource.rb', line 116 def gen_id SecureRandom.uuid end |
.interaction_model(link_header) ⇒ Class
Retrieves the correct interaction model from the Link headers.
Headers are handled intelligently, e.g. if a client sends a request with Resource, RDFSource, and BasicContainer headers, the server gives a BasicContainer. An error is thrown if the headers contain conflicting types (i.e. NonRDFSource and another Resource class).
158 159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/rdf/ldp/resource.rb', line 158 def interaction_model(link_header) models = LinkHeader.parse(link_header) .links.select { |link| link['rel'].casecmp 'type' } .map { |link| RDF::URI.intern(link.href) } return InteractionModel.default if models.empty? raise NotAcceptable unless InteractionModel.compatible?(models) InteractionModel.find(models) end |
.metagraph_name(uri) ⇒ Object
Build a graph name URI for the uri passed in
175 176 177 |
# File 'lib/rdf/ldp/resource.rb', line 175 def (uri) uri + '#meta' end |
.to_uri ⇒ RDF::URI
Returns uri with lexical representation ‘www.w3.org/ns/ldp#Resource’.
106 107 108 |
# File 'lib/rdf/ldp/resource.rb', line 106 def to_uri RDF::Vocab::LDP.Resource end |
Instance Method Details
#allowed_methods ⇒ Array<Symbol>
Returns a list of HTTP methods allowed by this resource.
354 355 356 357 358 |
# File 'lib/rdf/ldp/resource.rb', line 354 def allowed_methods [:GET, :POST, :PUT, :DELETE, :PATCH, :OPTIONS, :HEAD].select do |m| respond_to?(m.downcase, true) end end |
#container? ⇒ Boolean
Returns whether this is an ldp:Container.
368 369 370 |
# File 'lib/rdf/ldp/resource.rb', line 368 def container? false end |
#containers ⇒ Array<RDF::LDP::Resource>
Returns the container for this resource.
386 387 388 389 390 |
# File 'lib/rdf/ldp/resource.rb', line 386 def containers @data.query([:s, CONTAINS_URI, subject_uri]).map do |st| RDF::LDP::Resource.find(st.subject, @data) end end |
#create(_input, _content_type) {|tx| ... } ⇒ RDF::LDP::Resource
creates the resource
Returns self.
220 221 222 223 224 225 226 227 228 229 230 |
# File 'lib/rdf/ldp/resource.rb', line 220 def create(_input, _content_type) raise Conflict if exists? @data.transaction(mutable: true) do |transaction| set_interaction_model(transaction) yield transaction if block_given? set_last_modified(transaction) end self end |
#destroy {|tx| ... } ⇒ RDF::LDP::Resource
Use of owl:Nothing is probably problematic. Define an internal
Mark the resource as destroyed.
This adds a statment to the metagraph expressing that the resource has been deleted
namespace and class represeting deletion status as a stateful property.
271 272 273 274 275 276 277 278 279 280 281 |
# File 'lib/rdf/ldp/resource.rb', line 271 def destroy @data.transaction(mutable: true) do |transaction| containers.each { |c| c.remove(self, transaction) if c.container? } transaction.insert RDF::Statement(subject_uri, INVALIDATED_AT_URI, DateTime.now, graph_name: ) yield transaction if block_given? end self end |
#destroyed? ⇒ Boolean
Returns true if resource has been destroyed.
296 297 298 299 |
# File 'lib/rdf/ldp/resource.rb', line 296 def destroyed? times = @metagraph.query([subject_uri, INVALIDATED_AT_URI, nil]) !times.empty? end |
#etag ⇒ String
these etags are weak, but we allow clients to use them in ‘If-Match` headers, and use weak comparison. This is in conflict with tools.ietf.org/html/rfc7232#section-3.1. See: github.com/ruby-rdf/rdf-ldp/issues/68
Returns an Etag. This may be a strong or a weak ETag.
315 316 317 318 |
# File 'lib/rdf/ldp/resource.rb', line 315 def etag return nil unless exists? "W/\"#{last_modified.new_offset(0).iso8601(9)}\"" end |
#exists? ⇒ Boolean
destroyed resources continue to exist in the sense represeted by this method.
Gives the status of the resource’s existance.
290 291 292 |
# File 'lib/rdf/ldp/resource.rb', line 290 def exists? @data.has_graph? .graph_name end |
#last_modified ⇒ DateTime
handle cases where there is more than one RDF::DC.modified. check for the most recent date
Returns the time this resource was last modified; ‘nil` if the resource doesn’t exist and has no modified date.
328 329 330 331 332 333 334 335 336 337 |
# File 'lib/rdf/ldp/resource.rb', line 328 def last_modified results = @metagraph.query([subject_uri, RDF::Vocab::DC.modified, :time]) if results.empty? return nil unless exists? raise(RequestError, "Missing dc:modified date for #{subject_uri}") end results.first.object.object end |
#ldp_resource? ⇒ Boolean
Returns whether this is an ldp:Resource.
362 363 364 |
# File 'lib/rdf/ldp/resource.rb', line 362 def ldp_resource? true end |
#match?(tag) ⇒ Boolean
Returns whether the given tag matches ‘#etag`.
342 343 344 |
# File 'lib/rdf/ldp/resource.rb', line 342 def match?(tag) tag == etag end |
#non_rdf_source? ⇒ Boolean
Returns whether this is an ldp:NonRDFSource.
374 375 376 |
# File 'lib/rdf/ldp/resource.rb', line 374 def non_rdf_source? false end |
#rdf_source? ⇒ Boolean
Returns whether this is an ldp:RDFSource.
380 381 382 |
# File 'lib/rdf/ldp/resource.rb', line 380 def rdf_source? false end |
#request(method, status, headers, env) ⇒ Array<Fixnum, Hash<String, String>, #each] a new Rack response array.
Build the response for the HTTP ‘method` given.
The method passed in is symbolized, downcased, and sent to ‘self` with the other three parameters.
Request methods are expected to return an Array appropriate for a Rack response; to return this object (e.g. for a sucessful GET) the response may be ‘[status, headers, self]`.
If the method given is unimplemented, we understand it to require an HTTP 405 response, and throw the appropriate error.
427 428 429 430 431 432 433 434 |
# File 'lib/rdf/ldp/resource.rb', line 427 def request(method, status, headers, env) raise Gone if destroyed? begin send(method.to_sym.downcase, status, headers, env) rescue NotImplementedError raise MethodNotAllowed, method end end |
#to_response ⇒ Object Also known as: each
Runs the request and returns the object’s desired HTTP response body, conforming to the Rack interfare.
398 399 400 |
# File 'lib/rdf/ldp/resource.rb', line 398 def to_response [] end |
#to_uri ⇒ RDF::URI
Returns the subject URI for this resource.
348 349 350 |
# File 'lib/rdf/ldp/resource.rb', line 348 def to_uri subject_uri end |
#update(input, content_type) {|tx| ... } ⇒ RDF::LDP::Resource
update the resource
Returns self.
247 248 249 250 251 252 253 254 255 256 |
# File 'lib/rdf/ldp/resource.rb', line 247 def update(input, content_type, &block) return create(input, content_type, &block) unless exists? @data.transaction(mutable: true) do |transaction| yield transaction if block_given? set_last_modified(transaction) end self end |