class RDF::LDP::Container

An LDP Basic Container. This also serves as a base class for other container types. Containers are implemented as `RDF::LDP::RDFSources` with the ability to contain other resources.

Containers respond to `#post`, allowing new resources to be added to them. On the public interface (not running through HTTP/`#request`), this is supported by `#add` and `#remove`.

Containers will throw errors when attempting to edit them in conflict with LDP's restrictions on changing containment triples.

@see www.w3.org/TR/ldp/#dfn-linked-data-platform-container definition

of LDP Container

Public Class Methods

to_uri() click to toggle source

@return [RDF::URI] uri with lexical representation

'http://www.w3.org/ns/ldp#Container'
# File lib/rdf/ldp/container.rb, line 20
def self.to_uri
  RDF::Vocab::LDP.Container
end

Public Instance Methods

add(resource, transaction = nil) click to toggle source

Adds a member `resource` to the container. Handles containment and membership triples as appropriate for the container type.

If a transaction is passed as the second argument, the additon of the containment triple is completed when the transaction closes; otherwise it is handled atomically.

@param [RDF::Term] a new member for this container @param transaction [RDF::Transaction] an active transaction as context for

the addition

@return [Container] self

# File lib/rdf/ldp/container.rb, line 76
def add(resource, transaction = nil)
  add_containment_triple(resource.to_uri, transaction)
end
add_containment_triple(resource, transaction = nil) click to toggle source

Adds a containment triple for `resource` to the container's `#graph`.

If a transaction is passed as the second argument, the triple is added to the transaction's inserts; otherwise it is added directly to `#graph`.

@param resource [RDF::Term] a new member for this container @param transaction [RDF::Transaction] @return [Container] self

# File lib/rdf/ldp/container.rb, line 119
def add_containment_triple(resource, transaction = nil)
  target = transaction || graph
  target.insert make_containment_triple(resource)
  set_last_modified(transaction) # #set_last_modified handles nil case
  self
end
container?() click to toggle source

@return [Boolean] whether this is an ldp:Container

# File lib/rdf/ldp/container.rb, line 26
def container?
  true
end
container_class() click to toggle source

@return [RDF::URI] a URI representing the container type

# File lib/rdf/ldp/container.rb, line 32
def container_class
  CONTAINER_CLASSES[:basic]
end
containment_triples() click to toggle source

@return [RDF::Query::Enumerator] the containment triples

# File lib/rdf/ldp/container.rb, line 98
def containment_triples
  graph.query([subject_uri, CONTAINS_URI, nil]).statements
end
create(input, content_type) { |transaction| ... } click to toggle source

Create with validation as required for the LDP container.

@raise [RDF::LDP::Conflict] if the create inserts triples that are not

allowed by LDP for the container type

@see RDFSource#create

Calls superclass method
# File lib/rdf/ldp/container.rb, line 42
def create(input, content_type, &block)
  super do |transaction|
    validate_triples!(transaction)
    yield transaction if block_given?
  end
  self
end
has_containment_triple?(statement) click to toggle source

@param [RDF::Statement] statement

@return [Boolean] true if the containment triple exists

# File lib/rdf/ldp/container.rb, line 106
def has_containment_triple?(statement)
  !containment_triples.find { |t| statement == t }.nil?
end
make_containment_triple(resource) click to toggle source

@param [RDF::Term] a member to be represented in the containment triple

@return [RDF::URI] the containment triple, with a graph_name pointing

to `#graph`
# File lib/rdf/ldp/container.rb, line 147
def make_containment_triple(resource)
  RDF::Statement(subject_uri, CONTAINS_URI, resource,
                 graph_name: subject_uri)
end
remove(resource, transaction = nil) click to toggle source

Removes a member `resource` from the container. Handles containment and membership triples as appropriate for the container type.

If a transaction is passed as the second argument, the removal of the containment triple is completed when the transaction closes; otherwise it is handled atomically.

@param [RDF::Term] a new member for this container @param transaction [RDF::Transaction] an active transaction as context for

the removal

@return [Container] self

# File lib/rdf/ldp/container.rb, line 92
def remove(resource, transaction = nil)
  remove_containment_triple(resource.to_uri, transaction)
end
remove_containment_triple(resource, transaction = nil) click to toggle source

Remove a containment triple for `resource` to the container's `#graph`.

If a transaction is passed as the second argument, the triple is added to the transaction's deletes; otherwise it is deleted directly from `#graph`.

@param resource [RDF::Term] a member to remove from this container @param transaction [RDF::Transaction] @return [Container] self

# File lib/rdf/ldp/container.rb, line 135
def remove_containment_triple(resource, transaction = nil)
  target = transaction || graph
  target.delete(make_containment_triple(resource))
  set_last_modified(transaction) # #set_last_modified handles nil case
  self
end
update(input, content_type) { |transaction| ... } click to toggle source

Updates with validation as required for the LDP container.

@raise [RDF::LDP::Conflict] if the update edits triples that are not

allowed by LDP for the container type

@see RDFSource#update

Calls superclass method
# File lib/rdf/ldp/container.rb, line 56
def update(input, content_type, &block)
  super do |transaction|
    validate_triples!(transaction)
    yield transaction if block_given?
  end
  self
end

Private Instance Methods

patch(_status, headers, env) click to toggle source
# File lib/rdf/ldp/container.rb, line 154
def patch(_status, headers, env)
  check_precondition!(env)
  method = patch_types[env['CONTENT_TYPE']]

  raise UnsupportedMediaType unless method

  temp_data  = RDF::Repository.new << graph.statements
  temp_graph = RDF::Graph.new(graph_name: graph.name, data: temp_data)
  send(method, env['rack.input'], temp_graph)

  validate_statements!(temp_graph)
  graph.clear!
  graph << temp_graph.statements

  set_last_modified
  [200, update_headers(headers), self]
end
post(_status, headers, env) click to toggle source

Handles a POST request. Parses a graph in the body of `env` and treats all statements in that graph (irrespective of any graph names) as constituting the initial state of the created source.

@raise [RDF::LDP::RequestError] when creation fails

@return [Array<Fixnum, Hash<String, String>, each] a new Rack response

array.
# File lib/rdf/ldp/container.rb, line 181
def post(_status, headers, env)
  klass = self.class.interaction_model(env.fetch('HTTP_LINK', ''))
  slug = env['HTTP_SLUG']
  slug = klass.gen_id if slug.nil? || slug.empty?
  raise(NotAcceptable, 'Refusing to create resource with `#` in Slug') if
    slug.include? '#'

  id = (subject_uri / slug).canonicalize

  created = klass.new(id, @data)

  created.create(env['rack.input'], env['CONTENT_TYPE']) do |transaction|
    add(created, transaction)
  end

  headers['Location'] = created.subject_uri.to_s
  [201, created.send(:update_headers, headers), created]
end
validate_statements!(statements) click to toggle source

supports Patch.

# File lib/rdf/ldp/container.rb, line 223
def validate_statements!(statements)
  existing_triples = containment_triples.to_a
  statements.query(subject: subject_uri, predicate: CONTAINS_URI) do |st|
    existing_triples.delete(st) do
      raise(Conflict, 'Attempted to write unacceptable LDP ' \
                      "containment-triple: #{st}")
    end
  end

  return if existing_triples.empty?

  raise(Conflict, 'Cannot remove containment triples in updates. ' \
                  "Attepted to remove #{existing_triples}")
end
validate_triples!(transaction) click to toggle source
# File lib/rdf/ldp/container.rb, line 200
def validate_triples!(transaction)
  existing_triples = containment_triples.to_a

  tx_containment = transaction.query(subject: subject_uri,
                                     predicate: CONTAINS_URI)

  tx_containment.each do |statement|
    unless existing_triples.include?(statement)
      raise(Conflict, 'Attempted to write unacceptable LDP ' \
                      "containment-triple: #{statement}")
    end
  end

  deletes = existing_triples.reject { |st| tx_containment.include?(st) }

  return if deletes.empty?

  raise(Conflict, 'Cannot remove containment triples in updates. ' \
                  "Attepted to remove #{deletes}")
end