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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +8 -9
  3. data/CHANGES.md +69 -0
  4. data/Gemfile +0 -2
  5. data/Guardfile +1 -2
  6. data/active-triples.gemspec +3 -3
  7. data/lib/active/triples.rb +1 -0
  8. data/lib/active_triples.rb +4 -0
  9. data/lib/active_triples/configurable.rb +3 -1
  10. data/lib/active_triples/configuration.rb +1 -0
  11. data/lib/active_triples/configuration/item.rb +1 -0
  12. data/lib/active_triples/configuration/item_factory.rb +1 -0
  13. data/lib/active_triples/configuration/merge_item.rb +5 -2
  14. data/lib/active_triples/extension_strategy.rb +1 -0
  15. data/lib/active_triples/identifiable.rb +1 -0
  16. data/lib/active_triples/list.rb +2 -0
  17. data/lib/active_triples/nested_attributes.rb +1 -1
  18. data/lib/active_triples/node_config.rb +5 -3
  19. data/lib/active_triples/persistable.rb +1 -0
  20. data/lib/active_triples/persistence_strategies/parent_strategy.rb +104 -29
  21. data/lib/active_triples/persistence_strategies/persistence_strategy.rb +15 -7
  22. data/lib/active_triples/persistence_strategies/repository_strategy.rb +26 -22
  23. data/lib/active_triples/properties.rb +84 -6
  24. data/lib/active_triples/property.rb +35 -4
  25. data/lib/active_triples/property_builder.rb +38 -4
  26. data/lib/active_triples/rdf_source.rb +225 -75
  27. data/lib/active_triples/reflection.rb +42 -3
  28. data/lib/active_triples/relation.rb +330 -73
  29. data/lib/active_triples/repositories.rb +4 -2
  30. data/lib/active_triples/resource.rb +1 -0
  31. data/lib/active_triples/schema.rb +1 -0
  32. data/lib/active_triples/undefined_property_error.rb +27 -0
  33. data/lib/active_triples/version.rb +2 -1
  34. data/spec/active_triples/configurable_spec.rb +3 -2
  35. data/spec/active_triples/configuration_spec.rb +2 -1
  36. data/spec/active_triples/extension_strategy_spec.rb +2 -1
  37. data/spec/active_triples/identifiable_spec.rb +7 -11
  38. data/spec/active_triples/list_spec.rb +1 -4
  39. data/spec/active_triples/nested_attributes_spec.rb +4 -3
  40. data/spec/active_triples/persistable_spec.rb +4 -1
  41. data/spec/active_triples/persistence_strategies/parent_strategy_spec.rb +141 -11
  42. data/spec/active_triples/persistence_strategies/persistence_strategy_spec.rb +1 -0
  43. data/spec/active_triples/persistence_strategies/repository_strategy_spec.rb +32 -17
  44. data/spec/active_triples/properties_spec.rb +68 -33
  45. data/spec/active_triples/property_builder_spec.rb +36 -0
  46. data/spec/active_triples/property_spec.rb +15 -1
  47. data/spec/active_triples/rdf_source_spec.rb +544 -6
  48. data/spec/active_triples/reflection_spec.rb +78 -0
  49. data/spec/active_triples/relation_spec.rb +505 -3
  50. data/spec/active_triples/repositories_spec.rb +3 -1
  51. data/spec/active_triples/resource_spec.rb +90 -147
  52. data/spec/active_triples/schema_spec.rb +3 -2
  53. data/spec/active_triples_spec.rb +1 -0
  54. data/spec/integration/dummies/dummy_resource_a.rb +6 -0
  55. data/spec/integration/dummies/dummy_resource_b.rb +6 -0
  56. data/spec/integration/parent_persistence_spec.rb +18 -0
  57. data/spec/integration/reciprocal_properties_spec.rb +69 -0
  58. data/spec/pragmatic_context_spec.rb +10 -8
  59. data/spec/spec_helper.rb +5 -0
  60. data/spec/support/active_model_lint.rb +4 -6
  61. data/spec/support/dummies/basic_persistable.rb +2 -11
  62. data/spec/support/matchers.rb +11 -0
  63. data/spec/support/shared_examples/persistence_strategy.rb +3 -16
  64. 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 = model._active_triples_config.merge(name.to_s => reflection)
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
- def reflect_on_property(term)
18
- _active_triples_config[term.to_s]
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, then, express n binary relationships between the parent node and
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
- attr_accessor :parent, :value_arguments, :node_cache, :rel_args
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 *(Array.public_instance_methods - [:send, :__send__, :__id__, :class, :object_id] + [:as_json]), :to => :result
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
- def value_arguments=(value_args)
30
- if value_args.kind_of?(Array) && value_args.last.kind_of?(Hash)
31
- self.rel_args = value_args.pop
32
- end
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
- set(nil)
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
- parent.query(:subject => rdf_subject, :predicate => predicate)
42
- .each_with_object([]) do |x, collector|
43
- converted_object = convert_object(x.object)
44
- collector << converted_object unless converted_object.nil?
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
- def empty_property
59
- parent.query([rdf_subject, predicate, nil]).each_statement do |statement|
60
- if !uri_class(statement.object) || uri_class(statement.object) == class_for_property
61
- parent.delete(statement)
62
- end
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
- def first_or_create(attributes={})
81
- result.first || build(attributes)
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
- def delete(*values)
85
- values.each do |value|
86
- parent.delete([rdf_subject, predicate, value])
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
- def << (values)
91
- values = Array.wrap(result) | Array.wrap(values)
92
- self.set(values)
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
- alias_method :push, :<<
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
- def []=(index, value)
98
- values = Array.wrap(result)
99
- raise IndexError, "Index #{index} out of bounds." if values[index].nil?
100
- values[index] = value
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 (property == RDF.type || property.to_s == "type") && (!reflections.kind_of?(RDFSource) || !reflections.reflect_on_property(property))
313
+ return type_property if is_type?
314
+
106
315
  reflections.reflect_on_property(property)
107
316
  end
108
317
 
109
- def type_property
110
- { :predicate => RDF.type, :cast => false }
111
- end
112
-
113
- def reset!
114
- end
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
- object = val
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, object)
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 "value must be an RDF URI, Node, Literal, or a valid datatype." \
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,object=nil)
376
+ def add_child_node(resource)
147
377
  parent.insert [rdf_subject, predicate, resource.rdf_subject]
148
- unless resource.frozen?
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
- def predicate
157
- property.kind_of?(RDF::URI) ? property : property_config[:predicate]
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
- return nil if (property_config && property_config[:class_name]) && (class_for_value(value) != class_for_property)
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 property_config || (rel_args && rel_args[:cast])
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 property_config
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
- raise ArgumentError, "wrong number of arguments (#{value_arguments.length} for 1-2)" if value_arguments.length < 1 || value_arguments.length > 2
238
- if value_arguments.length > 1
239
- value_arguments.first
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