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/AUTHORS CHANGED
@@ -1,3 +1,5 @@
1
1
  * Ben Lavender <blavender@gmail.com>
2
2
  * Arto Bendiken <arto@bendiken.net>
3
3
  * Nicholas J Humfrey <njh@aelius.com>
4
+ * Slava Kravchenko <https://github.com/cordawyn>
5
+ * Aymeric Brisse <https://github.com/abrisse>
data/CHANGES.md CHANGED
@@ -1,4 +1,7 @@
1
- # Changelog for Spira <http://github.com/datagraph/spira>
1
+ # Changelog for Spira <http://github.com/rdf-ruby/spira>
2
+
3
+ ## 0.3.0
4
+ * General updates to bring up to date.
2
5
 
3
6
  ## 0.0.12
4
7
  * Implemented #validate, #validate! (refactored from #save!)
@@ -33,7 +36,7 @@
33
36
  ## 0.0.8
34
37
  * Remove type checking for repository addition. More power in return for
35
38
  slightly more difficult error messages.
36
- * Repositories added via klass, *arg are now only instantiated on first use
39
+ * Repositories added via klass, \*arg are now only instantiated on first use
37
40
  instead of immediately.
38
41
  * RDF::URI#as, RDF::Node#as, Resource.for, and Resource#new can now all accept
39
42
  a block, which yields the new instance and saves it after the block.
@@ -46,7 +49,7 @@
46
49
  permitted.
47
50
 
48
51
  ## 0.0.7
49
- * Added Resource.[], an alias for Resource.for
52
+ * Added Resource.\[\], an alias for Resource.for
50
53
  * Resource.each now correctly raises an exception when a repository isn't found
51
54
 
52
55
  ## 0.0.6
@@ -1,9 +1,7 @@
1
- # Spira
1
+ # Spira [![Build Status](https://travis-ci.org/ruby-rdf/spira.png?branch=master)](http://travis-ci.org/ruby-rdf/spira)
2
2
 
3
3
  It's time to breathe life into your linked data.
4
4
 
5
- ---
6
-
7
5
  ## Synopsis
8
6
  Spira is a framework for using the information in [RDF.rb][] repositories as model
9
7
  objects. It gives you the ability to work in a resource-oriented way without
@@ -17,11 +15,9 @@ A changelog is available in the {file:CHANGES.md} file.
17
15
 
18
16
  ### Example
19
17
 
20
- class Person
21
-
22
- include Spira::Resource
18
+ class Person < Spira::Base
23
19
 
24
- base_uri "http://example.org/example/people"
20
+ configure :base_uri => "http://example.org/example/people"
25
21
 
26
22
  property :name, :predicate => FOAF.name, :type => String
27
23
  property :age, :predicate => FOAF.age, :type => Integer
@@ -48,6 +44,49 @@ A changelog is available in the {file:CHANGES.md} file.
48
44
  * No need to put everything about an object into Spira
49
45
  * Easy to use a resource as multiple models
50
46
 
47
+ ## ActiveModel integration
48
+
49
+ This is a version of Spira that makes use of ActiveModel. The goal of this version is
50
+ to replace all the internals of Spira with ActiveModel hooks, and thus get rid of
51
+ superfluous code and increase compatibility with Rails stack. I want it to be
52
+ a drop-in replacement for ActiveRecord or any other mature ORM solution they use
53
+ with Ruby on Rails.
54
+
55
+ Although I've been trying to make the impact of this transition to be as little
56
+ as possible, there are a few changes that you should be aware of:
57
+
58
+ * Read the comments on "new_record?" and "reload" methods. They are key methods in
59
+ understanding how Spira is working with the repository. Basically, a Spira record
60
+ is new, if the repository has no statements with this record as subject. This means,
61
+ that *the repository is queried every time you invoke "new_record?"*.
62
+ Also note that if Spira.repository is not set, your Spira resource will always be "new".
63
+ Also note that instantiating a new Spira resource sends a query to the repository,
64
+ if it is set, but should work just fine even if it's not (until you try to "save" it).
65
+ * Customary Rails' record manipulation methods are preferred now.
66
+ This means, you should use more habitual "save", "destroy", "update_attributes", etc.
67
+ instead of the "save!", "destroy!", "update", "update!" and others, as introduced
68
+ by the original Spira gem.
69
+ * Callbacks are now handled by ActiveModel. Previous ways of defining them are
70
+ no longer valid. This also introduces the "before_", "after_" and "around_" callbacks
71
+ as well as their "_validation", "_save", "_update" and "_create" companions for you to enjoy.
72
+ * Validations are also handled by ActiveModel. With all the helper methods you have in
73
+ ActiveRecord.
74
+ * A spira resource (class) must be defined by *inheriting* it from Spira::Base.
75
+ Using "include Spira::Resource" is *temporarily* broken, but will be back at some point,
76
+ with improvements and stuff.
77
+ * "after/before_create" callbacks are *not* called when only the properties of your
78
+ Spira resource are getting persisted. That is, you may create a "type"-less Spira resource,
79
+ assign properties to it, then #save it -- "_create" callbacks will not be triggered,
80
+ because Spira cannot infer a resource definition ("resource - RDF.type - type")
81
+ for such resource and will only persist its properties.
82
+ Although this is how the original Spira behaves too, I thought I'd state it
83
+ explicitly here before you start freaking out.
84
+ * Configuration options "base_uri", "default_vocabulary" and "repository_name" are
85
+ now configured via "configure" method (see the examples below).
86
+ * A couple of (not so) subtle changes:
87
+ 1) Global caching is gone. This means that "artist.works.first.artist" (reverse lookup)
88
+ does not return the original artist, but its copy retrieved from the database.
89
+
51
90
  ## Getting Started
52
91
 
53
92
  The easiest way to work with Spira is to install it via Rubygems:
@@ -64,16 +103,14 @@ without the `RDF::` prefix. For example:
64
103
 
65
104
  require 'spira'
66
105
 
67
- class CD
68
- include Spira::Resource
69
- base_uri 'http://example.org/cds'
106
+ class CD < Spira::Base
107
+ configure :base_uri => 'http://example.org/cds'
70
108
  property :name, :predicate => DC.title, :type => XSD.string
71
109
  property :artist, :predicate => URI.new('http://example.org/vocab/artist'), :type => :artist
72
110
  end
73
111
 
74
- class Artist
75
- include Spira::Resource
76
- base_uri 'http://example.org/artists'
112
+ class Artist < Spira::Base
113
+ configure :base_uri => 'http://example.org/artists'
77
114
  property :name, :predicate => DC.title, :type => XSD.string
78
115
  has_many :cds, :predicate => URI.new('http://example.org/vocab/published_cd'), :type => XSD.string
79
116
  end
@@ -147,8 +184,7 @@ Example
147
184
 
148
185
  A class with a `type` set is assigned an `RDF.type` on creation and saving.
149
186
 
150
- class Album
151
- include Spira::Resource
187
+ class Album < Spira::Base
152
188
  type URI.new('http://example.org/types/album')
153
189
  property :name, :predicate => DC.title
154
190
  end
@@ -162,6 +198,21 @@ In addition, one can count the members of a class with a `type` defined:
162
198
 
163
199
  Album.count #=> 1
164
200
 
201
+
202
+ It is possible to assign multiple types to a Spira class:
203
+
204
+ class Man < Spira::Base
205
+ type RDF::URI.new('http://example.org/people/father')
206
+ type RDF::URI.new('http://example.org/people/cop')
207
+ end
208
+
209
+ All assigned types are accessible via "types":
210
+
211
+ Man.types #=> #<Set: {#<RDF::URI:0xd5ebc0(http://example.org/people/father)>, #<RDF::URI:0xd5e4b8(http://example.org/people/cop)>}>
212
+
213
+ Also note that "type" actually returns a first type from the list of types.
214
+
215
+
165
216
  #### property
166
217
 
167
218
  A class declares property members with the `property` function. See `Property Options` for more information.
@@ -174,10 +225,9 @@ A class declares list members with the `has_many` function. See `Property Optio
174
225
 
175
226
  A class with a `default_vocabulary` set will transparently create predicates for defined properties:
176
227
 
177
- class Song
178
- include Spira::Resource
179
- default_vocabulary URI.new('http://example.org/vocab')
180
- base_uri 'http://example.org/songs'
228
+ class Song < Spira::Base
229
+ configure :default_vocabulary => URI.new('http://example.org/vocab'),
230
+ :base_uri => 'http://example.org/songs'
181
231
  property :title
182
232
  property :author, :type => :artist
183
233
  end
@@ -189,22 +239,17 @@ A class with a `default_vocabulary` set will transparently create predicates for
189
239
  dancing_queen.has_predicate?(RDF::URI.new('http://example.org/vocab/title')) #=> true
190
240
  dancing_queen.has_predicate?(RDF::URI.new('http://example.org/vocab/artist')) #=> true
191
241
 
192
- #### default_source
242
+ #### repository_name
193
243
 
194
244
  Provides this class with a default repository to use instead of the `:default`
195
245
  repository if one is not set.
196
246
 
197
- class Song
198
- default_source :songs
247
+ class Song < Spira::Base
248
+ configure :repository_name => :songs
199
249
  end
200
250
 
201
251
  See 'Defining Repositories' for more information.
202
252
 
203
- #### validate
204
-
205
- Provides the name of a function which does some sort of validation. See
206
- 'Validations' for more information.
207
-
208
253
  ### Property Options
209
254
 
210
255
  Spira classes can have properties that are either singular or a list. For a
@@ -256,16 +301,15 @@ is usually expressed as a URI. Here is the built-in Spira Integer class:
256
301
  RDF::Literal.new(value)
257
302
  end
258
303
 
259
- register_alias XSD.integer
304
+ register_alias RDF::XSD.integer
260
305
  end
261
306
  end
262
307
 
263
308
  Classes can now use this particular type like so:
264
309
 
265
- class Test
266
- include Spira::Resource
310
+ class Test < Spira::Base
267
311
  property :test1, :type => Integer
268
- property :test2, :type => XSD.integer
312
+ property :test2, :type => RDF::XSD.integer
269
313
  end
270
314
 
271
315
  Spira classes include the Spira::Types namespace, where several default types
@@ -297,8 +341,7 @@ turn an RDF::Value into a ruby object, and vice versa.
297
341
  end
298
342
  end
299
343
 
300
- class MyClass
301
- include Spira::Resource
344
+ class MyClass < Spira::Base
302
345
  property :property1, :type => MyModule::MyType
303
346
  end
304
347
 
@@ -311,8 +354,12 @@ You can define multiple repositories with Spira, and use more than one at a time
311
354
  Spira.add_repository! :cds, RDF::Sesame::Repository.new 'some_server'
312
355
  Spira.add_repository! :albums, RDF::Repository.load('some_file.nt')
313
356
 
314
- CD.repository = :cds
315
- Album.repository = :albums
357
+ class CD < Spira::Base
358
+ configure :repository_name => :cds
359
+ end
360
+ class Album < Spira::Base
361
+ configure :repository_name => :albums
362
+ end
316
363
 
317
364
  Objects can reference each other cross-repository.
318
365
 
@@ -323,94 +370,23 @@ If no repository has been specified, the `:default` repository will be used.
323
370
  Artist.repository == repo #=> true
324
371
 
325
372
  Classes can specify a default repository to use other than `:default` with the
326
- `default_source` function:
373
+ `repository_name` function:
327
374
 
328
- class Song
329
- default_source :songs
375
+ class Song < Spira::Base
376
+ configure :repository_name => :songs
330
377
  end
331
378
 
332
379
  Song.repository #=> nil, won't use :default
333
380
 
334
381
  ## Validations
335
382
 
336
- You may declare any number of validation functions with the `validate` function.
337
- Before saving, each referenced validation will be run, and the instance's
338
- {Spira::Errors} object will be populated with any errors. You can use the
339
- built in `assert` and assert helpers such as `assert_set` and
340
- `asssert_numeric`.
341
-
342
-
343
- class CD
344
- validate :is_real_music
345
- def is_real_music
346
- assert(artist.name != "Nickelback", :artist, "cannot be Nickelback")
347
- end
348
-
349
- validate :track_count_numeric
350
- def track_count_numeric
351
- assert_numeric(track_count)
352
- end
353
- end
354
-
355
- dancing_queen.artist = nickelback
356
- dancing_queen.save! #=> ValidationError
357
- dancing_queen.errors.each #=> ["artist cannot be Nickelback"]
358
-
359
- dancing_queen.artist = abba
360
- dancing_queen.save! #=> true
383
+ [removed]
384
+ See the description of ActiveModel::Validations.
361
385
 
362
386
  ## Hooks
363
387
 
364
- Spira supports `before_create`, `after_create`, `after_update`, `before_save`,
365
- `after_save`, and `before_destroy` hooks:
366
-
367
- class CD
368
- def before_save
369
- self.publisher = 'No publisher set' if self.publisher.nil?
370
- end
371
- end
372
-
373
- The `after_update` hook only fires on the `update` method, not simple property
374
- accessors (to allow you to easily set properties in these without going into a
375
- recursive loop):
376
-
377
- class CD
378
- def after_update
379
- self.artist = 'Queen' # every artist should be Queen!
380
- end
381
- end
382
-
383
- # ...snip ...
384
- dancing_queen.artist
385
- #=> "ABBA"
386
- dancing_queen.name = "Dancing Queen"
387
- dancing_queen.artist
388
- #=> "ABBA"
389
- dancing_queen.update(:name => "Dancing Queen")
390
- dancing_queen.artist
391
- #=> "Queen"
392
-
393
- ## Inheritance
394
-
395
- You can extend Spira resources without a problem:
396
-
397
- class BoxedSet < CD
398
- include Spira::Resource
399
- property cd_count, :predicate => CD.count, :type => Integer
400
- end
401
-
402
- You can also make Spira modules and include them into other classes:
403
-
404
- module Media
405
- include Spira::Resource
406
- property :format, :predicate => Media.format
407
- end
408
-
409
- class CD
410
- include Spira::Resource
411
- include Media
412
- end
413
-
388
+ [removed]
389
+ See the description of ActiveModel::Callbacks.
414
390
 
415
391
  ## Using Model Objects as RDF.rb Objects
416
392
 
@@ -427,16 +403,11 @@ There are a number of ways to ask for help. In declining order of preference:
427
403
  * You can post issues to the Github issue queue
428
404
  * (there might one day be a google group or other such support channel, but not yet)
429
405
 
430
- ## Authors, Development, and License
431
-
432
- #### Authors
433
- * Ben Lavender <blavender@gmail.com>
434
-
435
- #### 'License'
406
+ ## 'License'
436
407
  Spira is free and unemcumbered software released into the public
437
408
  domain. For more information, see the included UNLICENSE file.
438
409
 
439
- #### Contributing
410
+ ## Contributing
440
411
  Fork it on Github and go. Please make sure you're kosher with the UNLICENSE
441
412
  file before contributing.
442
413
 
@@ -0,0 +1,37 @@
1
+ module RDF
2
+ class URI
3
+ ##
4
+ # Create a projection of this URI as the given Spira::Resource class.
5
+ # Equivalent to `klass.for(self, *args)`
6
+ #
7
+ # @example Instantiating a URI as a Spira Resource
8
+ # RDF::URI('http://example.org/person/bob').as(Person)
9
+ # @param [Class] klass
10
+ # @param [*Any] args Any arguments to pass to klass.for
11
+ # @yield [self] Executes a given block and calls `#save!`
12
+ # @yieldparam [self] self The newly created instance
13
+ # @return [Klass] An instance of klass
14
+ def as(klass, *args, &block)
15
+ raise ArgumentError, "#{klass} is not a Spira resource" unless klass.is_a?(Class) && klass.ancestors.include?(Spira::Base)
16
+ klass.for(self, *args, &block)
17
+ end
18
+ end
19
+
20
+ class Node
21
+ ##
22
+ # Create a projection of this Node as the given Spira::Resource class.
23
+ # Equivalent to `klass.for(self, *args)`
24
+ #
25
+ # @example Instantiating a blank node as a Spira Resource
26
+ # RDF::Node.new.as(Person)
27
+ # @param [Class] klass
28
+ # @param [*Any] args Any arguments to pass to klass.for
29
+ # @yield [self] Executes a given block and calls `#save!`
30
+ # @yieldparam [self] self The newly created instance
31
+ # @return [Klass] An instance of klass
32
+ def as(klass, *args)
33
+ raise ArgumentError, "#{klass} is not a Spira resource" unless klass.is_a?(Class) && klass.ancestors.include?(Spira::Base)
34
+ klass.for(self, *args)
35
+ end
36
+ end
37
+ end
data/lib/spira.rb CHANGED
@@ -1,6 +1,8 @@
1
- require 'rdf'
2
- require 'promise'
3
- require 'spira/exceptions'
1
+ require "rdf"
2
+ require "rdf/ext/uri"
3
+ require "promise"
4
+ require "spira/exceptions"
5
+ require "spira/utils"
4
6
 
5
7
  ##
6
8
  # Spira is a framework for building projections of RDF data into Ruby classes.
@@ -9,66 +11,59 @@ require 'spira/exceptions'
9
11
  # @see http://rdf.rubyforge.org
10
12
  # @see http://github.com/bhuga/spira
11
13
  # @see Spira::Resource
12
- module Spira
13
14
 
14
- ##
15
- # The list of repositories available for Spira resources
16
- #
17
- # @see http://rdf.rubyforge.org/RDF/Repository.html
18
- # @return [Hash{Symbol => RDF::Repository}]
19
- # @private
20
- def repositories
21
- settings[:repositories] ||= {}
15
+ module RDF
16
+ class Repository
22
17
  end
23
- module_function :repositories
18
+ end
19
+
20
+ module Spira
21
+
22
+ autoload :Base, 'spira/base'
23
+ autoload :Type, 'spira/type'
24
+ autoload :Types, 'spira/types'
25
+ autoload :VERSION, 'spira/version'
24
26
 
25
27
  ##
26
28
  # The list of all property types available for Spira resources
27
- #
29
+ #
28
30
  # @see Spira::Types
29
31
  # @return [Hash{Symbol => Spira::Type}]
30
32
  def types
31
- settings[:types] ||= {}
33
+ @types ||= {}
32
34
  end
33
35
  module_function :types
34
36
 
35
- ##
36
- # A thread-local hash for storing settings. Used by Resource classes.
37
- #
38
- # @see Spira::Resource
39
- # @see Spira.repositories
40
- # @see Spira.types
41
- def settings
42
- Thread.current[:spira] ||= {}
43
- end
44
- module_function :settings
45
-
46
37
  ##
47
38
  # Add a repository to Spira's list of repositories.
48
39
  #
49
40
  # @overload add_repository(name, repo)
50
- # @param [Symbol] name The name of this repository
51
- # @param [RDF::Repository] repo An RDF::Repository
41
+ # @param [Symbol] name The name of this repository
42
+ # @param [RDF::Repository] repo
43
+ #
52
44
  # @overload add_repository(name, klass, *args)
53
- # @param [Symbol] name The name of this repository
54
- # @param [RDF::Repository, Class] repo A Class that inherits from RDF::Repository
55
- # @param [*Object] The list of arguments to instantiate the class
45
+ # @param [Symbol] name The name of this repository
46
+ # @param [Class] klass
47
+ # A Class that inherits from `RDF::Repository`
48
+ # @param [Array] args
49
+ # The list of arguments to instantiate the class
50
+ #
56
51
  # @example Adding an ntriples file as a repository
57
52
  # Spira.add_repository(:default, RDF::Repository.load('http://datagraph.org/jhacker/foaf.nt'))
58
53
  # @example Adding an empty repository to be instantiated on use
59
54
  # Spira.add_repository(:default, RDF::Repository)
60
55
  # @return [Void]
61
- # @see RDF::Repository
62
56
  def add_repository(name, klass, *args)
63
- repositories[name] = case klass
57
+ repositories[name] =
58
+ case klass
64
59
  when Class
65
60
  promise { klass.new(*args) }
66
61
  else
67
62
  klass
68
- end
69
- if (name == :default) && settings[:repositories][name].nil?
70
- warn "WARNING: Adding nil default repository"
71
- end
63
+ end
64
+ if (name == :default) && repository(name).nil?
65
+ warn "WARNING: Adding nil default repository"
66
+ end
72
67
  end
73
68
  alias_method :add_repository!, :add_repository
74
69
  module_function :add_repository, :add_repository!
@@ -91,10 +86,23 @@ module Spira
91
86
  # @return [Void]
92
87
  # @private
93
88
  def clear_repositories!
94
- settings[:repositories] = {}
89
+ @repositories = {}
95
90
  end
96
91
  module_function :clear_repositories!
97
92
 
93
+
94
+ private
95
+
96
+ ##
97
+ # The list of repositories available for Spira resources
98
+ #
99
+ # @see http://rdf.rubyforge.org/RDF/Repository.html
100
+ # @return [Hash{Symbol => RDF::Repository}]
101
+ def repositories
102
+ @repositories ||= {}
103
+ end
104
+ module_function :repositories
105
+
98
106
  ##
99
107
  # Alias a property type to another. This allows a range of options to be
100
108
  # specified for a property type which all reference one Spira::Type
@@ -102,55 +110,8 @@ module Spira
102
110
  # @param [Any] new The new symbol or reference
103
111
  # @param [Any] original The type the new symbol should refer to
104
112
  # @return [Void]
105
- # @private
106
113
  def type_alias(new, original)
107
- types[new] = original
114
+ types[new] = original
108
115
  end
109
116
  module_function :type_alias
110
-
111
- autoload :Resource, 'spira/resource'
112
- autoload :Base, 'spira/base'
113
- autoload :Type, 'spira/type'
114
- autoload :Types, 'spira/types'
115
- autoload :Errors, 'spira/errors'
116
- autoload :VERSION, 'spira/version'
117
-
118
- end
119
-
120
- module RDF
121
- class URI
122
- ##
123
- # Create a projection of this URI as the given Spira::Resource class.
124
- # Equivalent to `klass.for(self, *args)`
125
- #
126
- # @example Instantiating a URI as a Spira Resource
127
- # RDF::URI('http://example.org/person/bob').as(Person)
128
- # @param [Class] klass
129
- # @param [*Any] args Any arguments to pass to klass.for
130
- # @yield [self] Executes a given block and calls `#save!`
131
- # @yieldparam [self] self The newly created instance
132
- # @return [Klass] An instance of klass
133
- def as(klass, *args, &block)
134
- raise ArgumentError, "#{klass} is not a Spira resource" unless klass.is_a?(Class) && klass.ancestors.include?(Spira::Resource)
135
- klass.for(self, *args, &block)
136
- end
137
- end
138
-
139
- class Node
140
- ##
141
- # Create a projection of this Node as the given Spira::Resource class.
142
- # Equivalent to `klass.for(self, *args)`
143
- #
144
- # @example Instantiating a blank node as a Spira Resource
145
- # RDF::Node.new.as(Person)
146
- # @param [Class] klass
147
- # @param [*Any] args Any arguments to pass to klass.for
148
- # @yield [self] Executes a given block and calls `#save!`
149
- # @yieldparam [self] self The newly created instance
150
- # @return [Klass] An instance of klass
151
- def as(klass, *args)
152
- raise ArgumentError, "#{klass} is not a Spira resource" unless klass.is_a?(Class) && klass.ancestors.include?(Spira::Resource)
153
- klass.for(self, *args)
154
- end
155
- end
156
117
  end