spira 0.0.8 → 0.0.9
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.
- data/AUTHORS +1 -0
- data/CHANGES.md +14 -0
- data/VERSION +1 -1
- data/lib/spira/extensions.rb +14 -0
- data/lib/spira/resource/class_methods.rb +49 -15
- data/lib/spira/resource/instance_methods.rb +105 -25
- data/lib/spira/types/any.rb +2 -2
- data/lib/spira/version.rb +1 -1
- metadata +6 -5
data/AUTHORS
CHANGED
data/CHANGES.md
CHANGED
@@ -1,4 +1,18 @@
|
|
1
1
|
# Changelog for Spira <http://github.com/datagraph/spira>
|
2
|
+
|
3
|
+
## 0.0.9
|
4
|
+
* Fix a bug with Spira::Types::Any that prevented blank node objects
|
5
|
+
* Added Spira::Resource#copy, #copy!, #copy_resource!, and #rename!
|
6
|
+
* Fix a bug with fragment RDF::URI arguments that prevented correct URI
|
7
|
+
construction
|
8
|
+
* Added Spira::Resource.project(subject, attributes, &block), which creates a
|
9
|
+
new instance without attempting to perform any base_uri logic on the given
|
10
|
+
subject. This provides a supported API entry point for implementors to
|
11
|
+
create their own domain-specific URI construction.
|
12
|
+
* Updating a value to nil will now remove it from the repository on #save!
|
13
|
+
* Tweaks to dirty tracking to correctly catch both changed and updated values.
|
14
|
+
All tests pass for the first time with this change.
|
15
|
+
|
2
16
|
## 0.0.8
|
3
17
|
* Remove type checking for repository addition. More power in return for
|
4
18
|
slightly more difficult error messages.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.9
|
@@ -43,7 +43,7 @@ module Spira
|
|
43
43
|
# Spira does not have 'find' or 'create' functions. As RDF identifiers
|
44
44
|
# are globally unique, they all simply 'are'.
|
45
45
|
#
|
46
|
-
# On calling `for`, a new
|
46
|
+
# On calling `for`, a new projection is created for the given URI. The
|
47
47
|
# first time access is attempted on a field, the repository will be
|
48
48
|
# queried for existing attributes, which will be used for the given URI.
|
49
49
|
# Underlying repositories are not accessed at the time of calling `for`.
|
@@ -66,11 +66,28 @@ module Spira
|
|
66
66
|
# @return [Spira::Resource] The newly created instance
|
67
67
|
# @see http://rdf.rubyforge.org/RDF/URI.html
|
68
68
|
def for(identifier, attributes = {}, &block)
|
69
|
+
self.project(id_for(identifier), attributes, &block)
|
70
|
+
end
|
71
|
+
|
72
|
+
##
|
73
|
+
# Create a new instance with the given subjet without any modification to
|
74
|
+
# the given subject at all. This method exists to provide an entry point
|
75
|
+
# for implementing classes that want to create a more intelligent .for
|
76
|
+
# and/or .id_for for their given use cases, such as simple string
|
77
|
+
# appending to base URIs or calculated URIs from other representations.
|
78
|
+
#
|
79
|
+
# @example Using simple string concatentation with base_uri in .for instead of joining delimiters
|
80
|
+
# def for(identifier, attributes = {}, &block)
|
81
|
+
# self.project(RDF::URI(self.base_uri.to_s + identifier.to_s), attributes, &block)
|
82
|
+
# end
|
83
|
+
# @param [RDF::URI, RDF::Node] subject
|
84
|
+
# @param [Hash{Symbol => Any}] attributes Initial attributes
|
85
|
+
# @return [Spira::Resource] the newly created instance
|
86
|
+
def project(subject, attributes = {}, &block)
|
69
87
|
if !self.type.nil? && attributes[:type]
|
70
88
|
raise TypeError, "#{self} has an RDF type, #{self.type}, and cannot accept one as an argument."
|
71
89
|
end
|
72
|
-
|
73
|
-
instance = self.new(attributes.merge(:_subject => subject), &block)
|
90
|
+
self.new(attributes.merge(:_subject => subject), &block)
|
74
91
|
end
|
75
92
|
|
76
93
|
##
|
@@ -92,23 +109,40 @@ module Spira
|
|
92
109
|
# @return [RDF::URI, RDF::Node]
|
93
110
|
# @raise [ArgumentError] If this class cannot create an identifier from the given argument
|
94
111
|
# @see http://rdf.rubyforge.org/RDF/URI.html
|
112
|
+
# @see Spira::Resource.base_uri
|
113
|
+
# @see Spira::Resource.for
|
95
114
|
def id_for(identifier)
|
96
|
-
case
|
97
|
-
#
|
98
|
-
when identifier.
|
99
|
-
identifier
|
100
|
-
#
|
115
|
+
case
|
116
|
+
# Absolute URI's go through unchanged
|
117
|
+
when identifier.is_a?(RDF::URI) && identifier.absolute?
|
118
|
+
identifier
|
119
|
+
# We don't have a base URI to join this fragment with, so go ahead and instantiate it as-is.
|
120
|
+
when identifier.is_a?(RDF::URI) && self.base_uri.nil?
|
121
|
+
identifier
|
122
|
+
# Blank nodes go through unchanged
|
101
123
|
when identifier.respond_to?(:node?) && identifier.node?
|
102
124
|
identifier
|
125
|
+
# Anything that can be an RDF::URI, we re-run this case statement
|
126
|
+
# on it for the fragment logic above.
|
127
|
+
when identifier.respond_to?(:to_uri) && !identifier.is_a?(RDF::URI)
|
128
|
+
id_for(identifier.to_uri)
|
129
|
+
# see comment with #to_uri above, this might be a fragment
|
103
130
|
when identifier.is_a?(Addressable::URI)
|
104
|
-
RDF::URI.new(identifier)
|
105
|
-
#
|
131
|
+
id_for(RDF::URI.new(identifier))
|
132
|
+
# This is a #to_s or a URI fragment with a base uri. We'll treat them the same.
|
133
|
+
# FIXME: when #/ makes it into RDF.rb proper, this can all be wrapped
|
134
|
+
# into the one case statement above.
|
106
135
|
else
|
107
|
-
uri = RDF::URI.new(identifier.to_s)
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
136
|
+
uri = identifier.is_a?(RDF::URI) ? identifier : RDF::URI.new(identifier.to_s)
|
137
|
+
case
|
138
|
+
when uri.absolute?
|
139
|
+
uri
|
140
|
+
when self.base_uri.nil?
|
141
|
+
raise ArgumentError, "Cannot create identifier for #{self} by String without base_uri; an RDF::URI is required" if self.base_uri.nil?
|
142
|
+
else
|
143
|
+
separator = self.base_uri.to_s[-1,1] =~ /(\/|#)/ ? '' : '/'
|
144
|
+
RDF::URI.new(self.base_uri.to_s + separator + identifier.to_s)
|
145
|
+
end
|
112
146
|
end
|
113
147
|
end
|
114
148
|
|
@@ -12,8 +12,13 @@ module Spira
|
|
12
12
|
# @see Spira::Resource::ClassMethods
|
13
13
|
# @see Spira::Resource::DSL
|
14
14
|
# @see Spira::Resource::Validations
|
15
|
-
module InstanceMethods
|
16
|
-
|
15
|
+
module InstanceMethods
|
16
|
+
|
17
|
+
# Marker for whether or not a field has been set or not; distinguishes
|
18
|
+
# nil and unset.
|
19
|
+
# @private
|
20
|
+
NOT_SET = ::Object.new.freeze
|
21
|
+
|
17
22
|
##
|
18
23
|
# This instance's URI.
|
19
24
|
#
|
@@ -39,6 +44,7 @@ module Spira
|
|
39
44
|
yield(self)
|
40
45
|
save!
|
41
46
|
end
|
47
|
+
self
|
42
48
|
end
|
43
49
|
|
44
50
|
##
|
@@ -52,12 +58,18 @@ module Spira
|
|
52
58
|
@cache = opts[:_cache] || RDF::Util::Cache.new
|
53
59
|
@cache[subject] = self
|
54
60
|
@dirty = {}
|
55
|
-
|
56
|
-
|
57
|
-
@attributes =
|
61
|
+
@attributes = {}
|
62
|
+
@attributes[:current] = {}
|
63
|
+
@attributes[:copied] = {}
|
58
64
|
self.class.properties.each do |name, predicate|
|
59
|
-
|
65
|
+
case opts[name].nil?
|
66
|
+
when false
|
67
|
+
attribute_set(name, opts[name])
|
68
|
+
when true
|
69
|
+
@attributes[:copied][name] = NOT_SET
|
70
|
+
end
|
60
71
|
end
|
72
|
+
@attributes[:original] = promise { reload_attributes }
|
61
73
|
end
|
62
74
|
|
63
75
|
##
|
@@ -68,8 +80,6 @@ module Spira
|
|
68
80
|
def reload_attributes()
|
69
81
|
statements = self.class.repository_or_fail.query(:subject => @subject)
|
70
82
|
attributes = {}
|
71
|
-
attributes[:original] = {}
|
72
|
-
attributes[:current] = {}
|
73
83
|
|
74
84
|
# Set attributes for each statement corresponding to a predicate
|
75
85
|
self.class.properties.each do |name, property|
|
@@ -81,22 +91,12 @@ module Spira
|
|
81
91
|
values << self.class.build_value(statement,property[:type], @cache)
|
82
92
|
end
|
83
93
|
end
|
84
|
-
attributes[
|
85
|
-
attributes[:original][name] = values.dup
|
94
|
+
attributes[name] = values
|
86
95
|
else
|
87
96
|
statement = statements.query(:subject => @subject, :predicate => property[:predicate]).first unless statements.empty?
|
88
|
-
attributes[
|
89
|
-
|
90
|
-
# Lots of things like Fixnums and Nil can't be dup'd, but they are
|
91
|
-
# all immutable, so if we can't dup, it's no problem for dirty tracking.
|
92
|
-
begin
|
93
|
-
attributes[:original][name] = attributes[:current][name].dup
|
94
|
-
rescue TypeError
|
95
|
-
attributes[:original][name] = attributes[:current][name]
|
96
|
-
end
|
97
|
+
attributes[name] = self.class.build_value(statement, property[:type], @cache)
|
97
98
|
end
|
98
99
|
end
|
99
|
-
|
100
100
|
attributes
|
101
101
|
end
|
102
102
|
|
@@ -225,11 +225,12 @@ module Spira
|
|
225
225
|
end
|
226
226
|
self.class.repository_or_fail.insert(*repo)
|
227
227
|
else
|
228
|
-
self.class.repository_or_fail.insert(RDF::Statement.new(subject, predicate[:predicate], self.class.build_rdf_value(attribute_get(property), self.class.properties[property][:type])))
|
228
|
+
self.class.repository_or_fail.insert(RDF::Statement.new(subject, predicate[:predicate], self.class.build_rdf_value(attribute_get(property), self.class.properties[property][:type]))) unless attribute_get(property).nil?
|
229
229
|
end
|
230
230
|
end
|
231
|
-
@dirty[property] = nil
|
232
231
|
@attributes[:original][property] = attribute_get(property)
|
232
|
+
@dirty[property] = nil
|
233
|
+
@attributes[:copied][property] = NOT_SET
|
233
234
|
end
|
234
235
|
self.class.repository_or_fail.insert(RDF::Statement.new(@subject, RDF.type, type)) unless type.nil?
|
235
236
|
end
|
@@ -309,8 +310,13 @@ module Spira
|
|
309
310
|
case
|
310
311
|
when @dirty[name] == true
|
311
312
|
true
|
312
|
-
else
|
313
|
-
|
313
|
+
else
|
314
|
+
case @attributes[:copied][name]
|
315
|
+
when NOT_SET
|
316
|
+
false
|
317
|
+
else
|
318
|
+
@attributes[:copied][name] != @attributes[:original][name]
|
319
|
+
end
|
314
320
|
end
|
315
321
|
end
|
316
322
|
end
|
@@ -320,7 +326,26 @@ module Spira
|
|
320
326
|
#
|
321
327
|
# @private
|
322
328
|
def attribute_get(name)
|
323
|
-
@
|
329
|
+
case @dirty[name]
|
330
|
+
when true
|
331
|
+
@attributes[:current][name]
|
332
|
+
else
|
333
|
+
case @attributes[:copied][name].equal?(NOT_SET)
|
334
|
+
when true
|
335
|
+
dup = if @attributes[:original][name].is_a?(Spira::Resource)
|
336
|
+
@attributes[:original][name]
|
337
|
+
else
|
338
|
+
begin
|
339
|
+
@attributes[:original][name].dup
|
340
|
+
rescue TypeError
|
341
|
+
@attributes[:original][name]
|
342
|
+
end
|
343
|
+
end
|
344
|
+
@attributes[:copied][name] = dup
|
345
|
+
when false
|
346
|
+
@attributes[:copied][name]
|
347
|
+
end
|
348
|
+
end
|
324
349
|
end
|
325
350
|
|
326
351
|
##
|
@@ -449,6 +474,61 @@ module Spira
|
|
449
474
|
self.class.repository.query(:subject => subject)
|
450
475
|
end
|
451
476
|
|
477
|
+
##
|
478
|
+
# Returns a new instance of this class with the new subject instead of self.subject
|
479
|
+
#
|
480
|
+
# @param [RDF::Resource] new_subject
|
481
|
+
# @return [Spira::Resource] copy
|
482
|
+
def copy(new_subject)
|
483
|
+
copy = self.class.for(new_subject)
|
484
|
+
self.class.properties.each_key { |property| copy.attribute_set(property, self.attribute_get(property)) }
|
485
|
+
copy
|
486
|
+
end
|
487
|
+
|
488
|
+
##
|
489
|
+
# Returns a new instance of this class with the new subject instead of
|
490
|
+
# self.subject after saving the new copy to the repository.
|
491
|
+
#
|
492
|
+
# @param [RDF::Resource] new_subject
|
493
|
+
# @return [Spira::Resource, String] copy
|
494
|
+
def copy!(new_subject)
|
495
|
+
copy(new_subject).save!
|
496
|
+
end
|
497
|
+
|
498
|
+
##
|
499
|
+
# Copies all data, including non-model data, about this resource to
|
500
|
+
# another URI. The copy is immediately saved to the repository.
|
501
|
+
#
|
502
|
+
# @param [RDF::Resource] new_subject
|
503
|
+
# @return [Spira::Resource, String] copy
|
504
|
+
def copy_resource!(new_subject)
|
505
|
+
new_subject = self.class.id_for(new_subject)
|
506
|
+
update_repository = RDF::Repository.new
|
507
|
+
data.each do |statement|
|
508
|
+
update_repository << RDF::Statement.new(new_subject, statement.predicate, statement.object)
|
509
|
+
end
|
510
|
+
self.class.repository.insert(update_repository)
|
511
|
+
new_subject.as(self.class)
|
512
|
+
end
|
513
|
+
|
514
|
+
##
|
515
|
+
# Rename this resource in the repository to the new given subject.
|
516
|
+
# Changes are immediately saved to the repository.
|
517
|
+
#
|
518
|
+
# @param [RDF::Resource] new_subject
|
519
|
+
# @return [Spira::Resource, String] new_resource
|
520
|
+
def rename!(new_subject)
|
521
|
+
new = copy_resource!(new_subject)
|
522
|
+
object_statements = self.class.repository.query(:object => subject)
|
523
|
+
update_repository = RDF::Repository.new
|
524
|
+
object_statements.each do |statement|
|
525
|
+
update_repository << RDF::Statement.new(statement.subject, statement.predicate, new.subject)
|
526
|
+
end
|
527
|
+
self.class.repository.insert(update_repository)
|
528
|
+
destroy!(:completely)
|
529
|
+
new
|
530
|
+
end
|
531
|
+
|
452
532
|
## We have defined #each and can do this fun RDF stuff by default
|
453
533
|
include ::RDF::Enumerable, ::RDF::Queryable
|
454
534
|
|
data/lib/spira/types/any.rb
CHANGED
@@ -12,12 +12,12 @@ module Spira::Types
|
|
12
12
|
include Spira::Type
|
13
13
|
|
14
14
|
def self.unserialize(value)
|
15
|
-
value.respond_to?(:
|
15
|
+
value.respond_to?(:object) ? value.object : value
|
16
16
|
end
|
17
17
|
|
18
18
|
def self.serialize(value)
|
19
19
|
raise TypeError, "Spira::Types::Any cannot serialize collections" if value.is_a?(Array)
|
20
|
-
value.
|
20
|
+
value.is_a?(RDF::Value) ? value : RDF::Literal.new(value)
|
21
21
|
end
|
22
22
|
|
23
23
|
end
|
data/lib/spira/version.rb
CHANGED
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 0.0.
|
8
|
+
- 9
|
9
|
+
version: 0.0.9
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Ben Lavender
|
@@ -15,7 +15,7 @@ bindir:
|
|
15
15
|
- bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-
|
18
|
+
date: 2010-10-20 00:00:00 +02:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -70,8 +70,8 @@ dependencies:
|
|
70
70
|
segments:
|
71
71
|
- 0
|
72
72
|
- 2
|
73
|
-
-
|
74
|
-
version: 0.2.
|
73
|
+
- 3
|
74
|
+
version: 0.2.3
|
75
75
|
type: :runtime
|
76
76
|
version_requirements: *id004
|
77
77
|
- !ruby/object:Gem::Dependency
|
@@ -119,6 +119,7 @@ files:
|
|
119
119
|
- lib/spira/base.rb
|
120
120
|
- lib/spira/errors.rb
|
121
121
|
- lib/spira/exceptions.rb
|
122
|
+
- lib/spira/extensions.rb
|
122
123
|
- lib/spira/resource/class_methods.rb
|
123
124
|
- lib/spira/resource/dsl.rb
|
124
125
|
- lib/spira/resource/instance_methods.rb
|