Class: ActiveTriples::Relation

Inherits:
Object
  • Object
show all
Includes:
Comparable, Enumerable
Defined in:
lib/active_triples/relation.rb

Overview

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:

<{#parent}> <{#predicate}> [term] .

Relations express a set of binary relationships (on a predicate) between the parent node and a term.

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) …

See Also:

  • RDF::Term

Defined Under Namespace

Classes: ValueError

Constant Summary collapse

TYPE_PROPERTY =
{ predicate: RDF.type, cast: false }.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(parent_source, value_arguments) ⇒ Relation

Returns a new instance of Relation.

Parameters:

  • parent_source (ActiveTriples::RDFSource)
  • value_arguments (Array<Symbol, Hash>)

    if a Hash is passed as the final element, it is removed and set to ‘@rel_args`.



45
46
47
48
49
50
51
52
53
# File 'lib/active_triples/relation.rb', line 45

def initialize(parent_source, value_arguments)
  self.parent = parent_source
  @reflections = parent_source.reflections
  self.rel_args ||= {}
  self.rel_args = value_arguments.pop if
    value_arguments.is_a?(Array) && value_arguments.last.is_a?(Hash)

  self.value_arguments = value_arguments
end

Instance Attribute Details

#parentRDFSource

Returns the resource that is the domain of this relation.

Returns:

  • (RDFSource)

    the resource that is the domain of this relation



36
37
38
# File 'lib/active_triples/relation.rb', line 36

def parent
  @parent
end

#reflectionsClass (readonly)

Returns:

  • (Class)


36
# File 'lib/active_triples/relation.rb', line 36

attr_accessor :parent, :value_arguments, :rel_args

#rel_argsHash

Returns:

  • (Hash)


36
# File 'lib/active_triples/relation.rb', line 36

attr_accessor :parent, :value_arguments, :rel_args

#value_argumentsArray<Object>

Returns:

  • (Array<Object>)


36
# File 'lib/active_triples/relation.rb', line 36

attr_accessor :parent, :value_arguments, :rel_args

Instance Method Details

#&(array) ⇒ Array

Note:

simply passes to ‘Array#&` unless argument is a Relation

Parameters:

Returns:

  • (Array)

See Also:

  • Array#&


62
63
64
65
66
67
# File 'lib/active_triples/relation.rb', line 62

def &(array)
  return to_a & array unless array.is_a? Relation

  (objects.to_a & array.objects.to_a)
    .map { |object| convert_object(object) }
end

#+(array) ⇒ Array

Note:

simply passes to ‘Array#+` unless argument is a Relation

Parameters:

Returns:

  • (Array)

See Also:

  • Array#+


90
91
92
93
94
95
# File 'lib/active_triples/relation.rb', line 90

def +(array)
  return to_a + array unless array.is_a? Relation

  (objects.to_a + array.objects.to_a)
    .map { |object| convert_object(object) }
end

#<<(values) ⇒ Relation Also known as: push

Adds values to the result set

Parameters:

  • values (Object, Array<Object>)

    values to add

Returns:

  • (Relation)

    a relation containing the set values; i.e. self



139
140
141
142
# File 'lib/active_triples/relation.rb', line 139

def <<(values)
  values = prepare_relation(values) if values.is_a?(Relation)
  self.set(objects.to_a | Array.wrap(values))
end

#<=>(other) ⇒ Object

Mimics ‘Set#<=>`, returning 0 when set membership is equivalent, and nil (as non-comparable) otherwise. Unlike `Set#<=>`, uses `#==` for member comparisons.

Parameters:

  • other (Object)

See Also:

  • Set#<=>


105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/active_triples/relation.rb', line 105

def <=>(other)
  return nil unless other.respond_to?(:each)

  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

  loop do
    begin
      cur = this.next
    rescue StopIteration
      return other.length == length ? 0 : nil
    end

    length += 1

    return nil if other.length < length || !other.include?(cur)
  end
end

#build(attributes = {}) ⇒ Object

Builds a node with the given attributes, adding it to the relation.

Nodes are built using the configured class_name for the relation. Attributes passed in the Hash argument are set on the new node through ‘RDFSource#attributes=`. If the attribute keys are not valid properties on the built node, we raise an error.

@todo: clarify class behavior; it is actually tied to type, in some cases.

Examples:

building an empty generic node

resource = ActiveTriples::Resource.new
resource.resource.get_values(RDF::Vocab::DC.relation).build
# => #<ActiveTriples::Resource:0x2b0(#<ActiveTriples::Resource:0x005>)>)

resource.dump :ttl
# => "\n [ <http://purl.org/dc/terms/relation> []] .\n"

building a node with attributes

class WithRelation
  include ActiveTriples::RDFSource
  property :relation, predicate:  RDF::Vocab::DC.relation,
    class_name: 'WithTitle'
end

class WithTitle
  include ActiveTriples::RDFSource
  property :title, predicate: RDF::Vocab::DC.title
end

resource = WithRelation.new
attributes = { id: 'http://ex.org/moomin', title: 'moomin' }

resource.get_values(:relation).build(attributes)
# => #<ActiveTriples::Resource:0x2b0(#<ActiveTriples::Resource:0x005>)>)

resource.dump :ttl
# => "\n<http://ex.org/moomin> <http://purl.org/dc/terms/title> \"moomin\" .\n\n [ <http://purl.org/dc/terms/relation> <http://ex.org/moomin>] .\n"

Parameters:

  • attributes (Hash) (defaults to: {})

    a hash of attribute names and values for the built node.

See Also:



190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/active_triples/relation.rb', line 190

def build(attributes={})
  new_subject = attributes.fetch('id') { RDF::Node.new }

  make_node(new_subject).tap do |node|
    node.attributes = attributes.except('id')
    if parent.kind_of? List::ListResource
      parent.list << node
    elsif node.kind_of? RDF::List
      self.push node.rdf_subject
    else
      self.push node
    end
  end
end

#clearRelation

Empties the Relation, deleting any associated triples from parent.

Returns:

  • (Relation)

    self; a now empty relation



209
210
211
212
213
# File 'lib/active_triples/relation.rb', line 209

def clear
  parent.delete([rdf_subject, predicate, nil])

  self
end

#delete(value) ⇒ ActiveTriples::Relation

Note:

this method behaves somewhat differently from ‘Array#delete`. It succeeds on deletion of non-existing values, always returning self unless an error is raised. There is no option to pass a block to evaluate if the value is not present. This is because access for value depends on query time. i.e. the Relation set does not have an underlying efficient data structure allowing a reliably cheap existence check.

Note:

symbols are treated as RDF::Nodes by default in ‘RDF::Mutable#delete`, but may also represent tokens in statements. This casts symbols to a literals, which gets us symmetric behavior between `#set(:sym)` and `#delete(:sym)`.

Returns self.

Examples:

deleting a value

resource = MySource.new
resource.title = ['moomin', 'valley']
resource.title.delete('moomin') # => ["valley"]
resource.title # => ['valley']

note the behavior of unmatched values

resource = MySource.new
resource.title = 'moomin'
resource.title.delete('valley') # => ["moomin"]
resource.title # => ['moomin']

Parameters:

  • value (Object)

    the value to delete from the relation

Returns:



243
244
245
246
247
248
# File 'lib/active_triples/relation.rb', line 243

def delete(value)
  value = RDF::Literal(value) if value.is_a? Symbol
  parent.delete([rdf_subject, predicate, value])

  self
end

#delete?(value) ⇒ Object?

A variation on #delete. This queries the relation for matching values before running the deletion, returning nil if it does not exist.

Parameters:

  • value (Object)

    the value to delete from the relation

Returns:

  • (Object, nil)

    nil if the value doesn’t exist; the value otherwise

See Also:



259
260
261
262
263
264
265
266
# File 'lib/active_triples/relation.rb', line 259

def delete?(value)
  value = RDF::Literal(value) if value.is_a? Symbol

  return nil if parent.query([rdf_subject, predicate, value]).nil?

  delete(value)
  value
end

#eachEnumerator<Object>

Gives a result set for the Relation.

By default, RDF::URI and RDF::Node results are cast to RDFSource. When cast? is false, RDF::Resource values are left in their raw form.

Literal results are cast as follows:

- Simple string literals are returned as `String`
- `rdf:langString` literals are always returned as raw `Literal` objects, 
   retaining their language tags.
- Typed literals are cast to their Ruby `#object` when their datatype 
  is associated with a `Literal` subclass.

Examples:

results with default casting

datatype = RDF::URI("http://example.com/custom_type")

parent << [parent.rdf_subject, predicate, 'my value']
parent << [parent.rdf_subject, predicate, RDF::Literal('my_value',
                                            datatype: datatype)]
parent << [parent.rdf_subject, predicate, Date.today]
parent << [parent.rdf_subject, predicate, RDF::URI('http://ex.org/#me')]
parent << [parent.rdf_subject, predicate, RDF::Node.new]

relation.to_a
# => ["my_value",
#     "my_value" R:L:(Literal),
#     Fri, 25 Sep 2015,
#     #<ActiveTriples::Resource:0x3f8...>,
#     #<ActiveTriples::Resource:0x3f8...>]

results with cast? set to false

relation.to_a
# => ["my_value",
#     "my_value" R:L:(Literal),
#     Fri, 25 Sep 2015,
#     #<RDF::URI:0x3f8... URI:http://ex.org/#me>,
#     #<RDF::Node:0x3f8...(_:g69843536054680)>]

Returns:

  • (Enumerator<Object>)

    the result set



309
310
311
312
313
314
315
316
317
318
319
320
# File 'lib/active_triples/relation.rb', line 309

def each
  return [].to_enum if predicate.nil?

  if block_given?
    objects do |object|
      converted_object = convert_object(object)
      yield converted_object unless converted_object.nil?
    end
  end

  to_enum
end

#empty?Boolean

Returns true if the results are empty.

Returns:

  • (Boolean)

    true if the results are empty.



324
325
326
# File 'lib/active_triples/relation.rb', line 324

def empty?
  objects.empty?
end

#first_or_create(attributes = {}) ⇒ Object

Deprecated.

for removal in 1.0.0. Use ‘first || build({})`, `build({}) if empty?` or similar logic.

Returns the first result, if present; else a newly built node.

Returns:

  • (Object)

    the first result, if present; else a newly built node

See Also:



335
336
337
338
# File 'lib/active_triples/relation.rb', line 335

def first_or_create(attributes={})
  warn 'DEPRECATION: #first_or_create is deprecated for removal in 1.0.0.'
  first || build(attributes)
end

#lengthInteger

Returns:

  • (Integer)


342
343
344
# File 'lib/active_triples/relation.rb', line 342

def length
  objects.to_a.length
end

#predicateRDF::Term?

Gives the predicate used by the Relation. Values of this object are those that match the pattern ‘<rdf_subject> <predicate> [value] .`

Returns:

  • (RDF::Term, nil)

    the predicate for this relation; nil if no predicate can be found

See Also:



354
355
356
357
# File 'lib/active_triples/relation.rb', line 354

def predicate
  return property if property.is_a?(RDF::Term)
  property_config[:predicate] if is_property?
end

#propertySymbol, RDF::URI

Returns the property for the Relation. This may be a registered property key or an RDF::URI.

Returns:

  • (Symbol, RDF::URI)

    the property for this Relation.

See Also:



365
366
367
# File 'lib/active_triples/relation.rb', line 365

def property
  value_arguments.last
end

#set(values) ⇒ Relation

Adds values to the relation

Parameters:

  • values (Array<RDF::Resource>, RDF::Resource)

    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

Returns:

  • (Relation)

    a relation containing the set values; i.e. self

Raises:

See Also:



384
385
386
387
388
389
390
391
392
393
394
395
# File 'lib/active_triples/relation.rb', line 384

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.persist! if parent.persistence_strategy.is_a? ParentStrategy
  self
end

#subtract(enum) ⇒ Relation #subtract(*values) ⇒ Relation

Note:

This casts symbols to a literals, which gets us symmetric behavior with ‘#set(:sym)`.

Returns self.

Overloads:

  • #subtract(enum) ⇒ Relation

    Deletes objects in the enumerable from the relation

    Parameters:

    • values (Enumerable)

      an enumerable of objects to delete

  • #subtract(*values) ⇒ Relation

    Deletes each argument value from the relation

    Parameters:

    • *values (Array<Object>)

      the objects to delete

Returns:

See Also:



410
411
412
413
414
415
416
417
418
419
# File 'lib/active_triples/relation.rb', line 410

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)
  self
end

#swap(swap_out, swap_in) ⇒ Relation

Replaces the first argument with the second as a value within the relation.

Parameters:

  • swap_out (Object)

    the value to delete

  • swap_in (Object)

    the replacement value

Returns:



429
430
431
# File 'lib/active_triples/relation.rb', line 429

def swap(swap_out, swap_in)
  self.<<(swap_in) if delete?(swap_out)
end

#|(array) ⇒ Array

Note:

simply passes to ‘Array#|` unless argument is a Relation

Parameters:

Returns:

  • (Array)

See Also:

  • Array#|


76
77
78
79
80
81
# File 'lib/active_triples/relation.rb', line 76

def |(array)
  return to_a | array unless array.is_a? Relation
  
  (objects.to_a | array.objects.to_a)
    .map { |object| convert_object(object) }
end