aqua 0.1.6
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/.document +5 -0
- data/.gitignore +7 -0
- data/Aqua.gemspec +121 -0
- data/LICENCE_COUCHREST +176 -0
- data/LICENSE +20 -0
- data/README.rdoc +105 -0
- data/Rakefile +83 -0
- data/VERSION +1 -0
- data/lib/aqua.rb +101 -0
- data/lib/aqua/object/config.rb +43 -0
- data/lib/aqua/object/extensions/ar_convert.rb +0 -0
- data/lib/aqua/object/extensions/ar_style.rb +0 -0
- data/lib/aqua/object/extensions/property.rb +0 -0
- data/lib/aqua/object/extensions/validation.rb +0 -0
- data/lib/aqua/object/pack.rb +306 -0
- data/lib/aqua/object/query.rb +18 -0
- data/lib/aqua/object/stub.rb +122 -0
- data/lib/aqua/object/tank.rb +54 -0
- data/lib/aqua/object/unpack.rb +253 -0
- data/lib/aqua/store/couch_db/attachments.rb +183 -0
- data/lib/aqua/store/couch_db/couch_db.rb +151 -0
- data/lib/aqua/store/couch_db/database.rb +186 -0
- data/lib/aqua/store/couch_db/design_document.rb +57 -0
- data/lib/aqua/store/couch_db/http_client/adapter/rest_client.rb +53 -0
- data/lib/aqua/store/couch_db/http_client/rest_api.rb +62 -0
- data/lib/aqua/store/couch_db/server.rb +103 -0
- data/lib/aqua/store/couch_db/storage_methods.rb +405 -0
- data/lib/aqua/store/storage.rb +59 -0
- data/lib/aqua/support/initializers.rb +216 -0
- data/lib/aqua/support/mash.rb +144 -0
- data/lib/aqua/support/set.rb +23 -0
- data/lib/aqua/support/string_extensions.rb +121 -0
- data/spec/aqua_spec.rb +19 -0
- data/spec/object/config_spec.rb +58 -0
- data/spec/object/object_fixtures/array_udder.rb +5 -0
- data/spec/object/object_fixtures/canned_hash.rb +5 -0
- data/spec/object/object_fixtures/gerbilmiester.rb +18 -0
- data/spec/object/object_fixtures/grounded.rb +13 -0
- data/spec/object/object_fixtures/log.rb +19 -0
- data/spec/object/object_fixtures/persistent.rb +12 -0
- data/spec/object/object_fixtures/sugar.rb +4 -0
- data/spec/object/object_fixtures/user.rb +38 -0
- data/spec/object/pack_spec.rb +607 -0
- data/spec/object/query_spec.rb +27 -0
- data/spec/object/stub_spec.rb +51 -0
- data/spec/object/tank_spec.rb +61 -0
- data/spec/object/unpack_spec.rb +361 -0
- data/spec/spec.opts +3 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/store/couchdb/attachments_spec.rb +164 -0
- data/spec/store/couchdb/couch_db_spec.rb +104 -0
- data/spec/store/couchdb/database_spec.rb +161 -0
- data/spec/store/couchdb/design_document_spec.rb +43 -0
- data/spec/store/couchdb/fixtures_and_data/document_fixture.rb +3 -0
- data/spec/store/couchdb/fixtures_and_data/image_attach.png +0 -0
- data/spec/store/couchdb/server_spec.rb +96 -0
- data/spec/store/couchdb/storage_methods_spec.rb +408 -0
- data/utils/code_statistics.rb +134 -0
- data/utils/console +11 -0
- metadata +136 -0
@@ -0,0 +1,18 @@
|
|
1
|
+
module Aqua::Query
|
2
|
+
|
3
|
+
def self.included( klass )
|
4
|
+
klass.class_eval do
|
5
|
+
extend ClassMethods
|
6
|
+
include InstanceMethods
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def query_index( *ivars )
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module InstanceMethods
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,122 @@
|
|
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
|
33
|
+
|
34
|
+
# Builds a new stub object which returns cached/stubbed methods until such a time as a non-cached method
|
35
|
+
# is requested.
|
36
|
+
#
|
37
|
+
# @param [Hash]
|
38
|
+
# @option opts [Array] :methods A hash of method names and values
|
39
|
+
# @option opts [String] :class The class of the object being stubbed
|
40
|
+
# @option opts [String] :id The id of the object being stubbed
|
41
|
+
#
|
42
|
+
# @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
|
51
|
+
|
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(', ')) )
|
58
|
+
else
|
59
|
+
__getobj__.send( method.to_sym )
|
60
|
+
end
|
61
|
+
else
|
62
|
+
raise NoMethodError
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
protected
|
68
|
+
attr_accessor :delegate_class, :delegate_id
|
69
|
+
|
70
|
+
def load_delegate
|
71
|
+
__setobj__( delegate_class.constantize.load( delegate_id ) )
|
72
|
+
end
|
73
|
+
public
|
74
|
+
|
75
|
+
end
|
76
|
+
|
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
|
89
|
+
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]
|
95
|
+
self.attachment_id = opts[:id]
|
96
|
+
end
|
97
|
+
|
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
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
protected
|
115
|
+
attr_accessor :parent, :attachment_id, :load_attempt
|
116
|
+
|
117
|
+
def load_delegate
|
118
|
+
__setobj__( parent.class::Storage.attachment( parent.id, attachment_id ) )
|
119
|
+
end
|
120
|
+
public
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
dir = File.dirname(__FILE__)
|
2
|
+
require dir + '/pack'
|
3
|
+
require dir + '/query'
|
4
|
+
require dir + '/unpack'
|
5
|
+
require dir + '/config'
|
6
|
+
require dir + '/stub'
|
7
|
+
|
8
|
+
module Aqua::Tank
|
9
|
+
def self.included( klass )
|
10
|
+
klass.class_eval do
|
11
|
+
include Aqua::Pack
|
12
|
+
include Aqua::Unpack
|
13
|
+
include Aqua::Config
|
14
|
+
include Aqua::Query
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Adds class method for declaring an object as
|
20
|
+
Object.class_eval do
|
21
|
+
|
22
|
+
# Used in class declarations to load an Aqua::Tank into the class, making it Aqua persistable
|
23
|
+
#
|
24
|
+
# @param [Hash]
|
25
|
+
# @option opts [String] :database Database name to use
|
26
|
+
# @option opts [true, false, Hash] :embed
|
27
|
+
# True will embed the object in another when it appears in an instance variable
|
28
|
+
# False will store it in its own document
|
29
|
+
# When size 1 hash which :stub as the key. It will store the object separately, but save certain values into the object.
|
30
|
+
#
|
31
|
+
# @api public
|
32
|
+
def self.aquatic( opts=nil )
|
33
|
+
include Aqua::Tank
|
34
|
+
configure_aqua( opts ) if opts
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns true of false depending on whether the class has Aqua::Tank modules extended into it.
|
38
|
+
# @return [true, false]
|
39
|
+
#
|
40
|
+
# @api public
|
41
|
+
def self.aquatic?
|
42
|
+
respond_to?( :configure_aqua )
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns true of false depending on whether object instance has Aqua::Tank modules included.
|
46
|
+
# @return [true, false]
|
47
|
+
#
|
48
|
+
# @api public
|
49
|
+
def aquatic?
|
50
|
+
self.class.aquatic?
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
@@ -0,0 +1,253 @@
|
|
1
|
+
module Aqua::Unpack
|
2
|
+
|
3
|
+
def self.included( klass )
|
4
|
+
klass.class_eval do
|
5
|
+
extend ClassMethods
|
6
|
+
include InstanceMethods
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
# Creates a new object with the class of the base class and loads it with data saved from the database.
|
12
|
+
# @param [String] Object id
|
13
|
+
# @return [Object]
|
14
|
+
#
|
15
|
+
# @api public
|
16
|
+
def load( id )
|
17
|
+
instance = new
|
18
|
+
instance.id = id
|
19
|
+
instance.reload
|
20
|
+
instance
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
module InstanceMethods
|
25
|
+
# Reloads database information into the object.
|
26
|
+
# @param [optional true, false] Default is true. If true the exceptions will be swallowed and
|
27
|
+
# false will be returned. If false, then any exceptions raised will stop the show.
|
28
|
+
# @return [Object, false] Will return false or raise error on failure and self on success.
|
29
|
+
#
|
30
|
+
# @api public
|
31
|
+
def reload( mask_exceptions = true )
|
32
|
+
if id.nil?
|
33
|
+
if mask_exceptions
|
34
|
+
false
|
35
|
+
else
|
36
|
+
raise ObjectNotFound, "#{self.class} instance must have an id to be reloaded"
|
37
|
+
end
|
38
|
+
else
|
39
|
+
begin
|
40
|
+
_reload
|
41
|
+
rescue Exception => e
|
42
|
+
if mask_exceptions
|
43
|
+
false
|
44
|
+
else
|
45
|
+
raise e
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Reloads database information into the object, and raises an error on failure.
|
52
|
+
# @return [Object] Will return raise error on failure and return self on success.
|
53
|
+
#
|
54
|
+
# @api public
|
55
|
+
def reload!
|
56
|
+
reload( false )
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
# Actual mechanism for reloading an object from stored data.
|
61
|
+
# @return [Object] Will return raise error on failure and return self on success.
|
62
|
+
#
|
63
|
+
# @api private
|
64
|
+
def _reload
|
65
|
+
_get_store
|
66
|
+
_unpack
|
67
|
+
_clear_store
|
68
|
+
self
|
69
|
+
end
|
70
|
+
|
71
|
+
# Retrieves objects storage from its engine.
|
72
|
+
# @return [Storage]
|
73
|
+
#
|
74
|
+
# @api private
|
75
|
+
def _get_store
|
76
|
+
# this is kind of klunky, should refactor
|
77
|
+
self._store = self.class::Storage.new(:id => self.id).retrieve
|
78
|
+
end
|
79
|
+
|
80
|
+
# Unpacks an object from hash representation of data and metadata
|
81
|
+
# @return [Storage]
|
82
|
+
# @todo Refactor to move more of this into individual classes
|
83
|
+
#
|
84
|
+
# @api private
|
85
|
+
def _unpack
|
86
|
+
if init = _unpack_initialization( _store )
|
87
|
+
replace( init )
|
88
|
+
end
|
89
|
+
if ivars = _store[:ivars]
|
90
|
+
_unpack_ivars( self, ivars )
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Makes @_store nil to converve on memory
|
95
|
+
#
|
96
|
+
# @api private
|
97
|
+
def _clear_store
|
98
|
+
@_store = nil
|
99
|
+
end
|
100
|
+
|
101
|
+
# Unpacks an object's instance variables
|
102
|
+
# @todo Refactor to move more of this into individual classes
|
103
|
+
#
|
104
|
+
# @api private
|
105
|
+
def _unpack_ivars( obj, data )
|
106
|
+
data.each do |ivar_name, data_package|
|
107
|
+
unpacked = if data_package.class == String
|
108
|
+
data_package
|
109
|
+
else
|
110
|
+
_unpack_object( data_package )
|
111
|
+
end
|
112
|
+
obj.instance_variable_set( ivar_name, unpacked )
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Unpacks an the initialization object from a hash into a real object.
|
117
|
+
# @return [Object] Generally a hash, array or string
|
118
|
+
# @todo Refactor to move more of this into individual classes
|
119
|
+
#
|
120
|
+
# @api private
|
121
|
+
def _unpack_initialization( obj )
|
122
|
+
if init = obj[:init]
|
123
|
+
init_class = init.class
|
124
|
+
if init_class == String
|
125
|
+
if init.match(/\A\/STUB_(\d*)\z/)
|
126
|
+
_unpack_stub( $1.to_i )
|
127
|
+
elsif init.match(/\A\/FILE_(.*)\z/)
|
128
|
+
_unpack_file( $1, obj )
|
129
|
+
else
|
130
|
+
init
|
131
|
+
end
|
132
|
+
elsif init.class == Array
|
133
|
+
_unpack_array( init )
|
134
|
+
else
|
135
|
+
_unpack_hash( init )
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Retrieves and unpacks a stubbed object from its separate storage area
|
141
|
+
# @return [Aqua::Stub] Delegate object for externally saved class
|
142
|
+
# @param [Fixnum] Array index for the stub details, garnered from the key name
|
143
|
+
#
|
144
|
+
# @api private
|
145
|
+
def _unpack_stub( index )
|
146
|
+
hash = _store[:stubs][index]
|
147
|
+
Aqua::Stub.new( hash )
|
148
|
+
end
|
149
|
+
|
150
|
+
# Retrieves and unpacks a stubbed object from its separate storage area
|
151
|
+
# @param [String] File name, and attachment id
|
152
|
+
# @return [Aqua::FileStub] Delegate object for file attachments
|
153
|
+
#
|
154
|
+
# @api private
|
155
|
+
def _unpack_file( name, obj )
|
156
|
+
hash = {
|
157
|
+
:parent => self,
|
158
|
+
:id => name,
|
159
|
+
:methods => obj[:methods]
|
160
|
+
}
|
161
|
+
Aqua::FileStub.new( hash )
|
162
|
+
end
|
163
|
+
|
164
|
+
# Unpacks an Array.
|
165
|
+
# @return [Object] Generally a hash, array or string
|
166
|
+
# @todo Refactor ?? move more of this into support/initializers Array
|
167
|
+
#
|
168
|
+
# @api private
|
169
|
+
def _unpack_array( obj )
|
170
|
+
arr = []
|
171
|
+
obj.each do |value|
|
172
|
+
value = _unpack_object( value ) unless value.class == String
|
173
|
+
arr << value
|
174
|
+
end
|
175
|
+
arr
|
176
|
+
end
|
177
|
+
|
178
|
+
# Unpacks a Hash.
|
179
|
+
# @return [Object] Generally a hash, array or string
|
180
|
+
# @todo Refactor ?? move more of this into support/initializers Hash
|
181
|
+
#
|
182
|
+
# @api private
|
183
|
+
def _unpack_hash( obj )
|
184
|
+
hash = {}
|
185
|
+
obj.each do |raw_key, value|
|
186
|
+
value = _unpack_object( value ) unless value.class == String
|
187
|
+
if raw_key.match(/\A(:)/)
|
188
|
+
key = raw_key.gsub($1, '').to_sym
|
189
|
+
elsif raw_key.match(/\A\/OBJECT_(\d*)\z/)
|
190
|
+
key = _unpack_object( self._store[:keys][$1.to_i] )
|
191
|
+
else
|
192
|
+
key = raw_key
|
193
|
+
end
|
194
|
+
hash[key] = value
|
195
|
+
end
|
196
|
+
hash
|
197
|
+
end
|
198
|
+
|
199
|
+
# The real workhorse behind object construction: it recursively rebuilds objects based on
|
200
|
+
# whether the passed in object is an Array, String or a Hash (true/false too now).
|
201
|
+
# A hash that has the class key
|
202
|
+
# is an object representation. If it does not have a hash key then it is an ordinary hash.
|
203
|
+
# An array will either have strings or object representations values.
|
204
|
+
#
|
205
|
+
# @param [Hash, Mash] Representation of the data in the aqua meta format
|
206
|
+
# @return [Object] The object represented by the data
|
207
|
+
#
|
208
|
+
# @api private
|
209
|
+
def _unpack_object( store_pack )
|
210
|
+
package_class = store_pack.class
|
211
|
+
if package_class == String || store_pack == true || store_pack == false
|
212
|
+
store_pack
|
213
|
+
elsif package_class == Array
|
214
|
+
_unpack_array( store_pack )
|
215
|
+
else # package_class == Hash -or- Mash
|
216
|
+
if store_pack['class']
|
217
|
+
# Constantize the objects class
|
218
|
+
obj_class = store_pack['class'].constantize rescue nil
|
219
|
+
|
220
|
+
# build from initialization
|
221
|
+
init = _unpack_initialization( store_pack )
|
222
|
+
return_object = if init
|
223
|
+
[Aqua::Stub, Aqua::FileStub].include?( obj_class ) ? init : obj_class.aqua_init( init )
|
224
|
+
end
|
225
|
+
|
226
|
+
# Build uninitialized object
|
227
|
+
if return_object.nil?
|
228
|
+
if obj_class
|
229
|
+
return_object = obj_class.new
|
230
|
+
else
|
231
|
+
# should log an error internally
|
232
|
+
return_object = OpenStruct.new
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
# add the ivars
|
237
|
+
if ivars = store_pack['ivars']
|
238
|
+
ivars.delete('@table') if obj_class.ancestors.include?( OpenStruct )
|
239
|
+
_unpack_ivars( return_object, ivars )
|
240
|
+
end
|
241
|
+
|
242
|
+
return_object
|
243
|
+
else # not a packaged object, just a hash, so unpack
|
244
|
+
_unpack_hash( hash )
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
end
|
249
|
+
|
250
|
+
public
|
251
|
+
end
|
252
|
+
|
253
|
+
end
|