spira 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.md +25 -0
- data/README +2 -0
- data/VERSION +1 -1
- data/lib/spira.rb +27 -0
- data/lib/spira/exceptions.rb +5 -1
- data/lib/spira/resource/class_methods.rb +37 -16
- data/lib/spira/resource/dsl.rb +5 -13
- data/lib/spira/resource/instance_methods.rb +71 -32
- data/lib/spira/version.rb +1 -1
- metadata +10 -9
data/CHANGES.md
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# Changelog for Spira <http://github.com/datagraph/spira>
|
2
|
+
|
3
|
+
## untagged
|
4
|
+
* Bumped promise dependency to 0.1.1 to fix a Ruby 1.9 warning
|
5
|
+
* Rework error handling when a repository is not configured; this should
|
6
|
+
always now raise a Spira::NoRepositoryError regardless of what operation
|
7
|
+
was attempted, and the error message was improved as well.
|
8
|
+
* A '/' is no longer appended to base URIs ending with a '#'
|
9
|
+
* Resources can now take a BNode as a subject. Implemented #node?, #uri,
|
10
|
+
#to_uri, #to_node, and #to_subject in support of this; see the yardocs for
|
11
|
+
exact semantics. RDF::Node is monkey patched with #as, just like RDF::URI,
|
12
|
+
for instantiation. Old code should not break, but if you want to add
|
13
|
+
BNodes, you may be using #uri where you want to now be using #subject.
|
14
|
+
|
15
|
+
## 0.0.2
|
16
|
+
* Implemented #each on resource classes, allowing classes with a defined RDF
|
17
|
+
type to be enumerated
|
18
|
+
* Fragment URIs are now used as strings, allowing i.e. Integers to be used as
|
19
|
+
the final portion of a URI for classes with a base_uri defined.
|
20
|
+
* Added an RDF::URI property type
|
21
|
+
* Implemented #to_rdf and #to_uri for increased compatibility with the RDF.rb
|
22
|
+
ecosystem
|
23
|
+
|
24
|
+
## 0.0.1
|
25
|
+
* Initial release
|
data/README
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.3
|
data/lib/spira.rb
CHANGED
@@ -85,6 +85,17 @@ module Spira
|
|
85
85
|
end
|
86
86
|
module_function :repository
|
87
87
|
|
88
|
+
##
|
89
|
+
# Clear all repositories from Spira's knowledge. Use it if you want, but
|
90
|
+
# it's really here for testing.
|
91
|
+
#
|
92
|
+
# @return [Void]
|
93
|
+
# @private
|
94
|
+
def clear_repositories!
|
95
|
+
settings[:repositories] = {}
|
96
|
+
end
|
97
|
+
module_function :clear_repositories!
|
98
|
+
|
88
99
|
##
|
89
100
|
# Alias a property type to another. This allows a range of options to be
|
90
101
|
# specified for a property type which all reference one Spira::Type
|
@@ -122,4 +133,20 @@ module RDF
|
|
122
133
|
klass.for(self, *args)
|
123
134
|
end
|
124
135
|
end
|
136
|
+
|
137
|
+
class Node
|
138
|
+
##
|
139
|
+
# Create a projection of this Node as the given Spira::Resource class.
|
140
|
+
# Equivalent to `klass.for(self, *args)`
|
141
|
+
#
|
142
|
+
# @example Instantiating a blank node as a Spira Resource
|
143
|
+
# RDF::Node.new.as(Person)
|
144
|
+
# @param [Class] klass
|
145
|
+
# @param [*Any] args Any arguments to pass to klass.for
|
146
|
+
# @return [Klass] An instance of klass
|
147
|
+
def as(klass, *args)
|
148
|
+
raise ArgumentError, "#{klass} is not a Spira resource" unless klass.is_a?(Class) && klass.ancestors.include?(Spira::Resource)
|
149
|
+
klass.for(self, *args)
|
150
|
+
end
|
151
|
+
end
|
125
152
|
end
|
data/lib/spira/exceptions.rb
CHANGED
@@ -7,5 +7,9 @@ module Spira
|
|
7
7
|
|
8
8
|
##
|
9
9
|
# For cases when a projection fails a validation check
|
10
|
-
class ValidationError < StandardError; end
|
10
|
+
class ValidationError < StandardError ; end
|
11
|
+
|
12
|
+
##
|
13
|
+
# For cases in which a repository is required but none has been given
|
14
|
+
class NoRepositoryError < StandardError ; end
|
11
15
|
end
|
@@ -10,19 +10,29 @@ module Spira
|
|
10
10
|
# @see Spira::Resource::DSL
|
11
11
|
module ClassMethods
|
12
12
|
|
13
|
+
##
|
14
|
+
# A symbol name for the repository this class is currently using.
|
15
|
+
attr_reader :repository_name
|
16
|
+
|
13
17
|
##
|
14
18
|
# The current repository for this class
|
15
19
|
#
|
16
|
-
# @
|
17
|
-
# @return [Void]
|
20
|
+
# @return [RDF::Repository, nil]
|
18
21
|
# @private
|
19
22
|
def repository
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
23
|
+
name = @repository_name || :default
|
24
|
+
Spira.repository(name)
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# Get the current repository for this class, and raise a
|
29
|
+
# Spira::NoRepositoryError if it is nil.
|
30
|
+
#
|
31
|
+
# @raise [Spira::NoRepositoryError]
|
32
|
+
# @return [RDF::Repository]
|
33
|
+
# @private
|
34
|
+
def repository_or_fail
|
35
|
+
repository || (raise Spira::NoRepositoryError, "#{self} is configured to use #{@repository_name} as a repository, but it has not been set.")
|
26
36
|
end
|
27
37
|
|
28
38
|
##
|
@@ -57,26 +67,37 @@ module Spira
|
|
57
67
|
if !self.type.nil? && attributes[:type]
|
58
68
|
raise TypeError, "#{self} has an RDF type, #{self.type}, and cannot accept one as an argument."
|
59
69
|
end
|
60
|
-
uri =
|
70
|
+
uri = id_for(identifier)
|
61
71
|
self.new(uri, attributes)
|
62
72
|
end
|
63
73
|
|
64
74
|
##
|
65
|
-
# Creates a URI based on a base_uri and string
|
75
|
+
# Creates a URI or RDF::Node based on a potential base_uri and string,
|
76
|
+
# URI, or Node, or Addressable::URI. If not a URI or Node, the given
|
77
|
+
# identifier should be a string representing an absolute URI, or
|
78
|
+
# something responding to to_s which can be appended to a base URI, which
|
79
|
+
# this class must have.
|
66
80
|
#
|
67
81
|
# @param [Any] Identifier
|
68
|
-
# @return [RDF::URI]
|
69
|
-
# @raise [ArgumentError] If this class cannot create an identifier from the given
|
82
|
+
# @return [RDF::URI, RDF::Node]
|
83
|
+
# @raise [ArgumentError] If this class cannot create an identifier from the given argument
|
70
84
|
# @see http://rdf.rubyforge.org/RDF/URI.html
|
71
|
-
def
|
72
|
-
case
|
73
|
-
|
85
|
+
def id_for(identifier)
|
86
|
+
case
|
87
|
+
# Catches RDF::URI and implementing subclasses
|
88
|
+
when identifier.respond_to?(:to_uri)
|
89
|
+
identifier.to_uri
|
90
|
+
# Catches RDF::Nodes
|
91
|
+
when identifier.respond_to?(:node?) && identifier.node?
|
74
92
|
identifier
|
93
|
+
when identifier.is_a?(Addressable::URI)
|
94
|
+
RDF::URI.new(identifier)
|
95
|
+
# Treat identifier as a string, and create a URI out of it.
|
75
96
|
else
|
76
97
|
uri = RDF::URI.new(identifier.to_s)
|
77
98
|
return uri if uri.absolute?
|
78
99
|
raise ArgumentError, "Cannot create identifier for #{self} by String without base_uri; RDF::URI required" if self.base_uri.nil?
|
79
|
-
separator = self.base_uri.to_s[-1,1]
|
100
|
+
separator = self.base_uri.to_s[-1,1] =~ /(\/|#)/ ? '' : '/'
|
80
101
|
RDF::URI.new(self.base_uri.to_s + separator + identifier.to_s)
|
81
102
|
end
|
82
103
|
end
|
data/lib/spira/resource/dsl.rb
CHANGED
@@ -132,7 +132,7 @@ module Spira
|
|
132
132
|
# Build a Ruby value from an RDF value.
|
133
133
|
#
|
134
134
|
# @private
|
135
|
-
def build_value(statement, type
|
135
|
+
def build_value(statement, type)
|
136
136
|
case
|
137
137
|
when statement == nil
|
138
138
|
nil
|
@@ -146,15 +146,9 @@ module Spira
|
|
146
146
|
raise TypeError, "#{type} is not a Spira Resource (referenced as #{type} by #{self}"
|
147
147
|
end
|
148
148
|
end
|
149
|
-
|
150
|
-
when false && existing_relation && (existing_relation.uri == statement.object.to_uri)
|
151
|
-
existing_relation
|
152
|
-
else
|
153
|
-
promise { klass.for(statement.object) ||
|
154
|
-
klass.create(statement.object) }
|
155
|
-
end
|
149
|
+
promise { klass.for(statement.object) }
|
156
150
|
else
|
157
|
-
raise TypeError, "Unable to unserialize #{statement.object}
|
151
|
+
raise TypeError, "Unable to unserialize #{statement.object} as #{type}"
|
158
152
|
end
|
159
153
|
end
|
160
154
|
|
@@ -165,11 +159,9 @@ module Spira
|
|
165
159
|
when type.is_a?(Class) && type.ancestors.include?(Spira::Type)
|
166
160
|
type.serialize(value)
|
167
161
|
when value && value.class.ancestors.include?(Spira::Resource)
|
168
|
-
value.
|
169
|
-
when type == RDF::URI && value.is_a?(RDF::URI)
|
170
|
-
value
|
162
|
+
value.subject
|
171
163
|
else
|
172
|
-
raise TypeError, "Unable to serialize #{value}
|
164
|
+
raise TypeError, "Unable to serialize #{value} as #{type}"
|
173
165
|
end
|
174
166
|
end
|
175
167
|
|
@@ -17,18 +17,18 @@ module Spira
|
|
17
17
|
# This instance's URI.
|
18
18
|
#
|
19
19
|
# @return [RDF::URI]
|
20
|
-
attr_reader :
|
20
|
+
attr_reader :subject
|
21
21
|
|
22
22
|
##
|
23
23
|
# Initialize a new Spira::Resource instance of this resource class. This
|
24
24
|
# method should not be called directly, use
|
25
25
|
# {Spira::Resource::ClassMethods#for} instead.
|
26
26
|
#
|
27
|
-
# @param [
|
27
|
+
# @param [RDF::URI, RDF::Node] identifier The URI or URI fragment for this instance
|
28
28
|
# @param [Hash] opts Default attributes for this instance
|
29
29
|
# @see Spira::Resource::ClassMethods#for
|
30
30
|
def initialize(identifier, opts = {})
|
31
|
-
@
|
31
|
+
@subject = identifier
|
32
32
|
reload(opts)
|
33
33
|
end
|
34
34
|
|
@@ -54,10 +54,7 @@ module Spira
|
|
54
54
|
# @return [Hash] @attributes
|
55
55
|
# @private
|
56
56
|
def reload_attributes()
|
57
|
-
|
58
|
-
raise RuntimeError, "#{self} is configured to use #{@repository_name} as a repository, but was unable to find it."
|
59
|
-
end
|
60
|
-
statements = self.class.repository.query(:subject => @uri)
|
57
|
+
statements = self.class.repository_or_fail.query(:subject => @subject)
|
61
58
|
@attributes = {}
|
62
59
|
|
63
60
|
unless statements.empty?
|
@@ -70,7 +67,7 @@ module Spira
|
|
70
67
|
# execute the promises to load those classes. Need an identity
|
71
68
|
# map of some sort to fix that.
|
72
69
|
values = []
|
73
|
-
collection = statements.query(:subject => @
|
70
|
+
collection = statements.query(:subject => @subject, :predicate => property[:predicate])
|
74
71
|
unless collection.nil?
|
75
72
|
collection.each do |statement|
|
76
73
|
values << self.class.build_value(statement,property[:type])
|
@@ -78,7 +75,7 @@ module Spira
|
|
78
75
|
end
|
79
76
|
attribute_set(name, values)
|
80
77
|
else
|
81
|
-
statement = statements.query(:subject => @
|
78
|
+
statement = statements.query(:subject => @subject, :predicate => property[:predicate]).first
|
82
79
|
attribute_set(name, self.class.build_value(statement, property[:type]))
|
83
80
|
end
|
84
81
|
end
|
@@ -105,8 +102,8 @@ module Spira
|
|
105
102
|
# @private
|
106
103
|
def _destroy_attributes(attributes, opts = {})
|
107
104
|
repository = repository_for_attributes(attributes)
|
108
|
-
repository.insert([@
|
109
|
-
self.class.
|
105
|
+
repository.insert([@subject, RDF.type, self.class.type]) if (self.class.type && opts[:destroy_type])
|
106
|
+
self.class.repository_or_fail.delete(*repository)
|
110
107
|
end
|
111
108
|
|
112
109
|
##
|
@@ -126,7 +123,7 @@ module Spira
|
|
126
123
|
#
|
127
124
|
# @return [true, false] Whether or not the destroy was successful
|
128
125
|
def destroy_resource!
|
129
|
-
self.class.
|
126
|
+
self.class.repository_or_fail.delete([@subject,nil,nil])
|
130
127
|
end
|
131
128
|
|
132
129
|
##
|
@@ -134,9 +131,6 @@ module Spira
|
|
134
131
|
#
|
135
132
|
# @return [true, false] Whether or not the save was successful
|
136
133
|
def save!
|
137
|
-
if self.class.repository.nil?
|
138
|
-
raise RuntimeError, "#{self} is configured to use #{@repository_name} as a repository, but was unable to find it."
|
139
|
-
end
|
140
134
|
unless self.class.validators.empty?
|
141
135
|
errors.clear
|
142
136
|
self.class.validators.each do | validator | self.send(validator) end
|
@@ -156,7 +150,7 @@ module Spira
|
|
156
150
|
# @private
|
157
151
|
def _update!
|
158
152
|
_destroy_attributes(@original_attributes)
|
159
|
-
self.class.
|
153
|
+
self.class.repository_or_fail.insert(*self)
|
160
154
|
@original_attributes = @attributes.dup
|
161
155
|
end
|
162
156
|
|
@@ -185,20 +179,12 @@ module Spira
|
|
185
179
|
self
|
186
180
|
end
|
187
181
|
|
188
|
-
##
|
189
|
-
# Returns the URI representation of this resource.
|
190
|
-
#
|
191
|
-
# @return [RDF::URI]
|
192
|
-
def to_uri
|
193
|
-
uri
|
194
|
-
end
|
195
|
-
|
196
182
|
##
|
197
183
|
# A developer-friendly view of this projection
|
198
184
|
#
|
199
185
|
# @private
|
200
186
|
def inspect
|
201
|
-
"<#{self.class}:#{self.object_id} uri: #{@
|
187
|
+
"<#{self.class}:#{self.object_id} uri: #{@subject}>"
|
202
188
|
end
|
203
189
|
|
204
190
|
##
|
@@ -211,13 +197,12 @@ module Spira
|
|
211
197
|
def each(*args, &block)
|
212
198
|
return RDF::Enumerator.new(self, :each) unless block_given?
|
213
199
|
repository = repository_for_attributes(@attributes)
|
214
|
-
repository.insert(RDF::Statement.new(@
|
200
|
+
repository.insert(RDF::Statement.new(@subject, RDF.type, type)) unless type.nil?
|
215
201
|
repository.each(*args, &block)
|
216
202
|
end
|
217
203
|
|
218
204
|
##
|
219
|
-
#
|
220
|
-
# private.
|
205
|
+
# Sets the given attribute to the given value.
|
221
206
|
#
|
222
207
|
# @private
|
223
208
|
def attribute_set(name, value)
|
@@ -225,7 +210,7 @@ module Spira
|
|
225
210
|
end
|
226
211
|
|
227
212
|
##
|
228
|
-
#
|
213
|
+
# Get the current value for the given attribute
|
229
214
|
#
|
230
215
|
# @private
|
231
216
|
def attribute_get(name)
|
@@ -251,12 +236,12 @@ module Spira
|
|
251
236
|
new = []
|
252
237
|
attribute.each do |value|
|
253
238
|
value = self.class.build_rdf_value(value, self.class.properties[name][:type])
|
254
|
-
new << RDF::Statement.new(@
|
239
|
+
new << RDF::Statement.new(@subject, self.class.properties[name][:predicate], value)
|
255
240
|
end
|
256
241
|
repo.insert(*new)
|
257
242
|
else
|
258
243
|
value = self.class.build_rdf_value(attribute, self.class.properties[name][:type])
|
259
|
-
repo.insert(RDF::Statement.new(@
|
244
|
+
repo.insert(RDF::Statement.new(@subject, self.class.properties[name][:predicate], value))
|
260
245
|
end
|
261
246
|
end
|
262
247
|
repo
|
@@ -273,7 +258,7 @@ module Spira
|
|
273
258
|
# TODO: define behavior for equality on subclasses.
|
274
259
|
# TODO: should we compare attributes here?
|
275
260
|
when self.class
|
276
|
-
@
|
261
|
+
@subject == other.uri
|
277
262
|
when RDF::Enumerable
|
278
263
|
self.isomorphic_with?(other)
|
279
264
|
else
|
@@ -281,6 +266,60 @@ module Spira
|
|
281
266
|
end
|
282
267
|
end
|
283
268
|
|
269
|
+
##
|
270
|
+
# Returns true for :to_uri if this instance's subject is a URI, and false if it is not.
|
271
|
+
# Returns true for :to_node if this instance's subject is a Node, and false if it is not.
|
272
|
+
# Calls super otherwise.
|
273
|
+
#
|
274
|
+
# @private
|
275
|
+
def respond_to?(*args)
|
276
|
+
case args[0]
|
277
|
+
when :to_uri
|
278
|
+
@subject.respond_to?(:to_uri)
|
279
|
+
when :to_node
|
280
|
+
@subject.node?
|
281
|
+
else
|
282
|
+
super(*args)
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
##
|
287
|
+
# Returns the RDF::URI associated with this instance if this instance's
|
288
|
+
# subject is an RDF::URI, and nil otherwise.
|
289
|
+
#
|
290
|
+
# @return [RDF::URI,nil]
|
291
|
+
def uri
|
292
|
+
@subject.respond_to?(:to_uri) ? @subject : nil
|
293
|
+
end
|
294
|
+
|
295
|
+
##
|
296
|
+
# Returns the URI representation of this resource, if available. If this
|
297
|
+
# resource's subject is a BNode, raises a NoMethodError.
|
298
|
+
#
|
299
|
+
# @return [RDF::URI]
|
300
|
+
# @raise [NoMethodError]
|
301
|
+
def to_uri
|
302
|
+
uri || (raise NoMethodError, "No such method: :to_uri (this instance's subject is not a URI")
|
303
|
+
end
|
304
|
+
|
305
|
+
##
|
306
|
+
# Returns true if the subject associated with this instance is a blank node.
|
307
|
+
#
|
308
|
+
# @return [true, false]
|
309
|
+
def node?
|
310
|
+
@subject.node?
|
311
|
+
end
|
312
|
+
|
313
|
+
##
|
314
|
+
# Returns the Node subject of this resource, if available. If this
|
315
|
+
# resource's subject is a URI, raises a NoMethodError.
|
316
|
+
#
|
317
|
+
# @return [RDF::Node]
|
318
|
+
# @raise [NoMethodError]
|
319
|
+
def to_node
|
320
|
+
@subject.node? ? @subject : (raise NoMethodError, "No such method: :to_uri (this instance's subject is not a URI")
|
321
|
+
end
|
322
|
+
|
284
323
|
##
|
285
324
|
# The validation errors collection associated with this instance.
|
286
325
|
#
|
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
|
+
- 3
|
9
|
+
version: 0.0.3
|
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-06-
|
18
|
+
date: 2010-06-07 00:00:00 +02:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -28,8 +28,8 @@ dependencies:
|
|
28
28
|
segments:
|
29
29
|
- 0
|
30
30
|
- 1
|
31
|
-
-
|
32
|
-
version: 0.1.
|
31
|
+
- 10
|
32
|
+
version: 0.1.10
|
33
33
|
type: :development
|
34
34
|
version_requirements: *id001
|
35
35
|
- !ruby/object:Gem::Dependency
|
@@ -70,8 +70,8 @@ dependencies:
|
|
70
70
|
segments:
|
71
71
|
- 0
|
72
72
|
- 1
|
73
|
-
-
|
74
|
-
version: 0.1.
|
73
|
+
- 10
|
74
|
+
version: 0.1.10
|
75
75
|
type: :runtime
|
76
76
|
version_requirements: *id004
|
77
77
|
- !ruby/object:Gem::Dependency
|
@@ -98,8 +98,8 @@ dependencies:
|
|
98
98
|
segments:
|
99
99
|
- 0
|
100
100
|
- 1
|
101
|
-
-
|
102
|
-
version: 0.1.
|
101
|
+
- 1
|
102
|
+
version: 0.1.1
|
103
103
|
type: :runtime
|
104
104
|
version_requirements: *id006
|
105
105
|
description: Spira is a framework for using the information in RDF.rb repositories as model objects.
|
@@ -111,6 +111,7 @@ extensions: []
|
|
111
111
|
extra_rdoc_files: []
|
112
112
|
|
113
113
|
files:
|
114
|
+
- CHANGES.md
|
114
115
|
- AUTHORS
|
115
116
|
- README
|
116
117
|
- UNLICENSE
|