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.



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

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.



326
327
328
# File 'lib/active_triples/rdf_source.rb', line 326

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

#attributesObject



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

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)


154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/active_triples/rdf_source.rb', line 154

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.



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

def base_uri
  self.class.base_uri
end

#destroyObject Also known as: destroy!



392
393
394
395
396
397
# File 'lib/active_triples/rdf_source.rb', line 392

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

#destroy_child(child) ⇒ Object



408
409
410
411
412
# File 'lib/active_triples/rdf_source.rb', line 408

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)


404
405
406
# File 'lib/active_triples/rdf_source.rb', line 404

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


180
181
182
183
184
185
186
# File 'lib/active_triples/rdf_source.rb', line 180

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



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

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.



242
243
244
# File 'lib/active_triples/rdf_source.rb', line 242

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

#final_parentObject



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

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



354
355
356
357
358
359
# File 'lib/active_triples/rdf_source.rb', line 354

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)


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

def get_values(*args)
  get_relation(args)
end

#idObject

A string identifier for the resource



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

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


110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/active_triples/rdf_source.rb', line 110

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



422
423
424
# File 'lib/active_triples/rdf_source.rb', line 422

def mark_for_destruction
  @marked_for_destruction = true
end

#marked_for_destruction?Boolean

Returns:

  • (Boolean)


426
427
428
# File 'lib/active_triples/rdf_source.rb', line 426

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)


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

def new_record?
  not persisted?
end

#node?Boolean

Returns:

  • (Boolean)

See Also:

  • RDF::Term#node?


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

def node?
  rdf_subject.node?
end

#persist!(opts = {}) ⇒ Object



263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/active_triples/rdf_source.rb', line 263

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


282
283
284
285
286
# File 'lib/active_triples/rdf_source.rb', line 282

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.



228
229
230
231
232
233
234
235
236
# File 'lib/active_triples/rdf_source.rb', line 228

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.



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

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

#reflectionsObject



150
151
152
# File 'lib/active_triples/rdf_source.rb', line 150

def reflections
  self.class
end

#reloadtrue, false

Repopulates the graph from the repository or parent resource.

Returns:

  • (true, false)


292
293
294
295
296
297
298
299
300
# File 'lib/active_triples/rdf_source.rb', line 292

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



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

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



373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
# File 'lib/active_triples/rdf_source.rb', line 373

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)


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

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



216
217
218
# File 'lib/active_triples/rdf_source.rb', line 216

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

#type=(type) ⇒ Object



220
221
222
223
# File 'lib/active_triples/rdf_source.rb', line 220

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)


95
96
97
# File 'lib/active_triples/rdf_source.rb', line 95

def writable?
  !frozen?
end