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.
Files changed (60) hide show
  1. data/.document +5 -0
  2. data/.gitignore +7 -0
  3. data/Aqua.gemspec +121 -0
  4. data/LICENCE_COUCHREST +176 -0
  5. data/LICENSE +20 -0
  6. data/README.rdoc +105 -0
  7. data/Rakefile +83 -0
  8. data/VERSION +1 -0
  9. data/lib/aqua.rb +101 -0
  10. data/lib/aqua/object/config.rb +43 -0
  11. data/lib/aqua/object/extensions/ar_convert.rb +0 -0
  12. data/lib/aqua/object/extensions/ar_style.rb +0 -0
  13. data/lib/aqua/object/extensions/property.rb +0 -0
  14. data/lib/aqua/object/extensions/validation.rb +0 -0
  15. data/lib/aqua/object/pack.rb +306 -0
  16. data/lib/aqua/object/query.rb +18 -0
  17. data/lib/aqua/object/stub.rb +122 -0
  18. data/lib/aqua/object/tank.rb +54 -0
  19. data/lib/aqua/object/unpack.rb +253 -0
  20. data/lib/aqua/store/couch_db/attachments.rb +183 -0
  21. data/lib/aqua/store/couch_db/couch_db.rb +151 -0
  22. data/lib/aqua/store/couch_db/database.rb +186 -0
  23. data/lib/aqua/store/couch_db/design_document.rb +57 -0
  24. data/lib/aqua/store/couch_db/http_client/adapter/rest_client.rb +53 -0
  25. data/lib/aqua/store/couch_db/http_client/rest_api.rb +62 -0
  26. data/lib/aqua/store/couch_db/server.rb +103 -0
  27. data/lib/aqua/store/couch_db/storage_methods.rb +405 -0
  28. data/lib/aqua/store/storage.rb +59 -0
  29. data/lib/aqua/support/initializers.rb +216 -0
  30. data/lib/aqua/support/mash.rb +144 -0
  31. data/lib/aqua/support/set.rb +23 -0
  32. data/lib/aqua/support/string_extensions.rb +121 -0
  33. data/spec/aqua_spec.rb +19 -0
  34. data/spec/object/config_spec.rb +58 -0
  35. data/spec/object/object_fixtures/array_udder.rb +5 -0
  36. data/spec/object/object_fixtures/canned_hash.rb +5 -0
  37. data/spec/object/object_fixtures/gerbilmiester.rb +18 -0
  38. data/spec/object/object_fixtures/grounded.rb +13 -0
  39. data/spec/object/object_fixtures/log.rb +19 -0
  40. data/spec/object/object_fixtures/persistent.rb +12 -0
  41. data/spec/object/object_fixtures/sugar.rb +4 -0
  42. data/spec/object/object_fixtures/user.rb +38 -0
  43. data/spec/object/pack_spec.rb +607 -0
  44. data/spec/object/query_spec.rb +27 -0
  45. data/spec/object/stub_spec.rb +51 -0
  46. data/spec/object/tank_spec.rb +61 -0
  47. data/spec/object/unpack_spec.rb +361 -0
  48. data/spec/spec.opts +3 -0
  49. data/spec/spec_helper.rb +16 -0
  50. data/spec/store/couchdb/attachments_spec.rb +164 -0
  51. data/spec/store/couchdb/couch_db_spec.rb +104 -0
  52. data/spec/store/couchdb/database_spec.rb +161 -0
  53. data/spec/store/couchdb/design_document_spec.rb +43 -0
  54. data/spec/store/couchdb/fixtures_and_data/document_fixture.rb +3 -0
  55. data/spec/store/couchdb/fixtures_and_data/image_attach.png +0 -0
  56. data/spec/store/couchdb/server_spec.rb +96 -0
  57. data/spec/store/couchdb/storage_methods_spec.rb +408 -0
  58. data/utils/code_statistics.rb +134 -0
  59. data/utils/console +11 -0
  60. 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