active-triples 0.8.1 → 0.8.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|