aqua 0.1.6 → 0.2.0

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.
Files changed (37) hide show
  1. data/.gitignore +2 -1
  2. data/Aqua.gemspec +14 -11
  3. data/Rakefile +1 -1
  4. data/VERSION +1 -1
  5. data/lib/aqua.rb +5 -7
  6. data/lib/aqua/object/config.rb +2 -3
  7. data/lib/aqua/object/initializers.rb +309 -0
  8. data/lib/aqua/object/pack.rb +56 -132
  9. data/lib/aqua/object/query.rb +30 -2
  10. data/lib/aqua/object/stub.rb +60 -95
  11. data/lib/aqua/object/tank.rb +1 -0
  12. data/lib/aqua/object/translator.rb +313 -0
  13. data/lib/aqua/object/unpack.rb +26 -227
  14. data/lib/aqua/store/couch_db/couch_db.rb +1 -0
  15. data/lib/aqua/store/couch_db/database.rb +1 -1
  16. data/lib/aqua/store/couch_db/design_document.rb +126 -2
  17. data/lib/aqua/store/couch_db/result_set.rb +36 -0
  18. data/lib/aqua/store/couch_db/storage_methods.rb +182 -17
  19. data/lib/aqua/store/storage.rb +4 -48
  20. data/lib/aqua/support/mash.rb +2 -3
  21. data/lib/aqua/support/set.rb +4 -16
  22. data/spec/object/object_fixtures/array_udder.rb +1 -1
  23. data/spec/object/object_fixtures/persistent.rb +0 -2
  24. data/spec/object/pack_spec.rb +137 -517
  25. data/spec/object/query_spec.rb +36 -6
  26. data/spec/object/stub_spec.rb +10 -9
  27. data/spec/object/translator_packing_spec.rb +402 -0
  28. data/spec/object/translator_unpacking_spec.rb +262 -0
  29. data/spec/object/unpack_spec.rb +162 -320
  30. data/spec/spec_helper.rb +18 -0
  31. data/spec/store/couchdb/design_document_spec.rb +148 -7
  32. data/spec/store/couchdb/result_set_spec.rb +95 -0
  33. data/spec/store/couchdb/storage_methods_spec.rb +150 -10
  34. metadata +13 -9
  35. data/lib/aqua/support/initializers.rb +0 -216
  36. data/spec/object/object_fixtures/grounded.rb +0 -13
  37. data/spec/object/object_fixtures/sugar.rb +0 -4
@@ -11,11 +11,11 @@ module Aqua
11
11
  extend ClassMethods
12
12
  include InstanceMethods
13
13
 
14
- unless instance_methods.include?( 'id=' ) || new.instance_variables.include?( '@id' )
14
+ unless instance_methods.include?( 'id=' ) # || new.instance_variables.include?( '@id' )
15
15
  attr_accessor :id
16
16
  end
17
17
 
18
- hide_attributes :_store, :__pack, :id, :_rev
18
+ hide_attributes :_store, :__pack, :id, :_rev, :_translator
19
19
  end
20
20
  end
21
21
 
@@ -65,8 +65,8 @@ module Aqua
65
65
  end
66
66
  end # InstanceMethods
67
67
  end
68
-
69
- module ClassMethods
68
+
69
+ module ClassMethods
70
70
  end # ClassMethods
71
71
 
72
72
  module InstanceMethods
@@ -91,12 +91,25 @@ module Aqua
91
91
  self.__pack = Storage.new
92
92
  self.__pack.id = @id if @id
93
93
  self.__pack[:_rev] = _rev if _rev
94
- self.__pack[:keys] = []
95
- self.__pack[:stubs] = []
96
- self.__pack.merge!( _pack_object( self ) )
97
- _pack_singletons
94
+ self.__pack.merge!( _translator.pack_object( self ) )
95
+ _pack_attachments
98
96
  __pack
99
97
  end
98
+
99
+ def _pack_attachments
100
+ _translator.attachments.each do |file|
101
+ self.__pack.attachments.add( file.filename, file )
102
+ end
103
+ end
104
+
105
+ # Translator object responsible for packing the object and keeping track of externally
106
+ # stored records and also attachments
107
+ # @return [Translator]
108
+ #
109
+ # @api private
110
+ def _translator
111
+ @_translator ||= Translator.new( self )
112
+ end
100
113
 
101
114
  # Details from configuration options for the objects class about embedability.
102
115
  # @return [true, false, Hash] If true then it should be embedded in the object at hand.
@@ -105,74 +118,21 @@ module Aqua
105
118
  # with a few cached methods as defined in the array.
106
119
  #
107
120
  # @api private
108
- def _embed_me
109
- self.class._aqua_opts[:embed]
121
+ def _stubbed_methods
122
+ meths = !_embedded? && self.class._aqua_opts[:embed] && self.class._aqua_opts[:embed][:stub]
123
+ meths ? [meths].flatten : []
110
124
  end
111
-
112
- # Packs an object into data and meta data. Works recursively sending out to array, hash, etc.
113
- # object packers, which send their values back to _pack_object
114
- #
115
- # @param Object to pack
116
- # @return [Mash] Indifferent hash that is the data/metadata deconstruction of an object.
117
- #
118
- # @api private
119
- def _pack_object( obj )
120
- klass = obj.class
121
- if klass == String
122
- obj
123
- elsif obj.respond_to?(:to_aqua) # Types requiring initialization
124
- obj.to_aqua( self )
125
- elsif obj.aquatic? && obj != self
126
- if obj._embed_me == true
127
- obj._pack
128
- else
129
- _build_stub( obj )
130
- end
131
- else # other object without initializations
132
- _pack_vanilla( obj )
133
- end
134
- end
135
-
136
- # Packs the ivars for a given object.
137
- #
138
- # @param Object to pack
139
- # @return [Mash] Indifferent hash that is the data/metadata deconstruction of an object.
140
- #
141
- # @api private
142
- def _pack_ivars( obj )
143
- return_hash = {}
144
- vars = obj.respond_to?(:_storable_attributes) ? obj._storable_attributes : obj.instance_variables
145
- vars.each do |ivar_name|
146
- ivar = obj.instance_variable_get( ivar_name )
147
- return_hash[ivar_name] = _pack_object( ivar ) unless ivar.nil?
148
- end
149
- return_hash
150
- end
151
-
152
- # Handles the case of an hash-like object with keys that are objects
153
- #
154
- # @param Object to pack
155
- # @return [Integer] Index of the object in the keys array, used by the hash packer to name the key
156
- #
125
+
126
+ # Details from configuration options for the objects class about embedability.
127
+ # @return [true, false] If true then it should be embedded in the base object.
128
+ # If false, then it should be saved externally.
129
+ #
157
130
  # @api private
158
- def _build_object_key( obj )
159
- index = self.__pack[:keys].length
160
- self.__pack[:keys] << _pack_object( obj )
161
- index # return key
131
+ def _embedded?
132
+ self.class._aqua_opts[:embed] == true
162
133
  end
163
134
 
164
- # Adds an attachment to the __pack document. Before save the attachments are encoded into the doc
165
- #
166
- # @param [String] filename used as the key/index
167
- # @param [File, Tempfile] file to be attached
168
- #
169
- # @api semi-public
170
- def _pack_file( filename, file )
171
- __pack.attachments.add( filename, file )
172
- end
173
-
174
-
175
- attr_accessor :_warnings
135
+ attr_accessor :_warnings, :_rev
176
136
 
177
137
  # Private/protected methods are all prefaced by an underscore to prevent
178
138
  # clogging the object instance space. Some of the public ones above are too!
@@ -187,7 +147,7 @@ module Aqua
187
147
  # do without this accessor, but it would mean that an extra get request would have to be
188
148
  # made with each PUT request so that the latest _rev could be obtained.
189
149
  #
190
- attr_accessor :_store, :__pack, :_rev
150
+ attr_accessor :_store, :__pack
191
151
 
192
152
  private
193
153
 
@@ -206,61 +166,13 @@ module Aqua
206
166
  if result
207
167
  self.id = __pack.id
208
168
  self._rev = __pack.rev
209
- _clear_accessors
169
+ _clear_aqua_accessors
210
170
  self
211
171
  else
212
172
  result
213
173
  end
214
174
  end
215
175
 
216
- # Object packing methods ------------
217
-
218
- # Packs the an object requiring no initialization.
219
- #
220
- # @param Object to pack
221
- # @return [Mash] Indifferent hash that is the data/metadata deconstruction of an object.
222
- #
223
- # @api private
224
- def _pack_vanilla( obj )
225
- {
226
- 'class' => obj.class.to_s,
227
- 'ivars' => _pack_ivars( obj )
228
- }
229
- end
230
-
231
- # Packs the stub for an externally saved object.
232
- #
233
- # @param Object to pack
234
- # @return [Mash] Indifferent hash that is the data/metadata deconstruction of an object.
235
- #
236
- # @api private
237
- def _build_stub( obj )
238
- index = self.__pack[:stubs].length
239
- stub = { :class => obj.class.to_s, :id => obj }
240
- # deal with cached methods
241
- if obj._embed_me && obj._embed_me.keys && stub_methods = obj._embed_me[:stub]
242
- stub[:methods] = {}
243
- if stub_methods.class == Symbol || stub_methods.class == String
244
- stub_method = stub_methods.to_sym
245
- stub[:methods][stub_method] = obj.send( stub_method )
246
- else # is an array of values
247
- stub_methods.each do |meth|
248
- stub_method = meth.to_sym
249
- stub[:methods][stub_method] = obj.send( stub_method )
250
- end
251
- end
252
- end
253
- # add the stub
254
- self.__pack[:stubs] << stub
255
- # return a hash
256
- {'class' => 'Aqua::Stub', 'init' => "/STUB_#{index}"}
257
- end
258
-
259
- def _pack_singletons
260
- # TODO: figure out 1.8 and 1.9 compatibility issues.
261
- # Also learn the library usage, without any docs :(
262
- end
263
-
264
176
  # Saves all self and nested object requiring independent saves
265
177
  #
266
178
  # @return [Object, false] Returns false on failure and self on success.
@@ -276,31 +188,43 @@ module Aqua
276
188
  #
277
189
  # @api private
278
190
  def _commit_externals
279
- __pack[:stubs].each_with_index do |obj_hash, index|
280
- obj = obj_hash[:id]
191
+ _translator.externals.each do |obj, path|
281
192
  if obj.commit
282
- obj_hash[:id] = obj.id
193
+ _update_external_id( path, obj.id )
283
194
  else
284
- if obj.id
285
- self._warnings << "Unable to save latest version of #{obj.inspect}, stubbed at index #{index}"
286
- obj_hash[:id] = obj.id if obj.id
287
- else
288
- self._warnings << "Unable to save #{obj.inspect}, stubbed at index #{index}"
289
- end
195
+ self._warnings << ( obj.id ?
196
+ "Unable to save latest version of #{obj.inspect}, stubbed at #{path}" :
197
+ "Unable to save #{obj.inspect}, stubbed at #{path}"
198
+ )
290
199
  end
291
200
  end
292
201
  end
202
+
203
+ # When external objects are saved to the base object, ids need to be updated after save
204
+ # This is the method used to locate the original id and updated it
205
+ #
206
+ # @param [String] path to external
207
+ # @param [String] id to save
208
+ #
209
+ # @api private
210
+ def _update_external_id( path, new_id )
211
+ __pack.instance_eval "self#{path}['init']['id'] = '#{new_id}'"
212
+ end
213
+
293
214
 
294
215
  # clears the __pack and _store accessors to save on memory after each pack and unpack
295
216
  #
296
217
  # @api private
297
- def _clear_accessors
218
+ def _clear_aqua_accessors
298
219
  self.__pack = nil
299
220
  self._store = nil
221
+ @_translator = nil
300
222
  end
301
223
 
302
224
  public
303
225
  end # InstanceMethods
304
226
  end # Pack
227
+
228
+
305
229
  end # Aqua
306
230
 
@@ -4,12 +4,40 @@ module Aqua::Query
4
4
  klass.class_eval do
5
5
  extend ClassMethods
6
6
  include InstanceMethods
7
+
8
+ klass.class_eval "
9
+ def self.storage
10
+ #{klass}::Storage
11
+ end
12
+ "
7
13
  end
8
14
  end
9
15
 
10
16
  module ClassMethods
11
- def query_index( *ivars )
12
- end
17
+ def index_on( *ivars )
18
+ ivars.each do |var|
19
+ storage.index_on_ivar( var )
20
+ end
21
+ end
22
+
23
+ def query( index, opts={} )
24
+ opts = Mash.new( opts )
25
+ equals = opts.delete(:equals)
26
+ opts[:equals] = _encode_query( equals ) if equals
27
+ _build_results( storage.query( index, opts ) )
28
+ end
29
+
30
+ def _encode_query( object )
31
+ CGI.escape( Aqua::Translator.pack_object( object ).pack.to_json )
32
+ end
33
+
34
+ def _build_results( docs )
35
+ if docs.is_a? Array
36
+ docs.map{ |doc| build( doc ) }
37
+ else
38
+ build( doc )
39
+ end
40
+ end
13
41
  end
14
42
 
15
43
  module InstanceMethods
@@ -1,35 +1,7 @@
1
- # NOTES: I just checked and Delegator does all its delegation through method missing, so
2
- # it probably makes sense to make this all one class, with method_missing doing the work.
3
- # It might be faster, but harder, to pass a reference to the parent object and the way that
4
- # the stub is accessed as a way to replace self with the actual object instead of stubbing.
5
- # Not sure how that would work, but I am looking for something like self = something_else,
6
- # which isn't kosher.
7
-
8
- module Aqua
9
- class TempStub
10
- def initialize( method_hash )
11
- method_hash.each do |method_name, value|
12
- self.class.class_eval("
13
- def #{method_name}
14
- #{value.inspect}
15
- end
16
- ")
17
- end
18
- end
19
- end
20
-
21
- module StubDelegate
22
- def __getobj__
23
- @_sd_obj # return object we are delegating to
24
- end
25
-
26
- def __setobj__(obj)
27
- @_sd_obj = obj # change delegation object
28
- end
29
- end
30
-
31
- class Stub < Delegator
32
- include StubDelegate
1
+ module Aqua
2
+ class Stub
3
+ attr_accessor :delegate, :delegate_class, :delegate_id,
4
+ :parent_object, :path_from_parent
33
5
 
34
6
  # Builds a new stub object which returns cached/stubbed methods until such a time as a non-cached method
35
7
  # is requested.
@@ -39,84 +11,77 @@ module Aqua
39
11
  # @option opts [String] :class The class of the object being stubbed
40
12
  # @option opts [String] :id The id of the object being stubbed
41
13
  #
14
+ # @todo pass in information about parent, and path to the stub such that method missing replaces stub
15
+ # with actual object being stubbed and delegated to.
42
16
  # @api semi-public
43
- def initialize( opts )
44
- meths = opts[:methods] || {}
45
- temp_stub = TempStub.new( meths )
46
- super( temp_stub )
47
- @_sd_obj = temp_stub
48
- self.delegate_class = opts[:class]
49
- self.delegate_id = opts[:id]
50
- end
17
+ def initialize(opts)
18
+ stub_methods( opts[:methods] || {} )
19
+
20
+ self.delegate_class = opts[:class]
21
+ self.delegate_id = opts[:id]
22
+ self.parent_object = opts[:parent]
23
+ self.path_from_parent = opts[:path]
24
+ end
51
25
 
52
- def method_missing( method, *args )
53
- if __getobj__.class.to_s != delegate_class.to_s
54
- load_delegate
55
- # resend!
56
- if (args.size == 1 && !args.first.nil?)
57
- __getobj__.send( method.to_sym, eval(args.map{|value| "'#{value}'"}.join(', ')) )
26
+ def self.aqua_init( init, opts=Translator::Opts.new )
27
+ new( init )
28
+ end
29
+
30
+ protected
31
+
32
+ def stub_methods( stubbed_methods )
33
+ stubbed_methods.each do |method_name, value|
34
+ self.class.class_eval("
35
+ def #{method_name}
36
+ #{value.inspect}
37
+ end
38
+ ")
39
+ end
40
+ end
41
+
42
+ def missing_delegate_error
43
+ raise ObjectNotFound, "Object of class '#{delegate_class}' and id '#{delegate_id}' not found"
44
+ end
45
+
46
+ def method_missing( method, *args, &block )
47
+ load_delegate if delegate.nil?
48
+ if delegate
49
+ delegate.send( method, *args, &block )
58
50
  else
59
- __getobj__.send( method.to_sym )
51
+ missing_delegate_error
60
52
  end
61
- else
62
- raise NoMethodError
63
53
  end
64
- end
65
-
66
-
67
- protected
68
- attr_accessor :delegate_class, :delegate_id
69
54
 
70
- def load_delegate
71
- __setobj__( delegate_class.constantize.load( delegate_id ) )
55
+ def load_delegate
56
+ self.delegate = delegate_class.constantize.load( delegate_id )
72
57
  end
73
- public
74
-
75
- end
58
+ end
76
59
 
77
- class FileStub < Delegator
78
- include StubDelegate
79
-
80
- # Builds a new stub object which returns cached/stubbed methods until such a time as a non-cached method
81
- # is requested.
82
- #
83
- # @param [Hash]
84
- # @option opts [Array] :methods A hash of method names and values
85
- # @option opts [String] :class The class of the object being stubbed
86
- # @option opts [String] :id The id of the object being stubbed
87
- #
88
- # @api semi-public
60
+ class FileStub < Stub
61
+ attr_accessor :base_class, :base_id, :attachment_id
62
+
89
63
  def initialize( opts )
90
- meths = opts[:methods] || {}
91
- temp_stub = TempStub.new( meths )
92
- super( temp_stub )
93
- @_sd_obj = temp_stub
94
- self.parent = opts[:parent]
64
+ super( opts )
65
+ self.base_class = opts[:base_object].class
66
+ self.base_id = opts[:base_id]
95
67
  self.attachment_id = opts[:id]
96
68
  end
97
69
 
98
- def method_missing( method, *args )
99
- if load_attempt != true
100
- load_delegate
101
- self.load_attempt = true
102
- # resend!
103
- if (args.size == 1 && !args.first.nil?)
104
- __getobj__.send( method.to_sym, eval(args.map{|value| "'#{value}'"}.join(', ')) )
105
- else
106
- __getobj__.send( method.to_sym )
107
- end
108
- else
109
- raise NoMethodError
110
- end
70
+ # This is what is actually called in the Aqua unpack process
71
+ def self.aqua_init( init, opts )
72
+ init['base_object'] = opts.base_object
73
+ init['base_id'] = opts.base_id || opts.base_object.id # this is needed when an object is loaded, not reloaded
74
+ super
111
75
  end
112
-
113
76
 
114
- protected
115
- attr_accessor :parent, :attachment_id, :load_attempt
77
+ protected
78
+ def missing_delegate_error
79
+ raise ObjectNotFound, "Attachment '#{attachment_id}' for '#{base_class}' with id='#{base_id}' not found."
80
+ end
116
81
 
117
82
  def load_delegate
118
- __setobj__( parent.class::Storage.attachment( parent.id, attachment_id ) )
83
+ self.delegate = base_class::Storage.attachment( base_id, attachment_id )
119
84
  end
120
- public
121
- end
85
+ end
86
+
122
87
  end