spira 0.0.12 → 0.5.0

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