spira 0.0.7 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
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