aqua 0.1.6

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