spira 0.0.12 → 0.5.0

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/lib/spira/errors.rb DELETED
@@ -1,94 +0,0 @@
1
- module Spira
2
-
3
- ##
4
- # Spira::Errors represents a collection of validation errors for a Spira
5
- # resource. It tracks a list of errors for each field.
6
- #
7
- # This class does not perform validations. It only tracks the results of
8
- # them.
9
- class Errors
10
-
11
- ##
12
- # Creates a new Spira::Errors
13
- #
14
- # @return [Spira::Errors]
15
- def initialize
16
- @errors = {}
17
- end
18
-
19
- ##
20
- # Returns true if there are no errors, false otherwise
21
- #
22
- # @return [true, false]
23
- def empty?
24
- @errors.all? do |field, errors| errors.empty? end
25
- end
26
-
27
- ##
28
- # Returns true if there are errors, false otherwise
29
- #
30
- # @return [true, false]
31
- def any?
32
- !empty?
33
- end
34
-
35
- ##
36
- # Returns true if the given property or list has any errors
37
- #
38
- # @param [Symbol] name The name of the property or list
39
- # @return [true, false]
40
- def any_for?(property)
41
- !(@errors[property].nil?) && !(@errors[property].empty?)
42
- end
43
-
44
- ##
45
- # Add an error to a given property or list
46
- #
47
- # @example Add an error to a property
48
- # errors.add(:username, "cannot be nil")
49
- # @param [Symbol] property The property or list to add the error to
50
- # @param [String] problem The error
51
- # @return [Void]
52
- def add(property, problem)
53
- @errors[property] ||= []
54
- @errors[property].push problem
55
- end
56
-
57
- ##
58
- # The list of errors for a given property or list
59
- #
60
- # @example Get the errors for the `:username` field
61
- # errors.add(:username, "cannot be nil")
62
- # errors.for(:username) #=> ["cannot be nil"]
63
- # @param [Symbol] property The property or list to check
64
- # @return [Array<String>] The list of errors
65
- def for(property)
66
- @errors[property]
67
- end
68
-
69
- ##
70
- # Clear all errors
71
- #
72
- # @return [Void]
73
- def clear
74
- @errors = {}
75
- end
76
-
77
- ##
78
- # Return all errors as strings
79
- #
80
- # @example Get all errors
81
- # errors.add(:username, "cannot be nil")
82
- # errors.each #=> ["username cannot be nil"]
83
- # @return [Array<String>]
84
- def each
85
- @errors.map do |property, problems|
86
- problems.map do |problem|
87
- property.to_s + " " + problem
88
- end
89
- end.flatten
90
- end
91
-
92
-
93
- end
94
- end
@@ -1,14 +0,0 @@
1
- # Extensions to other modules for use by Spira
2
- #
3
-
4
- class RDF::Literal
5
-
6
- def self.unserialize(rdf_value)
7
- rdf_value.object
8
- end
9
-
10
- def self.unserialize(ruby_value)
11
- self.class.new(ruby_value)
12
- end
13
-
14
- end
@@ -1,260 +0,0 @@
1
- module Spira
2
- module Resource
3
-
4
- ##
5
- # This module contains all class methods available to a declared Spira::Resource class.
6
- # {Spira::Resource} contains more information about Spira resources.
7
- #
8
- # @see Spira::Resource
9
- # @see Spira::Resource::InstanceMethods
10
- # @see Spira::Resource::DSL
11
- module ClassMethods
12
-
13
- ##
14
- # A symbol name for the repository this class is currently using.
15
- attr_reader :repository_name
16
-
17
- ##
18
- # The current repository for this class
19
- #
20
- # @return [RDF::Repository, nil]
21
- # @private
22
- def repository
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 || 'default'} as a repository, but it has not been set.")
36
- end
37
-
38
- ##
39
- # Create a new projection instance of this class for the given URI. If a
40
- # class has a base_uri given, and the argument is not an `RDF::URI`, the
41
- # given identifier will be appended to the base URI.
42
- #
43
- # Spira does not have 'find' or 'create' functions. As RDF identifiers
44
- # are globally unique, they all simply 'are'.
45
- #
46
- # On calling `for`, a new projection is created for the given URI. The
47
- # first time access is attempted on a field, the repository will be
48
- # queried for existing attributes, which will be used for the given URI.
49
- # Underlying repositories are not accessed at the time of calling `for`.
50
- #
51
- # A class with a base URI may still be projected for any URI, whether or
52
- # not it uses the given resource class' base URI.
53
- #
54
- # @raise [TypeError] if an RDF type is given in the attributes and one is
55
- # given in the attributes.
56
- # @raise [ArgumentError] if a non-URI is given and the class does not
57
- # have a base URI.
58
- # @overload for(uri, attributes = {})
59
- # @param [RDF::URI] uri The URI to create an instance for
60
- # @param [Hash{Symbol => Any}] attributes Initial attributes
61
- # @overload for(identifier, attributes = {})
62
- # @param [Any] uri The identifier to append to the base URI for this class
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
66
- # @return [Spira::Resource] The newly created instance
67
- # @see http://rdf.rubyforge.org/RDF/URI.html
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)
87
- if !self.type.nil? && attributes[:type]
88
- raise TypeError, "#{self} has an RDF type, #{self.type}, and cannot accept one as an argument."
89
- end
90
- self.new(attributes.merge(:_subject => subject), &block)
91
- end
92
-
93
- ##
94
- # Alias for #for
95
- #
96
- # @see #for
97
- def [](*args)
98
- self.for(*args)
99
- end
100
-
101
- ##
102
- # Creates a URI or RDF::Node based on a potential base_uri and string,
103
- # URI, or Node, or Addressable::URI. If not a URI or Node, the given
104
- # identifier should be a string representing an absolute URI, or
105
- # something responding to to_s which can be appended to a base URI, which
106
- # this class must have.
107
- #
108
- # @param [Any] Identifier
109
- # @return [RDF::URI, RDF::Node]
110
- # @raise [ArgumentError] If this class cannot create an identifier from the given argument
111
- # @see http://rdf.rubyforge.org/RDF/URI.html
112
- # @see Spira::Resource.base_uri
113
- # @see Spira::Resource.for
114
- def id_for(identifier)
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
123
- when identifier.respond_to?(:node?) && identifier.node?
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
130
- when identifier.is_a?(Addressable::URI)
131
- id_for(RDF::URI.intern(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.
135
- else
136
- uri = identifier.is_a?(RDF::URI) ? identifier : RDF::URI.intern(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.intern(self.base_uri.to_s + separator + identifier.to_s)
145
- end
146
- end
147
- end
148
-
149
-
150
- ##
151
- # The number of URIs projectable as a given class in the repository.
152
- # This method is only valid for classes which declare a `type` with the
153
- # `type` method in the DSL.
154
- #
155
- # @raise [Spira::NoTypeError] if the resource class does not have an RDF type declared
156
- # @return [Integer] the count
157
- # @see Spira::Resource::DSL
158
- def count
159
- raise Spira::NoTypeError, "Cannot count a #{self} without a reference type URI." if @type.nil?
160
- repository.query(:predicate => RDF.type, :object => @type).subjects.count
161
- end
162
-
163
- ##
164
- # A cache of iterated instances of this projection
165
- #
166
- # @return [RDF::Util::Cache]
167
- # @private
168
- def cache
169
- @cache ||= RDF::Util::Cache.new
170
- end
171
-
172
- ##
173
- # Clear the iteration cache
174
- #
175
- # @return [void]
176
- def reload
177
- @cache = nil
178
- end
179
-
180
- ##
181
- # Enumerate over all resources projectable as this class. This method is
182
- # only valid for classes which declare a `type` with the `type` method in
183
- # the DSL.
184
- #
185
- # @raise [Spira::NoTypeError] if the resource class does not have an RDF type declared
186
- # @overload each
187
- # @yield [instance] A block to perform for each available projection of this class
188
- # @yieldparam [self] instance
189
- # @yieldreturn [Void]
190
- # @return [Void]
191
- #
192
- # @overload each
193
- # @return [Enumerator]
194
- # @see Spira::Resource::DSL
195
- def each(&block)
196
- raise Spira::NoTypeError, "Cannot count a #{self} without a reference type URI." if @type.nil?
197
- case block_given?
198
- when false
199
- enum_for(:each)
200
- else
201
- repository_or_fail.query(:predicate => RDF.type, :object => @type).each_subject do |subject|
202
- self.cache[subject] ||= self.for(subject)
203
- block.call(cache[subject])
204
- end
205
- end
206
- end
207
-
208
- ##
209
- # Returns true if the given property is a has_many property, false otherwise
210
- #
211
- # @return [true, false]
212
- def is_list?(property)
213
- @lists.has_key?(property)
214
- end
215
-
216
- ##
217
- # Handling inheritance
218
- #
219
- # @private
220
- def inherited(child)
221
- child.instance_eval do
222
- include Spira::Resource
223
- end
224
- # FIXME: This is clearly brittle and ugly.
225
- [:@base_uri, :@default_vocabulary, :@repository_name, :@type].each do |variable|
226
- value = instance_variable_get(variable).nil? ? nil : instance_variable_get(variable).dup
227
- child.instance_variable_set(variable, value)
228
- end
229
- [:@properties, :@lists, :@validators].each do |variable|
230
- if child.instance_variable_get(variable).nil?
231
- if instance_variable_get(variable).nil?
232
- child.instance_variable_set(variable, nil)
233
- else
234
- child.instance_variable_set(variable, instance_variable_get(variable).dup)
235
- end
236
- elsif !(instance_variable_get(variable).nil?)
237
- child.instance_variable_set(variable, instance_variable_get(variable).dup.merge(child.instance_variable_get(variable)))
238
- end
239
- end
240
- end
241
-
242
- ##
243
- # Handling module inclusions
244
- #
245
- # @private
246
- def included(child)
247
- inherited(child)
248
- end
249
-
250
- ##
251
- # The list of validation functions for this projection
252
- #
253
- # @return [Array<Symbol>]
254
- def validators
255
- @validators ||= []
256
- end
257
-
258
- end
259
- end
260
- end
@@ -1,279 +0,0 @@
1
- module Spira
2
- module Resource
3
-
4
- ##
5
- # This module consists of Spira::Resource class methods which correspond to
6
- # the Spira resource class declaration DSL. See {Spira::Resource} for more
7
- # information.
8
- #
9
- # @see Spira::Resource
10
- # @see Spira::Resource::ClassMethods
11
- # @see Spira::Resource::InstanceMethods
12
- # @see Spira::Resource::Validations
13
- module DSL
14
-
15
- ##
16
- # The name of the default repository to use for this class. This
17
- # repository will be queried and written to instead of the :default
18
- # repository.
19
- #
20
- # @param [Symbol] name
21
- # @return [Void]
22
- def default_source(name)
23
- @repository_name = name
24
- @repository = Spira.repository(name)
25
- end
26
-
27
- ##
28
- # The base URI for this class. Attempts to create instances for non-URI
29
- # objects will be appended to this base URI.
30
- #
31
- # @param [String, RDF::URI] base uri
32
- # @return [Void]
33
- def base_uri(uri = nil)
34
- @base_uri = uri unless uri.nil?
35
- @base_uri
36
- end
37
-
38
- ##
39
- # The default vocabulary for this class. Setting a default vocabulary
40
- # will allow properties to be defined without a `:predicate` option.
41
- # Predicates will instead be created by appending the property name to
42
- # the given string.
43
- #
44
- # @param [String, RDF::URI] base uri
45
- # @return [Void]
46
- def default_vocabulary(uri)
47
- @default_vocabulary = uri
48
- end
49
-
50
-
51
- ##
52
- # Add a property to this class. A property is an accessor field that
53
- # represents an RDF predicate.
54
- #
55
- # @example A simple string property
56
- # property :name, :predicate => FOAF.name, :type => String
57
- # @example A property which defaults to {Spira::Types::Any}
58
- # property :name, :predicate => FOAF.name
59
- # @example An integer property
60
- # property :age, :predicate => FOAF.age, :type => Integer
61
- # @param [Symbol] name The name of this property
62
- # @param [Hash{Symbol => Any}] opts property options
63
- # @option opts [RDF::URI] :predicate The RDF predicate which will refer to this property
64
- # @option opts [Spira::Type, String] :type (Spira::Types::Any) The
65
- # type for this property. If a Spira::Type is given, that class will be
66
- # used to serialize and unserialize values. If a String is given, it
67
- # should be the String form of a Spira::Resource class name (Strings are
68
- # used to prevent issues with load order).
69
- # @see Spira::Types
70
- # @see Spira::Type
71
- # @return [Void]
72
- def property(name, opts = {} )
73
- predicate = predicate_for(opts[:predicate], name)
74
- type = type_for(opts[:type])
75
- @properties[name] = { :predicate => predicate, :type => type }
76
- add_accessors(name,opts)
77
- end
78
-
79
- ##
80
- # The plural form of `property`. `Has_many` has the same options as
81
- # `property`, but instead of a single value, a Ruby Array of objects will
82
- # be created instead.
83
- #
84
- # has_many corresponds to an RDF subject with several triples of the same
85
- # predicate. This corresponds to a Ruby Set, which will be returned when
86
- # the property is accessed. Arrays will be accepted for new values, but
87
- # ordering and duplicate values will be lost on save.
88
- #
89
- # @see Spira::Resource::DSL#property
90
- def has_many(name, opts = {})
91
- property(name, opts)
92
- @lists[name] = true
93
- end
94
-
95
- ##
96
- # Validate this model with the given validator function name.
97
- #
98
- # @example
99
- # class Person
100
- # include Spira::Resource
101
- # property :name, :predicate => FOAF.name
102
- # validate :is_awesome
103
- # def is_awesome
104
- # assert(name =~ /Thor/, :name, "not awesome")
105
- # end
106
- # end
107
- # @param [Symbol] validator
108
- # @return [Void]
109
- def validate(validator)
110
- validators << validator unless validators.include?(validator)
111
- end
112
-
113
-
114
- ##
115
- # Associate an RDF type with this class. RDF resources can be multiple
116
- # types at once, but if they have an `RDF.type` statement for the given
117
- # URI, this class can #count them.
118
- #
119
- # @param [RDF::URI] uri The URI object of the `RDF.type` triple
120
- # @return [Void]
121
- # @see http://rdf.rubyforge.net/RDF/URI.html
122
- # @see http://rdf.rubyforge.org/RDF.html#type-class_method
123
- # @see Spira::Resource::ClassMethods#count
124
- def type(uri = nil)
125
- unless uri.nil?
126
- @type = case uri
127
- when RDF::URI
128
- uri
129
- else
130
- raise TypeError, "Cannot assign type #{uri} (of type #{uri.class}) to #{self}, expected RDF::URI"
131
- end
132
- end
133
- @type
134
- end
135
-
136
- # Build a Ruby value from an RDF value.
137
- #
138
- # @private
139
- def build_value(statement, type, cache)
140
- case
141
- when statement == nil
142
- nil
143
- when !cache[statement.object].nil?
144
- cache[statement.object]
145
- when type.respond_to?(:unserialize)
146
- type.unserialize(statement.object)
147
- when type.is_a?(Symbol) || type.is_a?(String)
148
- klass = classize_resource(type)
149
- cache[statement.object] = promise { klass.for(statement.object, :_cache => cache) }
150
- cache[statement.object]
151
- else
152
- raise TypeError, "Unable to unserialize #{statement.object} as #{type}"
153
- end
154
- end
155
-
156
- # Build an RDF value from a Ruby value for a property
157
- # @private
158
- def build_rdf_value(value, type)
159
- case
160
- when type.respond_to?(:serialize)
161
- type.serialize(value)
162
- when value && value.class.ancestors.include?(Spira::Resource)
163
- klass = classize_resource(type)
164
- unless klass.ancestors.include?(value.class)
165
- raise TypeError, "#{value} is an instance of #{value.class}, expected #{klass}"
166
- end
167
- value.subject
168
- when type.is_a?(Symbol) || type.is_a?(String)
169
- klass = classize_resource(type)
170
- else
171
- raise TypeError, "Unable to serialize #{value} as #{type}"
172
- end
173
- end
174
-
175
- private
176
-
177
- # Return the appropriate class object for a string or symbol
178
- # representation. Throws errors correctly if the given class cannot be
179
- # located, or if it is not a Spira::Resource
180
- #
181
- def classize_resource(type)
182
- klass = nil
183
- begin
184
- klass = qualified_const_get(type.to_s)
185
- rescue NameError
186
- raise NameError, "Could not find relation class #{type} (referenced as #{type} by #{self})"
187
- klass.is_a?(Class) && klass.ancestors.include?(Spira::Resource)
188
- end
189
- unless klass.is_a?(Class) && klass.ancestors.include?(Spira::Resource)
190
- raise TypeError, "#{type} is not a Spira Resource (referenced as #{type} by #{self})"
191
- end
192
- klass
193
- end
194
-
195
- # Resolve a constant from a string, relative to this class' namespace, if
196
- # available, and from root, otherwise.
197
- #
198
- # FIXME: this is not really 'qualified', but it's one of those
199
- # impossible-to-name functions. Open to suggestions.
200
- #
201
- # @author njh
202
- # @private
203
- def qualified_const_get(str)
204
- path = str.to_s.split('::')
205
- from_root = path[0].empty?
206
- if from_root
207
- from_root = []
208
- path = path[1..-1]
209
- else
210
- start_ns = ((Class === self)||(Module === self)) ? self : self.class
211
- from_root = start_ns.to_s.split('::')
212
- end
213
- until from_root.empty?
214
- begin
215
- return (from_root+path).inject(Object) { |ns,name| ns.const_get(name) }
216
- rescue NameError
217
- from_root.delete_at(-1)
218
- end
219
- end
220
- path.inject(Object) { |ns,name| ns.const_get(name) }
221
- end
222
-
223
- ##
224
- # Determine the type for a property based on the given type option
225
- #
226
- # @param [nil, Spira::Type, Constant] type
227
- # @return Spira::Type
228
- # @private
229
- def type_for(type)
230
- case
231
- when type.nil?
232
- Spira::Types::Any
233
- when type.is_a?(Symbol) || type.is_a?(String)
234
- type
235
- when !(Spira.types[type].nil?)
236
- Spira.types[type]
237
- else
238
- raise TypeError, "Unrecognized type: #{type}"
239
- end
240
- end
241
-
242
- ##
243
- # Determine the predicate for a property based on the given predicate, name, and default vocabulary
244
- #
245
- # @param [#to_s, #to_uri] predicate
246
- # @param [Symbol] name
247
- # @return [RDF::URI]
248
- # @private
249
- def predicate_for(predicate, name)
250
- case
251
- when predicate.respond_to?(:to_uri) && predicate.to_uri.absolute?
252
- predicate
253
- when @default_vocabulary.nil?
254
- raise ResourceDeclarationError, "A :predicate option is required for types without a default vocabulary"
255
- else
256
- # FIXME: use rdf.rb smart separator after 0.3.0 release
257
- separator = @default_vocabulary.to_s[-1,1] =~ /(\/|#)/ ? '' : '/'
258
- RDF::URI.intern(@default_vocabulary.to_s + separator + name.to_s)
259
- end
260
- end
261
-
262
- ##
263
- # Add getters and setters for a property or list.
264
- # @private
265
- def add_accessors(name, opts)
266
- name_equals = (name.to_s + "=").to_sym
267
-
268
- self.send(:define_method,name_equals) do |arg|
269
- attribute_set(name, arg)
270
- end
271
- self.send(:define_method,name) do
272
- attribute_get(name)
273
- end
274
-
275
- end
276
-
277
- end
278
- end
279
- end