spira 0.0.8 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
data/AUTHORS CHANGED
@@ -1,2 +1,3 @@
1
1
  * Ben Lavender <blavender@gmail.com>
2
+ * Arto Bendiken <arto@bendiken.net>
2
3
  * Nicholas J Humfrey <njh@aelius.com>
data/CHANGES.md CHANGED
@@ -1,4 +1,18 @@
1
1
  # Changelog for Spira <http://github.com/datagraph/spira>
2
+
3
+ ## 0.0.9
4
+ * Fix a bug with Spira::Types::Any that prevented blank node objects
5
+ * Added Spira::Resource#copy, #copy!, #copy_resource!, and #rename!
6
+ * Fix a bug with fragment RDF::URI arguments that prevented correct URI
7
+ construction
8
+ * Added Spira::Resource.project(subject, attributes, &block), which creates a
9
+ new instance without attempting to perform any base_uri logic on the given
10
+ subject. This provides a supported API entry point for implementors to
11
+ create their own domain-specific URI construction.
12
+ * Updating a value to nil will now remove it from the repository on #save!
13
+ * Tweaks to dirty tracking to correctly catch both changed and updated values.
14
+ All tests pass for the first time with this change.
15
+
2
16
  ## 0.0.8
3
17
  * Remove type checking for repository addition. More power in return for
4
18
  slightly more difficult error messages.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.8
1
+ 0.0.9
@@ -0,0 +1,14 @@
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
@@ -43,7 +43,7 @@ module Spira
43
43
  # Spira does not have 'find' or 'create' functions. As RDF identifiers
44
44
  # are globally unique, they all simply 'are'.
45
45
  #
46
- # On calling `for`, a new instance is created for the given URI. The
46
+ # On calling `for`, a new projection is created for the given URI. The
47
47
  # first time access is attempted on a field, the repository will be
48
48
  # queried for existing attributes, which will be used for the given URI.
49
49
  # Underlying repositories are not accessed at the time of calling `for`.
@@ -66,11 +66,28 @@ module Spira
66
66
  # @return [Spira::Resource] The newly created instance
67
67
  # @see http://rdf.rubyforge.org/RDF/URI.html
68
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)
69
87
  if !self.type.nil? && attributes[:type]
70
88
  raise TypeError, "#{self} has an RDF type, #{self.type}, and cannot accept one as an argument."
71
89
  end
72
- subject = id_for(identifier)
73
- instance = self.new(attributes.merge(:_subject => subject), &block)
90
+ self.new(attributes.merge(:_subject => subject), &block)
74
91
  end
75
92
 
76
93
  ##
@@ -92,23 +109,40 @@ module Spira
92
109
  # @return [RDF::URI, RDF::Node]
93
110
  # @raise [ArgumentError] If this class cannot create an identifier from the given argument
94
111
  # @see http://rdf.rubyforge.org/RDF/URI.html
112
+ # @see Spira::Resource.base_uri
113
+ # @see Spira::Resource.for
95
114
  def id_for(identifier)
96
- case
97
- # Catches RDF::URI and implementing subclasses
98
- when identifier.respond_to?(:to_uri)
99
- identifier.to_uri
100
- # Catches RDF::Nodes
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
101
123
  when identifier.respond_to?(:node?) && identifier.node?
102
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
103
130
  when identifier.is_a?(Addressable::URI)
104
- RDF::URI.new(identifier)
105
- # Treat identifier as a string, and create a URI out of it.
131
+ id_for(RDF::URI.new(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.
106
135
  else
107
- uri = RDF::URI.new(identifier.to_s)
108
- return uri if uri.absolute?
109
- raise ArgumentError, "Cannot create identifier for #{self} by String without base_uri; RDF::URI required" if self.base_uri.nil?
110
- separator = self.base_uri.to_s[-1,1] =~ /(\/|#)/ ? '' : '/'
111
- RDF::URI.new(self.base_uri.to_s + separator + identifier.to_s)
136
+ uri = identifier.is_a?(RDF::URI) ? identifier : RDF::URI.new(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.new(self.base_uri.to_s + separator + identifier.to_s)
145
+ end
112
146
  end
113
147
  end
114
148
 
@@ -12,8 +12,13 @@ module Spira
12
12
  # @see Spira::Resource::ClassMethods
13
13
  # @see Spira::Resource::DSL
14
14
  # @see Spira::Resource::Validations
15
- module InstanceMethods
16
-
15
+ module InstanceMethods
16
+
17
+ # Marker for whether or not a field has been set or not; distinguishes
18
+ # nil and unset.
19
+ # @private
20
+ NOT_SET = ::Object.new.freeze
21
+
17
22
  ##
18
23
  # This instance's URI.
19
24
  #
@@ -39,6 +44,7 @@ module Spira
39
44
  yield(self)
40
45
  save!
41
46
  end
47
+ self
42
48
  end
43
49
 
44
50
  ##
@@ -52,12 +58,18 @@ module Spira
52
58
  @cache = opts[:_cache] || RDF::Util::Cache.new
53
59
  @cache[subject] = self
54
60
  @dirty = {}
55
- # We need to save all attributes twice to track state changes in
56
- # mutable objects, like lists
57
- @attributes = promise { reload_attributes }
61
+ @attributes = {}
62
+ @attributes[:current] = {}
63
+ @attributes[:copied] = {}
58
64
  self.class.properties.each do |name, predicate|
59
- attribute_set(name, opts[name]) unless opts[name].nil?
65
+ case opts[name].nil?
66
+ when false
67
+ attribute_set(name, opts[name])
68
+ when true
69
+ @attributes[:copied][name] = NOT_SET
70
+ end
60
71
  end
72
+ @attributes[:original] = promise { reload_attributes }
61
73
  end
62
74
 
63
75
  ##
@@ -68,8 +80,6 @@ module Spira
68
80
  def reload_attributes()
69
81
  statements = self.class.repository_or_fail.query(:subject => @subject)
70
82
  attributes = {}
71
- attributes[:original] = {}
72
- attributes[:current] = {}
73
83
 
74
84
  # Set attributes for each statement corresponding to a predicate
75
85
  self.class.properties.each do |name, property|
@@ -81,22 +91,12 @@ module Spira
81
91
  values << self.class.build_value(statement,property[:type], @cache)
82
92
  end
83
93
  end
84
- attributes[:current][name] = values
85
- attributes[:original][name] = values.dup
94
+ attributes[name] = values
86
95
  else
87
96
  statement = statements.query(:subject => @subject, :predicate => property[:predicate]).first unless statements.empty?
88
- attributes[:current][name] = self.class.build_value(statement, property[:type], @cache)
89
-
90
- # Lots of things like Fixnums and Nil can't be dup'd, but they are
91
- # all immutable, so if we can't dup, it's no problem for dirty tracking.
92
- begin
93
- attributes[:original][name] = attributes[:current][name].dup
94
- rescue TypeError
95
- attributes[:original][name] = attributes[:current][name]
96
- end
97
+ attributes[name] = self.class.build_value(statement, property[:type], @cache)
97
98
  end
98
99
  end
99
-
100
100
  attributes
101
101
  end
102
102
 
@@ -225,11 +225,12 @@ module Spira
225
225
  end
226
226
  self.class.repository_or_fail.insert(*repo)
227
227
  else
228
- self.class.repository_or_fail.insert(RDF::Statement.new(subject, predicate[:predicate], self.class.build_rdf_value(attribute_get(property), self.class.properties[property][:type])))
228
+ self.class.repository_or_fail.insert(RDF::Statement.new(subject, predicate[:predicate], self.class.build_rdf_value(attribute_get(property), self.class.properties[property][:type]))) unless attribute_get(property).nil?
229
229
  end
230
230
  end
231
- @dirty[property] = nil
232
231
  @attributes[:original][property] = attribute_get(property)
232
+ @dirty[property] = nil
233
+ @attributes[:copied][property] = NOT_SET
233
234
  end
234
235
  self.class.repository_or_fail.insert(RDF::Statement.new(@subject, RDF.type, type)) unless type.nil?
235
236
  end
@@ -309,8 +310,13 @@ module Spira
309
310
  case
310
311
  when @dirty[name] == true
311
312
  true
312
- else
313
- @attributes[:current][name] != @attributes[:original][name]
313
+ else
314
+ case @attributes[:copied][name]
315
+ when NOT_SET
316
+ false
317
+ else
318
+ @attributes[:copied][name] != @attributes[:original][name]
319
+ end
314
320
  end
315
321
  end
316
322
  end
@@ -320,7 +326,26 @@ module Spira
320
326
  #
321
327
  # @private
322
328
  def attribute_get(name)
323
- @attributes[:current][name]
329
+ case @dirty[name]
330
+ when true
331
+ @attributes[:current][name]
332
+ else
333
+ case @attributes[:copied][name].equal?(NOT_SET)
334
+ when true
335
+ dup = if @attributes[:original][name].is_a?(Spira::Resource)
336
+ @attributes[:original][name]
337
+ else
338
+ begin
339
+ @attributes[:original][name].dup
340
+ rescue TypeError
341
+ @attributes[:original][name]
342
+ end
343
+ end
344
+ @attributes[:copied][name] = dup
345
+ when false
346
+ @attributes[:copied][name]
347
+ end
348
+ end
324
349
  end
325
350
 
326
351
  ##
@@ -449,6 +474,61 @@ module Spira
449
474
  self.class.repository.query(:subject => subject)
450
475
  end
451
476
 
477
+ ##
478
+ # Returns a new instance of this class with the new subject instead of self.subject
479
+ #
480
+ # @param [RDF::Resource] new_subject
481
+ # @return [Spira::Resource] copy
482
+ def copy(new_subject)
483
+ copy = self.class.for(new_subject)
484
+ self.class.properties.each_key { |property| copy.attribute_set(property, self.attribute_get(property)) }
485
+ copy
486
+ end
487
+
488
+ ##
489
+ # Returns a new instance of this class with the new subject instead of
490
+ # self.subject after saving the new copy to the repository.
491
+ #
492
+ # @param [RDF::Resource] new_subject
493
+ # @return [Spira::Resource, String] copy
494
+ def copy!(new_subject)
495
+ copy(new_subject).save!
496
+ end
497
+
498
+ ##
499
+ # Copies all data, including non-model data, about this resource to
500
+ # another URI. The copy is immediately saved to the repository.
501
+ #
502
+ # @param [RDF::Resource] new_subject
503
+ # @return [Spira::Resource, String] copy
504
+ def copy_resource!(new_subject)
505
+ new_subject = self.class.id_for(new_subject)
506
+ update_repository = RDF::Repository.new
507
+ data.each do |statement|
508
+ update_repository << RDF::Statement.new(new_subject, statement.predicate, statement.object)
509
+ end
510
+ self.class.repository.insert(update_repository)
511
+ new_subject.as(self.class)
512
+ end
513
+
514
+ ##
515
+ # Rename this resource in the repository to the new given subject.
516
+ # Changes are immediately saved to the repository.
517
+ #
518
+ # @param [RDF::Resource] new_subject
519
+ # @return [Spira::Resource, String] new_resource
520
+ def rename!(new_subject)
521
+ new = copy_resource!(new_subject)
522
+ object_statements = self.class.repository.query(:object => subject)
523
+ update_repository = RDF::Repository.new
524
+ object_statements.each do |statement|
525
+ update_repository << RDF::Statement.new(statement.subject, statement.predicate, new.subject)
526
+ end
527
+ self.class.repository.insert(update_repository)
528
+ destroy!(:completely)
529
+ new
530
+ end
531
+
452
532
  ## We have defined #each and can do this fun RDF stuff by default
453
533
  include ::RDF::Enumerable, ::RDF::Queryable
454
534
 
@@ -12,12 +12,12 @@ module Spira::Types
12
12
  include Spira::Type
13
13
 
14
14
  def self.unserialize(value)
15
- value.respond_to?(:to_uri) ? value.to_uri : value.object
15
+ value.respond_to?(:object) ? value.object : value
16
16
  end
17
17
 
18
18
  def self.serialize(value)
19
19
  raise TypeError, "Spira::Types::Any cannot serialize collections" if value.is_a?(Array)
20
- value.respond_to?(:to_uri) ? value.to_uri : RDF::Literal.new(value)
20
+ value.is_a?(RDF::Value) ? value : RDF::Literal.new(value)
21
21
  end
22
22
 
23
23
  end
data/lib/spira/version.rb CHANGED
@@ -2,7 +2,7 @@ module Spira
2
2
  module VERSION
3
3
  MAJOR = 0
4
4
  MINOR = 0
5
- TINY = 8
5
+ TINY = 9
6
6
  EXTRA = nil
7
7
 
8
8
  STRING = [MAJOR, MINOR, TINY].join('.')
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 0
8
- - 8
9
- version: 0.0.8
8
+ - 9
9
+ version: 0.0.9
10
10
  platform: ruby
11
11
  authors:
12
12
  - Ben Lavender
@@ -15,7 +15,7 @@ bindir:
15
15
  - bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-09-22 00:00:00 -05:00
18
+ date: 2010-10-20 00:00:00 +02:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -70,8 +70,8 @@ dependencies:
70
70
  segments:
71
71
  - 0
72
72
  - 2
73
- - 2
74
- version: 0.2.2
73
+ - 3
74
+ version: 0.2.3
75
75
  type: :runtime
76
76
  version_requirements: *id004
77
77
  - !ruby/object:Gem::Dependency
@@ -119,6 +119,7 @@ files:
119
119
  - lib/spira/base.rb
120
120
  - lib/spira/errors.rb
121
121
  - lib/spira/exceptions.rb
122
+ - lib/spira/extensions.rb
122
123
  - lib/spira/resource/class_methods.rb
123
124
  - lib/spira/resource/dsl.rb
124
125
  - lib/spira/resource/instance_methods.rb