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