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