spira 0.0.7 → 0.0.8

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/CHANGES.md CHANGED
@@ -1,4 +1,22 @@
1
1
  # Changelog for Spira <http://github.com/datagraph/spira>
2
+ ## 0.0.8
3
+ * Remove type checking for repository addition. More power in return for
4
+ slightly more difficult error messages.
5
+ * Repositories added via klass, *arg are now only instantiated on first use
6
+ instead of immediately.
7
+ * RDF::URI#as, RDF::Node#as, Resource.for, and Resource#new can now all accept
8
+ a block, which yields the new instance and saves it after the block.
9
+ * Clarify error message when default repository is not setup
10
+ * Added a weak-reference identity map for each instance. Any circular references in
11
+ relations will now return the original object instead of querying for a new
12
+ one.
13
+ * Use a weak-reference identity map when iterating by class.
14
+ * When serializing/unserializing, duck typing (:serialize, :unserialize) is now
15
+ permitted.
16
+
17
+ ## 0.0.7
18
+ * Added Resource.[], an alias for Resource.for
19
+ * Resource.each now correctly raises an exception when a repository isn't found
2
20
 
3
21
  ## 0.0.6
4
22
  * Added #exists?, which returns a boolean if an instance exists in
data/README CHANGED
@@ -90,7 +90,7 @@ Then use your model classes, in a way more or less similar to any number of ORMs
90
90
  artist.cds = [cd]
91
91
  artist.save!
92
92
 
93
- queen = Arist.for('queen')
93
+ queen = Artist.for('queen')
94
94
  hits = CD.for 'queens-greatest-hits'
95
95
  hits.artist == artist == queen
96
96
 
@@ -117,7 +117,7 @@ Any call to 'for' with a valid identifier will always return an object with nil
117
117
  fields. It's a way of looking at a given resource, not a closed-world mapping
118
118
  to one.
119
119
 
120
- You can use also use blank nodes more or less as you would a URI:
120
+ You can also use blank nodes more or less as you would a URI:
121
121
 
122
122
  remix_artist = Artist.for(RDF::Node.new)
123
123
  # => <Artist @subject=#<RDF::Node:0xd1d314(_:g13751060)>>
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.7
1
+ 0.0.8
@@ -60,12 +60,10 @@ module Spira
60
60
  # @see RDF::Repository
61
61
  def add_repository(name, klass, *args)
62
62
  repositories[name] = case klass
63
- when RDF::Repository
64
- klass
65
63
  when Class
66
- klass.new(*args)
64
+ promise { klass.new(*args) }
67
65
  else
68
- raise ArgumentError, "Could not add repository #{klass} as #{name}; expected an RDF::Repository or class name"
66
+ klass
69
67
  end
70
68
  if (name == :default) && settings[:repositories][name].nil?
71
69
  warn "WARNING: Adding nil default repository"
@@ -128,10 +126,12 @@ module RDF
128
126
  # RDF::URI('http://example.org/person/bob').as(Person)
129
127
  # @param [Class] klass
130
128
  # @param [*Any] args Any arguments to pass to klass.for
129
+ # @yield [self] Executes a given block and calls `#save!`
130
+ # @yieldparam [self] self The newly created instance
131
131
  # @return [Klass] An instance of klass
132
- def as(klass, *args)
132
+ def as(klass, *args, &block)
133
133
  raise ArgumentError, "#{klass} is not a Spira resource" unless klass.is_a?(Class) && klass.ancestors.include?(Spira::Resource)
134
- klass.for(self, *args)
134
+ klass.for(self, *args, &block)
135
135
  end
136
136
  end
137
137
 
@@ -144,6 +144,8 @@ module RDF
144
144
  # RDF::Node.new.as(Person)
145
145
  # @param [Class] klass
146
146
  # @param [*Any] args Any arguments to pass to klass.for
147
+ # @yield [self] Executes a given block and calls `#save!`
148
+ # @yieldparam [self] self The newly created instance
147
149
  # @return [Klass] An instance of klass
148
150
  def as(klass, *args)
149
151
  raise ArgumentError, "#{klass} is not a Spira resource" unless klass.is_a?(Class) && klass.ancestors.include?(Spira::Resource)
@@ -32,7 +32,7 @@ module Spira
32
32
  # @return [RDF::Repository]
33
33
  # @private
34
34
  def repository_or_fail
35
- repository || (raise Spira::NoRepositoryError, "#{self} is configured to use #{@repository_name} as a repository, but it has not been set.")
35
+ repository || (raise Spira::NoRepositoryError, "#{self} is configured to use :#{@repository_name || 'default'} as a repository, but it has not been set.")
36
36
  end
37
37
 
38
38
  ##
@@ -61,14 +61,16 @@ module Spira
61
61
  # @overload for(identifier, attributes = {})
62
62
  # @param [Any] uri The identifier to append to the base URI for this class
63
63
  # @param [Hash{Symbol => Any}] attributes Initial attributes
64
+ # @yield [self] Executes a given block and calls `#save!`
65
+ # @yieldparam [self] self The newly created instance
64
66
  # @return [Spira::Resource] The newly created instance
65
67
  # @see http://rdf.rubyforge.org/RDF/URI.html
66
- def for(identifier, attributes = {})
68
+ def for(identifier, attributes = {}, &block)
67
69
  if !self.type.nil? && attributes[:type]
68
70
  raise TypeError, "#{self} has an RDF type, #{self.type}, and cannot accept one as an argument."
69
71
  end
70
72
  subject = id_for(identifier)
71
- self.new(attributes.merge(:_subject => subject))
73
+ instance = self.new(attributes.merge(:_subject => subject), &block)
72
74
  end
73
75
 
74
76
  ##
@@ -124,6 +126,23 @@ module Spira
124
126
  repository.query(:predicate => RDF.type, :object => @type).subjects.count
125
127
  end
126
128
 
129
+ ##
130
+ # A cache of iterated instances of this projection
131
+ #
132
+ # @return [RDF::Util::Cache]
133
+ # @private
134
+ def cache
135
+ @cache ||= RDF::Util::Cache.new
136
+ end
137
+
138
+ ##
139
+ # Clear the iteration cache
140
+ #
141
+ # @return [void]
142
+ def reload
143
+ @cache = nil
144
+ end
145
+
127
146
  ##
128
147
  # Enumerate over all resources projectable as this class. This method is
129
148
  # only valid for classes which declare a `type` with the `type` method in
@@ -146,12 +165,12 @@ module Spira
146
165
  enum_for(:each)
147
166
  else
148
167
  repository_or_fail.query(:predicate => RDF.type, :object => @type).each_subject do |subject|
149
- block.call(self.for(subject))
168
+ self.cache[subject] ||= self.for(subject)
169
+ block.call(cache[subject])
150
170
  end
151
171
  end
152
172
  end
153
173
 
154
-
155
174
  ##
156
175
  # Returns true if the given property is a has_many property, false otherwise
157
176
  #
@@ -132,15 +132,18 @@ module Spira
132
132
  # Build a Ruby value from an RDF value.
133
133
  #
134
134
  # @private
135
- def build_value(statement, type)
135
+ def build_value(statement, type, cache)
136
136
  case
137
137
  when statement == nil
138
138
  nil
139
- when type.is_a?(Class) && type.ancestors.include?(Spira::Type)
139
+ when !cache[statement.object].nil?
140
+ cache[statement.object]
141
+ when type.respond_to?(:unserialize)
140
142
  type.unserialize(statement.object)
141
143
  when type.is_a?(Symbol) || type.is_a?(String)
142
144
  klass = classize_resource(type)
143
- promise { klass.for(statement.object) }
145
+ cache[statement.object] = promise { klass.for(statement.object, :_cache => cache) }
146
+ cache[statement.object]
144
147
  else
145
148
  raise TypeError, "Unable to unserialize #{statement.object} as #{type}"
146
149
  end
@@ -150,7 +153,7 @@ module Spira
150
153
  # @private
151
154
  def build_rdf_value(value, type)
152
155
  case
153
- when type.is_a?(Class) && type.ancestors.include?(Spira::Type)
156
+ when type.respond_to?(:serialize)
154
157
  type.serialize(value)
155
158
  when value && value.class.ancestors.include?(Spira::Resource)
156
159
  klass = classize_resource(type)
@@ -27,12 +27,18 @@ module Spira
27
27
  # {Spira::Resource::ClassMethods#for} instead.
28
28
  #
29
29
  # @param [Hash{Symbol => Any}] opts Default attributes for this instance
30
+ # @yield [self] Executes a given block and calls `#save!`
31
+ # @yieldparam [self] self The newly created instance
30
32
  # @see Spira::Resource::ClassMethods#for
31
33
  # @see RDF::URI#as
32
34
  # @see RDF::Node#as
33
35
  def initialize(opts = {})
34
36
  @subject = opts[:_subject] || RDF::Node.new
35
37
  reload(opts)
38
+ if block_given?
39
+ yield(self)
40
+ save!
41
+ end
36
42
  end
37
43
 
38
44
  ##
@@ -43,6 +49,8 @@ module Spira
43
49
  # @param [Hash{Symbol => Any}] opts
44
50
  # @option opts [Symbol] :any A property name. Sets the given property to the given value.
45
51
  def reload(opts = {})
52
+ @cache = opts[:_cache] || RDF::Util::Cache.new
53
+ @cache[subject] = self
46
54
  @dirty = {}
47
55
  # We need to save all attributes twice to track state changes in
48
56
  # mutable objects, like lists
@@ -70,14 +78,14 @@ module Spira
70
78
  collection = statements.query(:subject => @subject, :predicate => property[:predicate]) unless statements.empty?
71
79
  unless collection.nil?
72
80
  collection.each do |statement|
73
- values << self.class.build_value(statement,property[:type])
81
+ values << self.class.build_value(statement,property[:type], @cache)
74
82
  end
75
83
  end
76
84
  attributes[:current][name] = values
77
85
  attributes[:original][name] = values.dup
78
86
  else
79
87
  statement = statements.query(:subject => @subject, :predicate => property[:predicate]).first unless statements.empty?
80
- attributes[:current][name] = self.class.build_value(statement, property[:type])
88
+ attributes[:current][name] = self.class.build_value(statement, property[:type], @cache)
81
89
 
82
90
  # Lots of things like Fixnums and Nil can't be dup'd, but they are
83
91
  # all immutable, so if we can't dup, it's no problem for dirty tracking.
@@ -2,7 +2,7 @@ module Spira
2
2
  module VERSION
3
3
  MAJOR = 0
4
4
  MINOR = 0
5
- TINY = 7
5
+ TINY = 8
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
- - 7
9
- version: 0.0.7
8
+ - 8
9
+ version: 0.0.8
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-06 00:00:00 -05:00
18
+ date: 2010-09-22 00:00:00 -05:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency