spira 0.0.8 → 0.0.9
Sign up to get free protection for your applications and to get access to all the features.
- 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
|