active-triples 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,23 +1,34 @@
1
1
  require 'active_support/core_ext/module/delegation'
2
2
 
3
3
  module ActiveTriples
4
- class Term
5
-
6
- attr_accessor :parent, :value_arguments, :node_cache, :term_args
4
+ ##
5
+ # A `Relation` represents the values of a specific property/predicate on an
6
+ # {RDFSource}. Each relation is a set ({Array}) of {RDF::Terms} that are
7
+ # objects in the of source's triples of the form:
8
+ #
9
+ # <{#parent}> <{#predicate}> [term] .
10
+ #
11
+ # Relations, then, express n binary relationships between the parent node and
12
+ # a term.
13
+ #
14
+ # @see RDF::Term
15
+ class Relation
16
+
17
+ attr_accessor :parent, :value_arguments, :node_cache, :rel_args
7
18
  attr_reader :reflections
8
19
 
9
20
  delegate *(Array.public_instance_methods - [:send, :__send__, :__id__, :class, :object_id] + [:as_json]), :to => :result
10
21
 
11
- def initialize(parent_resource, value_arguments)
12
- self.parent = parent_resource
13
- @reflections = parent_resource.reflections
14
- self.term_args ||= {}
22
+ def initialize(parent_source, value_arguments)
23
+ self.parent = parent_source
24
+ @reflections = parent_source.reflections
25
+ self.rel_args ||= {}
15
26
  self.value_arguments = value_arguments
16
27
  end
17
28
 
18
29
  def value_arguments=(value_args)
19
30
  if value_args.kind_of?(Array) && value_args.last.kind_of?(Hash)
20
- self.term_args = value_args.pop
31
+ self.rel_args = value_args.pop
21
32
  end
22
33
  @value_arguments = value_args
23
34
  end
@@ -36,7 +47,7 @@ module ActiveTriples
36
47
 
37
48
  def set(values)
38
49
  values = [values].compact unless values.kind_of?(Array)
39
- values = values.to_a if values.class == Term
50
+ values = values.to_a if values.class == Relation
40
51
  empty_property
41
52
  values.each do |val|
42
53
  set_value(val)
@@ -91,7 +102,7 @@ module ActiveTriples
91
102
  end
92
103
 
93
104
  def property_config
94
- return type_property if (property == RDF.type || property.to_s == "type") && (!reflections.kind_of?(Resource) || !reflections.reflect_on_property(property))
105
+ return type_property if (property == RDF.type || property.to_s == "type") && (!reflections.kind_of?(RDFSource) || !reflections.reflect_on_property(property))
95
106
  reflections.reflect_on_property(property)
96
107
  end
97
108
 
@@ -116,7 +127,7 @@ module ActiveTriples
116
127
  object = val
117
128
  val = val.resource if val.respond_to?(:resource)
118
129
  val = value_to_node(val)
119
- if val.kind_of? Resource
130
+ if val.kind_of? RDFSource
120
131
  node_cache[val.rdf_subject] = nil
121
132
  add_child_node(val, object)
122
133
  return
@@ -178,13 +189,13 @@ module ActiveTriples
178
189
  end
179
190
 
180
191
  def cast?
181
- return true unless property_config || (term_args && term_args[:cast])
182
- return term_args[:cast] if term_args.has_key?(:cast)
192
+ return true unless property_config || (rel_args && rel_args[:cast])
193
+ return rel_args[:cast] if rel_args.has_key?(:cast)
183
194
  !!property_config[:cast]
184
195
  end
185
196
 
186
197
  def return_literals?
187
- term_args && term_args[:literal]
198
+ rel_args && rel_args[:literal]
188
199
  end
189
200
 
190
201
  def final_parent
@@ -211,7 +222,8 @@ module ActiveTriples
211
222
  def class_for_property
212
223
  klass = property_config[:class_name] if property_config
213
224
  klass ||= Resource
214
- klass = ActiveTriples.class_from_string(klass, final_parent.class) if klass.kind_of? String
225
+ klass = ActiveTriples.class_from_string(klass, final_parent.class) if
226
+ klass.kind_of? String
215
227
  klass
216
228
  end
217
229
 
@@ -223,6 +235,19 @@ module ActiveTriples
223
235
  parent.rdf_subject
224
236
  end
225
237
  end
238
+ end
226
239
 
240
+ class Term < Relation
241
+ def self.inherited(*)
242
+ warn 'ActiveTriples::Term is deprecated! ' \
243
+ 'Use ActiveTriples::Relation instead.'
244
+ super
245
+ end
246
+
247
+ def initialize(*)
248
+ warn 'ActiveTriples::Term is deprecated! ' \
249
+ 'Use ActiveTriples::Relation instead.'
250
+ super
251
+ end
227
252
  end
228
253
  end
@@ -1,547 +1,22 @@
1
1
  require 'deprecation'
2
- require 'active_model'
3
- require 'active_support/core_ext/hash'
4
2
 
5
3
  module ActiveTriples
6
4
  ##
7
- # Defines a generic RDF `Resource` as an RDF::Graph with property
8
- # configuration, accessors, and some other methods for managing
9
- # resources as discrete subgraphs which can be maintained by a Hydra
10
- # datastream model.
11
- #
12
- # Resources can be instances of ActiveTriples::Resource
13
- # directly, but more often they will be instances of subclasses with
14
- # registered properties and configuration. e.g.
15
- #
16
- # class License < Resource
17
- # configure repository: :default
18
- # property :title, predicate: RDF::DC.title, class_name: RDF::Literal do |index|
19
- # index.as :displayable, :facetable
20
- # end
21
- # end
22
- class Resource < RDF::Graph
23
- extend Configurable
24
- include Properties
25
- extend Deprecation
26
- extend ActiveModel::Naming
27
- extend ActiveModel::Translation
28
- extend ActiveModel::Callbacks
29
- include ActiveModel::Validations
30
- include ActiveModel::Conversion
31
- include ActiveModel::Serialization
32
- include ActiveModel::Serializers::JSON
33
- include NestedAttributes
34
- include Reflection
35
- attr_accessor :parent
36
- define_model_callbacks :persist
5
+ # Defines a generic RDF `Resource` as an `ActiveTriples::Entity`. This
6
+ # provides a basic `Entity` type for classless resources.
7
+ class Resource
8
+ include RDFSource
37
9
 
38
10
  class << self
39
11
  def type_registry
40
- @@type_registry ||= {}
12
+ RDFSource.type_registry
41
13
  end
42
14
 
43
- ##
44
- # Adapter for a consistent interface for creating a new Resource
45
- # from a URI. Similar functionality should exist in all objects
46
- # which can become a Resource.
47
- #
48
- # @param uri [#to_uri, String]
49
- # @param vals values to pass as arguments to ::new
50
- #
51
- # @return [ActiveTriples::Resource] a Resource with
52
- def from_uri(uri,vals=nil)
53
- new(uri, vals)
15
+ def property(*)
16
+ raise "Properties not definable directly on ActiveTriples::Resource, use a subclass" if
17
+ self == ActiveTriples::Resource
18
+ super
54
19
  end
55
20
  end
56
-
57
- ##
58
- # Specifies whether the object is currently writable.
59
- #
60
- # @return [true, false]
61
- def writable?
62
- !frozen?
63
- end
64
-
65
- ##
66
- # Initialize an instance of this resource class. Defaults to a
67
- # blank node subject. In addition to RDF::Graph parameters, you
68
- # can pass in a URI and/or a parent to build a resource from a
69
- # existing data.
70
- #
71
- # You can pass in only a parent with:
72
- # Resource.new(nil, parent)
73
- #
74
- # @see RDF::Graph
75
- def initialize(*args, &block)
76
- resource_uri = args.shift unless args.first.is_a?(Hash)
77
- self.parent = args.shift unless args.first.is_a?(Hash)
78
- set_subject!(resource_uri) if resource_uri
79
- super(*args, &block)
80
- reload
81
- # Append type to graph if necessary.
82
- self.get_values(:type) << self.class.type if self.class.type.kind_of?(RDF::URI) && type.empty?
83
- end
84
-
85
- ##
86
- # Returns the current object.
87
- #
88
- # @deprecated redundant, simply returns self.
89
- #
90
- # @return [self]
91
- def graph
92
- Deprecation.warn Resource, "graph is redundant & deprecated. It will be removed in ActiveTriples 0.2.0.", caller
93
- self
94
- end
95
-
96
- def final_parent
97
- @final_parent ||= begin
98
- parent = self.parent
99
- while parent && parent.parent && parent.parent != parent
100
- parent = parent.parent
101
- end
102
- parent
103
- end
104
- end
105
-
106
- def attributes
107
- attrs = {}
108
- attrs['id'] = id if id
109
- fields.map { |f| attrs[f.to_s] = get_values(f) }
110
- unregistered_predicates.map { |uri| attrs[uri.to_s] = get_values(uri) }
111
- attrs
112
- end
113
-
114
- def serializable_hash(options = nil)
115
- attrs = (fields.map { |f| f.to_s }) << 'id'
116
- hash = super(:only => attrs)
117
- unregistered_predicates.map { |uri| hash[uri.to_s] = get_values(uri) }
118
- hash
119
- end
120
-
121
- def reflections
122
- self.class
123
- end
124
-
125
- def attributes=(values)
126
- raise ArgumentError, "values must be a Hash, you provided #{values.class}" unless values.kind_of? Hash
127
- values = values.with_indifferent_access
128
- id = values.delete(:id)
129
- set_subject!(id) if id && node?
130
- values.each do |key, value|
131
- if reflections.reflect_on_property(key)
132
- set_value(rdf_subject, key, value)
133
- elsif nested_attributes_options.keys.map { |k| "#{k}_attributes" }.include?(key)
134
- send("#{key}=".to_sym, value)
135
- else
136
- raise ArgumentError, "No association found for name `#{key}'. Has it been defined yet?"
137
- end
138
- end
139
- end
140
-
141
- ##
142
- # Returns a serialized string representation of self.
143
- # Extends the base implementation builds a JSON-LD context if the
144
- # specified format is :jsonld and a context is provided by
145
- # #jsonld_context
146
- #
147
- # @see RDF::Enumerable#dump
148
- #
149
- # @param args [Array<Object>]
150
- # @return [String]
151
- def dump(*args)
152
- if args.first == :jsonld and respond_to?(:jsonld_context)
153
- args << {} unless args.last.is_a?(Hash)
154
- args.last[:context] ||= jsonld_context
155
- end
156
- super
157
- end
158
-
159
- ##
160
- # @return [RDF::URI, RDF::Node] a URI or Node which the resource's
161
- # properties are about.
162
- def rdf_subject
163
- @rdf_subject ||= RDF::Node.new
164
- end
165
- alias_method :to_term, :rdf_subject
166
-
167
- ##
168
- # A string identifier for the resource
169
- def id
170
- node? ? nil : rdf_subject.to_s
171
- end
172
-
173
- def node?
174
- return true if rdf_subject.kind_of? RDF::Node
175
- false
176
- end
177
-
178
- ##
179
- # @return [String, nil] the base URI the resource will use when
180
- # setting its subject. `nil` if none is used.
181
- def base_uri
182
- self.class.base_uri
183
- end
184
-
185
- def type
186
- self.get_values(:type).to_a
187
- end
188
-
189
- def type=(type)
190
- raise "Type must be an RDF::URI" unless type.kind_of? RDF::URI
191
- self.update(RDF::Statement.new(rdf_subject, RDF.type, type))
192
- end
193
-
194
- ##
195
- # Looks for labels in various default fields, prioritizing
196
- # configured label fields.
197
- def rdf_label
198
- labels = Array(self.class.rdf_label)
199
- labels += default_labels
200
- labels.each do |label|
201
- values = get_values(label)
202
- return values unless values.empty?
203
- end
204
- node? ? [] : [rdf_subject.to_s]
205
- end
206
-
207
- ##
208
- # Lists fields registered as properties on the object.
209
- #
210
- # @return [Array<Symbol>] the list of registered properties.
211
- def fields
212
- properties.keys.map(&:to_sym).reject{|x| x == :type}
213
- end
214
-
215
- ##
216
- # Load data from the #rdf_subject URI. Retrieved data will be
217
- # parsed into the Resource's graph from available RDF::Readers
218
- # and available from property accessors if if predicates are
219
- # registered.
220
- #
221
- # osu = ActiveTriples::Resource.new('http://dbpedia.org/resource/Oregon_State_University')
222
- # osu.fetch
223
- # osu.rdf_label.first
224
- # # => "Oregon State University"
225
- #
226
- # @return [ActiveTriples::Resource] self
227
- def fetch
228
- load(rdf_subject)
229
- self
230
- end
231
-
232
- def persist!(opts={})
233
- return if @persisting
234
- return false if opts[:validate] && !valid?
235
- @persisting = true
236
- run_callbacks :persist do
237
- raise "failed when trying to persist to non-existant repository or parent resource" unless repository
238
- erase_old_resource
239
- repository << self
240
- @persisted = true
241
- end
242
- @persisting = false
243
- true
244
- end
245
-
246
- ##
247
- # Indicates if the resource is persisted.
248
- #
249
- # @see #persist
250
- # @return [true, false]
251
- def persisted?
252
- @persisted ||= false
253
- return (@persisted and parent.persisted?) if parent
254
- @persisted
255
- end
256
-
257
- ##
258
- # Repopulates the graph from the repository or parent resource.
259
- #
260
- # @return [true, false]
261
- def reload
262
- @term_cache ||= {}
263
- return false unless repository
264
- self << repository.query(subject: rdf_subject)
265
- unless empty?
266
- @persisted = true
267
- end
268
- true
269
- end
270
-
271
- ##
272
- # Adds or updates a property with supplied values.
273
- #
274
- # Handles two argument patterns. The recommended pattern is:
275
- # set_value(property, values)
276
- #
277
- # For backwards compatibility, there is support for explicitly
278
- # passing the rdf_subject to be used in the statement:
279
- # set_value(uri, property, values)
280
- #
281
- # @note This method will delete existing statements with the correct subject and predicate from the graph
282
- def set_value(*args)
283
- # Add support for legacy 3-parameter syntax
284
- if args.length > 3 || args.length < 2
285
- raise ArgumentError, "wrong number of arguments (#{args.length} for 2-3)"
286
- end
287
- values = args.pop
288
- get_term(args).set(values)
289
- end
290
-
291
- ##
292
- # Adds or updates a property with supplied values.
293
- #
294
- # @note This method will delete existing statements with the correct subject and predicate from the graph
295
- def []=(uri_or_term_property, value)
296
- self[uri_or_term_property].set(value)
297
- end
298
-
299
- ##
300
- # Returns an array of values belonging to the property
301
- # requested. Elements in the array may RdfResource objects or a
302
- # valid datatype.
303
- #
304
- # Handles two argument patterns. The recommended pattern is:
305
- # get_values(property)
306
- #
307
- # For backwards compatibility, there is support for explicitly
308
- # passing the rdf_subject to be used in th statement:
309
- # get_values(uri, property)
310
- def get_values(*args)
311
- get_term(args)
312
- end
313
-
314
- ##
315
- # Returns an array of values belonging to the property
316
- # requested. Elements in the array may RdfResource objects or a
317
- # valid datatype.
318
- def [](uri_or_term_property)
319
- get_term([uri_or_term_property])
320
- end
321
-
322
-
323
- def get_term(args)
324
- @term_cache ||= {}
325
- term = Term.new(self, args)
326
- @term_cache["#{term.send(:rdf_subject)}/#{term.property}/#{term.term_args}"] ||= term
327
- @term_cache["#{term.send(:rdf_subject)}/#{term.property}/#{term.term_args}"]
328
- end
329
-
330
- ##
331
- # Set a new rdf_subject for the resource.
332
- #
333
- # This raises an error if the current subject is not a blank node,
334
- # and returns false if it can't figure out how to make a URI from
335
- # the param. Otherwise it creates a URI for the resource and
336
- # rebuilds the graph with the updated URI.
337
- #
338
- # Will try to build a uri as an extension of the class's base_uri
339
- # if appropriate.
340
- #
341
- # @param [#to_uri, #to_s] uri_or_str the uri or string to use
342
- def set_subject!(uri_or_str)
343
- raise "Refusing update URI when one is already assigned!" unless node? or rdf_subject == RDF::URI(nil)
344
- # Refusing set uri to an empty string.
345
- return false if uri_or_str.nil? or (uri_or_str.to_s.empty? and not uri_or_str.kind_of? RDF::URI)
346
- # raise "Refusing update URI! This object is persisted to a datastream." if persisted?
347
- old_subject = rdf_subject
348
- @rdf_subject = get_uri(uri_or_str)
349
-
350
- each_statement do |statement|
351
- if statement.subject == old_subject
352
- delete(statement)
353
- self << RDF::Statement.new(rdf_subject, statement.predicate, statement.object)
354
- elsif statement.object == old_subject
355
- delete(statement)
356
- self << RDF::Statement.new(statement.subject, statement.predicate, rdf_subject)
357
- end
358
- end
359
- end
360
-
361
- def destroy
362
- clear
363
- persist! if repository
364
- parent.destroy_child(self) if parent
365
- @destroyed = true
366
- end
367
- alias_method :destroy!, :destroy
368
-
369
- ##
370
- # Indicates if the Resource has been destroyed.
371
- #
372
- # @return [true, false]
373
- def destroyed?
374
- @destroyed ||= false
375
- end
376
-
377
- def destroy_child(child)
378
- statements.each do |statement|
379
- delete_statement(statement) if statement.subject == child.rdf_subject || statement.object == child.rdf_subject
380
- end
381
- end
382
-
383
- ##
384
- # Indicates if the record is 'new' (has not yet been persisted).
385
- #
386
- # @return [true, false]
387
- def new_record?
388
- not persisted?
389
- end
390
-
391
- ##
392
- # @return [String] the string representation of the resource
393
- def solrize
394
- node? ? rdf_label : rdf_subject.to_s
395
- end
396
-
397
- def mark_for_destruction
398
- @marked_for_destruction = true
399
- end
400
-
401
- def marked_for_destruction?
402
- @marked_for_destruction
403
- end
404
-
405
- protected
406
-
407
- #Clear out any old assertions in the repository about this node or statement
408
- # thus preparing to receive the updated assertions.
409
- def erase_old_resource
410
- if node?
411
- repository.statements.each do |statement|
412
- repository.send(:delete_statement, statement) if statement.subject == rdf_subject
413
- end
414
- else
415
- repository.delete [rdf_subject, nil, nil]
416
- end
417
- end
418
-
419
- ##
420
- # Test if the rdf_subject that would be generated using a
421
- # specific ID is already in use in the triplestore.
422
- #
423
- # @param [Integer, #read] ID to test
424
- #
425
- # @return [TrueClass, FalseClass] true, if the ID is in
426
- # use in the triplestore; otherwise, false.
427
- # NOTE: If the ID is in use in an object not yet
428
- # persisted, false will be returned presenting
429
- # a window of opportunity for an ID clash.
430
- def self.id_persisted?(test_id)
431
- rdf_subject = self.new(test_id).rdf_subject
432
- ActiveTriples::Repositories.has_subject?(rdf_subject)
433
- end
434
-
435
- ##
436
- # Test if the rdf_subject that would be generated using a
437
- # specific URI is already in use in the triplestore.
438
- #
439
- # @param [String, RDF::URI, #read] URI to test
440
- #
441
- # @return [TrueClass, FalseClass] true, if the URI is in
442
- # use in the triplestore; otherwise, false.
443
- # NOTE: If the URI is in use in an object not yet
444
- # persisted, false will be returned presenting
445
- # a window of opportunity for an ID clash.
446
- def self.uri_persisted?(test_uri)
447
- rdf_subject = test_uri.kind_of?(RDF::URI) ? test_uri : RDF::URI(test_uri)
448
- ActiveTriples::Repositories.has_subject?(rdf_subject)
449
- end
450
-
451
- private
452
-
453
- ##
454
- # Returns the properties registered and their configurations.
455
- #
456
- # @return [ActiveSupport::HashWithIndifferentAccess{String => ActiveTriples::NodeConfig}]
457
- def properties
458
- _active_triples_config
459
- end
460
-
461
- ##
462
- # List of RDF predicates registered as properties on the object.
463
- #
464
- # @return [Array<RDF::URI>]
465
- def registered_predicates
466
- properties.values.map { |config| config.predicate }
467
- end
468
-
469
- ##
470
- # List of RDF predicates used in the Resource's triples, but not
471
- # mapped to any property or accessor methods.
472
- #
473
- # @return [Array<RDF::URI>]
474
- def unregistered_predicates
475
- preds = registered_predicates
476
- preds << RDF.type
477
- predicates.select { |p| !preds.include? p }
478
- end
479
-
480
- ##
481
- # Given a predicate which has been registered to a property,
482
- # returns the name of the matching property.
483
- #
484
- # @param predicate [RDF::URI]
485
- #
486
- # @return [String, nil] the name of the property mapped to the
487
- # predicate provided
488
- def property_for_predicate(predicate)
489
- properties.each do |property, values|
490
- return property if values[:predicate] == predicate
491
- end
492
- return nil
493
- end
494
-
495
- def default_labels
496
- [RDF::SKOS.prefLabel,
497
- RDF::DC.title,
498
- RDF::RDFS.label,
499
- RDF::SKOS.altLabel,
500
- RDF::SKOS.hiddenLabel]
501
- end
502
-
503
- ##
504
- # Return the repository (or parent) that this resource should
505
- # write to when persisting.
506
- #
507
- # @return [RDF::Repository, ActiveTriples::Resource] the target
508
- # repository
509
- def repository
510
- @repository ||=
511
- if self.class.repository == :parent
512
- final_parent
513
- else
514
- repo = Repositories.repositories[self.class.repository]
515
- raise RepositoryNotFoundError, "The class #{self.class} expects a repository called #{self.class.repository}, but none was declared" unless repo
516
- repo
517
- end
518
- end
519
-
520
- ##
521
- # Takes a URI or String and aggressively tries to convert it into
522
- # an RDF term. If a String is given, first tries to interpret it
523
- # as a valid URI, then tries to append it to base_uri. Finally,
524
- # raises an error if no valid term can be built.
525
- #
526
- # The argument must be an RDF::Node, an object that responds to
527
- # #to_uri, a String that represents a valid URI, or a String that
528
- # appends to the Resource's base_uri to create a valid URI.
529
- #
530
- # @TODO: URI.scheme_list is naive and incomplete. Find a better
531
- # way to check for an existing scheme.
532
- #
533
- # @param uri_or_str [RDF::Resource, String]
534
- #
535
- # @return [RDF::Resource] A term
536
- # @raise [RuntimeError] no valid RDF term could be built
537
- def get_uri(uri_or_str)
538
- return uri_or_str.to_uri if uri_or_str.respond_to? :to_uri
539
- return uri_or_str if uri_or_str.kind_of? RDF::Node
540
- uri_or_str = uri_or_str.to_s
541
- return RDF::Node(uri_or_str[2..-1]) if uri_or_str.start_with? '_:'
542
- return RDF::URI(uri_or_str) if RDF::URI(uri_or_str).valid? and (URI.scheme_list.include?(RDF::URI.new(uri_or_str).scheme.upcase) or RDF::URI.new(uri_or_str).scheme == 'info')
543
- return RDF::URI(self.base_uri.to_s + (self.base_uri.to_s[-1,1] =~ /(\/|#)/ ? '' : '/') + uri_or_str) if base_uri && !uri_or_str.start_with?(base_uri.to_s)
544
- raise RuntimeError, "could not make a valid RDF::URI from #{uri_or_str}"
545
- end
546
21
  end
547
22
  end