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