lib/active_triples/relation.rb in active-triples-0.11.0 vs lib/active_triples/relation.rb in active-triples-1.0.0.rc1

- old
+ new

@@ -2,24 +2,22 @@ require 'active_support/core_ext/module/delegation' module ActiveTriples ## # A `Relation` represents the values of a specific property/predicate on an - # {RDFSource}. Each relation is a set ({Array}) of {RDF::Terms} that are - # objects in the of source's triples of the form: + # `RDFSource`. Each relation is a set (`Enumerable` of the `RDF::Term`s that + # are objects in the of source's triples of the form: # - # <{#parent}> <{#predicate}> [term] . + # <{#parent}> <{#predicate}> [term] . # - # Relations express a set of binary relationships (on a predicate) between - # the parent node and a term. + # Relations express a binary relationships (over a predicate) between the + # parent node and a set of terms. # - # When the term is a URI or Blank Node, it is represented in the results - # {Array} as an {RDFSource} with a graph selected as a subgraph of the - # parent's. The triples in this subgraph are: (a) those whose subject is the - # term; (b) ... + # When the term is a URI or Blank Node, it is represented in the results as an + # `RDFSource`. Literal values are cast to strings, Ruby native types, or + # remain as an `RDF::Literal` as documented in `#each`. # - # # @see RDF::Term class Relation include Enumerable include Comparable @@ -27,31 +25,31 @@ # @!attribute [rw] parent # @return [RDFSource] the resource that is the domain of this relation # @!attribute [rw] value_arguments # @return [Array<Object>] - # @!attribute [rw] rel_args + # @!attribute [r] rel_args # @return [Hash] # @!attribute [r] reflections # @return [Class] - attr_accessor :parent, :value_arguments, :rel_args - attr_reader :reflections + attr_accessor :parent, :value_arguments + attr_reader :reflections, :rel_args delegate :[], :inspect, :last, :size, :join, to: :to_a ## # @param [ActiveTriples::RDFSource] parent_source # @param [Array<Symbol, Hash>] value_arguments if a Hash is passed as the # final element, it is removed and set to `@rel_args`. def initialize(parent_source, value_arguments) self.parent = parent_source @reflections = parent_source.reflections - self.rel_args ||= {} - self.rel_args = value_arguments.pop if + @rel_args ||= {} + @rel_args = value_arguments.pop if value_arguments.is_a?(Array) && value_arguments.last.is_a?(Hash) - self.value_arguments = value_arguments + @value_arguments = value_arguments end ## # @param array [#to_ary, ActiveTriples::Relation] # @return [Array] @@ -103,32 +101,37 @@ # # @see Set#<=> def <=>(other) return nil unless other.respond_to?(:each) + # If we're empty, avoid calling `#to_a` on other. if empty? return 0 if other.each.first.nil? return nil end # We'll need to traverse `other` repeatedly, so we get a stable `Array` # representation. This avoids any repeated query cost if `other` is a # `Relation`. - length = 0 - other = other.to_a - this = each + length = 0 + other = other.to_a + other_length = other.length + this = each loop do begin - cur = this.next + current = this.next rescue StopIteration - return other.length == length ? 0 : nil + # If we die, we are equal to other so far, check length and walk away. + return other_length == length ? 0 : nil end length += 1 - - return nil if other.length < length || !other.include?(cur) + + # Return as not comparable if we have seen more terms than are in other, + # or if other does not include the current term. + return nil if other_length < length || !other.include?(current) end end ## # Adds values to the result set @@ -205,11 +208,13 @@ ## # Empties the `Relation`, deleting any associated triples from `parent`. # # @return [Relation] self; a now empty relation def clear + return self if empty? parent.delete([rdf_subject, predicate, nil]) + parent.notify_observers(property) self end ## @@ -240,12 +245,15 @@ # # @param value [Object] the value to delete from the relation # @return [ActiveTriples::Relation] self def delete(value) value = RDF::Literal(value) if value.is_a? Symbol - parent.delete([rdf_subject, predicate, value]) + return self if parent.query([rdf_subject, predicate, value]).nil? + + parent.delete([rdf_subject, predicate, value]) + parent.notify_observers(property) self end ## # A variation on `#delete`. This queries the relation for matching @@ -365,34 +373,37 @@ def property value_arguments.last end ## - # Adds values to the relation + # Set the values of the Relation # # @param [Array<RDF::Resource>, RDF::Resource] values an array of values - # or a single value. If not an {RDF::Resource}, the values will be - # coerced to an {RDF::Literal} or {RDF::Node} by {RDF::Statement} + # or a single value. If not an `RDF::Resource`, the values will be + # coerced to an `RDF::Literal` or `RDF::Node` by `RDF::Statement` # # @return [Relation] a relation containing the set values; i.e. `self` # # @raise [ActiveTriples::UndefinedPropertyError] if the property is not - # already an {RDF::Term} and is not defined in `#property_config` + # already an `RDF::Term` and is not defined in `#property_config` # # @see http://www.rubydoc.info/github/ruby-rdf/rdf/RDF/Statement For - # documentation on {RDF::Statement} and the handling of - # non-{RDF::Resource} values. + # documentation on `RDF::Statement` and the handling of + # non-`RDF::Resource` values. def set(values) raise UndefinedPropertyError.new(property, reflections) if predicate.nil? values = prepare_relation(values) if values.is_a?(Relation) values = [values].compact unless values.kind_of?(Array) clear values.each { |val| set_value(val) } + parent.notify_observers(property) + + parent.persist! if parent.persistence_strategy.respond_to?(:ancestors) && + parent.persistence_strategy.ancestors.any? { |r| r.is_a?(ActiveTriples::List::ListResource) } - parent.persist! if parent.persistence_strategy.is_a? ParentStrategy self end ## # @overload subtract(enum) @@ -404,19 +415,23 @@ # # @return [Relation] self # # @note This casts symbols to a literals, which gets us symmetric behavior # with `#set(:sym)`. + # + # @note This method treats all calls as changes for the purpose of observer + # notifications # @see #delete def subtract(*values) values = values.first if values.first.is_a? Enumerable statements = values.map do |value| value = RDF::Literal(value) if value.is_a? Symbol [rdf_subject, predicate, value] end parent.delete(*statements) + parent.notify_observers(property) self end ## # Replaces the first argument with the second as a value within the @@ -449,11 +464,11 @@ RDF::Literal.datatyped_class(value.datatype.to_s) ? value.object : value else value end when RDF::Resource - make_node(value) + cast? ? make_node(value) : value else value end end @@ -536,11 +551,10 @@ unless new_resource == parent || (parent.persistence_strategy.is_a?(ParentStrategy) && parent.persistence_strategy.ancestors.find { |a| a == new_resource }) new_resource.set_persistence_strategy(ParentStrategy) new_resource.parent = parent - new_resource.persist! end self.node_cache[resource.rdf_subject] = (resource == object ? new_resource : object) end @@ -559,26 +573,31 @@ # Builds the resource from the class_name specified for the # property. # # @private def make_node(value) - return value unless cast? klass = class_for_value(value) value = RDF::Node.new if value.nil? - node = node_cache[value] if node_cache[value] - node ||= klass.from_uri(value,parent) - node.set_persistence_strategy(property_config[:persist_to]) if - is_property? && property_config[:persist_to] - return nil if (is_property? && property_config[:class_name]) && (class_for_value(value) != class_for_property) + + return node_cache[value] if node_cache[value] + + node = klass.from_uri(value, parent) + + if is_property? && property_config[:persist_to] + node.set_persistence_strategy(property_config[:persist_to]) + + node.persistence_strategy.parent = parent if + node.persistence_strategy.is_a?(ParentStrategy) + end + self.node_cache[value] ||= node - node end ## # @private def cast? - return true unless is_property? || (rel_args && rel_args[:cast]) + return true unless is_property? || rel_args[:cast] return rel_args[:cast] if rel_args.has_key?(:cast) !!property_config[:cast] end ## @@ -603,10 +622,10 @@ ## # @private def uri_class(v) v = RDF::URI.intern(v) if v.kind_of? String type_uri = parent.query([v, RDF.type, nil]).to_a.first.try(:object) - Resource.type_registry[type_uri] + RDFSource.type_registry[type_uri] end ## # @private def class_for_property