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,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'active_support/core_ext/class'
|
2
3
|
|
3
4
|
module ActiveTriples
|
@@ -9,22 +10,60 @@ module ActiveTriples
|
|
9
10
|
self._active_triples_config = {}
|
10
11
|
end
|
11
12
|
|
13
|
+
##
|
14
|
+
# Gives access to a `Reflection` of the properties configured on this class
|
15
|
+
#
|
16
|
+
# @example
|
17
|
+
# my_source.reflections.has_property?(:title)
|
18
|
+
# my_source.reflections.reflect_on_property(:title)
|
19
|
+
# @return [Class] gives `self#class`
|
20
|
+
#
|
21
|
+
def reflections
|
22
|
+
self.class
|
23
|
+
end
|
24
|
+
|
12
25
|
def self.add_reflection(model, name, reflection)
|
13
|
-
model._active_triples_config =
|
26
|
+
model._active_triples_config =
|
27
|
+
model._active_triples_config.merge(name.to_s => reflection)
|
14
28
|
end
|
15
29
|
|
16
30
|
module ClassMethods
|
17
|
-
|
18
|
-
|
31
|
+
##
|
32
|
+
# @param [#to_s] property
|
33
|
+
#
|
34
|
+
# @return [ActiveTriples::NodeConfig] the configuration for the property
|
35
|
+
#
|
36
|
+
# @raise [ActiveTriples::UndefinedPropertyError] when the property does
|
37
|
+
# not exist
|
38
|
+
def reflect_on_property(property)
|
39
|
+
_active_triples_config.fetch(property.to_s) do
|
40
|
+
raise ActiveTriples::UndefinedPropertyError.new(property.to_s, self)
|
41
|
+
end
|
19
42
|
end
|
20
43
|
|
44
|
+
##
|
45
|
+
# @return [Hash{String=>ActiveTriples::NodeConfig}] a hash of property
|
46
|
+
# names and their configurations
|
21
47
|
def properties
|
22
48
|
_active_triples_config
|
23
49
|
end
|
24
50
|
|
51
|
+
##
|
52
|
+
# @param [Hash{String=>ActiveTriples::NodeConfig}] a complete config hash
|
53
|
+
# to set the properties to.
|
54
|
+
# @return [Hash{String=>ActiveTriples::NodeConfig}] a hash of property
|
55
|
+
# names and their configurations
|
25
56
|
def properties=(val)
|
26
57
|
self._active_triples_config = val
|
27
58
|
end
|
59
|
+
|
60
|
+
##
|
61
|
+
# @param [#to_s] property
|
62
|
+
#
|
63
|
+
# @return [Boolean] true if the property exsits; false otherwise
|
64
|
+
def has_property?(property)
|
65
|
+
_active_triples_config.keys.include? property.to_s
|
66
|
+
end
|
28
67
|
end
|
29
68
|
end
|
30
69
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'active_support/core_ext/module/delegation'
|
2
3
|
|
3
4
|
module ActiveTriples
|
@@ -8,63 +9,179 @@ module ActiveTriples
|
|
8
9
|
#
|
9
10
|
# <{#parent}> <{#predicate}> [term] .
|
10
11
|
#
|
11
|
-
# Relations
|
12
|
-
# a term.
|
12
|
+
# Relations express a set of binary relationships (on a predicate) between
|
13
|
+
# the parent node and a term.
|
14
|
+
#
|
15
|
+
# When the term is a URI or Blank Node, it is represented in the results
|
16
|
+
# {Array} as an {RDFSource} with a graph selected as a subgraph of the
|
17
|
+
# parent's. The triples in this subgraph are: (a) those whose subject is the
|
18
|
+
# term; (b) ...
|
19
|
+
#
|
13
20
|
#
|
14
21
|
# @see RDF::Term
|
15
22
|
class Relation
|
16
|
-
|
17
|
-
|
23
|
+
include Enumerable
|
24
|
+
|
25
|
+
# @!attribute [rw] parent
|
26
|
+
# @return [RDFSource] the resource that is the domain of this relation
|
27
|
+
# @!attribute [rw] value_arguments
|
28
|
+
# @return [Array<Object>]
|
29
|
+
# @!attribute [rw] rel_args
|
30
|
+
# @return [Hash]
|
31
|
+
# @!attribute [r] reflections
|
32
|
+
# @return [Class]
|
33
|
+
attr_accessor :parent, :value_arguments, :rel_args
|
18
34
|
attr_reader :reflections
|
19
35
|
|
20
|
-
delegate
|
36
|
+
delegate :<=>, :==, :===, :[], :each, :empty?, :equal, :inspect, :last,
|
37
|
+
:to_a, :to_ary, :size, :join, :to => :result
|
21
38
|
|
39
|
+
##
|
40
|
+
# @param [ActiveTriples::RDFSource] parent_source
|
41
|
+
# @param [Array<Symbol, Hash>] value_arguments if a Hash is passed as the
|
42
|
+
# final element, it is removed and set to `@rel_args`.
|
22
43
|
def initialize(parent_source, value_arguments)
|
23
44
|
self.parent = parent_source
|
24
45
|
@reflections = parent_source.reflections
|
25
46
|
self.rel_args ||= {}
|
47
|
+
self.rel_args = value_arguments.pop if value_arguments.is_a?(Array) &&
|
48
|
+
value_arguments.last.is_a?(Hash)
|
26
49
|
self.value_arguments = value_arguments
|
27
50
|
end
|
28
51
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
@value_arguments = value_args
|
34
|
-
end
|
35
|
-
|
52
|
+
##
|
53
|
+
# Empties the `Relation`, deleting any associated triples from `parent`.
|
54
|
+
#
|
55
|
+
# @return [Relation] self; a now empty relation
|
36
56
|
def clear
|
37
|
-
|
57
|
+
parent.delete([rdf_subject, predicate, nil])
|
58
|
+
|
59
|
+
self
|
38
60
|
end
|
39
61
|
|
62
|
+
##
|
63
|
+
# Gives an {Array} containing the result set for the {Relation}.
|
64
|
+
#
|
65
|
+
# By default, {RDF::URI} and {RDF::Node} results are cast to `RDFSource`.
|
66
|
+
# {Literal} results are given as their `#object` representations (e.g.
|
67
|
+
# {String}, {Date}.
|
68
|
+
#
|
69
|
+
# @example results with default casting
|
70
|
+
# parent << [parent.rdf_subject, predicate, 'my value']
|
71
|
+
# parent << [parent.rdf_subject, predicate, Date.today]
|
72
|
+
# parent << [parent.rdf_subject, predicate, RDF::URI('http://ex.org/#me')]
|
73
|
+
# parent << [parent.rdf_subject, predicate, RDF::Node.new]
|
74
|
+
# relation.result
|
75
|
+
# # => ["my_value",
|
76
|
+
# # Fri, 25 Sep 2015,
|
77
|
+
# # #<ActiveTriples::Resource:0x3f8...>,
|
78
|
+
# # #<ActiveTriples::Resource:0x3f8...>]
|
79
|
+
#
|
80
|
+
# When `cast?` is `false`, {RDF::Resource} values are left in their raw
|
81
|
+
# form. Similarly, when `#return_literals?` is `true`, literals are
|
82
|
+
# returned in their {RDF::Literal} form, preserving language tags,
|
83
|
+
# datatype, and value.
|
84
|
+
#
|
85
|
+
# @example results with `cast?` set to `false`
|
86
|
+
# relation.result
|
87
|
+
# # => ["my_value",
|
88
|
+
# # Fri, 25 Sep 2015,
|
89
|
+
# # #<RDF::URI:0x3f8... URI:http://ex.org/#me>,
|
90
|
+
# # #<RDF::Node:0x3f8...(_:g69843536054680)>]
|
91
|
+
#
|
92
|
+
# @example results with `return_literals?` set to `true`
|
93
|
+
# relation.result
|
94
|
+
# # => [#<RDF::Literal:0x3f8...("my_value")>,
|
95
|
+
# # #<RDF::Literal::Date:0x3f8...("2015-09-25"^^<http://www.w3.org/2001/XMLSchema#date>)>,
|
96
|
+
# # #<ActiveTriples::Resource:0x3f8...>,
|
97
|
+
# # #<ActiveTriples::Resource:0x3f8...>]
|
98
|
+
#
|
99
|
+
# @return [Array<Object>] the result set
|
40
100
|
def result
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
101
|
+
return [] if predicate.nil?
|
102
|
+
statements = parent.query(:subject => rdf_subject,
|
103
|
+
:predicate => predicate)
|
104
|
+
statements.each_with_object([]) do |x, collector|
|
105
|
+
converted_object = convert_object(x.object)
|
106
|
+
collector << converted_object unless converted_object.nil?
|
45
107
|
end
|
46
108
|
end
|
47
109
|
|
110
|
+
##
|
111
|
+
# Adds values to the relation
|
112
|
+
#
|
113
|
+
# @param [Array<RDF::Resource>, RDF::Resource] values an array of values
|
114
|
+
# or a single value. If not an {RDF::Resource}, the values will be
|
115
|
+
# coerced to an {RDF::Literal} or {RDF::Node} by {RDF::Statement}
|
116
|
+
#
|
117
|
+
# @return [Relation] a relation containing the set values; i.e. `self`
|
118
|
+
#
|
119
|
+
# @raise [ActiveTriples::UndefinedPropertyError] if the property is not
|
120
|
+
# already an {RDF::Term} and is not defined in `#property_config`
|
121
|
+
#
|
122
|
+
# @see http://www.rubydoc.info/github/ruby-rdf/rdf/RDF/Statement For
|
123
|
+
# documentation on {RDF::Statement} and the handling of
|
124
|
+
# non-{RDF::Resource} values.
|
48
125
|
def set(values)
|
126
|
+
raise UndefinedPropertyError.new(property, reflections) if predicate.nil?
|
127
|
+
values = values.to_a if values.is_a? Relation
|
49
128
|
values = [values].compact unless values.kind_of?(Array)
|
50
|
-
values = values.to_a if values.class == Relation
|
51
|
-
empty_property
|
52
|
-
values.each do |val|
|
53
|
-
set_value(val)
|
54
|
-
end
|
55
|
-
parent.persist! if parent.persistence_strategy.is_a? ParentStrategy
|
56
|
-
end
|
57
129
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
end
|
130
|
+
clear
|
131
|
+
values.each { |val| set_value(val) }
|
132
|
+
|
133
|
+
parent.persist! if parent.persistence_strategy.is_a? ParentStrategy
|
134
|
+
self
|
64
135
|
end
|
65
136
|
|
137
|
+
##
|
138
|
+
# Builds a node with the given attributes, adding it to the relation.
|
139
|
+
#
|
140
|
+
# @param attributes [Hash] a hash of attribute names and values for the
|
141
|
+
# built node.
|
142
|
+
#
|
143
|
+
# @example building an empty generic node
|
144
|
+
# resource = ActiveTriples::Resource.new
|
145
|
+
# resource.resource.get_values(RDF::Vocab::DC.relation).build
|
146
|
+
# # => #<ActiveTriples::Resource:0x2b0(#<ActiveTriples::Resource:0x005>)>)
|
147
|
+
#
|
148
|
+
# resource.dump :ttl
|
149
|
+
# # => "\n [ <http://purl.org/dc/terms/relation> []] .\n"
|
150
|
+
#
|
151
|
+
# Nodes are built using the configured `class_name` for the relation.
|
152
|
+
# Attributes passed in the Hash argument are set on the new node through
|
153
|
+
# `RDFSource#attributes=`. If the attribute keys are not valid properties
|
154
|
+
# on the built node, we raise an error.
|
155
|
+
#
|
156
|
+
# @example building a node with attributes
|
157
|
+
# class WithRelation
|
158
|
+
# include ActiveTriples::RDFSource
|
159
|
+
# property :relation, predicate: RDF::Vocab::DC.relation,
|
160
|
+
# class_name: 'WithTitle'
|
161
|
+
# end
|
162
|
+
#
|
163
|
+
# class WithTitle
|
164
|
+
# include ActiveTriples::RDFSource
|
165
|
+
# property :title, predicate: RDF::Vocab::DC.title
|
166
|
+
# end
|
167
|
+
#
|
168
|
+
# resource = WithRelation.new
|
169
|
+
# attributes = { id: 'http://ex.org/moomin', title: 'moomin' }
|
170
|
+
#
|
171
|
+
# resource.get_values(:relation).build(attributes)
|
172
|
+
# # => #<ActiveTriples::Resource:0x2b0(#<ActiveTriples::Resource:0x005>)>)
|
173
|
+
#
|
174
|
+
# resource.dump :ttl
|
175
|
+
# # => "\n<http://ex.org/moomin> <http://purl.org/dc/terms/title> \"moomin\" .\n\n [ <http://purl.org/dc/terms/relation> <http://ex.org/moomin>] .\n"
|
176
|
+
#
|
177
|
+
# @todo: clarify class behavior; it is actually tied to type, in some cases.
|
178
|
+
#
|
179
|
+
# @see RDFSource#attributes=
|
180
|
+
# @see http://guides.rubyonrails.org/active_model_basics.html for some
|
181
|
+
# context on ActiveModel attributes.
|
66
182
|
def build(attributes={})
|
67
183
|
new_subject = attributes.fetch('id') { RDF::Node.new }
|
184
|
+
|
68
185
|
make_node(new_subject).tap do |node|
|
69
186
|
node.attributes = attributes.except('id')
|
70
187
|
if parent.kind_of? List::ListResource
|
@@ -77,84 +194,198 @@ module ActiveTriples
|
|
77
194
|
end
|
78
195
|
end
|
79
196
|
|
80
|
-
|
81
|
-
|
197
|
+
##
|
198
|
+
# @note this method behaves somewhat differently from `Array#delete`.
|
199
|
+
# It succeeds on deletion of non-existing values, always returning
|
200
|
+
# `self` unless an error is raised. There is no option to pass a block
|
201
|
+
# to evaluate if the value is not present. This is because access for
|
202
|
+
# `value` depends on query time. i.e. the `Relation` set does not have an
|
203
|
+
# underlying efficient data structure allowing a reliably cheap existence
|
204
|
+
# check.
|
205
|
+
#
|
206
|
+
# @note symbols are treated as RDF::Nodes by default in
|
207
|
+
# `RDF::Mutable#delete`, but may also represent tokens in statements.
|
208
|
+
# This casts symbols to a literals, which gets us symmetric behavior
|
209
|
+
# between `#set(:sym)` and `#delete(:sym)`.
|
210
|
+
#
|
211
|
+
# @example deleting a value
|
212
|
+
# resource = MySource.new
|
213
|
+
# resource.title = ['moomin', 'valley']
|
214
|
+
# resource.title.delete('moomin') # => ["valley"]
|
215
|
+
# resource.title # => ['valley']
|
216
|
+
#
|
217
|
+
# @example note the behavior of unmatched values
|
218
|
+
# resource = MySource.new
|
219
|
+
# resource.title = 'moomin'
|
220
|
+
# resource.title.delete('valley') # => ["moomin"]
|
221
|
+
# resource.title # => ['moomin']
|
222
|
+
#
|
223
|
+
# @param value [Object] the value to delete from the relation
|
224
|
+
# @return [ActiveTriples::Relation] self
|
225
|
+
def delete(value)
|
226
|
+
value = RDF::Literal(value) if value.is_a? Symbol
|
227
|
+
parent.delete([rdf_subject, predicate, value])
|
228
|
+
|
229
|
+
self
|
82
230
|
end
|
83
231
|
|
84
|
-
|
85
|
-
|
86
|
-
|
232
|
+
##
|
233
|
+
# A variation on `#delete`. This queries the relation for matching
|
234
|
+
# values before running the deletion, returning `nil` if it does not exist.
|
235
|
+
#
|
236
|
+
# @param value [Object] the value to delete from the relation
|
237
|
+
#
|
238
|
+
# @return [Object, nil] `nil` if the value doesn't exist; the value
|
239
|
+
# otherwise
|
240
|
+
# @see #delete
|
241
|
+
def delete?(value)
|
242
|
+
value = RDF::Literal(value) if value.is_a? Symbol
|
243
|
+
|
244
|
+
return nil if parent.query([rdf_subject, predicate, value]).empty?
|
245
|
+
|
246
|
+
delete(value)
|
247
|
+
value
|
248
|
+
end
|
249
|
+
|
250
|
+
##
|
251
|
+
# @overload subtract(enum)
|
252
|
+
# Deletes objects in the enumerable from the relation
|
253
|
+
# @param values [Enumerable] an enumerable of objects to delete
|
254
|
+
# @overload subtract(*values)
|
255
|
+
# Deletes each argument value from the relation
|
256
|
+
# @param *values [Array<Object>] the objects to delete
|
257
|
+
#
|
258
|
+
# @return [Relation] self
|
259
|
+
#
|
260
|
+
# @note This casts symbols to a literals, which gets us symmetric behavior
|
261
|
+
# with `#set(:sym)`.
|
262
|
+
# @see #delete
|
263
|
+
def subtract(*values)
|
264
|
+
values = values.first if values.first.is_a? Enumerable
|
265
|
+
statements = values.map do |value|
|
266
|
+
value = RDF::Literal(value) if value.is_a? Symbol
|
267
|
+
[rdf_subject, predicate, value]
|
87
268
|
end
|
269
|
+
|
270
|
+
parent.delete(*statements)
|
271
|
+
self
|
88
272
|
end
|
89
273
|
|
90
|
-
|
91
|
-
|
92
|
-
|
274
|
+
##
|
275
|
+
# Replaces the first argument with the second as a value within the
|
276
|
+
# relation.
|
277
|
+
#
|
278
|
+
# @example
|
279
|
+
#
|
280
|
+
#
|
281
|
+
# @param swap_out [Object] the value to delete
|
282
|
+
# @param swap_in [Object] the replacement value
|
283
|
+
#
|
284
|
+
# @return [Relation] self
|
285
|
+
def swap(swap_out, swap_in)
|
286
|
+
self.<<(swap_in) if delete?(swap_out)
|
93
287
|
end
|
94
288
|
|
95
|
-
|
289
|
+
##
|
290
|
+
# @return [Object] the first result, if present; else a newly built node
|
291
|
+
#
|
292
|
+
# @see #build
|
293
|
+
def first_or_create(attributes={})
|
294
|
+
result.first || build(attributes)
|
295
|
+
end
|
96
296
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
297
|
+
##
|
298
|
+
# Adds values to the result set
|
299
|
+
#
|
300
|
+
# @param values [Object, Array<Object>] values to add
|
301
|
+
#
|
302
|
+
# @return [Relation] a relation containing the set values; i.e. `self`
|
303
|
+
def <<(values)
|
304
|
+
values = Array.wrap(result) | Array.wrap(values)
|
101
305
|
self.set(values)
|
102
306
|
end
|
307
|
+
alias_method :push, :<<
|
103
308
|
|
309
|
+
##
|
310
|
+
# @return [Hash<Symbol, ]
|
311
|
+
# @todo find a way to simplify this?
|
104
312
|
def property_config
|
105
|
-
return type_property if
|
313
|
+
return type_property if is_type?
|
314
|
+
|
106
315
|
reflections.reflect_on_property(property)
|
107
316
|
end
|
108
317
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
318
|
+
##
|
319
|
+
# Returns the property for the Relation. This may be a registered
|
320
|
+
# property key or an {RDF::URI}.
|
321
|
+
#
|
322
|
+
# @return [Symbol, RDF::URI] the property for this Relation.
|
323
|
+
# @see #predicate
|
116
324
|
def property
|
117
325
|
value_arguments.last
|
118
326
|
end
|
119
327
|
|
328
|
+
##
|
329
|
+
# Gives the predicate used by the Relation. Values of this object are
|
330
|
+
# those that match the pattern `<rdf_subject> <predicate> [value] .`
|
331
|
+
#
|
332
|
+
# @return [RDF::Term, nil] the predicate for this relation; nil if
|
333
|
+
# no predicate can be found
|
334
|
+
#
|
335
|
+
# @see #property
|
336
|
+
def predicate
|
337
|
+
return property if property.is_a?(RDF::Term)
|
338
|
+
property_config[:predicate] if is_property?
|
339
|
+
end
|
340
|
+
|
120
341
|
protected
|
121
342
|
|
122
343
|
def node_cache
|
123
344
|
@node_cache ||= {}
|
124
345
|
end
|
125
346
|
|
347
|
+
def is_property?
|
348
|
+
reflections.has_property?(property) || is_type?
|
349
|
+
end
|
350
|
+
|
351
|
+
def is_type?
|
352
|
+
(property == RDF.type || property.to_s == "type") &&
|
353
|
+
(!reflections.kind_of?(RDFSource) || !is_property?)
|
354
|
+
end
|
355
|
+
|
126
356
|
def set_value(val)
|
127
|
-
|
128
|
-
val = val.resource if val.respond_to?(:resource)
|
129
|
-
val = value_to_node(val)
|
357
|
+
val = value_to_node(val.respond_to?(:resource) ? val.resource : val)
|
130
358
|
if val.kind_of? RDFSource
|
131
359
|
node_cache[val.rdf_subject] = nil
|
132
|
-
add_child_node(val
|
360
|
+
add_child_node(val)
|
133
361
|
return
|
134
362
|
end
|
135
363
|
val = val.to_uri if val.respond_to? :to_uri
|
136
|
-
raise
|
137
|
-
" See RDF::Literal.\n\tYou provided #{val.inspect}" unless
|
138
|
-
val.kind_of? RDF::Term
|
364
|
+
raise ValueError, val unless val.kind_of? RDF::Term
|
139
365
|
parent.insert [rdf_subject, predicate, val]
|
140
366
|
end
|
141
367
|
|
368
|
+
def type_property
|
369
|
+
{ :predicate => RDF.type, :cast => false }
|
370
|
+
end
|
371
|
+
|
142
372
|
def value_to_node(val)
|
143
373
|
valid_datatype?(val) ? RDF::Literal(val) : val
|
144
374
|
end
|
145
375
|
|
146
|
-
def add_child_node(resource
|
376
|
+
def add_child_node(resource)
|
147
377
|
parent.insert [rdf_subject, predicate, resource.rdf_subject]
|
148
|
-
|
378
|
+
|
379
|
+
resource = resource.dup
|
380
|
+
unless resource == parent ||
|
381
|
+
(parent.persistence_strategy.is_a?(ParentStrategy) &&
|
382
|
+
parent.persistence_strategy.ancestors.find { |a| a == resource })
|
149
383
|
resource.set_persistence_strategy(ParentStrategy)
|
150
384
|
resource.parent = parent
|
151
385
|
end
|
152
|
-
self.node_cache[resource.rdf_subject] = (object ? object : resource)
|
153
|
-
resource.persist! if resource.persistence_strategy.is_a? ParentStrategy
|
154
|
-
end
|
155
386
|
|
156
|
-
|
157
|
-
|
387
|
+
self.node_cache[resource.rdf_subject] = resource
|
388
|
+
resource.persist! if resource.persistence_strategy.is_a? ParentStrategy
|
158
389
|
end
|
159
390
|
|
160
391
|
def valid_datatype?(val)
|
@@ -189,13 +420,15 @@ module ActiveTriples
|
|
189
420
|
value = RDF::Node.new if value.nil?
|
190
421
|
node = node_cache[value] if node_cache[value]
|
191
422
|
node ||= klass.from_uri(value,parent)
|
192
|
-
|
423
|
+
node.set_persistence_strategy(property_config[:persist_to]) if
|
424
|
+
is_property? && property_config[:persist_to]
|
425
|
+
return nil if (is_property? && property_config[:class_name]) && (class_for_value(value) != class_for_property)
|
193
426
|
self.node_cache[value] ||= node
|
194
427
|
node
|
195
428
|
end
|
196
429
|
|
197
430
|
def cast?
|
198
|
-
return true unless
|
431
|
+
return true unless is_property? || (rel_args && rel_args[:cast])
|
199
432
|
return rel_args[:cast] if rel_args.has_key?(:cast)
|
200
433
|
!!property_config[:cast]
|
201
434
|
end
|
@@ -226,20 +459,44 @@ module ActiveTriples
|
|
226
459
|
end
|
227
460
|
|
228
461
|
def class_for_property
|
229
|
-
klass = property_config[:class_name] if
|
462
|
+
klass = property_config[:class_name] if is_property?
|
230
463
|
klass ||= Resource
|
231
464
|
klass = ActiveTriples.class_from_string(klass, final_parent.class) if
|
232
465
|
klass.kind_of? String
|
233
466
|
klass
|
234
467
|
end
|
235
468
|
|
469
|
+
##
|
470
|
+
# @return [RDF::Term] the subject of the relation
|
236
471
|
def rdf_subject
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
else
|
241
|
-
parent.rdf_subject
|
472
|
+
if value_arguments.length < 1 || value_arguments.length > 2
|
473
|
+
raise(ArgumentError,
|
474
|
+
"wrong number of arguments (#{value_arguments.length} for 1-2)")
|
242
475
|
end
|
476
|
+
|
477
|
+
value_arguments.length > 1 ? value_arguments.first : parent.rdf_subject
|
243
478
|
end
|
479
|
+
|
480
|
+
public
|
481
|
+
|
482
|
+
##
|
483
|
+
# An error class for unallowable values in relations.
|
484
|
+
class ValueError < ArgumentError
|
485
|
+
# @!attribute [r] value
|
486
|
+
attr_reader :value
|
487
|
+
|
488
|
+
##
|
489
|
+
# @param value [Object]
|
490
|
+
def initialize(value)
|
491
|
+
@value = value
|
492
|
+
end
|
493
|
+
|
494
|
+
##
|
495
|
+
# @return [String]
|
496
|
+
def message
|
497
|
+
'value must be an RDF URI, Node, Literal, or a valid datatype. '\
|
498
|
+
"See RDF::Literal.\n\tYou provided #{value.inspect}"
|
499
|
+
end
|
500
|
+
end
|
244
501
|
end
|
245
502
|
end
|