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.
- data/.gitignore +2 -1
- data/Aqua.gemspec +14 -11
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/lib/aqua.rb +5 -7
- data/lib/aqua/object/config.rb +2 -3
- data/lib/aqua/object/initializers.rb +309 -0
- data/lib/aqua/object/pack.rb +56 -132
- data/lib/aqua/object/query.rb +30 -2
- data/lib/aqua/object/stub.rb +60 -95
- data/lib/aqua/object/tank.rb +1 -0
- data/lib/aqua/object/translator.rb +313 -0
- data/lib/aqua/object/unpack.rb +26 -227
- data/lib/aqua/store/couch_db/couch_db.rb +1 -0
- data/lib/aqua/store/couch_db/database.rb +1 -1
- data/lib/aqua/store/couch_db/design_document.rb +126 -2
- data/lib/aqua/store/couch_db/result_set.rb +36 -0
- data/lib/aqua/store/couch_db/storage_methods.rb +182 -17
- data/lib/aqua/store/storage.rb +4 -48
- data/lib/aqua/support/mash.rb +2 -3
- data/lib/aqua/support/set.rb +4 -16
- data/spec/object/object_fixtures/array_udder.rb +1 -1
- data/spec/object/object_fixtures/persistent.rb +0 -2
- data/spec/object/pack_spec.rb +137 -517
- data/spec/object/query_spec.rb +36 -6
- data/spec/object/stub_spec.rb +10 -9
- data/spec/object/translator_packing_spec.rb +402 -0
- data/spec/object/translator_unpacking_spec.rb +262 -0
- data/spec/object/unpack_spec.rb +162 -320
- data/spec/spec_helper.rb +18 -0
- data/spec/store/couchdb/design_document_spec.rb +148 -7
- data/spec/store/couchdb/result_set_spec.rb +95 -0
- data/spec/store/couchdb/storage_methods_spec.rb +150 -10
- metadata +13 -9
- data/lib/aqua/support/initializers.rb +0 -216
- data/spec/object/object_fixtures/grounded.rb +0 -13
- data/spec/object/object_fixtures/sugar.rb +0 -4
data/lib/aqua/object/pack.rb
CHANGED
@@ -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
|
95
|
-
|
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
|
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
|
-
#
|
113
|
-
#
|
114
|
-
#
|
115
|
-
#
|
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
|
159
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
280
|
-
obj = obj_hash[:id]
|
191
|
+
_translator.externals.each do |obj, path|
|
281
192
|
if obj.commit
|
282
|
-
|
193
|
+
_update_external_id( path, obj.id )
|
283
194
|
else
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
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
|
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
|
|
data/lib/aqua/object/query.rb
CHANGED
@@ -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
|
12
|
-
|
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
|
data/lib/aqua/object/stub.rb
CHANGED
@@ -1,35 +1,7 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
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(
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
self.
|
49
|
-
self.
|
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
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
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
|
-
|
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
|
-
|
55
|
+
def load_delegate
|
56
|
+
self.delegate = delegate_class.constantize.load( delegate_id )
|
72
57
|
end
|
73
|
-
|
74
|
-
|
75
|
-
end
|
58
|
+
end
|
76
59
|
|
77
|
-
class FileStub <
|
78
|
-
|
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
|
-
|
91
|
-
|
92
|
-
|
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
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
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
|
-
|
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
|
-
|
83
|
+
self.delegate = base_class::Storage.attachment( base_id, attachment_id )
|
119
84
|
end
|
120
|
-
|
121
|
-
|
85
|
+
end
|
86
|
+
|
122
87
|
end
|