spira 0.0.1.pre

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/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
+