active-triples 0.8.1 → 0.8.2
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.
- checksums.yaml +4 -4
- data/.travis.yml +8 -9
- data/CHANGES.md +69 -0
- data/Gemfile +0 -2
- data/Guardfile +1 -2
- data/active-triples.gemspec +3 -3
- data/lib/active/triples.rb +1 -0
- data/lib/active_triples.rb +4 -0
- data/lib/active_triples/configurable.rb +3 -1
- data/lib/active_triples/configuration.rb +1 -0
- data/lib/active_triples/configuration/item.rb +1 -0
- data/lib/active_triples/configuration/item_factory.rb +1 -0
- data/lib/active_triples/configuration/merge_item.rb +5 -2
- data/lib/active_triples/extension_strategy.rb +1 -0
- data/lib/active_triples/identifiable.rb +1 -0
- data/lib/active_triples/list.rb +2 -0
- data/lib/active_triples/nested_attributes.rb +1 -1
- data/lib/active_triples/node_config.rb +5 -3
- data/lib/active_triples/persistable.rb +1 -0
- data/lib/active_triples/persistence_strategies/parent_strategy.rb +104 -29
- data/lib/active_triples/persistence_strategies/persistence_strategy.rb +15 -7
- data/lib/active_triples/persistence_strategies/repository_strategy.rb +26 -22
- data/lib/active_triples/properties.rb +84 -6
- data/lib/active_triples/property.rb +35 -4
- data/lib/active_triples/property_builder.rb +38 -4
- data/lib/active_triples/rdf_source.rb +225 -75
- data/lib/active_triples/reflection.rb +42 -3
- data/lib/active_triples/relation.rb +330 -73
- data/lib/active_triples/repositories.rb +4 -2
- data/lib/active_triples/resource.rb +1 -0
- data/lib/active_triples/schema.rb +1 -0
- data/lib/active_triples/undefined_property_error.rb +27 -0
- data/lib/active_triples/version.rb +2 -1
- data/spec/active_triples/configurable_spec.rb +3 -2
- data/spec/active_triples/configuration_spec.rb +2 -1
- data/spec/active_triples/extension_strategy_spec.rb +2 -1
- data/spec/active_triples/identifiable_spec.rb +7 -11
- data/spec/active_triples/list_spec.rb +1 -4
- data/spec/active_triples/nested_attributes_spec.rb +4 -3
- data/spec/active_triples/persistable_spec.rb +4 -1
- data/spec/active_triples/persistence_strategies/parent_strategy_spec.rb +141 -11
- data/spec/active_triples/persistence_strategies/persistence_strategy_spec.rb +1 -0
- data/spec/active_triples/persistence_strategies/repository_strategy_spec.rb +32 -17
- data/spec/active_triples/properties_spec.rb +68 -33
- data/spec/active_triples/property_builder_spec.rb +36 -0
- data/spec/active_triples/property_spec.rb +15 -1
- data/spec/active_triples/rdf_source_spec.rb +544 -6
- data/spec/active_triples/reflection_spec.rb +78 -0
- data/spec/active_triples/relation_spec.rb +505 -3
- data/spec/active_triples/repositories_spec.rb +3 -1
- data/spec/active_triples/resource_spec.rb +90 -147
- data/spec/active_triples/schema_spec.rb +3 -2
- data/spec/active_triples_spec.rb +1 -0
- data/spec/integration/dummies/dummy_resource_a.rb +6 -0
- data/spec/integration/dummies/dummy_resource_b.rb +6 -0
- data/spec/integration/parent_persistence_spec.rb +18 -0
- data/spec/integration/reciprocal_properties_spec.rb +69 -0
- data/spec/pragmatic_context_spec.rb +10 -8
- data/spec/spec_helper.rb +5 -0
- data/spec/support/active_model_lint.rb +4 -6
- data/spec/support/dummies/basic_persistable.rb +2 -11
- data/spec/support/matchers.rb +11 -0
- data/spec/support/shared_examples/persistence_strategy.rb +3 -16
- metadata +20 -13
@@ -1,8 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module ActiveTriples
|
2
3
|
##
|
3
4
|
# @abstract defines the basic interface for persistence of {RDFSource}'s.
|
4
5
|
#
|
5
|
-
# A `PersistenceStrategy` has an underlying
|
6
|
+
# A `PersistenceStrategy` has an underlying resource which should
|
6
7
|
# be an `RDFSource` or equivalent. Strategies can be injected into `RDFSource`
|
7
8
|
# instances at runtime to change the target datastore, repository, or object
|
8
9
|
# the instance syncs its graph with on save and reload operations.
|
@@ -16,14 +17,20 @@ module ActiveTriples
|
|
16
17
|
#
|
17
18
|
module PersistenceStrategy
|
18
19
|
##
|
19
|
-
# Deletes the resource from the repository.
|
20
|
+
# Deletes the resource from the datastore / repository.
|
20
21
|
#
|
21
22
|
# @yield prior to persisting, yields to allow a block that performs
|
22
23
|
# deletions in the persisted graph(s).
|
23
24
|
# @return [Boolean] true if the resource was sucessfully destroyed
|
24
25
|
def destroy(&block)
|
25
|
-
obj.clear
|
26
26
|
yield if block_given?
|
27
|
+
|
28
|
+
# Provide a warning for strategies relying on #destroy to clear the resource
|
29
|
+
if defined? obj
|
30
|
+
warn "DEPRECATION WARNING: #destroy implementations must now explicitly call 'source.clear'"
|
31
|
+
obj.clear
|
32
|
+
end
|
33
|
+
|
27
34
|
persist!
|
28
35
|
@destroyed = true
|
29
36
|
end
|
@@ -38,7 +45,7 @@ module ActiveTriples
|
|
38
45
|
end
|
39
46
|
|
40
47
|
##
|
41
|
-
# Indicates if the resource is persisted to the repository
|
48
|
+
# Indicates if the resource is persisted to the datastore / repository
|
42
49
|
#
|
43
50
|
# @return [Boolean] true if persisted; else false.
|
44
51
|
def persisted?
|
@@ -46,7 +53,7 @@ module ActiveTriples
|
|
46
53
|
end
|
47
54
|
|
48
55
|
##
|
49
|
-
# @abstract save the
|
56
|
+
# @abstract save the resource according to the strategy and set the
|
50
57
|
# @persisted flag to `true`
|
51
58
|
#
|
52
59
|
# @see #persisted?
|
@@ -57,8 +64,9 @@ module ActiveTriples
|
|
57
64
|
end
|
58
65
|
|
59
66
|
##
|
60
|
-
# @abstract Clear out any old assertions in the
|
61
|
-
# or statement thus preparing to receive the updated
|
67
|
+
# @abstract Clear out any old assertions in the datastore / repository
|
68
|
+
# about this node or statement thus preparing to receive the updated
|
69
|
+
# assertions.
|
62
70
|
#
|
63
71
|
# @return [Boolean]
|
64
72
|
def erase_old_resource
|
@@ -1,41 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module ActiveTriples
|
2
3
|
##
|
3
4
|
# Persistence strategy for projecting `RDFSource` to `RDF::Repositories`.
|
4
5
|
class RepositoryStrategy
|
5
6
|
include PersistenceStrategy
|
6
7
|
|
7
|
-
# @!attribute [r]
|
8
|
-
# the
|
9
|
-
attr_reader :
|
8
|
+
# @!attribute [r] source
|
9
|
+
# the resource to persist with this strategy
|
10
|
+
attr_reader :source
|
10
11
|
|
11
12
|
##
|
12
|
-
# @param
|
13
|
+
# @param source [RDFSource, RDF::Enumerable] the `RDFSource` (or other
|
13
14
|
# `RDF::Enumerable` to persist with the strategy.
|
14
|
-
def initialize(
|
15
|
-
@
|
15
|
+
def initialize(source)
|
16
|
+
@source = source
|
17
|
+
end
|
18
|
+
|
19
|
+
##
|
20
|
+
# Deletes the resource from the repository.
|
21
|
+
#
|
22
|
+
def destroy
|
23
|
+
super { source.clear }
|
16
24
|
end
|
17
25
|
|
18
26
|
##
|
19
27
|
# Clear out any old assertions in the repository about this node or statement
|
20
28
|
# thus preparing to receive the updated assertions.
|
21
29
|
def erase_old_resource
|
22
|
-
if
|
30
|
+
if source.node?
|
23
31
|
repository.statements.each do |statement|
|
24
32
|
repository.send(:delete_statement, statement) if
|
25
|
-
statement.subject ==
|
33
|
+
statement.subject == source
|
26
34
|
end
|
27
35
|
else
|
28
|
-
repository.delete [
|
36
|
+
repository.delete [source.to_term, nil, nil]
|
29
37
|
end
|
30
38
|
end
|
31
39
|
|
32
40
|
##
|
33
|
-
# Persists the
|
41
|
+
# Persists the resource to the repository
|
34
42
|
#
|
35
43
|
# @return [true] returns true if the save did not error
|
36
44
|
def persist!
|
37
45
|
erase_old_resource
|
38
|
-
repository <<
|
46
|
+
repository << source
|
39
47
|
@persisted = true
|
40
48
|
end
|
41
49
|
|
@@ -44,13 +52,13 @@ module ActiveTriples
|
|
44
52
|
#
|
45
53
|
# @return [Boolean]
|
46
54
|
def reload
|
47
|
-
|
48
|
-
@persisted = true unless
|
55
|
+
source << repository.query(subject: source)
|
56
|
+
@persisted = true unless source.empty?
|
49
57
|
true
|
50
58
|
end
|
51
59
|
|
52
60
|
##
|
53
|
-
# @return [RDF::Repository] The RDF::Repository that the
|
61
|
+
# @return [RDF::Repository] The RDF::Repository that the resource will project
|
54
62
|
# itself on when persisting.
|
55
63
|
def repository
|
56
64
|
@repository ||= set_repository
|
@@ -59,20 +67,16 @@ module ActiveTriples
|
|
59
67
|
private
|
60
68
|
|
61
69
|
##
|
62
|
-
# Finds an appropriate repository from the calling
|
70
|
+
# Finds an appropriate repository from the calling resource's configuration.
|
63
71
|
# If no repository is configured, builds an ephemeral in-memory
|
64
72
|
# repository and 'persists' there.
|
65
73
|
#
|
66
74
|
# @todo find a way to move this logic out (PersistenceStrategyBuilder?).
|
67
75
|
# so the dependency on Repositories is externalized.
|
68
76
|
def set_repository
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
else
|
73
|
-
repo = Repositories.repositories[repo_sym]
|
74
|
-
repo || raise(RepositoryNotFoundError, "The class #{obj.class} expects a repository called #{repo_sym}, but none was declared")
|
75
|
-
end
|
77
|
+
return RDF::Repository.new if source.class.repository.nil?
|
78
|
+
repo = Repositories.repositories[source.class.repository]
|
79
|
+
repo || raise(RepositoryNotFoundError, "The class #{source.class} expects a repository called #{source.class.repository}, but none was declared")
|
76
80
|
end
|
77
81
|
end
|
78
82
|
end
|
@@ -1,32 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'active_support/core_ext/hash'
|
2
3
|
|
3
4
|
module ActiveTriples
|
4
5
|
##
|
5
|
-
# Implements property configuration
|
6
|
-
#
|
7
|
-
#
|
6
|
+
# Implements property configuration in the style of RDFSource. It does its
|
7
|
+
# work at the class level, and is meant to be extended.
|
8
|
+
#
|
9
|
+
# Collaborates closely with ActiveTriples::Reflection
|
8
10
|
#
|
9
11
|
# Define properties at the class level with:
|
10
12
|
#
|
11
13
|
# property :title, predicate: RDF::DC.title, class_name: ResourceClass
|
12
14
|
#
|
15
|
+
# @see {ActiveTriples::Reflection}
|
16
|
+
# @see {ActiveTriples::PropertyBuilder}
|
13
17
|
module Properties
|
14
18
|
extend ActiveSupport::Concern
|
15
19
|
|
16
20
|
included do
|
21
|
+
include Reflection
|
17
22
|
initialize_generated_modules
|
18
23
|
end
|
19
24
|
|
25
|
+
private
|
26
|
+
|
27
|
+
##
|
28
|
+
# Returns the properties registered and their configurations.
|
29
|
+
#
|
30
|
+
# @return [ActiveSupport::HashWithIndifferentAccess{String => ActiveTriples::NodeConfig}]
|
31
|
+
def properties
|
32
|
+
_active_triples_config
|
33
|
+
end
|
34
|
+
|
35
|
+
##
|
36
|
+
# Lists fields registered as properties on the object.
|
37
|
+
#
|
38
|
+
# @return [Array<Symbol>] the list of registered properties.
|
39
|
+
def fields
|
40
|
+
properties.keys.map(&:to_sym).reject{ |x| x == :type }
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# List of RDF predicates registered as properties on the object.
|
45
|
+
#
|
46
|
+
# @return [Array<RDF::URI>]
|
47
|
+
def registered_predicates
|
48
|
+
properties.values.map { |config| config.predicate }
|
49
|
+
end
|
50
|
+
|
51
|
+
##
|
52
|
+
# List of RDF predicates used in the Resource's triples, but not
|
53
|
+
# mapped to any property or accessor methods.
|
54
|
+
#
|
55
|
+
# @return [Array<RDF::URI>]
|
56
|
+
def unregistered_predicates
|
57
|
+
preds = registered_predicates
|
58
|
+
preds << RDF.type
|
59
|
+
predicates.select { |p| !preds.include? p }
|
60
|
+
end
|
61
|
+
|
62
|
+
public
|
63
|
+
|
64
|
+
##
|
65
|
+
# Class methods for classes with `Properties`
|
20
66
|
module ClassMethods
|
21
67
|
def inherited(child_class) #:nodoc:
|
22
68
|
child_class.initialize_generated_modules
|
23
69
|
super
|
24
70
|
end
|
25
71
|
|
26
|
-
|
72
|
+
##
|
73
|
+
# If the property methods are not yet present, generates them.
|
74
|
+
#
|
75
|
+
# @return [Module] a module self::GeneratedPropertyMethods which is
|
76
|
+
# included in self and defines the property methods
|
77
|
+
#
|
78
|
+
# @note this is an alias to #generated_property_methods. Use it when you
|
79
|
+
# intend to initialize, rather than retrieve, the methods for code
|
80
|
+
# readability
|
81
|
+
# @see #generated_property_methods
|
82
|
+
def initialize_generated_modules
|
27
83
|
generated_property_methods
|
28
84
|
end
|
29
85
|
|
86
|
+
##
|
87
|
+
# Gives existing generated property methods. If the property methods are
|
88
|
+
# not yet present, generates them as a new Module and includes it.
|
89
|
+
#
|
90
|
+
# @return [Module] a module self::GeneratedPropertyMethods which is
|
91
|
+
# included in self and defines the property methods
|
92
|
+
#
|
93
|
+
# @note use the alias #initialize_generated_modules for clarity of intent
|
94
|
+
# where appropriate
|
95
|
+
# @see #initialize_generated_modules
|
30
96
|
def generated_property_methods
|
31
97
|
@generated_property_methods ||= begin
|
32
98
|
mod = const_set(:GeneratedPropertyMethods, Module.new)
|
@@ -37,15 +103,27 @@ module ActiveTriples
|
|
37
103
|
|
38
104
|
##
|
39
105
|
# Registers properties for Resource-like classes
|
106
|
+
#
|
40
107
|
# @param [Symbol] name of the property (and its accessor methods)
|
41
108
|
# @param [Hash] opts for this property, must include a :predicate
|
42
109
|
# @yield [index] index sets solr behaviors for the property
|
110
|
+
#
|
111
|
+
# @return [Hash{String=>ActiveTriples::NodeConfig}] the full current
|
112
|
+
# property configuration for the class
|
43
113
|
def property(name, opts={}, &block)
|
44
114
|
raise ArgumentError, "#{name} is a keyword and not an acceptable property name." if protected_property_name? name
|
45
115
|
reflection = PropertyBuilder.build(self, name, opts, &block)
|
46
116
|
Reflection.add_reflection self, name, reflection
|
47
117
|
end
|
48
118
|
|
119
|
+
##
|
120
|
+
# Checks potential property names for conflicts with existing class
|
121
|
+
# instance methods. We avoid setting properties with these names to
|
122
|
+
# prevent catastrophic method overwriting.
|
123
|
+
#
|
124
|
+
# @param [Symblol] name A potential property name.
|
125
|
+
# @return [Boolean] true if the given name matches an existing instance
|
126
|
+
# method which is not an ActiveTriples property.
|
49
127
|
def protected_property_name?(name)
|
50
128
|
reject = self.instance_methods.map! { |s| s.to_s.gsub(/=$/, '').to_sym }
|
51
129
|
reject -= properties.keys.map { |k| k.to_sym }
|
@@ -56,9 +134,9 @@ module ActiveTriples
|
|
56
134
|
# Given a property name or a predicate, return the configuration
|
57
135
|
# for the matching property.
|
58
136
|
#
|
59
|
-
# @param
|
137
|
+
# @param [#to_sym, RDF::Resource] term property name or predicate
|
60
138
|
#
|
61
|
-
# @return [ActiveTriples::NodeConfig]
|
139
|
+
# @return [ActiveTriples::NodeConfig] the configuration for the property
|
62
140
|
def config_for_term_or_uri(term)
|
63
141
|
return properties[term.to_s] unless
|
64
142
|
term.is_a?(RDF::Resource) && !term.is_a?(RDFSource)
|
@@ -1,13 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module ActiveTriples
|
2
3
|
##
|
3
4
|
# A value object to encapsulate what a Property is. Instantiate with a hash of
|
4
5
|
# options.
|
5
|
-
|
6
|
+
#
|
7
|
+
# @todo Should we enforce the interface on the various attributes that are set?
|
8
|
+
class Property
|
9
|
+
def initialize(options = {})
|
10
|
+
self.name = options.fetch(:name)
|
11
|
+
self.attributes = options.except(:name)
|
12
|
+
end
|
13
|
+
|
14
|
+
# @return Symbol
|
15
|
+
attr_reader :name
|
16
|
+
|
17
|
+
# @return Boolean
|
18
|
+
def cast
|
19
|
+
attributes.fetch(:cast, false)
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return Class
|
23
|
+
def class_name
|
24
|
+
attributes[:class_name]
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return RDF::Vocabulary::Term
|
28
|
+
def predicate
|
29
|
+
attributes[:predicate]
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
attr_writer :name
|
35
|
+
attr_accessor :attributes
|
36
|
+
|
37
|
+
alias_method :to_h, :attributes
|
38
|
+
|
6
39
|
# Returns the property's configuration values. Will not return #name, which is
|
7
40
|
# meant to only be accessible via the accessor.
|
8
41
|
# @return [Hash] Configuration values for this property.
|
9
|
-
|
10
|
-
super.except(:name)
|
11
|
-
end
|
42
|
+
public :to_h
|
12
43
|
end
|
13
44
|
end
|
@@ -1,22 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module ActiveTriples
|
3
|
+
##
|
4
|
+
# A builder for property `NodeConfig`s
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# PropertyBuilder.build(:creator, predicate: RDF::Vocab::DC.creator)
|
8
|
+
#
|
9
|
+
# @see NodeConfig
|
2
10
|
class PropertyBuilder
|
3
11
|
|
12
|
+
# @!attribute [r] name
|
13
|
+
# @return
|
14
|
+
# @!attribute [r] options
|
15
|
+
# @return
|
4
16
|
attr_reader :name, :options
|
5
17
|
|
18
|
+
##
|
19
|
+
# @param name []
|
20
|
+
# @param options []
|
6
21
|
def initialize(name, options, &block)
|
7
22
|
@name = name
|
8
23
|
@options = options
|
9
24
|
end
|
10
25
|
|
26
|
+
##
|
27
|
+
# @param name [Symbol]
|
28
|
+
# @param options [Hash<Symbol>]
|
29
|
+
# @option options [RDF::URI] :predicate
|
30
|
+
# @option options [String, Class] :class_name
|
31
|
+
# @option options [Boolean] :cast
|
32
|
+
#
|
33
|
+
# @yield yields to block configuring index behaviors
|
34
|
+
# @yieldparam index_object [NodeConfig::IndexObject]
|
35
|
+
#
|
36
|
+
# @return [PropertyBuilder]
|
37
|
+
# @raise [ArgumentError] if name is not a symbol and/or :predicate can't be
|
38
|
+
# coerced into a URI
|
39
|
+
#
|
40
|
+
# @see #build
|
11
41
|
def self.create_builder(name, options, &block)
|
12
42
|
raise ArgumentError, "property names must be a Symbol" unless
|
13
43
|
name.kind_of?(Symbol)
|
14
44
|
|
15
|
-
options[:predicate] = RDF::
|
16
|
-
raise ArgumentError, "must provide an RDF::
|
45
|
+
options[:predicate] = RDF::URI.new(options[:predicate])
|
46
|
+
raise ArgumentError, "must provide an RDF::URI to :predicate" unless
|
17
47
|
options[:predicate].valid?
|
18
48
|
|
19
|
-
|
20
49
|
new(name, options, &block)
|
21
50
|
end
|
22
51
|
|
@@ -58,7 +87,12 @@ module ActiveTriples
|
|
58
87
|
end
|
59
88
|
CODE
|
60
89
|
end
|
61
|
-
|
90
|
+
|
91
|
+
##
|
92
|
+
# @yield yields to block configuring index behaviors
|
93
|
+
# @yieldparam index_object [NodeConfig::IndexObject]
|
94
|
+
#
|
95
|
+
# @return [NodeConfig] a new property node config
|
62
96
|
def build(&block)
|
63
97
|
NodeConfig.new(name,
|
64
98
|
options[:predicate],
|
@@ -1,5 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'active_model'
|
2
3
|
require 'active_support/core_ext/hash'
|
4
|
+
require 'active_support/core_ext/array/wrap'
|
3
5
|
|
4
6
|
module ActiveTriples
|
5
7
|
##
|
@@ -34,7 +36,7 @@ module ActiveTriples
|
|
34
36
|
# An `RDFSource` is an {RDF::Term}---it can be used as a subject, predicate,
|
35
37
|
# object, or context in an {RDF::Statement}.
|
36
38
|
#
|
37
|
-
# @todo complete RDF::Value/RDF::Term/RDF::Resource interfaces
|
39
|
+
# @todo complete RDF::Value/RDF::Term/RDF::Resource interfaces
|
38
40
|
#
|
39
41
|
# @see ActiveModel
|
40
42
|
# @see RDF::Resource
|
@@ -45,7 +47,6 @@ module ActiveTriples
|
|
45
47
|
include NestedAttributes
|
46
48
|
include Persistable
|
47
49
|
include Properties
|
48
|
-
include Reflection
|
49
50
|
include RDF::Value
|
50
51
|
include RDF::Queryable
|
51
52
|
include ActiveModel::Validations
|
@@ -74,7 +75,7 @@ module ActiveTriples
|
|
74
75
|
define_model_callbacks :persist
|
75
76
|
end
|
76
77
|
|
77
|
-
delegate :each, :load!, :count, :has_statement?, :to => :graph
|
78
|
+
delegate :query, :each, :load!, :count, :has_statement?, :to => :graph
|
78
79
|
delegate :to_base, :term?, :escape, :to => :to_term
|
79
80
|
|
80
81
|
##
|
@@ -102,7 +103,7 @@ module ActiveTriples
|
|
102
103
|
reload
|
103
104
|
|
104
105
|
# Append type to graph if necessary.
|
105
|
-
Array(self.class.type).each do |type|
|
106
|
+
Array.wrap(self.class.type).each do |type|
|
106
107
|
unless self.get_values(:type).include?(type)
|
107
108
|
self.get_values(:type) << type
|
108
109
|
end
|
@@ -124,49 +125,57 @@ module ActiveTriples
|
|
124
125
|
end
|
125
126
|
|
126
127
|
##
|
127
|
-
#
|
128
|
+
# Gives a hash containing both the registered and unregistered attributes of
|
129
|
+
# the resource. Unregistered attributes are given with full URIs.
|
130
|
+
#
|
131
|
+
# @example
|
132
|
+
# class WithProperties
|
133
|
+
# include ActiveTriples::RDFSource
|
134
|
+
# property :title, predicate: RDF::Vocab::DC.title
|
135
|
+
# property :creator, predicate: RDF::Vocab::DC.creator,
|
136
|
+
# class_name: 'Agent'
|
137
|
+
# end
|
128
138
|
#
|
129
|
-
#
|
130
|
-
#
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
#
|
137
|
-
#
|
138
|
-
def parent=(parent)
|
139
|
-
persistence_strategy.respond_to?(:parent=) ? (persistence_strategy.parent = parent) : nil
|
140
|
-
end
|
139
|
+
# class Agent; include ActiveTriples::RDFSource; end
|
140
|
+
#
|
141
|
+
# resource = WithProperties.new
|
142
|
+
#
|
143
|
+
# resource.attributes
|
144
|
+
# # => {"id"=>"g47123700054720", "title"=>[], "creator"=>[]}
|
145
|
+
#
|
146
|
+
# resource.creator.build
|
147
|
+
# resource.title << ['Comet in Moominland', 'Christmas in Moominvalley']
|
141
148
|
|
149
|
+
# resource.attributes
|
150
|
+
# # => {"id"=>"g47123700054720",
|
151
|
+
# # "title"=>["Comet in Moominland", "Christmas in Moominvalley"],
|
152
|
+
# # "creator"=>[#<Agent:0x2adbd76f1a5c(#<Agent:0x0055b7aede34b8>)>]}
|
153
|
+
#
|
154
|
+
# resource << [resource, RDF::Vocab::DC.relation, 'Helsinki']
|
155
|
+
# # => {"id"=>"g47123700054720",
|
156
|
+
# # "title"=>["Comet in Moominland", "Christmas in Moominvalley"],
|
157
|
+
# # "creator"=>[#<Agent:0x2adbd76f1a5c(#<Agent:0x0055b7aede34b8>)>],
|
158
|
+
# # "http://purl.org/dc/terms/relation"=>["Helsinki"]}]}
|
159
|
+
#
|
160
|
+
# @return [Hash<String, Array<Object>>]
|
161
|
+
#
|
162
|
+
# @todo: should this, `#attributes=`, and `#serializable_hash` be moved out
|
163
|
+
# into a dedicated `Serializer` object?
|
142
164
|
def attributes
|
143
165
|
attrs = {}
|
144
|
-
attrs['id'] = id
|
166
|
+
attrs['id'] = id
|
145
167
|
fields.map { |f| attrs[f.to_s] = get_values(f) }
|
146
168
|
unregistered_predicates.map { |uri| attrs[uri.to_s] = get_values(uri) }
|
147
169
|
attrs
|
148
170
|
end
|
149
171
|
|
150
|
-
def serializable_hash(options = nil)
|
151
|
-
attrs = (fields.map { |f| f.to_s }) << 'id'
|
152
|
-
hash = super(:only => attrs)
|
153
|
-
unregistered_predicates.map { |uri| hash[uri.to_s] = get_values(uri) }
|
154
|
-
hash
|
155
|
-
end
|
156
|
-
|
157
|
-
##
|
158
|
-
# @return [Class] gives `self#class`
|
159
|
-
def reflections
|
160
|
-
self.class
|
161
|
-
end
|
162
|
-
|
163
172
|
def attributes=(values)
|
164
173
|
raise ArgumentError, "values must be a Hash, you provided #{values.class}" unless values.kind_of? Hash
|
165
174
|
values = values.with_indifferent_access
|
166
175
|
id = values.delete(:id)
|
167
|
-
set_subject!(id) if
|
176
|
+
set_subject!(id) if node?
|
168
177
|
values.each do |key, value|
|
169
|
-
if reflections.
|
178
|
+
if reflections.has_property?(key)
|
170
179
|
set_value(rdf_subject, key, value)
|
171
180
|
elsif nested_attributes_options.keys.map { |k| "#{k}_attributes" }.include?(key)
|
172
181
|
send("#{key}=".to_sym, value)
|
@@ -176,6 +185,13 @@ module ActiveTriples
|
|
176
185
|
end
|
177
186
|
end
|
178
187
|
|
188
|
+
def serializable_hash(options = nil)
|
189
|
+
attrs = (fields.map { |f| f.to_s }) << 'id'
|
190
|
+
hash = super(:only => attrs)
|
191
|
+
unregistered_predicates.map { |uri| hash[uri.to_s] = get_values(uri) }
|
192
|
+
hash
|
193
|
+
end
|
194
|
+
|
179
195
|
##
|
180
196
|
# Returns a serialized string representation of self.
|
181
197
|
# Extends the base implementation builds a JSON-LD context if the
|
@@ -195,7 +211,23 @@ module ActiveTriples
|
|
195
211
|
end
|
196
212
|
|
197
213
|
##
|
198
|
-
#
|
214
|
+
# Delegate parent to the persistence strategy if possible
|
215
|
+
#
|
216
|
+
# @todo establish a better pattern for this. `#parent` has been a public
|
217
|
+
# method in the past, but it's probably time to deprecate it.
|
218
|
+
def parent
|
219
|
+
persistence_strategy.respond_to?(:parent) ? persistence_strategy.parent : nil
|
220
|
+
end
|
221
|
+
|
222
|
+
##
|
223
|
+
# @todo deprecate/remove
|
224
|
+
# @see #parent
|
225
|
+
def parent=(parent)
|
226
|
+
persistence_strategy.respond_to?(:parent=) ? (persistence_strategy.parent = parent) : nil
|
227
|
+
end
|
228
|
+
|
229
|
+
##
|
230
|
+
# Gives the representation of this RDFSource as an RDF::Term
|
199
231
|
#
|
200
232
|
# @return [RDF::URI, RDF::Node] the URI that identifies this `RDFSource`;
|
201
233
|
# or a bnode identifier
|
@@ -206,6 +238,21 @@ module ActiveTriples
|
|
206
238
|
end
|
207
239
|
alias_method :to_term, :rdf_subject
|
208
240
|
|
241
|
+
##
|
242
|
+
# Returns `nil` as the `graph_name`. This behavior mimics an `RDF::Graph`
|
243
|
+
# with no graph name, or one without named graph support.
|
244
|
+
#
|
245
|
+
# @note: it's possible to think of an `RDFSource` as "supporting named
|
246
|
+
# graphs" in the sense that the `#rdf_subject` is an implied graph name.
|
247
|
+
# For RDF.rb's purposes, however, it has a nil graph name: when
|
248
|
+
# enumerating statements, we treat them as triples.
|
249
|
+
#
|
250
|
+
# @return [nil]
|
251
|
+
# @sse RDF::Graph.graph_name
|
252
|
+
def graph_name
|
253
|
+
nil
|
254
|
+
end
|
255
|
+
|
209
256
|
##
|
210
257
|
# @return [String] A string identifier for the resource; '' if the
|
211
258
|
# resource is a node
|
@@ -216,7 +263,7 @@ module ActiveTriples
|
|
216
263
|
##
|
217
264
|
# @return [RDF::URI] the uri
|
218
265
|
def to_uri
|
219
|
-
|
266
|
+
rdf_subject if uri?
|
220
267
|
end
|
221
268
|
|
222
269
|
##
|
@@ -251,7 +298,7 @@ module ActiveTriples
|
|
251
298
|
end
|
252
299
|
|
253
300
|
def type
|
254
|
-
self.get_values(:type)
|
301
|
+
self.get_values(:type)
|
255
302
|
end
|
256
303
|
|
257
304
|
def type=(type)
|
@@ -262,8 +309,10 @@ module ActiveTriples
|
|
262
309
|
##
|
263
310
|
# Looks for labels in various default fields, prioritizing
|
264
311
|
# configured label fields.
|
312
|
+
#
|
313
|
+
# @see #default_labels
|
265
314
|
def rdf_label
|
266
|
-
labels = Array(self.class.rdf_label)
|
315
|
+
labels = Array.wrap(self.class.rdf_label)
|
267
316
|
labels += default_labels
|
268
317
|
labels.each do |label|
|
269
318
|
values = get_values(label)
|
@@ -272,34 +321,108 @@ module ActiveTriples
|
|
272
321
|
node? ? [] : [rdf_subject.to_s]
|
273
322
|
end
|
274
323
|
|
324
|
+
##
|
325
|
+
# @return [Array<RDF::URI>] a group of properties to use for default labels.
|
326
|
+
def default_labels
|
327
|
+
[RDF::SKOS.prefLabel,
|
328
|
+
RDF::DC.title,
|
329
|
+
RDF::RDFS.label,
|
330
|
+
RDF::SKOS.altLabel,
|
331
|
+
RDF::SKOS.hiddenLabel]
|
332
|
+
end
|
333
|
+
|
275
334
|
##
|
276
335
|
# Load data from the #rdf_subject URI. Retrieved data will be
|
277
336
|
# parsed into the Resource's graph from available RDF::Readers
|
278
337
|
# and available from property accessors if if predicates are
|
279
338
|
# registered.
|
280
339
|
#
|
340
|
+
# @example
|
281
341
|
# osu = new('http://dbpedia.org/resource/Oregon_State_University')
|
282
342
|
# osu.fetch
|
283
343
|
# osu.rdf_label.first
|
284
344
|
# # => "Oregon State University"
|
285
345
|
#
|
286
|
-
# @
|
287
|
-
|
288
|
-
|
346
|
+
# @example with default action block
|
347
|
+
# my_source = new('http://example.org/dead_url')
|
348
|
+
# my_source.fetch { |obj| obj.status = 'dead link' }
|
349
|
+
#
|
350
|
+
# @yield gives self to block if this is a node, or an error is raised during
|
351
|
+
# load
|
352
|
+
# @yieldparam [ActiveTriples::RDFSource] resource self
|
353
|
+
#
|
354
|
+
# @return [ActiveTriples::RDFSource] self
|
355
|
+
def fetch(*args, &block)
|
356
|
+
begin
|
357
|
+
load(rdf_subject, *args)
|
358
|
+
rescue => e
|
359
|
+
if block_given?
|
360
|
+
yield(self)
|
361
|
+
else
|
362
|
+
raise "#{self} is a blank node; Cannot fetch a resource without a URI" if
|
363
|
+
node?
|
364
|
+
raise e
|
365
|
+
end
|
366
|
+
end
|
289
367
|
self
|
290
368
|
end
|
291
369
|
|
292
370
|
##
|
293
|
-
# Adds or updates a property
|
371
|
+
# Adds or updates a property by creating triples for each of the supplied
|
372
|
+
# values.
|
294
373
|
#
|
295
|
-
#
|
296
|
-
#
|
374
|
+
# The `property` argument may be either a symbol representing a registered
|
375
|
+
# property name, or an RDF::Term to use as the predicate.
|
297
376
|
#
|
298
|
-
#
|
299
|
-
#
|
300
|
-
#
|
377
|
+
# @example setting with a property name
|
378
|
+
# class Thing
|
379
|
+
# include ActiveTriples::RDFSource
|
380
|
+
# property :creator, predicate: RDF::DC.creator
|
381
|
+
# end
|
301
382
|
#
|
302
|
-
#
|
383
|
+
# t = Thing.new
|
384
|
+
# t.set_value(:creator, 'Tove Jansson') # => ['Tove Jansson']
|
385
|
+
#
|
386
|
+
#
|
387
|
+
# @example setting with a predicate
|
388
|
+
# t = Thing.new
|
389
|
+
# t.set_value(RDF::DC.creator, 'Tove Jansson') # => ['Tove Jansson']
|
390
|
+
#
|
391
|
+
#
|
392
|
+
# The recommended pattern, which sets properties directly on this
|
393
|
+
# RDFSource, is: `set_value(property, values)`
|
394
|
+
#
|
395
|
+
# @overload set_value(property, values)
|
396
|
+
# Updates the values for the property, using this RDFSource as the subject
|
397
|
+
#
|
398
|
+
# @param [RDF::Term, #to_sym] property a symbol with the property name
|
399
|
+
# or an RDF::Term to use as a predicate.
|
400
|
+
# @param [Array<RDF::Resource>, RDF::Resource] values an array of values
|
401
|
+
# or a single value. If not an {RDF::Resource}, the values will be
|
402
|
+
# coerced to an {RDF::Literal} or {RDF::Node} by {RDF::Statement}
|
403
|
+
#
|
404
|
+
# @overload set_value(subject, property, values)
|
405
|
+
# Updates the values for the property, using the given term as the subject
|
406
|
+
#
|
407
|
+
# @param [RDF::Term] subject the term representing the
|
408
|
+
# @param [RDF::Term, #to_sym] property a symbol with the property name
|
409
|
+
# or an RDF::Term to use as a predicate.
|
410
|
+
# @param [Array<RDF::Resource>, RDF::Resource] values an array of values
|
411
|
+
# or a single value. If not an {RDF::Resource}, the values will be
|
412
|
+
# coerced to an {RDF::Literal} or {RDF::Node} by {RDF::Statement}
|
413
|
+
#
|
414
|
+
# @return [ActiveTriples::Relation] an array {Relation} containing the
|
415
|
+
# values of the property
|
416
|
+
#
|
417
|
+
# @raise [ActiveTriples::Relation::ValueError] when the given value can't be
|
418
|
+
# coerced into an acceptable `RDF::Term`.
|
419
|
+
#
|
420
|
+
# @note This method will delete existing statements with the given
|
421
|
+
# subject and predicate from the graph
|
422
|
+
#
|
423
|
+
# @see http://www.rubydoc.info/github/ruby-rdf/rdf/RDF/Statement For
|
424
|
+
# documentation on {RDF::Statement} and the handling of
|
425
|
+
# non-{RDF::Resource} values.
|
303
426
|
def set_value(*args)
|
304
427
|
# Add support for legacy 3-parameter syntax
|
305
428
|
if args.length > 3 || args.length < 2
|
@@ -309,38 +432,61 @@ module ActiveTriples
|
|
309
432
|
get_relation(args).set(values)
|
310
433
|
end
|
311
434
|
|
312
|
-
##
|
313
|
-
# Adds or updates a property with supplied values.
|
314
|
-
#
|
315
|
-
# @note This method will delete existing statements with the correct subject and predicate from the graph
|
316
|
-
def []=(uri_or_term_property, value)
|
317
|
-
self[uri_or_term_property].set(value)
|
318
|
-
end
|
319
|
-
|
320
435
|
##
|
321
436
|
# Returns an array of values belonging to the property
|
322
437
|
# requested. Elements in the array may RdfResource objects or a
|
323
438
|
# valid datatype.
|
324
439
|
#
|
325
|
-
# Handles two argument patterns. The recommended pattern
|
440
|
+
# Handles two argument patterns. The recommended pattern, which accesses
|
441
|
+
# properties directly on this RDFSource, is:
|
326
442
|
# get_values(property)
|
327
443
|
#
|
328
|
-
#
|
329
|
-
#
|
330
|
-
#
|
444
|
+
# @overload get_values(property)
|
445
|
+
# Gets values on the RDFSource for the given property
|
446
|
+
# @param [String, #to_term] property the property for the values
|
447
|
+
#
|
448
|
+
# @overload get_values(uri, property)
|
449
|
+
# For backwards compatibility, explicitly passing the term used as the
|
450
|
+
# subject {ActiveTriples::Relation#rdf_subject} of the returned relation.
|
451
|
+
# @param [RDF::Term] uri the term to use as the subject
|
452
|
+
# @param [String, #to_term] property the property for the values
|
453
|
+
#
|
454
|
+
# @return [ActiveTriples::Relation] an array {Relation} containing the
|
455
|
+
# values of the property
|
456
|
+
#
|
457
|
+
# @todo should this raise an error when the property argument is not an
|
458
|
+
# {RDF::Term} or a registered property key?
|
331
459
|
def get_values(*args)
|
332
460
|
get_relation(args)
|
333
461
|
end
|
334
462
|
|
335
463
|
##
|
336
|
-
# Returns an array of values belonging to the property
|
337
|
-
#
|
338
|
-
#
|
339
|
-
|
340
|
-
|
464
|
+
# Returns an array of values belonging to the property requested. Elements
|
465
|
+
# in the array may RdfResource objects or a valid datatype.
|
466
|
+
#
|
467
|
+
# @param [RDF::Term, :to_s] term_or_property
|
468
|
+
def [](term_or_property)
|
469
|
+
get_relation([term_or_property])
|
470
|
+
end
|
471
|
+
|
472
|
+
##
|
473
|
+
# Adds or updates a property with supplied values.
|
474
|
+
#
|
475
|
+
# @param [RDF::Term, :to_s] term_or_property
|
476
|
+
# @param [Array<RDF::Resource>, RDF::Resource] values an array of values
|
477
|
+
# or a single value to set the property to.
|
478
|
+
#
|
479
|
+
# @note This method will delete existing statements with the correct
|
480
|
+
# subject and predicate from the graph
|
481
|
+
def []=(term_or_property, value)
|
482
|
+
self[term_or_property].set(value)
|
341
483
|
end
|
342
484
|
|
485
|
+
##
|
486
|
+
# @see #get_values
|
487
|
+
# @todo deprecate and remove? this is an alias to `#get_values`
|
343
488
|
def get_relation(args)
|
489
|
+
reload if (persistence_strategy.respond_to? :loaded?) && !persistence_strategy.loaded?
|
344
490
|
@relation_cache ||= {}
|
345
491
|
rel = Relation.new(self, args)
|
346
492
|
@relation_cache["#{rel.send(:rdf_subject)}/#{rel.property}/#{rel.rel_args}"] ||= rel
|
@@ -378,12 +524,6 @@ module ActiveTriples
|
|
378
524
|
end
|
379
525
|
end
|
380
526
|
|
381
|
-
def destroy_child(child)
|
382
|
-
statements.each do |statement|
|
383
|
-
delete_statement(statement) if statement.subject == child.rdf_subject || statement.object == child.rdf_subject
|
384
|
-
end
|
385
|
-
end
|
386
|
-
|
387
527
|
##
|
388
528
|
# Indicates if the record is 'new' (has not yet been persisted).
|
389
529
|
#
|
@@ -402,11 +542,21 @@ module ActiveTriples
|
|
402
542
|
|
403
543
|
private
|
404
544
|
|
545
|
+
##
|
546
|
+
# This gives the {RDF::Graph} which represents the current state of this
|
547
|
+
# resource.
|
548
|
+
#
|
549
|
+
# @return [RDF::Graph] the underlying graph representation of the
|
550
|
+
# `RDFSource`.
|
551
|
+
#
|
552
|
+
# @see http://www.w3.org/TR/2014/REC-rdf11-concepts-20140225/#change-over-time
|
553
|
+
# RDF Concepts and Abstract Syntax comment on "RDF source"
|
405
554
|
def graph
|
406
555
|
@graph
|
407
556
|
end
|
408
557
|
|
409
558
|
##
|
559
|
+
|
410
560
|
# Lists fields registered as properties on the object.
|
411
561
|
#
|
412
562
|
# @return [Array<Symbol>] the list of registered properties.
|
@@ -442,11 +592,11 @@ module ActiveTriples
|
|
442
592
|
end
|
443
593
|
|
444
594
|
def default_labels
|
445
|
-
[RDF::SKOS.prefLabel,
|
446
|
-
RDF::DC.title,
|
595
|
+
[RDF::Vocab::SKOS.prefLabel,
|
596
|
+
RDF::Vocab::DC.title,
|
447
597
|
RDF::RDFS.label,
|
448
|
-
RDF::SKOS.altLabel,
|
449
|
-
RDF::SKOS.hiddenLabel]
|
598
|
+
RDF::Vocab::SKOS.altLabel,
|
599
|
+
RDF::Vocab::SKOS.hiddenLabel]
|
450
600
|
end
|
451
601
|
|
452
602
|
##
|
@@ -494,7 +644,7 @@ module ActiveTriples
|
|
494
644
|
|
495
645
|
##
|
496
646
|
# Apply a predicate mapping using a given strategy.
|
497
|
-
#
|
647
|
+
#
|
498
648
|
# @param [ActiveTriples::Schema, #properties] schema A schema to apply.
|
499
649
|
# @param [#apply!] strategy A strategy for applying. Defaults
|
500
650
|
# to ActiveTriples::ExtensionStrategy
|