spira 0.0.1.pre

Sign up to get free protection for your applications and to get access to all the features.
data/AUTHORS ADDED
@@ -0,0 +1 @@
1
+ * Ben Lavender <blavender@gmail.com>
data/README ADDED
@@ -0,0 +1,271 @@
1
+ # Spira
2
+
3
+ It's time to breathe life into your linked data.
4
+
5
+ ---
6
+
7
+ ## Synopsis
8
+ Spira is a framework for using the information in RDF.rb repositories as model
9
+ objects. It gives you the ability to work in a resource-oriented way without
10
+ losing access to statement-oriented nature of linked data, if you so choose.
11
+ It can be used either to access existing RDF data in a resource-oriented way,
12
+ or to create a new store of RDF data based on simple defaults.
13
+
14
+ ### Example
15
+
16
+ class Person
17
+
18
+ include Spira::Resource
19
+
20
+ base_uri "http://example.org/example/people"
21
+
22
+ property :name, :predicate => RDF::FOAF.name, :type => String
23
+ property :age, :predicate => RDF::FOAF.age, :type => Integer
24
+
25
+ end
26
+
27
+ bob = Person.create 'bob'
28
+ bob.age = 15
29
+ bob.name = "Bob Smith"
30
+ bob.save!
31
+
32
+ bob.each_statement {|s| puts s}
33
+ #=> RDF::Statement:0x80abb80c(<http://example.org/example/people/bob> <http://xmlns.com/foaf/0.1/name> "Bob Smith" .)
34
+ #=> RDF::Statement:0x80abb8fc(<http://example.org/example/people/bob> <http://xmlns.com/foaf/0.1/age> "15"^^<http://www.w3.org/2001/XMLSchema#integer> .)
35
+
36
+ ### Features
37
+
38
+ * Extensible validations system
39
+ * Easy to use multiple data sources
40
+ * Easy to adapt models to existing data
41
+ * Objects are still RDF.rb-compatible enumerable objects
42
+ * No need to put everything about an object into Spira
43
+ * Easy to use a resource as multiple models
44
+
45
+ ## Getting Started
46
+
47
+ By far the easiest way to work with Spira is to install it via Rubygems:
48
+
49
+ $ sudo gem install spira
50
+
51
+ Nonetheless, downloads are available at the Github project page.
52
+
53
+ ## Defining Model Classes
54
+
55
+ To use Spira, define model classes for your RDF data. Spira classes include
56
+ RDF, and thus have access to all `RDF::Vocabulary` classes and `RDF::URI`
57
+ without the `RDF::` prefix. For example:
58
+
59
+ require 'spira'
60
+
61
+ class CD
62
+ include Spira::Resource
63
+ base_uri 'http://example.org/cds'
64
+ property :name, :predicate => DC.title, :type => XSD.string
65
+ property :artist, :predicate => URI.new('http://example.org/vocab/artist'), :type => :artist
66
+ end
67
+
68
+ class Artist
69
+ include Spira::Resource
70
+ base_uri 'http://example.org/artists'
71
+ property :name, :predicate => DC.title, :type => XSD.string
72
+ has_many :cds, :predicate => URI.new('http://example.org/vocab/published_cd'), :type => XSD.string
73
+ end
74
+
75
+ Then use your model classes, in a way more or less similar to any number of ORMs:
76
+
77
+ cd = CD.create "queens-greatest-hits"
78
+ cd.name = "Queen's greatest hits"
79
+ artist = Artist.create "queen"
80
+ artist.name = "Queen"
81
+
82
+ cd.artist = artist
83
+ cd.save!
84
+ artist.cds = [cd]
85
+ artist.save!
86
+
87
+ queen = Arist.find 'queen'
88
+ hits = CD.find 'queens-greatest-hits'
89
+ hits.artist == artist == queen
90
+
91
+ ### Absolute and Relative URIs
92
+
93
+ A class with a base URI can reference objects by a short name:
94
+
95
+ Artist.find 'queen'
96
+
97
+ However, a class is not required to have a base URI, and even if it does, it
98
+ can always access classes with a full URI:
99
+
100
+ nk = Artist.find RDF::URI.new('http://example.org/my-hidden-cds/new-kids')
101
+
102
+ ### Class Options
103
+
104
+ A number of options are available for Spira classes.
105
+
106
+ #### base_uri
107
+
108
+ A class with a `base_uri` set (either an `RDF::URI` or a `String`) will
109
+ use that URI as a base URI for non-absolute `create` and `find` calls.
110
+
111
+ Example
112
+ CD.find 'queens-greatest-hits' # is the same as...
113
+ CD.find RDF::URI.new('http://example.org/cds/queens-greatest-hits')
114
+
115
+ #### type
116
+
117
+ A class with a `type` set is assigned an `RDF.type` on creation and saving.
118
+
119
+ class Album
120
+ include Spira::Resource
121
+ type RDF::URI.new('http://example.org/types/album')
122
+ property :name, :predicate => DC.title
123
+ end
124
+
125
+ rolling_stones = Album.create RDF::URI.new('http://example.org/cds/rolling-stones-hits')
126
+ # See RDF.rb for more information about #has_predicate?
127
+ rolling_stones.has_predicate?(RDF.type) #=> true
128
+ Album.type #=> RDF::URI('http://example.org/types/album')
129
+
130
+ In addition, one can count the members of a class with a `type` defined:
131
+
132
+ Album.count #=> 1
133
+
134
+ #### property
135
+
136
+ A class declares property members with the `property` function. See `Property Options` for more information.
137
+
138
+ #### has_many
139
+
140
+ A class declares list members with the `has_many` function. See `Property Options` for more information.
141
+
142
+ #### default_vocabulary
143
+
144
+ A class with a `default_vocabulary` set will transparently create predicates for defined properties:
145
+
146
+ class Song
147
+ include Spira::Resource
148
+ default_vocabulary RDF::URI.new('http://example.org/vocab')
149
+ base_uri 'http://example.org/songs'
150
+ property :title
151
+ property :author, :type => :artist
152
+ end
153
+
154
+ dancing_queen = Song.create 'dancing-queen'
155
+ dancing_queen.title = "Dancing Queen"
156
+ dancing_queen.artist = abba
157
+ dancing_queen.has_predicate?(RDF::URI.new('http://example.org/vocab/title')) #=> true
158
+ dancing_queen.has_predicate?(RDF::URI.new('http://example.org/vocab/artist')) #=> true
159
+
160
+ #### default_source
161
+
162
+ Provides this class with a default repository to use instead of the `:default`
163
+ repository if one is not set.
164
+
165
+ class Song
166
+ default_source :songs
167
+ end
168
+
169
+ See 'Defining Repositories' for more information.
170
+
171
+ ### Property Options
172
+
173
+ Spira classes can have properties that are either singular or a list. For a
174
+ list, define the property with `has_many`, for a property with a single item,
175
+ use `property`. The semantics are otherwise the same. A `has_many` property
176
+ will always return a list, including an empty list for no value. All options
177
+ for `property` work for `has_many`.
178
+
179
+ property :artist, :type => :artist #=> cd.artist returns a single value
180
+ has_many :cds, :type => :cd #=> artist.cds returns an array
181
+
182
+ Property always takes a symbol name as a name, and a variable list of options. The supported options are:
183
+
184
+ * `:type`: The type for this property. This can be a Ruby base class, an
185
+ RDF::XSD entry, or another Spira model class, referenced as a symbol. Default: `String`
186
+ * `:predicate`: The predicate to use for this type. This can be any RDF URI.
187
+ This option is required unless the `default_vocabulary` has been used.
188
+
189
+ ### Relations
190
+
191
+ If the `:type` of a spira class is the name of another Spira class as a symbol,
192
+ such as `:artist` for `Artist`, Spira will attempt to load the referenced
193
+ object when the appropriate property is accessed.
194
+
195
+ In the RDF store, this will be represented by the URI of the referenced object.
196
+
197
+ ## Defining Repositories
198
+
199
+ You can define multiple repositories with Spira, and use more than one at a time:
200
+
201
+ require 'rdf/ntriples'
202
+ require 'rdf/sesame'
203
+ Spira.add_repository! :cds, RDF::Sesame::Repository.new 'some_server'
204
+ Spira.add_repository! :albums, RDF::Repository.load('some_file.nt')
205
+
206
+ CD.repository = :cds
207
+ Album.repository = :albums
208
+
209
+ Objects can reference each other cross-repository.
210
+
211
+ If no repository has been specified, the `:default` repository will be used.
212
+
213
+ repo = RDF::Repository.new
214
+ Spira.add_repository! :default, repo
215
+ Artist.repository == repo #=> true
216
+
217
+ Classes can specify a default repository to use other than `:default` with the
218
+ `default_source` function:
219
+
220
+ class Song
221
+ default_source :songs
222
+ end
223
+
224
+ Song.repository #=> nil, won't use :default
225
+
226
+ ## Validations
227
+
228
+ Before saving, each object will run a `validate` function, if one exists. You
229
+ can use the built in `assert` and assert helpers such as `assert_set` and
230
+ `asssert_numeric`.
231
+
232
+
233
+ class CD
234
+ def validate
235
+ # the only valid CDs are ABBA CD's!
236
+ assert(artist.name == "Abba","Could not save a CD made by #{artist.name}")
237
+ end
238
+ end
239
+
240
+ dancing-queen.artist = nil
241
+ dancing-queen.save! #=> ValidationError
242
+
243
+ dancing-queen.artist = abba
244
+ dancing-queen.save! #=> true
245
+
246
+ ## Using Model Objects as RDF.rb Objects
247
+
248
+ All model objects are fully-functional as `RDF::Enumerable`, `RDF::Queryable`,
249
+ and `RDF::Mutable`. This lets you manipulate objects on the RDF statement
250
+ level. You can also access attributes that are not defined as properties.
251
+
252
+ ## Support
253
+
254
+ There are a number of ways to ask for help. In declining order of likelihood of response:
255
+
256
+ * Fork the project and write a failing test, or a pending test for a feature request
257
+ * You can post issues to the Github issue queue
258
+ * (there might one day be a google group or other such support channel, but not yet)
259
+
260
+ ## Authors, Development, and License
261
+
262
+ #### Authors
263
+ * Ben Lavender <blavender@gmail.com>
264
+
265
+ #### 'License'
266
+ Spira is free and unemcumbered software released into the public
267
+ domain. For more information, see the included UNLICENSE file.
268
+
269
+ #### Contributing
270
+ Fork it on Github and go. Please make sure you're kosher with the UNLICENSE
271
+ file before contributing.
@@ -0,0 +1,24 @@
1
+ This is free and unencumbered software released into the public domain.
2
+
3
+ Anyone is free to copy, modify, publish, use, compile, sell, or
4
+ distribute this software, either in source code form or as a compiled
5
+ binary, for any purpose, commercial or non-commercial, and by any
6
+ means.
7
+
8
+ In jurisdictions that recognize copyright laws, the author or authors
9
+ of this software dedicate any and all copyright interest in the
10
+ software to the public domain. We make this dedication for the benefit
11
+ of the public at large and to the detriment of our heirs and
12
+ successors. We intend this dedication to be an overt act of
13
+ relinquishment in perpetuity of all present and future rights to this
14
+ software under copyright law.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ For more information, please refer to <http://unlicense.org/>
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1.pre
@@ -0,0 +1,40 @@
1
+ require 'rdf'
2
+
3
+ module Spira
4
+
5
+
6
+ def repositories
7
+ settings[:repositories] ||= {}
8
+ end
9
+ module_function :repositories
10
+
11
+ def settings
12
+ Thread.current[:spira] ||= {}
13
+ end
14
+ module_function :settings
15
+
16
+ def add_repository(name, klass, *args)
17
+ repositories[name] = case klass
18
+ when RDF::Repository
19
+ klass
20
+ when Class
21
+ klass.new(*args)
22
+ else
23
+ raise ArgumentError, "Could not add repository #{klass} as #{name}; expected an RDF::Repository or class name"
24
+ end
25
+ if (name == :default) && settings[:repositories][name].nil?
26
+ warn "WARNING: Adding nil default repository"
27
+ end
28
+ end
29
+ alias_method :add_repository!, :add_repository
30
+ module_function :add_repository, :add_repository!
31
+
32
+ def repository(name)
33
+ repositories[name]
34
+ end
35
+ module_function :repository
36
+
37
+ autoload :Resource, 'spira/resource'
38
+
39
+ class ValidationError < StandardError; end
40
+ end
@@ -0,0 +1,26 @@
1
+ module Spira
2
+ module Resource
3
+
4
+ autoload :DSL, 'spira/resource/dsl'
5
+ autoload :ClassMethods, 'spira/resource/class_methods'
6
+ autoload :InstanceMethods, 'spira/resource/instance_methods'
7
+ autoload :Validations, 'spira/resource/validations'
8
+
9
+ def self.included(child)
10
+ child.extend DSL
11
+ child.extend ClassMethods
12
+ child.instance_eval do
13
+ class << self
14
+ attr_accessor :properties, :lists
15
+ end
16
+ @properties = {}
17
+ @lists = {}
18
+ end
19
+ end
20
+
21
+ # This lets including classes reference vocabularies without the RDF:: prefix
22
+ include RDF
23
+ include InstanceMethods
24
+
25
+ end
26
+ end
@@ -0,0 +1,87 @@
1
+ module Spira
2
+ module Resource
3
+
4
+ # This module contains all class methods available to a Spira::Resource class
5
+ #
6
+ #
7
+ module ClassMethods
8
+ def repository=(repo)
9
+ @repository = repo
10
+ end
11
+
12
+ def repository
13
+ case @repository_name
14
+ when nil
15
+ Spira.repository(:default)
16
+ else
17
+ Spira.repository(@repository_name)
18
+ end
19
+ end
20
+
21
+ def oldrepo
22
+ case
23
+ #when !@repository.nil?
24
+ # @repository
25
+ when !@repository_name.nil?
26
+ Spira.repository(@repository_name) || raise(RuntimeError, "#{self} is configured to use #{@repository_name} as a repository, but was unable to find it.")
27
+ #@repository = Spira.repository(@repository_name)
28
+ #if @repository.nil?
29
+ # raise RuntimeError, "#{self} is configured to use #{@repository_name} as a repository, but was unable to find it."
30
+ #end
31
+ #@repository
32
+ else
33
+ @repository = Spira.repository(:default)
34
+ if @repository.nil?
35
+ raise RuntimeError, "#{self} has no configured repository and was unable to find a default repository."
36
+ end
37
+ @repository
38
+ end
39
+ #@repository
40
+ end
41
+
42
+ def find(identifier)
43
+ if repository.nil?
44
+ raise RuntimeError, "#{self} is configured to use #{@repository_name} as a repository, but was unable to find it."
45
+ end
46
+ uri = case identifier
47
+ when RDF::URI
48
+ identifier
49
+ when String
50
+ raise ArgumentError, "Cannot find #{self} by String without base_uri; RDF::URI required" if self.base_uri.nil?
51
+ separator = self.base_uri.to_s[-1,1] == "/" ? '' : '/'
52
+ RDF::URI.parse(self.base_uri.to_s + separator + identifier)
53
+ else
54
+ raise ArgumentError, "Cannot instantiate #{self} from #{identifier}, expected RDF::URI or String"
55
+ end
56
+ statements = self.repository.query(:subject => uri)
57
+ if statements.empty?
58
+ nil
59
+ else
60
+ self.new(identifier, :statements => statements)
61
+ end
62
+ end
63
+
64
+ def count
65
+ raise TypeError, "Cannot count a #{self} without a reference type URI." if @type.nil?
66
+ result = repository.query(:predicate => RDF.type, :object => @type)
67
+ result.count
68
+ end
69
+
70
+ def create(name, attributes = {})
71
+ # TODO: validate attributes
72
+ unless @type.nil?
73
+ if attributes[:type]
74
+ raise TypeError, "Cannot assign type to new instance of #{self}; this class is associated with #{@type}"
75
+ end
76
+ attributes[:type] = @type
77
+ end
78
+ resource = self.new(name, attributes)
79
+ end
80
+
81
+ def is_list?(property)
82
+ @lists.has_key?(property)
83
+ end
84
+
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,164 @@
1
+ require 'promise'
2
+
3
+ module Spira
4
+ module Resource
5
+
6
+ # This module contains all user-exposed methods for use in building a model class.
7
+ # It is used to extend classes that include Spira::Resource.
8
+ # @see a little bit of magic in Spira::Resource#included as well--some
9
+ # tricks need class_eval before this module is included.
10
+ # @see Spira::Resource::ClassMethods for class methods available after class
11
+ # definition
12
+ # @see Spira::Resource::InstanceMethods for instance methods available after
13
+ # class definition
14
+ module DSL
15
+
16
+ def default_source(name)
17
+ @repository_name = name
18
+ @repository = Spira.repository(name)
19
+ end
20
+
21
+ def base_uri(uri = nil)
22
+ @base_uri = uri unless uri.nil?
23
+ @base_uri
24
+ end
25
+
26
+ def default_vocabulary(uri)
27
+ @default_vocabulary = uri
28
+ end
29
+
30
+ def property(name, opts = {} )
31
+ add_accessors(name,opts,:hash_accessors)
32
+ end
33
+
34
+ def has_many(name, opts = {})
35
+ add_accessors(name,opts,:hash_accessors)
36
+ @lists[name] = true
37
+ end
38
+
39
+ def type(uri = nil)
40
+ unless uri.nil?
41
+ @type = case uri
42
+ when RDF::URI
43
+ uri
44
+ else
45
+ raise TypeError, "Cannot assign type #{uri} (of type #{uri.class}) to #{self}, expected RDF::URI"
46
+ end
47
+ end
48
+ @type
49
+ end
50
+
51
+ #
52
+ # @private
53
+ def build_value(statement, type)
54
+ case
55
+ when statement == nil
56
+ nil
57
+ when type == String
58
+ statement.object.object.to_s
59
+ when type == Integer
60
+ statement.object.object
61
+ when type.is_a?(Symbol)
62
+ klass = Kernel.const_get(type.to_s.capitalize)
63
+ raise TypeError, "#{klass} is not a Spira Resource (referenced as #{type} by #{self}" unless klass.ancestors.include? Spira::Resource
64
+ promise { klass.find(statement.object) || klass.create(statement.object) }
65
+ end
66
+ end
67
+
68
+ # @private
69
+ def build_rdf_value(value, type)
70
+ case
71
+ when value.class.ancestors.include?(Spira::Resource)
72
+ value.uri
73
+ when type == nil
74
+ value
75
+ when type == RDF::URI && value.is_a?(RDF::URI)
76
+ value
77
+ when type.is_a?(RDF::URI)
78
+ RDF::Literal.new(value, :datatype => type)
79
+ else
80
+ RDF::Literal.new(value)
81
+ end
82
+ end
83
+
84
+ private
85
+
86
+ def add_accessors(name, opts, accessors_method)
87
+ predicate = case
88
+ when opts[:predicate]
89
+ opts[:predicate]
90
+ when @default_vocabulary.nil?
91
+ raise TypeError, "A :predicate option is required for types without a default vocabulary"
92
+ else @default_vocabulary
93
+ separator = @default_vocabulary.to_s[-1,1] == "/" ? '' : '/'
94
+ RDF::URI.new(@default_vocabulary.to_s + separator + name.to_s)
95
+ end
96
+
97
+ type = opts[:type] || String
98
+ @properties[name] = {}
99
+ @properties[name][:predicate] = predicate
100
+ @properties[name][:type] = type
101
+ name_equals = (name.to_s + "=").to_sym
102
+
103
+ (getter,setter) = self.send(accessors_method, name, predicate, type)
104
+ self.send(:define_method,name_equals, &setter)
105
+ self.send(:define_method,name, &getter)
106
+
107
+ end
108
+
109
+ def hash_accessors(name, predicate, type)
110
+ setter = lambda do |arg|
111
+ attribute_set(name,arg)
112
+ end
113
+
114
+ getter = lambda do
115
+ attribute_get(name)
116
+ end
117
+
118
+ [getter, setter]
119
+ end
120
+
121
+ def list_accessors(name, predicate, type)
122
+
123
+ setter = lambda do |arg|
124
+ old = @repo.query(:subject => @uri, :predicate => predicate)
125
+ @repo.delete(*old.to_a) unless old.empty?
126
+ new = []
127
+ arg.each do |value|
128
+ value = self.class.build_rdf_value(value, type)
129
+ new << RDF::Statement.new(@uri, predicate, value)
130
+ end
131
+ @repo.insert(*new)
132
+ end
133
+
134
+ getter = lambda do
135
+ values = []
136
+ statements = @repo.query(:subject => @uri, :predicate => predicate)
137
+ statements.each do |statement|
138
+ values << self.class.build_value(statement, type)
139
+ end
140
+ values
141
+ end
142
+
143
+ [getter, setter]
144
+ end
145
+
146
+ def single_accessors(name, predicate, type)
147
+ setter = lambda do |arg|
148
+ old = @repo.query(:subject => @uri, :predicate => predicate)
149
+ @repo.delete(*old.to_a) unless old.empty?
150
+ arg = self.class.build_rdf_value(arg, type)
151
+ @repo.insert(RDF::Statement.new(@uri, predicate, arg))
152
+ end
153
+
154
+ getter = lambda do
155
+ statement = @repo.query(:subject => @uri, :predicate => predicate).first
156
+ self.class.build_value(statement, type)
157
+ end
158
+
159
+ [getter, setter]
160
+ end
161
+
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,175 @@
1
+ require 'rdf/isomorphic'
2
+
3
+ module Spira
4
+ module Resource
5
+ module InstanceMethods
6
+
7
+ attr_reader :uri
8
+
9
+ # Initialize a new instance of a spira resource.
10
+ # The new instance can be instantiated with an opts[:statements] or opts[:attributes], but not both.
11
+ def initialize(identifier, opts = {})
12
+
13
+ @attributes = {}
14
+
15
+ if identifier.is_a? RDF::URI
16
+ @uri = identifier
17
+ else
18
+ if (self.class.base_uri)
19
+ separator = self.class.base_uri.to_s[-1,1] == "/" ? '' : '/'
20
+ @uri = RDF::URI.parse(self.class.base_uri.to_s + separator + identifier)
21
+ else
22
+ raise ArgumentError, "#{self.class} has no base URI configured, and can thus only be created using RDF::URIs (got #{identifier.inspect})"
23
+ end
24
+ end
25
+
26
+ #@repo = RDF::Repository.new
27
+ #@repo.insert(*(opts[:statements])) unless opts[:statements].nil?
28
+ #@repo.insert(*[RDF::Statement.new(@uri, RDF.type, opts[:type])]) if opts[:type]
29
+
30
+ # If we got statements, we are being loaded, not created
31
+ if opts[:statements]
32
+ # Set attributes for each statement corresponding to a predicate
33
+ self.class.properties.each do |name, property|
34
+ if self.class.is_list?(name)
35
+ values = []
36
+ statements = opts[:statements].query(:subject => @uri, :predicate => property[:predicate])
37
+ unless statements.nil?
38
+ statements.each do |statement|
39
+ values << self.class.build_value(statement,property[:type])
40
+ end
41
+ end
42
+ attribute_set(name, values)
43
+ else
44
+ statement = opts[:statements].query(:subject => @uri, :predicate => property[:predicate]).first
45
+ attribute_set(name, self.class.build_value(statement, property[:type]))
46
+ end
47
+ end
48
+ else
49
+ self.class.properties.each do |name, predicate|
50
+ attribute_set(name, opts[name]) unless opts[name].nil?
51
+ end
52
+
53
+ end
54
+
55
+
56
+ #@repo = RDF::Repository.new
57
+ #@repo.insert(*(opts[:statements])) unless opts[:statements].nil?
58
+ #@repo.insert(*[RDF::Statement.new(@uri, RDF.type, opts[:type])]) if opts[:type]
59
+
60
+ #self.class.properties.each do |name, predicate|
61
+ # send(((name.to_s)+"=").to_sym, opts[name]) unless opts[name].nil?
62
+ #end
63
+ @original_attributes = @attributes.dup
64
+ @original_attributes.each do | name, value |
65
+ @original_attributes[name] = value.dup if value.is_a?(Array)
66
+ end
67
+ end
68
+
69
+ def _destroy_attributes(attributes)
70
+ repository = repository_for_attributes(attributes)
71
+ self.class.repository.delete(*repository)
72
+ end
73
+
74
+ def destroy!
75
+ _destroy_attributes(@attributes)
76
+ end
77
+
78
+ def save!
79
+ if self.class.repository.nil?
80
+ raise RuntimeError, "#{self} is configured to use #{@repository_name} as a repository, but was unable to find it."
81
+ end
82
+ if respond_to?(:validate)
83
+ errors.clear
84
+ validate
85
+ if errors.empty?
86
+ _update!
87
+ else
88
+ raise(ValidationError, "Could not save #{self.inspect} due to validation errors: " + errors.join(';'))
89
+ end
90
+ else
91
+ _update!
92
+ end
93
+ end
94
+
95
+ def _update!
96
+ _destroy_attributes(@original_attributes)
97
+ self.class.repository.insert(*self)
98
+ @original_attributes = @attributes.dup
99
+ end
100
+
101
+ def type
102
+ self.class.type
103
+ end
104
+
105
+ def type=(type)
106
+ raise TypeError, "Cannot reassign RDF.type for #{self}; consider appending to a has_many :types"
107
+ end
108
+
109
+ def inspect
110
+ "<#{self.class}:#{self.object_id} uri: #{@uri}>"
111
+ end
112
+
113
+ def each(*args, &block)
114
+ repository = repository_for_attributes(@attributes)
115
+ repository.insert(RDF::Statement.new(@uri, RDF.type, type)) unless type.nil?
116
+ if block_given?
117
+ repository.each(*args, &block)
118
+ else
119
+ ::Enumerable::Enumerator.new(self, :each)
120
+ end
121
+ end
122
+
123
+ def attribute_set(name, value)
124
+ @attributes[name] = value
125
+ end
126
+
127
+ def attribute_get(name)
128
+ case self.class.is_list?(name)
129
+ when true
130
+ @attributes[name] ||= []
131
+ when false
132
+ @attributes[name]
133
+ end
134
+ end
135
+
136
+ def repository_for_attributes(attributes)
137
+ repo = RDF::Repository.new
138
+ attributes.each do | name, attribute |
139
+ if self.class.is_list?(name)
140
+ #old = @repo.query(:subject => @uri, :predicate => predicate)
141
+ #@repo.delete(*old.to_a) unless old.empty?
142
+ new = []
143
+ attribute.each do |value|
144
+ value = self.class.build_rdf_value(value, self.class.properties[name][:type])
145
+ new << RDF::Statement.new(@uri, self.class.properties[name][:predicate], value)
146
+ end
147
+ repo.insert(*new)
148
+ else
149
+ value = self.class.build_rdf_value(attribute, self.class.properties[name][:type])
150
+ repo.insert(RDF::Statement.new(@uri, self.class.properties[name][:predicate], value))
151
+ end
152
+ end
153
+ repo
154
+ end
155
+
156
+ def ==(other)
157
+ case other
158
+ # TODO: define behavior for equality on subclasses. also subclasses.
159
+ when self.class
160
+ @uri == other.uri
161
+ when RDF::Enumerable
162
+ self.isomorphic_with?(other)
163
+ else
164
+ false
165
+ end
166
+ end
167
+
168
+
169
+ include ::RDF::Enumerable, ::RDF::Queryable
170
+
171
+ include Spira::Resource::Validations
172
+
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,23 @@
1
+ module Spira
2
+ module Resource
3
+ module Validations
4
+
5
+ def errors
6
+ @errors ||= []
7
+ end
8
+
9
+ def assert(boolean, message)
10
+ errors.push(message) unless boolean
11
+ end
12
+
13
+ def assert_set(name)
14
+ assert(!(self.send(name).nil?), "#{name.to_s} cannot be nil")
15
+ end
16
+
17
+ def assert_numeric(name)
18
+ assert(self.send(name).is_a?(Numeric), "#{name.to_s} must be numeric (was #{self.send(name)})")
19
+ end
20
+
21
+ end
22
+ end
23
+ end
File without changes
File without changes
metadata ADDED
@@ -0,0 +1,162 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: spira
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: true
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ - pre
10
+ version: 0.0.1.pre
11
+ platform: ruby
12
+ authors:
13
+ - Ben Lavender
14
+ autorequire:
15
+ bindir:
16
+ - bin
17
+ cert_chain: []
18
+
19
+ date: 2010-04-17 00:00:00 +02:00
20
+ default_executable:
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: rdf-spec
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ segments:
30
+ - 0
31
+ - 1
32
+ - 0
33
+ version: 0.1.0
34
+ type: :development
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: rspec
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ segments:
44
+ - 1
45
+ - 3
46
+ - 0
47
+ version: 1.3.0
48
+ type: :development
49
+ version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
51
+ name: yard
52
+ prerelease: false
53
+ requirement: &id003 !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ segments:
58
+ - 0
59
+ - 5
60
+ - 3
61
+ version: 0.5.3
62
+ type: :development
63
+ version_requirements: *id003
64
+ - !ruby/object:Gem::Dependency
65
+ name: rdf
66
+ prerelease: false
67
+ requirement: &id004 !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ segments:
72
+ - 0
73
+ - 1
74
+ - 0
75
+ version: 0.1.0
76
+ type: :runtime
77
+ version_requirements: *id004
78
+ - !ruby/object:Gem::Dependency
79
+ name: rdf-isomorphic
80
+ prerelease: false
81
+ requirement: &id005 !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ segments:
86
+ - 0
87
+ - 1
88
+ - 2
89
+ version: 0.1.2
90
+ type: :runtime
91
+ version_requirements: *id005
92
+ - !ruby/object:Gem::Dependency
93
+ name: promise
94
+ prerelease: false
95
+ requirement: &id006 !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ segments:
100
+ - 0
101
+ - 1
102
+ - 0
103
+ version: 0.1.0
104
+ type: :runtime
105
+ version_requirements: *id006
106
+ description: Spira is a framework for using the information in RDF.rb repositories as model objects.
107
+ email: blavender@gmail.com
108
+ executables: []
109
+
110
+ extensions: []
111
+
112
+ extra_rdoc_files: []
113
+
114
+ files:
115
+ - AUTHORS
116
+ - README
117
+ - UNLICENSE
118
+ - VERSION
119
+ - lib/spira/resource/class_methods.rb
120
+ - lib/spira/resource/dsl.rb
121
+ - lib/spira/resource/instance_methods.rb
122
+ - lib/spira/resource/validations.rb
123
+ - lib/spira/resource.rb
124
+ - lib/spira/type.rb
125
+ - lib/spira/types/boolean.rb
126
+ - lib/spira.rb
127
+ has_rdoc: false
128
+ homepage: http://spira.rubyforge.org
129
+ licenses:
130
+ - Public Domain
131
+ post_install_message:
132
+ rdoc_options: []
133
+
134
+ require_paths:
135
+ - lib
136
+ required_ruby_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ segments:
141
+ - 1
142
+ - 8
143
+ - 2
144
+ version: 1.8.2
145
+ required_rubygems_version: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ">"
148
+ - !ruby/object:Gem::Version
149
+ segments:
150
+ - 1
151
+ - 3
152
+ - 1
153
+ version: 1.3.1
154
+ requirements: []
155
+
156
+ rubyforge_project: spira
157
+ rubygems_version: 1.3.6
158
+ signing_key:
159
+ specification_version: 3
160
+ summary: A framework for using the information in RDF.rb repositories as model objects.
161
+ test_files: []
162
+