Module: ActiveTriples::RDFSource

Extended by:
ActiveSupport::Concern
Includes:
ActiveModel::Conversion, ActiveModel::Serialization, ActiveModel::Serializers::JSON, ActiveModel::Validations, NestedAttributes, Persistable, Properties, RDF::Queryable, RDF::Value
Included in:
List::ListResource, Resource
Defined in:
lib/active_triples/rdf_source.rb

Overview

TODO:

complete RDF::Value/RDF::Term/RDF::Resource interfaces

Defines a concern for managing RDF::Graph driven Resources as discrete, stateful graphs using ActiveModel-style objects.

An ‘RDFSource` models a resource (RDF::Resource) with a state that may change over time. The current state is represented by an RDF::Graph, accessible as #graph. The source is an RDF::Resource represented by #rdf_subject, which may be either an RDF::URI or an RDF::Node.

The graph of a source may contain contain arbitrary triples, including full representations of the state of other sources. The triples in the graph should be limited to statements that have bearing on the resource’s state.

Properties may be defined on inheriting classes to configure accessor methods for predicates.

An ‘RDFSource` is an RDF::Term—it can be used as a subject, predicate, object, or context in an RDF::Statement.

Examples:

class License
  include Active::Triples::RDFSource

  configure repository: :default
  property :title, predicate: RDF::DC.title, class_name: RDF::Literal
end

See Also:

Defined Under Namespace

Modules: ClassMethods

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Persistable

#delete_statement, #destroy, #destroyed?, #each, #insert_statement, #persist!, #persisted?, #persistence_strategy, #reload, #set_persistence_strategy

Class Method Details

.type_registryObject



57
58
59
# File 'lib/active_triples/rdf_source.rb', line 57

def type_registry
  @@type_registry ||= {}
end

Instance Method Details

#==(other) ⇒ Object

Compares self to other for RDF::Term equality.

Delegates the check to ‘other#==` passing it the term version of `self`.

Parameters:

  • other (Object)

See Also:

  • RDF::Term#==
  • RDF::Node#==
  • RDF::URI#==


123
124
125
# File 'lib/active_triples/rdf_source.rb', line 123

def ==(other)
  other == to_term
end

#[](term_or_property) ⇒ Object

Returns an array of values belonging to the property requested. Elements in the array may RdfResource objects or a valid datatype.

Parameters:

  • term_or_property (RDF::Term, :to_s)


468
469
470
# File 'lib/active_triples/rdf_source.rb', line 468

def [](term_or_property)
  get_relation([term_or_property])
end

#[]=(term_or_property, value) ⇒ Object

Note:

This method will delete existing statements with the correct subject and predicate from the graph

Adds or updates a property with supplied values.

Parameters:

  • term_or_property (RDF::Term, :to_s)
  • values (Array<RDF::Resource>, RDF::Resource)

    an array of values or a single value to set the property to.



481
482
483
# File 'lib/active_triples/rdf_source.rb', line 481

def []=(term_or_property, value)
  self[term_or_property].set(value)
end

#attributesHash<String, Array<Object>>

resource.attributes

# => {"id"=>"g47123700054720",
#     "title"=>["Comet in Moominland", "Christmas in Moominvalley"],
#     "creator"=>[#<Agent:0x2adbd76f1a5c(#<Agent:0x0055b7aede34b8>)>]}

resource << [resource, RDF::Vocab::DC.relation, 'Helsinki']
# => {"id"=>"g47123700054720",
#     "title"=>["Comet in Moominland", "Christmas in Moominvalley"],
#     "creator"=>[#<Agent:0x2adbd76f1a5c(#<Agent:0x0055b7aede34b8>)>],
#     "http://purl.org/dc/terms/relation"=>["Helsinki"]}]}

@todo: should this, ‘#attributes=`, and `#serializable_hash` be moved out

into a dedicated `Serializer` object?

Returns:

  • (Hash<String, Array<Object>>)


164
165
166
167
168
169
170
# File 'lib/active_triples/rdf_source.rb', line 164

def attributes
  attrs = {}
  attrs['id'] = id
  fields.map { |f| attrs[f.to_s] = get_values(f) }
  unregistered_predicates.map { |uri| attrs[uri.to_s] = get_values(uri) }
  attrs
end

#attributes=(values) ⇒ Object

Raises:

  • (ArgumentError)


172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/active_triples/rdf_source.rb', line 172

def attributes=(values)
  raise ArgumentError, "values must be a Hash, you provided #{values.class}" unless values.kind_of? Hash
  values = values.with_indifferent_access
  id = values.delete(:id)
  set_subject!(id) if node?
  values.each do |key, value|
    if reflections.has_property?(key)
      set_value(rdf_subject, key, value)
    elsif nested_attributes_options.keys.map { |k| "#{k}_attributes" }.include?(key)
      send("#{key}=".to_sym, value)
    else
      raise ArgumentError, "No association found for name `#{key}'. Has it been defined yet?"
    end
  end
end

#base_uriString?

Returns the base URI the resource will use when setting its subject. ‘nil` if none is used.

Returns:

  • (String, nil)

    the base URI the resource will use when setting its subject. ‘nil` if none is used.



296
297
298
# File 'lib/active_triples/rdf_source.rb', line 296

def base_uri
  self.class.base_uri
end

#dump(*args) ⇒ String

Returns a serialized string representation of self. Extends the base implementation builds a JSON-LD context if the specified format is :jsonld and a context is provided by #jsonld_context

Parameters:

  • args (Array<Object>)

Returns:

  • (String)

See Also:

  • RDF::Enumerable#dump


205
206
207
208
209
210
211
# File 'lib/active_triples/rdf_source.rb', line 205

def dump(*args)
  if args.first == :jsonld and respond_to?(:jsonld_context)
    args << {} unless args.last.is_a?(Hash)
    args.last[:context] ||= jsonld_context
  end
  super
end

#fetch(*args) {|resource| ... } ⇒ ActiveTriples::RDFSource

Load data from the #rdf_subject URI. Retrieved data will be parsed into the Resource’s graph from available RDF::Readers and available from property accessors if if predicates are registered.

Examples:

osu = new('http://dbpedia.org/resource/Oregon_State_University')
osu.fetch
osu.rdf_label.first
# => "Oregon State University"

with default action block

my_source = new('http://example.org/dead_url')
my_source.fetch { |obj| obj.status = 'dead link' }

Yields:

  • gives self to block if this is a node, or an error is raised during load

Yield Parameters:

Returns:



355
356
357
358
359
360
361
362
363
364
365
366
367
368
# File 'lib/active_triples/rdf_source.rb', line 355

def fetch(*args, &block)
  begin
    load(rdf_subject, *args)
  rescue => e
    if block_given?
      yield(self)
    else
      raise "#{self} is a blank node; Cannot fetch a resource without a URI" if
        node?
      raise e
    end
  end
  self
end

#get_relation(args) ⇒ Object

TODO:

deprecate and remove? this is an alias to ‘#get_values`

See Also:



488
489
490
491
492
493
494
# File 'lib/active_triples/rdf_source.rb', line 488

def get_relation(args)
  reload if (persistence_strategy.respond_to? :loaded?) && !persistence_strategy.loaded?
  @relation_cache ||= {}
  rel = Relation.new(self, args)
  @relation_cache["#{rel.send(:rdf_subject)}/#{rel.property}/#{rel.rel_args}"] ||= rel
  @relation_cache["#{rel.send(:rdf_subject)}/#{rel.property}/#{rel.rel_args}"]
end

#get_values(property) ⇒ ActiveTriples::Relation #get_values(uri, property) ⇒ ActiveTriples::Relation

TODO:

should this raise an error when the property argument is not an RDF::Term or a registered property key?

Returns an array of values belonging to the property requested. Elements in the array may RdfResource objects or a valid datatype.

Handles two argument patterns. The recommended pattern, which accesses properties directly on this RDFSource, is:

get_values(property)

Overloads:

  • #get_values(property) ⇒ ActiveTriples::Relation

    Gets values on the RDFSource for the given property

    Parameters:

    • property (String, #to_term)

      the property for the values

  • #get_values(uri, property) ⇒ ActiveTriples::Relation

    For backwards compatibility, explicitly passing the term used as the subject ActiveTriples::Relation#rdf_subject of the returned relation.

    Parameters:

    • uri (RDF::Term)

      the term to use as the subject

    • property (String, #to_term)

      the property for the values

Returns:



459
460
461
# File 'lib/active_triples/rdf_source.rb', line 459

def get_values(*args)
  get_relation(args)
end

#graph_namenil

Returns ‘nil` as the `graph_name`. This behavior mimics an `RDF::Graph` with no graph name, or one without named graph support.

@note: it’s possible to think of an ‘RDFSource` as “supporting named

graphs" in the sense that the `#rdf_subject` is an implied graph name.
For RDF.rb's purposes, however, it has a nil graph name: when 
enumerating statements, we treat them as triples.

Returns:

  • (nil)


252
253
254
# File 'lib/active_triples/rdf_source.rb', line 252

def graph_name
  nil
end

#humanizeString

Returns A string identifier for the resource; ” if the resource is a node.

Returns:

  • (String)

    A string identifier for the resource; ” if the resource is a node



259
260
261
# File 'lib/active_triples/rdf_source.rb', line 259

def humanize
  node? ? '' : rdf_subject.to_s
end

#idString

Returns:

  • (String)

See Also:

  • RDF::Node#id


273
274
275
# File 'lib/active_triples/rdf_source.rb', line 273

def id
  node? ? rdf_subject.id : rdf_subject.to_s
end

#initialize(*args, &block) ⇒ Object

TODO:

move this logic out to a Builder?

Initialize an instance of this resource class. Defaults to a blank node subject. In addition to RDF::Graph parameters, you can pass in a URI and/or a parent to build a resource from a existing data.

You can pass in only a parent with:

new(nil, parent)

See Also:

  • RDF::Graph


92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/active_triples/rdf_source.rb', line 92

def initialize(*args, &block)
  resource_uri = args.shift unless args.first.is_a?(Hash)
  @rdf_subject = get_uri(resource_uri) if resource_uri
  unless args.first.is_a?(Hash) || args.empty?
    set_persistence_strategy(ParentStrategy)
    persistence_strategy.parent = args.shift
  else
    set_persistence_strategy(RepositoryStrategy)
  end
  @graph = RDF::Graph.new(*args, &block)

  reload

  # Append type to graph if necessary.
  Array.wrap(self.class.type).each do |type|
    unless self.get_values(:type).include?(type)
      self.get_values(:type) << type
    end
  end
end

#mark_for_destructionObject



535
536
537
# File 'lib/active_triples/rdf_source.rb', line 535

def mark_for_destruction
  @marked_for_destruction = true
end

#marked_for_destruction?Boolean

Returns:

  • (Boolean)


539
540
541
# File 'lib/active_triples/rdf_source.rb', line 539

def marked_for_destruction?
  @marked_for_destruction
end

#new_record?Boolean

Indicates if the record is ‘new’ (has not yet been persisted).

Returns:

  • (Boolean)


531
532
533
# File 'lib/active_triples/rdf_source.rb', line 531

def new_record?
  not persisted?
end

#node?Boolean

Returns true if the Term is a node.

Returns:

  • (Boolean)

    true if the Term is a node

See Also:

  • RDF::Term#node?


281
282
283
# File 'lib/active_triples/rdf_source.rb', line 281

def node?
  rdf_subject.node?
end

#parentObject

TODO:

establish a better pattern for this. ‘#parent` has been a public method in the past, but it’s probably time to deprecate it.

Delegate parent to the persistence strategy if possible



218
219
220
# File 'lib/active_triples/rdf_source.rb', line 218

def parent
  persistence_strategy.respond_to?(:parent) ? persistence_strategy.parent : nil
end

#parent=(parent) ⇒ Object

TODO:

deprecate/remove

See Also:



225
226
227
# File 'lib/active_triples/rdf_source.rb', line 225

def parent=(parent)
  persistence_strategy.respond_to?(:parent=) ? (persistence_strategy.parent = parent) : nil
end

#rdf_labelObject

Looks for labels in various default fields, prioritizing configured label fields.

See Also:

  • #default_labels


314
315
316
317
318
319
320
321
322
# File 'lib/active_triples/rdf_source.rb', line 314

def rdf_label
  labels = Array.wrap(self.class.rdf_label)
  labels += default_labels
  labels.each do |label|
    values = get_values(label)
    return values unless values.empty?
  end
  node? ? [] : [rdf_subject.to_s]
end

#rdf_subjectRDF::URI, RDF::Node Also known as: to_term

Gives the representation of this RDFSource as an RDF::Term

Returns:

  • (RDF::URI, RDF::Node)

    the URI that identifies this ‘RDFSource`; or a bnode identifier

See Also:

  • RDF::Term#to_term


236
237
238
# File 'lib/active_triples/rdf_source.rb', line 236

def rdf_subject
  @rdf_subject ||= RDF::Node.new
end

#serializable_hash(options = nil) ⇒ Object



188
189
190
191
192
193
# File 'lib/active_triples/rdf_source.rb', line 188

def serializable_hash(options = nil)
  attrs = (fields.map { |f| f.to_s }) << 'id'
  hash = super(:only => attrs)
  unregistered_predicates.map { |uri| hash[uri.to_s] = get_values(uri) }
  hash
end

#set_subject!(uri_or_str) ⇒ Object

Set a new rdf_subject for the resource.

This raises an error if the current subject is not a blank node, and returns false if it can’t figure out how to make a URI from the param. Otherwise it creates a URI for the resource and rebuilds the graph with the updated URI.

Will try to build a uri as an extension of the class’s base_uri if appropriate.

Parameters:

  • uri_or_str (#to_uri, #to_s)

    the uri or string to use



508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
# File 'lib/active_triples/rdf_source.rb', line 508

def set_subject!(uri_or_str)
  raise "Refusing update URI when one is already assigned!" unless node? or rdf_subject == RDF::URI(nil)
  # Refusing set uri to an empty string.
  return false if uri_or_str.nil? or (uri_or_str.to_s.empty? and not uri_or_str.kind_of? RDF::URI)
  # raise "Refusing update URI! This object is persisted to a datastream." if persisted?
  old_subject = rdf_subject
  @rdf_subject = get_uri(uri_or_str)

  each_statement do |statement|
    if statement.subject == old_subject
      delete(statement)
      self << RDF::Statement.new(rdf_subject, statement.predicate, statement.object)
    elsif statement.object == old_subject
      delete(statement)
      self << RDF::Statement.new(statement.subject, statement.predicate, rdf_subject)
    end
  end
end

#set_value(property, values) ⇒ ActiveTriples::Relation #set_value(subject, property, values) ⇒ ActiveTriples::Relation

Note:

This method will delete existing statements with the given subject and predicate from the graph

Adds or updates a property by creating triples for each of the supplied values.

The ‘property` argument may be either a symbol representing a registered property name, or an RDF::Term to use as the predicate.

The recommended pattern, which sets properties directly on this RDFSource, is: ‘set_value(property, values)`

Examples:

setting with a property name

class Thing
  include ActiveTriples::RDFSource
  property :creator, predicate: RDF::DC.creator
end

t = Thing.new
t.set_value(:creator, 'Tove Jansson')  # => ['Tove Jansson']

setting with a predicate

t = Thing.new
t.set_value(RDF::DC.creator, 'Tove Jansson')  # => ['Tove Jansson']

Overloads:

  • #set_value(property, values) ⇒ ActiveTriples::Relation

    Updates the values for the property, using this RDFSource as the subject

    Parameters:

    • property (RDF::Term, #to_sym)

      a symbol with the property name or an RDF::Term to use as a predicate.

    • 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

  • #set_value(subject, property, values) ⇒ ActiveTriples::Relation

    Updates the values for the property, using the given term as the subject

    Parameters:

    • subject (RDF::Term)

      the term representing the

    • property (RDF::Term, #to_sym)

      a symbol with the property name or an RDF::Term to use as a predicate.

    • 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:

Raises:

See Also:



426
427
428
429
430
431
432
433
# File 'lib/active_triples/rdf_source.rb', line 426

def set_value(*args)
  # Add support for legacy 3-parameter syntax
  if args.length > 3 || args.length < 2
    raise ArgumentError, "wrong number of arguments (#{args.length} for 2-3)"
  end
  values = args.pop
  get_relation(args).set(values)
end

#to_uriRDF::URI

Returns the uri.

Returns:

  • (RDF::URI)

    the uri



265
266
267
# File 'lib/active_triples/rdf_source.rb', line 265

def to_uri
  rdf_subject if uri?
end

#typeObject



300
301
302
# File 'lib/active_triples/rdf_source.rb', line 300

def type
  self.get_values(:type)
end

#type=(type) ⇒ Object



304
305
306
307
# File 'lib/active_triples/rdf_source.rb', line 304

def type=(type)
  raise "Type must be an RDF::URI" unless type.kind_of? RDF::URI
  self.update(RDF::Statement.new(rdf_subject, RDF.type, type))
end

#uri?Boolean

Returns true if the Term is a uri.

Returns:

  • (Boolean)

    true if the Term is a uri

See Also:

  • RDF::Term#uri?


289
290
291
# File 'lib/active_triples/rdf_source.rb', line 289

def uri?
  rdf_subject.uri?
end