spira 0.0.7 → 0.0.8
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 +18 -0
- data/README +2 -2
- data/VERSION +1 -1
- data/lib/spira.rb +8 -6
- data/lib/spira/resource/class_methods.rb +24 -5
- data/lib/spira/resource/dsl.rb +7 -4
- data/lib/spira/resource/instance_methods.rb +10 -2
- data/lib/spira/version.rb +1 -1
- metadata +3 -3
data/CHANGES.md
CHANGED
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
# Changelog for Spira <http://github.com/datagraph/spira>
|
|
2
|
+
## 0.0.8
|
|
3
|
+
* Remove type checking for repository addition. More power in return for
|
|
4
|
+
slightly more difficult error messages.
|
|
5
|
+
* Repositories added via klass, *arg are now only instantiated on first use
|
|
6
|
+
instead of immediately.
|
|
7
|
+
* RDF::URI#as, RDF::Node#as, Resource.for, and Resource#new can now all accept
|
|
8
|
+
a block, which yields the new instance and saves it after the block.
|
|
9
|
+
* Clarify error message when default repository is not setup
|
|
10
|
+
* Added a weak-reference identity map for each instance. Any circular references in
|
|
11
|
+
relations will now return the original object instead of querying for a new
|
|
12
|
+
one.
|
|
13
|
+
* Use a weak-reference identity map when iterating by class.
|
|
14
|
+
* When serializing/unserializing, duck typing (:serialize, :unserialize) is now
|
|
15
|
+
permitted.
|
|
16
|
+
|
|
17
|
+
## 0.0.7
|
|
18
|
+
* Added Resource.[], an alias for Resource.for
|
|
19
|
+
* Resource.each now correctly raises an exception when a repository isn't found
|
|
2
20
|
|
|
3
21
|
## 0.0.6
|
|
4
22
|
* Added #exists?, which returns a boolean if an instance exists in
|
data/README
CHANGED
|
@@ -90,7 +90,7 @@ Then use your model classes, in a way more or less similar to any number of ORMs
|
|
|
90
90
|
artist.cds = [cd]
|
|
91
91
|
artist.save!
|
|
92
92
|
|
|
93
|
-
queen =
|
|
93
|
+
queen = Artist.for('queen')
|
|
94
94
|
hits = CD.for 'queens-greatest-hits'
|
|
95
95
|
hits.artist == artist == queen
|
|
96
96
|
|
|
@@ -117,7 +117,7 @@ Any call to 'for' with a valid identifier will always return an object with nil
|
|
|
117
117
|
fields. It's a way of looking at a given resource, not a closed-world mapping
|
|
118
118
|
to one.
|
|
119
119
|
|
|
120
|
-
You can
|
|
120
|
+
You can also use blank nodes more or less as you would a URI:
|
|
121
121
|
|
|
122
122
|
remix_artist = Artist.for(RDF::Node.new)
|
|
123
123
|
# => <Artist @subject=#<RDF::Node:0xd1d314(_:g13751060)>>
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.0.
|
|
1
|
+
0.0.8
|
data/lib/spira.rb
CHANGED
|
@@ -60,12 +60,10 @@ module Spira
|
|
|
60
60
|
# @see RDF::Repository
|
|
61
61
|
def add_repository(name, klass, *args)
|
|
62
62
|
repositories[name] = case klass
|
|
63
|
-
when RDF::Repository
|
|
64
|
-
klass
|
|
65
63
|
when Class
|
|
66
|
-
klass.new(*args)
|
|
64
|
+
promise { klass.new(*args) }
|
|
67
65
|
else
|
|
68
|
-
|
|
66
|
+
klass
|
|
69
67
|
end
|
|
70
68
|
if (name == :default) && settings[:repositories][name].nil?
|
|
71
69
|
warn "WARNING: Adding nil default repository"
|
|
@@ -128,10 +126,12 @@ module RDF
|
|
|
128
126
|
# RDF::URI('http://example.org/person/bob').as(Person)
|
|
129
127
|
# @param [Class] klass
|
|
130
128
|
# @param [*Any] args Any arguments to pass to klass.for
|
|
129
|
+
# @yield [self] Executes a given block and calls `#save!`
|
|
130
|
+
# @yieldparam [self] self The newly created instance
|
|
131
131
|
# @return [Klass] An instance of klass
|
|
132
|
-
def as(klass, *args)
|
|
132
|
+
def as(klass, *args, &block)
|
|
133
133
|
raise ArgumentError, "#{klass} is not a Spira resource" unless klass.is_a?(Class) && klass.ancestors.include?(Spira::Resource)
|
|
134
|
-
klass.for(self, *args)
|
|
134
|
+
klass.for(self, *args, &block)
|
|
135
135
|
end
|
|
136
136
|
end
|
|
137
137
|
|
|
@@ -144,6 +144,8 @@ module RDF
|
|
|
144
144
|
# RDF::Node.new.as(Person)
|
|
145
145
|
# @param [Class] klass
|
|
146
146
|
# @param [*Any] args Any arguments to pass to klass.for
|
|
147
|
+
# @yield [self] Executes a given block and calls `#save!`
|
|
148
|
+
# @yieldparam [self] self The newly created instance
|
|
147
149
|
# @return [Klass] An instance of klass
|
|
148
150
|
def as(klass, *args)
|
|
149
151
|
raise ArgumentError, "#{klass} is not a Spira resource" unless klass.is_a?(Class) && klass.ancestors.include?(Spira::Resource)
|
|
@@ -32,7 +32,7 @@ module Spira
|
|
|
32
32
|
# @return [RDF::Repository]
|
|
33
33
|
# @private
|
|
34
34
|
def repository_or_fail
|
|
35
|
-
repository || (raise Spira::NoRepositoryError, "#{self} is configured to use
|
|
35
|
+
repository || (raise Spira::NoRepositoryError, "#{self} is configured to use :#{@repository_name || 'default'} as a repository, but it has not been set.")
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
##
|
|
@@ -61,14 +61,16 @@ module Spira
|
|
|
61
61
|
# @overload for(identifier, attributes = {})
|
|
62
62
|
# @param [Any] uri The identifier to append to the base URI for this class
|
|
63
63
|
# @param [Hash{Symbol => Any}] attributes Initial attributes
|
|
64
|
+
# @yield [self] Executes a given block and calls `#save!`
|
|
65
|
+
# @yieldparam [self] self The newly created instance
|
|
64
66
|
# @return [Spira::Resource] The newly created instance
|
|
65
67
|
# @see http://rdf.rubyforge.org/RDF/URI.html
|
|
66
|
-
def for(identifier, attributes = {})
|
|
68
|
+
def for(identifier, attributes = {}, &block)
|
|
67
69
|
if !self.type.nil? && attributes[:type]
|
|
68
70
|
raise TypeError, "#{self} has an RDF type, #{self.type}, and cannot accept one as an argument."
|
|
69
71
|
end
|
|
70
72
|
subject = id_for(identifier)
|
|
71
|
-
self.new(attributes.merge(:_subject => subject))
|
|
73
|
+
instance = self.new(attributes.merge(:_subject => subject), &block)
|
|
72
74
|
end
|
|
73
75
|
|
|
74
76
|
##
|
|
@@ -124,6 +126,23 @@ module Spira
|
|
|
124
126
|
repository.query(:predicate => RDF.type, :object => @type).subjects.count
|
|
125
127
|
end
|
|
126
128
|
|
|
129
|
+
##
|
|
130
|
+
# A cache of iterated instances of this projection
|
|
131
|
+
#
|
|
132
|
+
# @return [RDF::Util::Cache]
|
|
133
|
+
# @private
|
|
134
|
+
def cache
|
|
135
|
+
@cache ||= RDF::Util::Cache.new
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
##
|
|
139
|
+
# Clear the iteration cache
|
|
140
|
+
#
|
|
141
|
+
# @return [void]
|
|
142
|
+
def reload
|
|
143
|
+
@cache = nil
|
|
144
|
+
end
|
|
145
|
+
|
|
127
146
|
##
|
|
128
147
|
# Enumerate over all resources projectable as this class. This method is
|
|
129
148
|
# only valid for classes which declare a `type` with the `type` method in
|
|
@@ -146,12 +165,12 @@ module Spira
|
|
|
146
165
|
enum_for(:each)
|
|
147
166
|
else
|
|
148
167
|
repository_or_fail.query(:predicate => RDF.type, :object => @type).each_subject do |subject|
|
|
149
|
-
|
|
168
|
+
self.cache[subject] ||= self.for(subject)
|
|
169
|
+
block.call(cache[subject])
|
|
150
170
|
end
|
|
151
171
|
end
|
|
152
172
|
end
|
|
153
173
|
|
|
154
|
-
|
|
155
174
|
##
|
|
156
175
|
# Returns true if the given property is a has_many property, false otherwise
|
|
157
176
|
#
|
data/lib/spira/resource/dsl.rb
CHANGED
|
@@ -132,15 +132,18 @@ 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, cache)
|
|
136
136
|
case
|
|
137
137
|
when statement == nil
|
|
138
138
|
nil
|
|
139
|
-
when
|
|
139
|
+
when !cache[statement.object].nil?
|
|
140
|
+
cache[statement.object]
|
|
141
|
+
when type.respond_to?(:unserialize)
|
|
140
142
|
type.unserialize(statement.object)
|
|
141
143
|
when type.is_a?(Symbol) || type.is_a?(String)
|
|
142
144
|
klass = classize_resource(type)
|
|
143
|
-
promise { klass.for(statement.object) }
|
|
145
|
+
cache[statement.object] = promise { klass.for(statement.object, :_cache => cache) }
|
|
146
|
+
cache[statement.object]
|
|
144
147
|
else
|
|
145
148
|
raise TypeError, "Unable to unserialize #{statement.object} as #{type}"
|
|
146
149
|
end
|
|
@@ -150,7 +153,7 @@ module Spira
|
|
|
150
153
|
# @private
|
|
151
154
|
def build_rdf_value(value, type)
|
|
152
155
|
case
|
|
153
|
-
when type.
|
|
156
|
+
when type.respond_to?(:serialize)
|
|
154
157
|
type.serialize(value)
|
|
155
158
|
when value && value.class.ancestors.include?(Spira::Resource)
|
|
156
159
|
klass = classize_resource(type)
|
|
@@ -27,12 +27,18 @@ module Spira
|
|
|
27
27
|
# {Spira::Resource::ClassMethods#for} instead.
|
|
28
28
|
#
|
|
29
29
|
# @param [Hash{Symbol => Any}] opts Default attributes for this instance
|
|
30
|
+
# @yield [self] Executes a given block and calls `#save!`
|
|
31
|
+
# @yieldparam [self] self The newly created instance
|
|
30
32
|
# @see Spira::Resource::ClassMethods#for
|
|
31
33
|
# @see RDF::URI#as
|
|
32
34
|
# @see RDF::Node#as
|
|
33
35
|
def initialize(opts = {})
|
|
34
36
|
@subject = opts[:_subject] || RDF::Node.new
|
|
35
37
|
reload(opts)
|
|
38
|
+
if block_given?
|
|
39
|
+
yield(self)
|
|
40
|
+
save!
|
|
41
|
+
end
|
|
36
42
|
end
|
|
37
43
|
|
|
38
44
|
##
|
|
@@ -43,6 +49,8 @@ module Spira
|
|
|
43
49
|
# @param [Hash{Symbol => Any}] opts
|
|
44
50
|
# @option opts [Symbol] :any A property name. Sets the given property to the given value.
|
|
45
51
|
def reload(opts = {})
|
|
52
|
+
@cache = opts[:_cache] || RDF::Util::Cache.new
|
|
53
|
+
@cache[subject] = self
|
|
46
54
|
@dirty = {}
|
|
47
55
|
# We need to save all attributes twice to track state changes in
|
|
48
56
|
# mutable objects, like lists
|
|
@@ -70,14 +78,14 @@ module Spira
|
|
|
70
78
|
collection = statements.query(:subject => @subject, :predicate => property[:predicate]) unless statements.empty?
|
|
71
79
|
unless collection.nil?
|
|
72
80
|
collection.each do |statement|
|
|
73
|
-
values << self.class.build_value(statement,property[:type])
|
|
81
|
+
values << self.class.build_value(statement,property[:type], @cache)
|
|
74
82
|
end
|
|
75
83
|
end
|
|
76
84
|
attributes[:current][name] = values
|
|
77
85
|
attributes[:original][name] = values.dup
|
|
78
86
|
else
|
|
79
87
|
statement = statements.query(:subject => @subject, :predicate => property[:predicate]).first unless statements.empty?
|
|
80
|
-
attributes[:current][name] = self.class.build_value(statement, property[:type])
|
|
88
|
+
attributes[:current][name] = self.class.build_value(statement, property[:type], @cache)
|
|
81
89
|
|
|
82
90
|
# Lots of things like Fixnums and Nil can't be dup'd, but they are
|
|
83
91
|
# all immutable, so if we can't dup, it's no problem for dirty tracking.
|
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
|
+
- 8
|
|
9
|
+
version: 0.0.8
|
|
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-09-
|
|
18
|
+
date: 2010-09-22 00:00:00 -05:00
|
|
19
19
|
default_executable:
|
|
20
20
|
dependencies:
|
|
21
21
|
- !ruby/object:Gem::Dependency
|