Module: ActiveTriples::RDFSource

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

Overview

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.

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

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Reflection

add_reflection

Instance Attribute Details

#parentObject

Returns the value of attribute parent.



55
56
57
# File 'lib/active_triples/rdf_source.rb', line 55

def parent
  @parent
end

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

#[](uri_or_term_property) ⇒ Object

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



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

def [](uri_or_term_property)
  get_relation([uri_or_term_property])
end

#[]=(uri_or_term_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.



319
320
321
# File 'lib/active_triples/rdf_source.rb', line 319

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

#attributesObject



128
129
130
131
132
133
134
# File 'lib/active_triples/rdf_source.rb', line 128

def attributes
  attrs = {}
  attrs['id'] = id if 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)


147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/active_triples/rdf_source.rb', line 147

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 id && node?
  values.each do |key, value|
    if reflections.reflect_on_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.



205
206
207
# File 'lib/active_triples/rdf_source.rb', line 205

def base_uri
  self.class.base_uri
end

#destroyObject Also known as: destroy!



385
386
387
388
389
390
# File 'lib/active_triples/rdf_source.rb', line 385

def destroy
  clear
  persist! if repository
  parent.destroy_child(self) if parent
  @destroyed = true
end

#destroy_child(child) ⇒ Object



401
402
403
404
405
# File 'lib/active_triples/rdf_source.rb', line 401

def destroy_child(child)
  statements.each do |statement|
    delete_statement(statement) if statement.subject == child.rdf_subject || statement.object == child.rdf_subject
  end
end

#destroyed?true, false

Indicates if the Resource has been destroyed.

Returns:

  • (true, false)


397
398
399
# File 'lib/active_triples/rdf_source.rb', line 397

def destroyed?
  @destroyed ||= false
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


173
174
175
176
177
178
179
# File 'lib/active_triples/rdf_source.rb', line 173

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

#fetchActiveTriples::Entity

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.

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

Returns:

  • (ActiveTriples::Entity)

    self



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

def fetch
  load(rdf_subject)
  self
end

#fieldsArray<Symbol>

Lists fields registered as properties on the object.

Returns:

  • (Array<Symbol>)

    the list of registered properties.



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

def fields
  properties.keys.map(&:to_sym).reject{|x| x == :type}
end

#final_parentObject



118
119
120
121
122
123
124
125
126
# File 'lib/active_triples/rdf_source.rb', line 118

def final_parent
  @final_parent ||= begin
    parent = self.parent
    while parent && parent.parent && parent.parent != parent
      parent = parent.parent
    end
    parent
  end
end

#get_relation(args) ⇒ Object



347
348
349
350
351
352
# File 'lib/active_triples/rdf_source.rb', line 347

def get_relation(args)
  @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(*args) ⇒ Object

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

get_values(property)

For backwards compatibility, there is support for explicitly passing the rdf_subject to be used in th statement:

get_values(uri, property)


334
335
336
# File 'lib/active_triples/rdf_source.rb', line 334

def get_values(*args)
  get_relation(args)
end

#idObject

A string identifier for the resource



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

def id
  node? ? nil : 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


103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/active_triples/rdf_source.rb', line 103

def initialize(*args, &block)
  resource_uri = args.shift unless args.first.is_a?(Hash)
  self.parent = args.shift unless args.first.is_a?(Hash)
  @graph = RDF::Graph.new(*args, &block)
  set_subject!(resource_uri) if resource_uri

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

#mark_for_destructionObject



415
416
417
# File 'lib/active_triples/rdf_source.rb', line 415

def mark_for_destruction
  @marked_for_destruction = true
end

#marked_for_destruction?Boolean

Returns:

  • (Boolean)


419
420
421
# File 'lib/active_triples/rdf_source.rb', line 419

def marked_for_destruction?
  @marked_for_destruction
end

#new_record?true, false

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

Returns:

  • (true, false)


411
412
413
# File 'lib/active_triples/rdf_source.rb', line 411

def new_record?
  not persisted?
end

#node?Boolean

Returns:

  • (Boolean)

See Also:

  • RDF::Term#node?


198
199
200
# File 'lib/active_triples/rdf_source.rb', line 198

def node?
  rdf_subject.node?
end

#persist!(opts = {}) ⇒ Object



256
257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/active_triples/rdf_source.rb', line 256

def persist!(opts={})
  return if @persisting
  return false if opts[:validate] && !valid?
  @persisting = true
  run_callbacks :persist do
    raise "failed when trying to persist to non-existant repository or parent resource" unless repository
    erase_old_resource
    repository << self
    @persisted = true
  end
  @persisting = false
  true
end

#persisted?true, false

Indicates if the resource is persisted.

Returns:

  • (true, false)

See Also:

  • #persist


275
276
277
278
279
# File 'lib/active_triples/rdf_source.rb', line 275

def persisted?
  @persisted ||= false
  return (@persisted and parent.persisted?) if parent
  @persisted
end

#rdf_labelObject

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



221
222
223
224
225
226
227
228
229
# File 'lib/active_triples/rdf_source.rb', line 221

def rdf_label
  labels = Array(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

Returns a URI or Node which the resource’s properties are about.

Returns:

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

    a URI or Node which the resource’s properties are about.



184
185
186
# File 'lib/active_triples/rdf_source.rb', line 184

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

#reflectionsObject



143
144
145
# File 'lib/active_triples/rdf_source.rb', line 143

def reflections
  self.class
end

#reloadtrue, false

Repopulates the graph from the repository or parent resource.

Returns:

  • (true, false)


285
286
287
288
289
290
291
292
293
# File 'lib/active_triples/rdf_source.rb', line 285

def reload
  @relation_cache ||= {}
  return false unless repository
  self << repository.query(subject: rdf_subject)
  unless empty?
    @persisted = true
  end
  true
end

#serializable_hash(options = nil) ⇒ Object



136
137
138
139
140
141
# File 'lib/active_triples/rdf_source.rb', line 136

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



366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
# File 'lib/active_triples/rdf_source.rb', line 366

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(*args) ⇒ 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.

Handles two argument patterns. The recommended pattern is:

set_value(property, values)

For backwards compatibility, there is support for explicitly passing the rdf_subject to be used in the statement:

set_value(uri, property, values)


306
307
308
309
310
311
312
313
# File 'lib/active_triples/rdf_source.rb', line 306

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

#typeObject



209
210
211
# File 'lib/active_triples/rdf_source.rb', line 209

def type
  self.get_values(:type).to_a
end

#type=(type) ⇒ Object



213
214
215
216
# File 'lib/active_triples/rdf_source.rb', line 213

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

#writable?true, false

Specifies whether the object is currently writable.

Returns:

  • (true, false)


88
89
90
# File 'lib/active_triples/rdf_source.rb', line 88

def writable?
  !frozen?
end