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?, #graph, #insert_statement, #persist!, #persisted?, #persistence_strategy, #reload, #set_persistence_strategy

Class Method Details

.type_registryObject



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

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


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

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)


518
519
520
# File 'lib/active_triples/rdf_source.rb', line 518

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.



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

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

#add_observer(observer) ⇒ Object

Parameters:

  • observer (#notify)


590
591
592
# File 'lib/active_triples/rdf_source.rb', line 590

def add_observer(observer)
  @observers.add(observer)
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>>)


185
186
187
188
189
190
191
# File 'lib/active_triples/rdf_source.rb', line 185

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)


193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/active_triples/rdf_source.rb', line 193

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.



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

def base_uri
  self.class.base_uri
end

#countObject



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

delegate :query, :each, :load!, :count, :has_statement?, to: :graph

#default_labelsArray<RDF::URI>

Returns a group of properties to use for default labels.

Returns:

  • (Array<RDF::URI>)

    a group of properties to use for default labels.



216
217
218
219
220
221
222
# File 'lib/active_triples/rdf_source.rb', line 216

def default_labels
  [RDF::Vocab::SKOS.prefLabel,
   RDF::Vocab::DC.title,
   RDF::RDFS.label,
   RDF::Vocab::SKOS.altLabel,
   RDF::Vocab::SKOS.hiddenLabel]
end

#delete_observer(observer) ⇒ #notify?

Returns the deleted observer; nil if the observer was not registered.

Parameters:

  • observer (#notify)

    an observer to delete

Returns:

  • (#notify, nil)

    the deleted observer; nil if the observer was not registered



599
600
601
# File 'lib/active_triples/rdf_source.rb', line 599

def delete_observer(observer)
  @observers.delete?(observer)
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


244
245
246
247
248
249
250
# File 'lib/active_triples/rdf_source.rb', line 244

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

#eachObject



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

delegate :query, :each, :load!, :count, :has_statement?, to: :graph

#escapeObject



99
# File 'lib/active_triples/rdf_source.rb', line 99

delegate :to_base, :term?, :escape, to: :to_term

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



401
402
403
404
405
406
407
408
409
410
411
412
413
414
# File 'lib/active_triples/rdf_source.rb', line 401

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` insctead.

See Also:



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

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:



506
507
508
509
510
511
# File 'lib/active_triples/rdf_source.rb', line 506

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)


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

def graph_name
  nil
end

#has_statement?Object



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

delegate :query, :each, :load!, :count, :has_statement?, to: :graph

#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



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

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

#idString

Returns:

  • (String)

See Also:

  • RDF::Node#id


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

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


112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/active_triples/rdf_source.rb', line 112

def initialize(*args, &block)
  @observers   = Set.new

  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

#inspectString

Note:

Without a custom #inspect, we inherit from RDF::Value.

Returns:

  • (String)


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

def inspect
  sprintf("#<%s:%#0x ID:%s>", self.class.to_s, self.object_id, self.to_base)
end

#load!Object



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

delegate :query, :each, :load!, :count, :has_statement?, to: :graph

#mark_for_destructionObject



578
579
580
# File 'lib/active_triples/rdf_source.rb', line 578

def mark_for_destruction
  @marked_for_destruction = true
end

#marked_for_destruction?Boolean

Returns:

  • (Boolean)


582
583
584
# File 'lib/active_triples/rdf_source.rb', line 582

def marked_for_destruction?
  @marked_for_destruction
end

#new_record?Boolean

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

Returns:

  • (Boolean)


574
575
576
# File 'lib/active_triples/rdf_source.rb', line 574

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?


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

def node?
  rdf_subject.node?
end

#notify_observers(property) ⇒ void

Note:

We short circuit to avoid query costs if no observers are present. If there are regisetred observers, values are returned as an array. This means that we incur query costs immediately and only once.

This method returns an undefined value.

Sends ‘#notify` messages with the property symbol and the current values for the property to each observer.

Examples:

Setting up observers

class MyObserver
  def notify(property, values)
    # do something
  end
end

observer = MyObserver.new
my_source.add_observer(observer)

my_source.creator = 'Moomin'
# the observer recieves a #notify(:creator, ['Moomin']) message here.

Parameters:

  • property (Symbol)


627
628
629
630
631
# File 'lib/active_triples/rdf_source.rb', line 627

def notify_observers(property)
  return if @observers.empty?
  values = get_values(property).to_a
  @observers.each { |o| o.notify(property, values) }
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



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

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

  nil
end

#parent=(parent) ⇒ Object

TODO:

deprecate/remove

See Also:



267
268
269
270
271
272
# File 'lib/active_triples/rdf_source.rb', line 267

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

  nil
end

#queryObject



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

delegate :query, :each, :load!, :count, :has_statement?, to: :graph

#rdf_labelObject

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

See Also:



370
371
372
373
374
375
376
377
378
# File 'lib/active_triples/rdf_source.rb', line 370

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


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

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

#serializable_hashHash

Returns:

  • (Hash)


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

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.



557
558
559
560
561
562
563
564
565
566
567
568
# File 'lib/active_triples/rdf_source.rb', line 557

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:



472
473
474
475
476
477
478
479
480
# File 'lib/active_triples/rdf_source.rb', line 472

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

#term?Object



99
# File 'lib/active_triples/rdf_source.rb', line 99

delegate :to_base, :term?, :escape, to: :to_term

#to_baseObject



99
# File 'lib/active_triples/rdf_source.rb', line 99

delegate :to_base, :term?, :escape, to: :to_term

#to_uriRDF::URI

Returns the uri.

Returns:

  • (RDF::URI)

    the uri



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

def to_uri
  rdf_subject if uri?
end

#typeObject



353
354
355
# File 'lib/active_triples/rdf_source.rb', line 353

def type
  get_values(:type)
end

#type=(type) ⇒ Object

Raises:

  • (ArgumentError)


357
358
359
360
361
362
363
# File 'lib/active_triples/rdf_source.rb', line 357

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?


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

def uri?
  rdf_subject.uri?
end