spira 0.0.5 → 0.0.6
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/CHANGES.md +25 -0
- data/VERSION +1 -1
- data/lib/spira/exceptions.rb +4 -0
- data/lib/spira/resource/dsl.rb +7 -18
- data/lib/spira/resource/instance_methods.rb +171 -61
- data/lib/spira/types/any.rb +3 -2
- data/lib/spira/version.rb +1 -1
- metadata +9 -9
data/CHANGES.md
CHANGED
@@ -1,5 +1,30 @@
|
|
1
1
|
# Changelog for Spira <http://github.com/datagraph/spira>
|
2
2
|
|
3
|
+
## 0.0.6
|
4
|
+
* Added #exists?, which returns a boolean if an instance exists in
|
5
|
+
the backing store.
|
6
|
+
* Added #data, which returns an enumerator of all RDF data for a subject, not
|
7
|
+
just model data.
|
8
|
+
* #save! and #update! now return self for chaining
|
9
|
+
* Implemented #update and #update!, which allow setting multiple properties
|
10
|
+
at once
|
11
|
+
* Existing values not matching a model's defined type will now be deleted on
|
12
|
+
#save!
|
13
|
+
* Saving resources will now only update dirty fields
|
14
|
+
* Saving resources now removes all existing triples for a given predicate
|
15
|
+
if the field was updated instead of only removing one.
|
16
|
+
* Implemented and documented #destroy!, #destroy!(:subject),
|
17
|
+
#destroy!(:object), and #destroy!(:completely). Removed #destroy_resource!
|
18
|
+
* has_many collections are now Sets and not Arrays, more accurately reflecting
|
19
|
+
RDF semantics.
|
20
|
+
* The Any (default) property type will now work fine with URIs
|
21
|
+
* Added ResourceDeclarationError to replace various errors that occur during
|
22
|
+
invalid class declarations via the DSL.
|
23
|
+
* Raise an error if a non-URI predicate is given in the DSL
|
24
|
+
* Small updates for RDF.rb 0.2.0
|
25
|
+
* Implemented dirty field tracking. Resource#dirty?(:name) will now report if
|
26
|
+
a field has not been saved.
|
27
|
+
|
3
28
|
## 0.0.5
|
4
29
|
* Relations can now find related classes in modules, either by absolute
|
5
30
|
reference, or by class name if they are in the same namespace.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.6
|
data/lib/spira/exceptions.rb
CHANGED
@@ -12,4 +12,8 @@ module Spira
|
|
12
12
|
##
|
13
13
|
# For cases in which a repository is required but none has been given
|
14
14
|
class NoRepositoryError < StandardError ; end
|
15
|
+
|
16
|
+
##
|
17
|
+
# For errors in the DSL, such as invalid predicates
|
18
|
+
class ResourceDeclarationError < StandardError ; end
|
15
19
|
end
|
data/lib/spira/resource/dsl.rb
CHANGED
@@ -221,11 +221,14 @@ module Spira
|
|
221
221
|
when opts[:predicate]
|
222
222
|
opts[:predicate]
|
223
223
|
when @default_vocabulary.nil?
|
224
|
-
raise
|
224
|
+
raise ResourceDeclarationError, "A :predicate option is required for types without a default vocabulary"
|
225
225
|
else @default_vocabulary
|
226
226
|
separator = @default_vocabulary.to_s[-1,1] =~ /(\/|#)/ ? '' : '/'
|
227
227
|
RDF::URI.new(@default_vocabulary.to_s + separator + name.to_s)
|
228
228
|
end
|
229
|
+
if !(predicate.respond_to?(:to_uri))
|
230
|
+
raise ResourceDeclarationError, ":predicate options must be RDF::URIs or strings with a default vocabulary declared"
|
231
|
+
end
|
229
232
|
type = case
|
230
233
|
when opts[:type].nil?
|
231
234
|
Spira::Types::Any
|
@@ -241,27 +244,13 @@ module Spira
|
|
241
244
|
@properties[name][:type] = type
|
242
245
|
name_equals = (name.to_s + "=").to_sym
|
243
246
|
|
244
|
-
|
245
|
-
|
246
|
-
self.send(:define_method,name, &getter)
|
247
|
-
|
248
|
-
end
|
249
|
-
|
250
|
-
##
|
251
|
-
# Getter and Setter methods for predicates.
|
252
|
-
# FIXME: this and add_accessors are from an older version in which
|
253
|
-
# multiple versions of accessors existed, and can be refactored.
|
254
|
-
# @private
|
255
|
-
def hash_accessors(name, predicate, type)
|
256
|
-
setter = lambda do |arg|
|
257
|
-
attribute_set(name,arg)
|
247
|
+
self.send(:define_method,name_equals) do |arg|
|
248
|
+
attribute_set(name, arg)
|
258
249
|
end
|
259
|
-
|
260
|
-
getter = lambda do
|
250
|
+
self.send(:define_method,name) do
|
261
251
|
attribute_get(name)
|
262
252
|
end
|
263
253
|
|
264
|
-
[getter, setter]
|
265
254
|
end
|
266
255
|
|
267
256
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'rdf/isomorphic'
|
2
|
+
require 'set'
|
2
3
|
|
3
4
|
module Spira
|
4
5
|
module Resource
|
@@ -25,7 +26,7 @@ module Spira
|
|
25
26
|
# attributes. To use a URI or existing blank node as a subject, use
|
26
27
|
# {Spira::Resource::ClassMethods#for} instead.
|
27
28
|
#
|
28
|
-
# @param [Hash] opts Default attributes for this instance
|
29
|
+
# @param [Hash{Symbol => Any}] opts Default attributes for this instance
|
29
30
|
# @see Spira::Resource::ClassMethods#for
|
30
31
|
# @see RDF::URI#as
|
31
32
|
# @see RDF::Node#as
|
@@ -42,8 +43,10 @@ module Spira
|
|
42
43
|
# @param [Hash{Symbol => Any}] opts
|
43
44
|
# @option opts [Symbol] :any A property name. Sets the given property to the given value.
|
44
45
|
def reload(opts = {})
|
46
|
+
@dirty = {}
|
47
|
+
# We need to save all attributes twice to track state changes in
|
48
|
+
# mutable objects, like lists
|
45
49
|
@attributes = promise { reload_attributes }
|
46
|
-
@original_attributes = promise { @attributes.force ; @original_attributes }
|
47
50
|
self.class.properties.each do |name, predicate|
|
48
51
|
attribute_set(name, opts[name]) unless opts[name].nil?
|
49
52
|
end
|
@@ -52,47 +55,53 @@ module Spira
|
|
52
55
|
##
|
53
56
|
# Load this instance's attributes. Overwrite loaded values with attributes in the given options.
|
54
57
|
#
|
55
|
-
# @
|
56
|
-
# @return [Hash] @attributes
|
58
|
+
# @return [Hash{Symbol => Any}] attributes
|
57
59
|
# @private
|
58
60
|
def reload_attributes()
|
59
61
|
statements = self.class.repository_or_fail.query(:subject => @subject)
|
60
|
-
|
62
|
+
attributes = {}
|
63
|
+
attributes[:original] = {}
|
64
|
+
attributes[:current] = {}
|
61
65
|
|
62
|
-
|
63
|
-
|
64
|
-
self.class.
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
# map of some sort to fix that.
|
71
|
-
values = []
|
72
|
-
collection = statements.query(:subject => @subject, :predicate => property[:predicate])
|
73
|
-
unless collection.nil?
|
74
|
-
collection.each do |statement|
|
75
|
-
values << self.class.build_value(statement,property[:type])
|
76
|
-
end
|
66
|
+
# Set attributes for each statement corresponding to a predicate
|
67
|
+
self.class.properties.each do |name, property|
|
68
|
+
if self.class.is_list?(name)
|
69
|
+
values = Set.new
|
70
|
+
collection = statements.query(:subject => @subject, :predicate => property[:predicate]) unless statements.empty?
|
71
|
+
unless collection.nil?
|
72
|
+
collection.each do |statement|
|
73
|
+
values << self.class.build_value(statement,property[:type])
|
77
74
|
end
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
75
|
+
end
|
76
|
+
attributes[:current][name] = values
|
77
|
+
attributes[:original][name] = values.dup
|
78
|
+
else
|
79
|
+
statement = statements.query(:subject => @subject, :predicate => property[:predicate]).first unless statements.empty?
|
80
|
+
attributes[:current][name] = self.class.build_value(statement, property[:type])
|
81
|
+
|
82
|
+
# Lots of things like Fixnums and Nil can't be dup'd, but they are
|
83
|
+
# all immutable, so if we can't dup, it's no problem for dirty tracking.
|
84
|
+
begin
|
85
|
+
attributes[:original][name] = attributes[:current][name].dup
|
86
|
+
rescue TypeError
|
87
|
+
attributes[:original][name] = attributes[:current][name]
|
82
88
|
end
|
83
89
|
end
|
84
90
|
end
|
85
91
|
|
86
|
-
|
87
|
-
|
88
|
-
# as many triples for a subject and predicate as we want.
|
89
|
-
@original_attributes = {}
|
90
|
-
@original_attributes = @attributes.dup
|
91
|
-
@original_attributes.each do | name, value |
|
92
|
-
@original_attributes[name] = value.dup if value.is_a?(Array)
|
93
|
-
end
|
92
|
+
attributes
|
93
|
+
end
|
94
94
|
|
95
|
-
|
95
|
+
##
|
96
|
+
# Returns a hash of name => value for this instance's attributes
|
97
|
+
#
|
98
|
+
# @return [Hash{Symbol => Any}] attributes
|
99
|
+
def attributes
|
100
|
+
attributes = {}
|
101
|
+
self.class.properties.keys.each do |property|
|
102
|
+
attributes[property] = attribute_get(property)
|
103
|
+
end
|
104
|
+
attributes
|
96
105
|
end
|
97
106
|
|
98
107
|
##
|
@@ -109,29 +118,35 @@ module Spira
|
|
109
118
|
end
|
110
119
|
|
111
120
|
##
|
112
|
-
#
|
113
|
-
# not associated with this model.
|
121
|
+
# Delete this instance from the repository.
|
114
122
|
#
|
123
|
+
# @param [Symbol] what
|
124
|
+
# @example Delete all fields defined in the model
|
125
|
+
# @object.destroy!
|
126
|
+
# @example Delete all instances of this object as the subject of a triple, including non-model data @object.destroy!
|
127
|
+
# @object.destroy!(:subject)
|
128
|
+
# @example Delete all instances of this object as the object of a triple
|
129
|
+
# @object.destroy!(:object)
|
130
|
+
# @example Delete all triples with this object as the subject or object
|
131
|
+
# @object.destroy!(:completely)
|
115
132
|
# @return [true, false] Whether or not the destroy was successful
|
116
|
-
def destroy!
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
def destroy_resource!
|
128
|
-
self.class.repository_or_fail.delete([@subject,nil,nil])
|
133
|
+
def destroy!(what = nil)
|
134
|
+
case what
|
135
|
+
when nil
|
136
|
+
_destroy_attributes(attributes, :destroy_type => true) != nil
|
137
|
+
when :subject
|
138
|
+
self.class.repository_or_fail.delete([subject, nil, nil]) != nil
|
139
|
+
when :object
|
140
|
+
self.class.repository_or_fail.delete([nil, nil, subject]) != nil
|
141
|
+
when :completely
|
142
|
+
destroy!(:subject) && destroy!(:object)
|
143
|
+
end
|
129
144
|
end
|
130
145
|
|
131
146
|
##
|
132
147
|
# Save changes in this instance to the repository.
|
133
148
|
#
|
134
|
-
# @return [
|
149
|
+
# @return [self] self
|
135
150
|
def save!
|
136
151
|
unless self.class.validators.empty?
|
137
152
|
errors.clear
|
@@ -144,6 +159,47 @@ module Spira
|
|
144
159
|
else
|
145
160
|
_update!
|
146
161
|
end
|
162
|
+
self
|
163
|
+
end
|
164
|
+
|
165
|
+
##
|
166
|
+
# Update multiple attributes of this repository.
|
167
|
+
#
|
168
|
+
# @example Update multiple attributes
|
169
|
+
# person.update(:name => 'test', :age => 10)
|
170
|
+
# #=> person
|
171
|
+
# person.name
|
172
|
+
# #=> 'test'
|
173
|
+
# person.age
|
174
|
+
# #=> 10
|
175
|
+
# person.dirty?
|
176
|
+
# #=> true
|
177
|
+
# @param [Hash{Symbol => Any}] properties
|
178
|
+
# @return [self]
|
179
|
+
def update(properties)
|
180
|
+
properties.each do |property, value|
|
181
|
+
attribute_set(property, value)
|
182
|
+
end
|
183
|
+
self
|
184
|
+
end
|
185
|
+
|
186
|
+
##
|
187
|
+
# Equivalent to #update followed by #save!
|
188
|
+
#
|
189
|
+
# @example Update multiple attributes and save the changes
|
190
|
+
# person.update!(:name => 'test', :age => 10)
|
191
|
+
# #=> person
|
192
|
+
# person.name
|
193
|
+
# #=> 'test'
|
194
|
+
# person.age
|
195
|
+
# #=> 10
|
196
|
+
# person.dirty?
|
197
|
+
# #=> false
|
198
|
+
# @param [Hash{Symbol => Any}] properties
|
199
|
+
# @return [self]
|
200
|
+
def update!(properties)
|
201
|
+
update(properties)
|
202
|
+
save!
|
147
203
|
end
|
148
204
|
|
149
205
|
##
|
@@ -151,9 +207,23 @@ module Spira
|
|
151
207
|
#
|
152
208
|
# @private
|
153
209
|
def _update!
|
154
|
-
|
155
|
-
|
156
|
-
|
210
|
+
self.class.properties.each do |property, predicate|
|
211
|
+
if dirty?(property)
|
212
|
+
self.class.repository_or_fail.delete([subject, predicate[:predicate], nil])
|
213
|
+
if self.class.is_list?(property)
|
214
|
+
repo = RDF::Repository.new
|
215
|
+
attribute_get(property).each do |value|
|
216
|
+
repo << RDF::Statement.new(subject, predicate[:predicate], self.class.build_rdf_value(value, self.class.properties[property][:type]))
|
217
|
+
end
|
218
|
+
self.class.repository_or_fail.insert(*repo)
|
219
|
+
else
|
220
|
+
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])))
|
221
|
+
end
|
222
|
+
end
|
223
|
+
@dirty[property] = nil
|
224
|
+
@attributes[:original][property] = attribute_get(property)
|
225
|
+
end
|
226
|
+
self.class.repository_or_fail.insert(RDF::Statement.new(@subject, RDF.type, type)) unless type.nil?
|
157
227
|
end
|
158
228
|
|
159
229
|
##
|
@@ -197,18 +267,44 @@ module Spira
|
|
197
267
|
#
|
198
268
|
# @see http://rdf.rubyforge.org/RDF/Enumerable.html
|
199
269
|
def each(*args, &block)
|
200
|
-
return
|
201
|
-
repository = repository_for_attributes(
|
270
|
+
return enum_for(:each) unless block_given?
|
271
|
+
repository = repository_for_attributes(attributes)
|
202
272
|
repository.insert(RDF::Statement.new(@subject, RDF.type, type)) unless type.nil?
|
203
273
|
repository.each(*args, &block)
|
204
274
|
end
|
205
275
|
|
276
|
+
##
|
277
|
+
# The number of RDF::Statements this projection has.
|
278
|
+
#
|
279
|
+
# @see http://rdf.rubyforge.org/RDF/Enumerable.html#count
|
280
|
+
def count
|
281
|
+
each.size
|
282
|
+
end
|
283
|
+
|
206
284
|
##
|
207
285
|
# Sets the given attribute to the given value.
|
208
286
|
#
|
209
287
|
# @private
|
210
288
|
def attribute_set(name, value)
|
211
|
-
@
|
289
|
+
@dirty[name] = true
|
290
|
+
@attributes[:current][name] = value
|
291
|
+
end
|
292
|
+
|
293
|
+
##
|
294
|
+
# Returns true if the given attribute has been changed from the backing store
|
295
|
+
#
|
296
|
+
def dirty?(name = nil)
|
297
|
+
case name
|
298
|
+
when nil
|
299
|
+
self.class.properties.keys.any? { |key| dirty?(key) }
|
300
|
+
else
|
301
|
+
case
|
302
|
+
when @dirty[name] == true
|
303
|
+
true
|
304
|
+
else
|
305
|
+
@attributes[:current][name] != @attributes[:original][name]
|
306
|
+
end
|
307
|
+
end
|
212
308
|
end
|
213
309
|
|
214
310
|
##
|
@@ -216,12 +312,7 @@ module Spira
|
|
216
312
|
#
|
217
313
|
# @private
|
218
314
|
def attribute_get(name)
|
219
|
-
|
220
|
-
when true
|
221
|
-
@attributes[name] ||= []
|
222
|
-
when false
|
223
|
-
@attributes[name]
|
224
|
-
end
|
315
|
+
@attributes[:current][name]
|
225
316
|
end
|
226
317
|
|
227
318
|
##
|
@@ -331,6 +422,25 @@ module Spira
|
|
331
422
|
@errors ||= Spira::Errors.new
|
332
423
|
end
|
333
424
|
|
425
|
+
##
|
426
|
+
# Returns true if any data exists for this subject in the backing RDF store
|
427
|
+
#
|
428
|
+
# @return [Boolean]
|
429
|
+
def exists?
|
430
|
+
!data.empty?
|
431
|
+
end
|
432
|
+
alias_method :exist?, :exists?
|
433
|
+
|
434
|
+
##
|
435
|
+
# Returns an Enumerator of all RDF data for this subject, not just model data.
|
436
|
+
#
|
437
|
+
# @see #each
|
438
|
+
# @see http://rdf.rubyforge.org/RDF/Enumerable.html
|
439
|
+
# @return [Enumerator]
|
440
|
+
def data
|
441
|
+
self.class.repository.query(:subject => subject)
|
442
|
+
end
|
443
|
+
|
334
444
|
## We have defined #each and can do this fun RDF stuff by default
|
335
445
|
include ::RDF::Enumerable, ::RDF::Queryable
|
336
446
|
|
data/lib/spira/types/any.rb
CHANGED
@@ -12,11 +12,12 @@ module Spira::Types
|
|
12
12
|
include Spira::Type
|
13
13
|
|
14
14
|
def self.unserialize(value)
|
15
|
-
value.object
|
15
|
+
value.respond_to?(:to_uri) ? value.to_uri : value.object
|
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.respond_to?(:to_uri) ? value.to_uri : RDF::Literal.new(value)
|
20
21
|
end
|
21
22
|
|
22
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
|
+
- 6
|
9
|
+
version: 0.0.6
|
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-08-23 00:00:00 -04: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
|
- 2
|
31
|
-
-
|
32
|
-
version: 0.2.
|
31
|
+
- 2
|
32
|
+
version: 0.2.2
|
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
|
- 2
|
73
|
-
-
|
74
|
-
version: 0.2.
|
73
|
+
- 2
|
74
|
+
version: 0.2.2
|
75
75
|
type: :runtime
|
76
76
|
version_requirements: *id004
|
77
77
|
- !ruby/object:Gem::Dependency
|
@@ -97,9 +97,9 @@ dependencies:
|
|
97
97
|
- !ruby/object:Gem::Version
|
98
98
|
segments:
|
99
99
|
- 0
|
100
|
-
-
|
100
|
+
- 3
|
101
101
|
- 0
|
102
|
-
version: 0.
|
102
|
+
version: 0.3.0
|
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.
|