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