lib/rdf/ldp/container.rb in rdf-ldp-0.3.0 vs lib/rdf/ldp/container.rb in rdf-ldp-0.4.0

- old
+ new

@@ -32,39 +32,67 @@ def container_class CONTAINER_CLASSES[:basic] end ## + # 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 - def create(input, content_type) - super { |statements| validate_triples!(statements) } + def create(input, content_type, &block) + super do |transaction| + validate_triples!(transaction) + yield transaction if block_given? + end + self end ## + # 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 - def update(input, content_type) - super { |statements| validate_triples!(statements) } + def update(input, content_type, &block) + super do |transaction| + validate_triples!(transaction) + yield transaction if block_given? + end + self end ## # 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 - def add(resource) - add_containment_triple(resource.to_uri) + def add(resource, transaction = nil) + add_containment_triple(resource.to_uri, transaction) end ## # 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 - def remove(resource) - remove_containment_triple(resource.to_uri) + def remove(resource, transaction = nil) + remove_containment_triple(resource.to_uri, transaction) end ## # @return [RDF::Query::Enumerator] the containment triples def containment_triples @@ -75,43 +103,54 @@ ## # @param [RDF::Statement] statement # # @return [Boolean] true if the containment triple exists - # - # @todo for some reason `#include?` doesn't work! figure out why, this is - # clumsy. def has_containment_triple?(statement) - !(containment_triples.select { |t| statement == t }.empty?) + !(containment_triples.find { |t| statement == t }.nil?) end ## # Adds a containment triple for `resource` to the container's `#graph`. # - # @param [RDF::Term] a new member for this container + # 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 - def add_containment_triple(resource) - graph << make_containment_triple(resource) + def add_containment_triple(resource, transaction = nil) + target = transaction || graph + target << make_containment_triple(resource) + set_last_modified(transaction) self end ## # Remove a containment triple for `resource` to the container's `#graph`. # - # @param [RDF::Term] a member to remove from this container + # 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 - def remove_containment_triple(resource) - graph.delete(make_containment_triple(resource)) + def remove_containment_triple(resource, transaction = nil) + target = transaction || graph + target.delete(make_containment_triple(resource)) + set_last_modified(transaction) self end ## - # @param [RDF::Term] a member for this container + # @param [RDF::Term] a member to be represented in the containment triple # - # @return [RDF::URI] the containment triple + # @return [RDF::URI] the containment triple, with a graph_name pointing + # to `#graph` def make_containment_triple(resource) - RDF::Statement(subject_uri, RDF::Vocab::LDP.contains, resource) + RDF::Statement(subject_uri, RDF::Vocab::LDP.contains, resource, + graph_name: subject_uri) end private def patch(status, headers, env) @@ -121,14 +160,15 @@ raise UnsupportedMediaType unless method temp_graph = RDF::Graph.new << graph.statements send(method, env['rack.input'], temp_graph) - validate_triples!(temp_graph) + validate_statements!(temp_graph) graph.clear! graph << temp_graph.statements + set_last_modified [200, update_headers(headers), self] end ## # Handles a POST request. Parses a graph in the body of `env` and treats all @@ -147,17 +187,48 @@ slug.include? '#' id = (subject_uri / slug).canonicalize created = klass.new(id, @data) - .create(env['rack.input'], env['CONTENT_TYPE']) + + created.create(env['rack.input'], env['CONTENT_TYPE']) do |transaction| + add(created, transaction) + end - add(created) headers['Location'] = created.subject_uri.to_s [201, created.send(:update_headers, headers), created] end - def validate_triples!(statements) + def validate_triples!(transaction) + existing_triples = containment_triples.to_a + + inserts = transaction.inserts.select do |st| + st.subject == subject_uri && st.predicate == RDF::Vocab::LDP.contains + end + + inserts.each do |statement| + existing_triples.delete(statement) do + raise Conflict.new('Attempted to write unacceptable LDP ' \ + "containment-triple: #{statement}") + end + end + + deletes = transaction.deletes.select do |st| + st.subject == subject_uri && + predicate == RDF::Vocab::LDP.contains && + !inserts.include?(st) + end + + deletes = deletes + existing_triples + + raise Conflict.new('Cannot remove containment triples in updates. ' \ + "Attepted to remove #{deletes}") unless + deletes.empty? + end + + ## + # supports Patch. + def validate_statements!(statements) existing_triples = containment_triples.to_a statements.query(subject: subject_uri, predicate: RDF::Vocab::LDP.contains) do |statement| existing_triples.delete(statement) do raise Conflict.new('Attempted to write unacceptable LDP ' \