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


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

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)


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

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.



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

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


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

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)


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

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.



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

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


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

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:



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

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:



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

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:



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

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)


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

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



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

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

#idString

Returns:

  • (String)

See Also:

  • RDF::Node#id


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

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



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

def mark_for_destruction
  @marked_for_destruction = true
end

#marked_for_destruction?Boolean

Returns:

  • (Boolean)


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

def marked_for_destruction?
  @marked_for_destruction
end

#new_record?Boolean

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

Returns:

  • (Boolean)


528
529
530
# File 'lib/active_triples/rdf_source.rb', line 528

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?


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

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



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

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

#parent=(parent) ⇒ Object

TODO:

deprecate/remove

See Also:



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

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


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

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


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

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

#serializable_hash(options = nil) ⇒ Object



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

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) ⇒ 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.



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

def set_subject!(uri_or_str)
  raise "Refusing update URI when one is already assigned!" unless 
    node? || rdf_subject == RDF::URI(nil)
    
  return false if uri_or_str.nil? || 
                  (uri_or_str.to_s.empty? &&
                   !uri_or_str.kind_of?(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:



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

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



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

def to_uri
  rdf_subject if uri?
end

#typeObject



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

def type
  self.get_values(:type)
end

#type=(type) ⇒ Object



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

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?


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

def uri?
  rdf_subject.uri?
end