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 Persistable#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, #graph, #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#==


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

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)


478
479
480
# File 'lib/active_triples/rdf_source.rb', line 478

def [](term_or_property)
  get_values(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.



491
492
493
# File 'lib/active_triples/rdf_source.rb', line 491

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

#attributesHash<String, Array<Object>>

Gives a hash containing both the registered and unregistered attributes of the resource. Unregistered attributes are given with full URIs.

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

into a dedicated `Serializer` object?

Examples:

class WithProperties
  include ActiveTriples::RDFSource
  property :title,   predicate:  RDF::Vocab::DC.title
  property :creator, predicate:  RDF::Vocab::DC.creator,
                     class_name: 'Agent'
end

class Agent; include ActiveTriples::RDFSource; end

resource = WithProperties.new

resource.attributes
# => {"id"=>"g47123700054720", "title"=>[], "creator"=>[]}

resource.creator.build
resource.title << ['Comet in Moominland', 'Christmas in Moominvalley']

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"]}]}

Returns:

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


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

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)


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

def attributes=(values)
  raise(ArgumentError, "values must be a Hash. Got: #{values.class}") unless
    values.is_a? Hash

  values = values.with_indifferent_access
  id = values.delete(:id)
  set_subject!(id) if node? && id && get_uri(id).uri?

  values.each do |key, value|
    if reflections.has_property?(key)
      set_value(key, value)
    elsif nested_attributes_options
          .keys.any? { |k| key == "#{k}_attributes" }
      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.



309
310
311
# File 'lib/active_triples/rdf_source.rb', line 309

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


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

def dump(*args)
  if args.first == :jsonld && 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:



361
362
363
364
365
366
367
368
369
370
371
372
373
374
# File 'lib/active_triples/rdf_source.rb', line 361

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

Deprecated.

for removal in 1.0; use ‘#get_values` instead.

See Also:



498
499
500
501
502
# File 'lib/active_triples/rdf_source.rb', line 498

def get_relation(args)
  warn 'DEPRECATION: `ActiveTriples::RDFSource#get_relation` will be' \
       'removed in 1.0; use `#get_values` instead.'
  get_values(*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:



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

def get_values(*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

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


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

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



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

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

#idString

Returns:

  • (String)

See Also:

  • RDF::Node#id


286
287
288
# File 'lib/active_triples/rdf_source.rb', line 286

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
# 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

  if args.first.is_a?(Hash) || args.empty?
    set_persistence_strategy(RepositoryStrategy)
  else
    set_persistence_strategy(ParentStrategy)
    persistence_strategy.parent = args.shift
  end

  persistence_strategy.graph = RDF::Graph.new(*args, &block)
  reload

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

#mark_for_destructionObject



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

def mark_for_destruction
  @marked_for_destruction = true
end

#marked_for_destruction?Boolean

Returns:

  • (Boolean)


542
543
544
# File 'lib/active_triples/rdf_source.rb', line 542

def marked_for_destruction?
  @marked_for_destruction
end

#new_record?Boolean

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

Returns:

  • (Boolean)


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

def new_record?
  !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?


294
295
296
# File 'lib/active_triples/rdf_source.rb', line 294

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



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

def parent
  return persistence_strategy.parent if
    persistence_strategy.respond_to?(:parent)

  nil
end

#parent=(parent) ⇒ Object

TODO:

deprecate/remove

See Also:



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

def parent=(parent)
  return persistence_strategy.parent = parent if
    persistence_strategy.respond_to?(:parent=)

  nil
end

#rdf_labelObject

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

See Also:

  • #default_labels


330
331
332
333
334
335
336
337
338
# File 'lib/active_triples/rdf_source.rb', line 330

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


249
250
251
# File 'lib/active_triples/rdf_source.rb', line 249

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

#serializable_hashHash

Returns:

  • (Hash)


194
195
196
197
198
199
200
# File 'lib/active_triples/rdf_source.rb', line 194

def serializable_hash(*)
  attrs = fields.map(&: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) ⇒ void

This method returns an undefined value.

Set a new rdf_subject for the resource.

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

Raises:

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



517
518
519
520
521
522
523
524
525
526
527
528
# File 'lib/active_triples/rdf_source.rb', line 517

def set_subject!(uri_or_str)
  raise 'Refusing to update URI when one is already assigned!' unless
    node? || rdf_subject == RDF::URI(nil)

  return if uri_or_str.nil? ||
            (uri_or_str.to_s.empty? && !uri_or_str.is_a?(RDF::URI))

  new_subject = get_uri(uri_or_str)
  rewrite_statement_uris(rdf_subject, new_subject)

  @rdf_subject = new_subject
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:



432
433
434
435
436
437
438
439
440
# File 'lib/active_triples/rdf_source.rb', line 432

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_values(*args).set(values)
end

#to_uriRDF::URI

Returns the uri.

Returns:

  • (RDF::URI)

    the uri



278
279
280
# File 'lib/active_triples/rdf_source.rb', line 278

def to_uri
  rdf_subject if uri?
end

#typeObject



313
314
315
# File 'lib/active_triples/rdf_source.rb', line 313

def type
  get_values(:type)
end

#type=(type) ⇒ Object

Raises:

  • (ArgumentError)


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

def type=(type)
  raise(ArgumentError,
        "Type must be an RDF::URI. Got: #{type.class}, #{type}") unless
    type.is_a? RDF::URI

  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?


302
303
304
# File 'lib/active_triples/rdf_source.rb', line 302

def uri?
  rdf_subject.uri?
end