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
data/spec/aqua_spec.rb ADDED
@@ -0,0 +1,19 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "Aqua" do
4
+ describe 'loading internal storage libraries' do
5
+ it 'loading without an argument should not raise an error' do
6
+ lambda{ Aqua.set_storage_engine }.should_not raise_error
7
+ end
8
+ end
9
+
10
+ describe 'loading external storage libraries' do
11
+ end
12
+
13
+ describe 'configuration generalities' do
14
+ it 'gem should allow persistance on all objects'
15
+ it 'gem should allow persistance with a class declaration'
16
+ it 'gem should allow persistance via module inclusion'
17
+ it 'gem should allow persistance via Aquaed inheritance'
18
+ end
19
+ end
@@ -0,0 +1,58 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+ require_fixtures
3
+
4
+ describe Aqua::Config do
5
+ it 'should add a class method used in class declaration for configuring called "configure_aqua"' do
6
+ User.should respond_to( :configure_aqua )
7
+ end
8
+
9
+ describe 'storage class' do
10
+ it 'should be establish on aquatic declaration' do
11
+ lambda{ User::Storage }.should_not raise_error
12
+ end
13
+
14
+ it 'should store the parent class' do
15
+ User::Storage.parent_class.should == 'User'
16
+ end
17
+ end
18
+
19
+ it 'should set default configuration options on module load' do
20
+ opts = Persistent.aquatic_options
21
+ opts.should_not be_nil
22
+ Persistent::Storage.database.uri.should == "http://127.0.0.1:5984/aqua" # default when database option is nil
23
+ opts[:embed].should be_false
24
+ end
25
+
26
+ it 'should be able to add to the default configurations' do
27
+ User.class_eval do
28
+ configure_aqua :database => 'someplace_else', :embed => { :stub => [:username] }
29
+ end
30
+ opts = User.aquatic_options
31
+ User::Storage.database.name.should == 'someplace_else'
32
+ opts[:embed].should_not be_false
33
+ opts[:embed][:stub].class.should == Array
34
+ end
35
+
36
+ it 'should be able to add to already custom configured options' do
37
+ opts = User.aquatic_options
38
+ User::Storage.database.name.should == 'someplace_else' # make sure it is held over from the last test
39
+ User.class_eval do
40
+ configure_aqua :database => 'newer_than_that'
41
+ end
42
+ opts = User.aquatic_options
43
+ User::Storage.database.name.should == 'newer_than_that'
44
+ opts[:embed].should_not be_false
45
+ opts[:embed][:stub].class.should == Array
46
+ end
47
+
48
+ it 'should receive options passed to the class "aquatic" declaration' do
49
+ opts = Log.aquatic_options
50
+ opts[:embed].should == true
51
+ end
52
+
53
+ it 'each class should have different Storage class options' do
54
+ Persistent::Storage.database.should_not == User::Storage.database
55
+ end
56
+
57
+ end
58
+
@@ -0,0 +1,5 @@
1
+ class ArrayUdder < Array
2
+ def udder
3
+ @udder ||= 'Squeeze out some array milk'
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class CannedHash < Mash
2
+ def yum
3
+ @yum ||= 'Corned Beef!'
4
+ end
5
+ end
@@ -0,0 +1,18 @@
1
+ class Gerbilmiester
2
+ aquatic :embed => [:gerbil, :bacon]
3
+
4
+ # saved state instance variable
5
+ def gerbil
6
+ @gerbil ||= true
7
+ end
8
+
9
+ # not an instance method with saved state,
10
+ # but we should be able to stub this.
11
+ def bacon
12
+ 'chunky'
13
+ end
14
+
15
+ def herd
16
+ gerbil ? 'Yah, yah, little gerbil' : 'Nothing to herd here, move along!'
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ require 'ostruct'
2
+ # this is a non-aquatic object
3
+ class Grounded
4
+ def initialize( hash = {} )
5
+ hash.each do |key, value|
6
+ send( "#{key}=", value )
7
+ end
8
+ end
9
+
10
+ attr_accessor :hash_up, # a hash
11
+ :arraynged, # an array
12
+ :openly_structured # an ostruct
13
+ end
@@ -0,0 +1,19 @@
1
+ class Log
2
+ aquatic :embed => true
3
+
4
+ attr_accessor :created_at, :message
5
+
6
+ def initialize( hash={} )
7
+ hash.each do |key, value|
8
+ send( "#{key}=", value )
9
+ end
10
+ end
11
+
12
+ # convenience methods for inspection during testing
13
+ # -------------------------------------------------
14
+ # for testing class level configuration options
15
+ def self.aquatic_options
16
+ _aqua_opts
17
+ end
18
+
19
+ end
@@ -0,0 +1,12 @@
1
+ class Persistent
2
+ include Aqua::Tank
3
+
4
+ # for testing class level configuration options
5
+ def self.aquatic_options
6
+ _aqua_opts
7
+ end
8
+
9
+
10
+
11
+ end
12
+
@@ -0,0 +1,4 @@
1
+ class Sugar
2
+ aquatic # configured to :emded => false by default
3
+ attr_accessor :sweetness
4
+ end
@@ -0,0 +1,38 @@
1
+ class User
2
+ aquatic :embed => { :stub => :username }
3
+
4
+ attr_accessor :name, # An array of strings or a hash of strings
5
+ :created_at,# Time
6
+ :dob, # Date
7
+ :username, # simple string
8
+ :log, # an Aquatic Object
9
+ :password, # hidden value
10
+ :grab_bag, # non Aquatic Object
11
+ :other_user # a non-embeddable Aquatic Object
12
+ hide_attributes :password
13
+
14
+ def initialize( hash={} )
15
+ hash.each do |key, value|
16
+ send( "#{key}=", value )
17
+ end
18
+ end
19
+
20
+
21
+ # convenience methods for inspection during testing
22
+ # -------------------------------------------------
23
+
24
+ def simple_classes
25
+ _simple_classes
26
+ end
27
+
28
+ # for testing whether attributse are hidden
29
+ def visible_attr
30
+ _storable_attributes
31
+ end
32
+
33
+ # for testing class level configuration options
34
+ def self.aquatic_options
35
+ _aqua_opts
36
+ end
37
+
38
+ end
@@ -0,0 +1,607 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+ require_fixtures
3
+
4
+ Aqua.set_storage_engine('CouchDB') # to initialize CouchDB
5
+ CouchDB = Aqua::Store::CouchDB unless defined?( CouchDB )
6
+
7
+ describe Aqua::Pack do
8
+ before(:each) do
9
+ @time = Time.now
10
+ @date = Date.parse('12/23/1969')
11
+ @log = Log.new( :message => "Hello World! This is a log entry", :created_at => Time.now )
12
+ @user = User.new(
13
+ :username => 'kane',
14
+ :name => ['Kane', 'Baccigalupi'],
15
+ :dob => @date,
16
+ :created_at => @time,
17
+ :log => @log,
18
+ :password => 'my secret!'
19
+ )
20
+
21
+ def pack_grab_bag( value )
22
+ @user.grab_bag = value
23
+ @user._pack[:ivars][:@grab_bag]
24
+ end
25
+ end
26
+
27
+ describe 'packing classes' do
28
+ it 'should pack class variables'
29
+ it 'should pack class level instance variables'
30
+ it 'should pack class definition'
31
+ it 'should save all the class details to the design document'
32
+ it 'should package views/finds in the class and save them to the design document\'s view attribute'
33
+ it 'should be saved into the design document'
34
+ end
35
+
36
+ describe 'external saves and stubs' do
37
+ before(:each) do
38
+ User::Storage.database.delete_all
39
+ @graeme = User.new(:username => 'graeme', :name => ['Graeme', 'Nelson'])
40
+ @user.other_user = @graeme
41
+ @pack = @user._pack
42
+ end
43
+
44
+ describe 'packing' do
45
+ it 'should pack a stubbed object representation under __pack[:stubs]' do
46
+ @pack[:stubs].size.should == 1
47
+ other_user_pack = @pack[:stubs].first
48
+ other_user_pack[:class].should == "User"
49
+ other_user_pack[:id].should == @graeme
50
+ end
51
+
52
+ it 'should pack the values of a stubbed methods' do
53
+ other_user_pack = @pack[:stubs].first
54
+ other_user_pack[:methods].size.should == 1
55
+ other_user_pack[:methods][:username].should == 'graeme'
56
+ end
57
+
58
+ it "should pack a stub of an object with embed=>false" do
59
+ sugar = Sugar.new
60
+ sugar.sweetness = Sugar.new
61
+ lambda {sugar._pack}.should_not raise_error
62
+ end
63
+
64
+ it 'should pack an array of stubbed methods' do
65
+ User.configure_aqua( :embed => {:stub => [:username, :name] } )
66
+ @user = User.new(
67
+ :username => 'kane',
68
+ :name => ['Kane', 'Baccigalupi'],
69
+ :dob => @date,
70
+ :created_at => @time,
71
+ :log => @log,
72
+ :password => 'my secret!',
73
+ :other_user => @graeme
74
+ )
75
+
76
+ @pack = @user._pack
77
+ other_user_pack = @pack[:stubs].first
78
+ other_user_pack[:methods].size.should == 2
79
+ other_user_pack[:methods][:username].should == 'graeme'
80
+ other_user_pack[:methods][:name].should == ['Graeme', 'Nelson']
81
+
82
+ # reseting the User model, and @user instance
83
+ User.configure_aqua( :embed => {:stub => :username } )
84
+ end
85
+
86
+ it 'should pack the object itself with the class "Aqua::Stub"' do
87
+ @pack[:ivars][:@other_user][:class].should == "Aqua::Stub"
88
+ end
89
+
90
+ it 'should pack the object itself with a reference to the __pack[:stubs] object' do
91
+ @pack[:ivars][:@other_user][:init].should == "/STUB_0"
92
+ end
93
+ end
94
+
95
+ describe 'commiting' do
96
+ it 'should commit external objects' do
97
+ @user.commit!
98
+ db_docs = CouchDB::Database.new.documents
99
+ db_docs['total_rows'].should == 2
100
+ end
101
+
102
+ it 'should save the id to the stub after commiting' do
103
+ @user.commit!
104
+ doc = CouchDB.get( "http://127.0.0.1:5984/aqua/#{@user.id}" )
105
+ doc["stubs"].first["id"].class.should == String
106
+ end
107
+
108
+ it 'should log a warning if an external object doesn\'t commit' do
109
+ @graeme.should_receive(:commit).and_return(false)
110
+ @user.commit!
111
+ @user._warnings.size.should == 1
112
+ @user._warnings.first.should match(/unable to save/i)
113
+ end
114
+
115
+ it 'should log a warning and save the id if an object has an id' do
116
+ @graeme.commit!
117
+ @graeme.should_receive(:commit).and_return(false)
118
+ @user.commit!
119
+ @user._warnings.size.should == 1
120
+ @user._warnings.first.should match(/unable to save latest/i)
121
+ doc = CouchDB.get( "http://127.0.0.1:5984/aqua/#{@user.id}" )
122
+ doc["stubs"].first["id"].class.should == String
123
+ end
124
+
125
+ it 'should rollback external commits if the parent object doesn\'t save'
126
+ end
127
+
128
+ end
129
+
130
+ describe 'hiding attributes' do
131
+ it 'should add a class method for designating hidden instance variables' do
132
+ User.should respond_to( :hide_attributes )
133
+ end
134
+
135
+ it 'class method should hide instance variables designated by the user as hidden' do
136
+ @user.visible_attr.should_not include('@password')
137
+ @user.class._hidden_attributes.should include('@password')
138
+ end
139
+
140
+ it 'should hide the @__pack variable' do
141
+ @user.visible_attr.should_not include('@__pack')
142
+ @user.class._hidden_attributes.should include('@__pack')
143
+ end
144
+
145
+ it 'should hide the @_store variable' do
146
+ @user.visible_attr.should_not include('@_store')
147
+ @user.class._hidden_attributes.should include('@_store')
148
+ end
149
+
150
+ it 'should not pack hidden variables' do
151
+ @pack = @user._pack
152
+ @pack[:ivars].keys.should_not include("@password")
153
+ end
154
+ end
155
+
156
+ describe 'packing up object instances:' do
157
+ before(:each) do
158
+ @pack = @user._pack
159
+ end
160
+
161
+ it 'should save its class name as an attribute on the pack document' do
162
+ @pack[:class].should == 'User'
163
+ end
164
+
165
+ it 'should add an :id accessor if :id is not already an instance method' do
166
+ @user.should respond_to(:id=)
167
+ end
168
+
169
+ it 'should pack an id if an id is present' # TODO
170
+
171
+ it 'should not pack an id if an id is not present' do
172
+ @pack.id.should be_nil
173
+ end
174
+
175
+ it 'should pack the _rev if it is present' do
176
+ @user.instance_variable_set("@_rev", '1-2222222')
177
+ pack = @user._pack
178
+ pack[:_rev].should == '1-2222222'
179
+ end
180
+
181
+ describe 'instance variables, ' do
182
+ describe 'hashes'
183
+ it 'should be in a hash-like object with the key :ivars' do
184
+ @pack[:ivars].should_not be_nil
185
+ @pack[:ivars].should respond_to(:keys)
186
+ end
187
+
188
+ it 'should save symbol keys differently that string keys' do
189
+ @user.name = {:first => 'Kane', 'last' => 'Baccigalupi'}
190
+ pack = @user._pack
191
+ pack[:ivars][:@name][:init].keys.sort.should == [':first', 'last']
192
+ end
193
+
194
+ describe 'basic ivars types' do
195
+ it 'should pack strings as strings' do
196
+ @pack[:ivars][:@username].should == 'kane'
197
+ end
198
+
199
+ it 'should pack an array of strings as a hash with the :class "Array" and :init as the original array' do
200
+ @pack[:ivars][:@name].should == {'class' => 'Array', 'init' => ['Kane', 'Baccigalupi']}
201
+ end
202
+ end
203
+
204
+ describe 'objects: ' do
205
+ # TODO: http://www.ruby-doc.org/core/
206
+ # make sure all the basic types work
207
+
208
+ describe 'Time' do
209
+ it 'should save as a hash with the class and to_s as the value' do
210
+ time_hash = @pack[:ivars][:@created_at]
211
+ time_hash['class'].should == 'Time'
212
+ time_hash['init'].class.should == String
213
+ end
214
+
215
+ it 'the value should be reconstitutable with Time.parse' do
216
+ # comparing times directly never works for me. It is probably a micro second issue or something
217
+ @time.to_s.should == Time.parse( @pack[:ivars][:@created_at]['init'] ).to_s
218
+ end
219
+ end
220
+
221
+ describe 'true and false' do
222
+ it 'should save as self' do
223
+ @user.grab_bag = true
224
+ pack = @user._pack
225
+ pack[:ivars][:@grab_bag].should == true
226
+
227
+ @user.grab_bag = false
228
+ pack = @user._pack
229
+ pack[:ivars][:@grab_bag].should == false
230
+ end
231
+ end
232
+
233
+ describe 'Date' do
234
+ it 'should save as a hash with the class and to_s as the value' do
235
+ time_hash = @pack[:ivars][:@dob]
236
+ time_hash['class'].should == 'Date'
237
+ time_hash['init'].class.should == String
238
+ end
239
+
240
+ it 'the value should be reconstitutable with Date.parse' do
241
+ @date.should == Date.parse( @pack[:ivars][:@dob]['init'] )
242
+ end
243
+
244
+ it 'should not pack internally used ivars as specified by the class' do
245
+ @pack[:ivars][:@dob][:ivars].keys.should_not include('@sg', '@of', '@ajd')
246
+ end
247
+ end
248
+
249
+ describe 'Numbers' do
250
+ it 'should pack Fixnums with correct class and value' do
251
+ pack = pack_grab_bag( 42 )
252
+ pack[:class].should == 'Fixnum'
253
+ pack[:init].should == '42'
254
+ end
255
+
256
+ it 'should pack Bignums with correct class and value' do
257
+ pack = pack_grab_bag( 123456789123456789 )
258
+ pack[:class].should == 'Bignum'
259
+ pack[:init].should == '123456789123456789'
260
+ end
261
+
262
+ it 'should pack Floats with correct class and value' do
263
+ pack = pack_grab_bag( 3.2 )
264
+ pack[:class].should == 'Float'
265
+ pack[:init].should == '3.2'
266
+ end
267
+
268
+ it 'should pack Rationals with the correct class and values' do
269
+ pack = pack_grab_bag( Rational( 1, 17 ) )
270
+ pack[:class].should == 'Rational'
271
+ pack[:init].should == ['1', '17']
272
+ end
273
+
274
+ end
275
+
276
+ describe 'hashes with object as keys' do
277
+ it 'should pack an hash containing only strings/symbols for keys and values, with an init value that is that hash and a class key' do
278
+ @user.name = {'first' => 'Kane', 'last' => 'Baccigalupi'}
279
+ pack = @user._pack
280
+ pack[:ivars][:@name].should == {'class' => 'Hash', 'init' => {'first' => 'Kane', 'last' => 'Baccigalupi'} }
281
+ end
282
+
283
+ it 'should pack a numeric object key' do
284
+ pack = pack_grab_bag( {1 => 'first', 2 => 'second'} )
285
+ keys = pack[:init].keys
286
+ keys.should include( '/OBJECT_0', '/OBJECT_1' )
287
+ user_pack = @user.instance_variable_get("@__pack")
288
+ user_pack[:keys].size.should == 2
289
+ user_pack[:keys].first['class'].should == 'Fixnum'
290
+ end
291
+
292
+ it 'should pack a more complex object as a key' do
293
+ struct = OpenStruct.new( :gerbil => true )
294
+ pack = pack_grab_bag( { struct => 'first'} )
295
+ keys = pack[:init].keys
296
+ keys.should include( '/OBJECT_0' )
297
+ user_pack = @user.instance_variable_get("@__pack")
298
+ user_pack[:keys].size.should == 1
299
+ user_pack[:keys].first['class'].should == 'OpenStruct'
300
+ end
301
+ end
302
+
303
+ describe 'embeddable aquatic' do
304
+ it 'aquatic objects should have packing instructions in the form of #_embed_me' do
305
+ @user._embed_me.should == {'stub' => :username }
306
+ Log.new._embed_me.should == true
307
+ User.configure_aqua( :embed => {:stub => [:username, :name] } )
308
+ @user._embed_me.should == { 'stub' => [:username, :name] }
309
+ # reset for future tests
310
+ User.configure_aqua( :embed => {:stub => :username } )
311
+ end
312
+
313
+ it 'should save their ivars correctly' do
314
+ @pack[:ivars][:@log].keys.should include('ivars')
315
+ @pack[:ivars][:@log]['ivars'].keys.should == ['@created_at', '@message']
316
+ @pack[:ivars][:@log]['ivars']['@message'].should == "Hello World! This is a log entry"
317
+ end
318
+
319
+ it 'should correctly pack Array derivatives' do
320
+ class Arrayed < Array
321
+ aquatic
322
+ attr_accessor :my_accessor
323
+ end
324
+ arrayish = Arrayed['a', 'b', 'c', 'd']
325
+ arrayish.my_accessor = 'Newt'
326
+ pack = arrayish._pack
327
+ pack.keys.should include('class', 'init', 'ivars')
328
+ pack['init'].class.should == Array
329
+ pack['init'].should == ['a', 'b', 'c', 'd']
330
+ pack['ivars']['@my_accessor'].should == 'Newt'
331
+ end
332
+
333
+ it 'should correctly pack Hash derivative' do
334
+ class Hashed < Hash
335
+ aquatic
336
+ attr_accessor :my_accessor
337
+ end
338
+ hashish = Hashed.new
339
+ hashish['1'] = '2'
340
+ hashish.my_accessor = 'Newt'
341
+ pack = hashish._pack
342
+ pack.keys.should include('class', 'init', 'ivars')
343
+ pack['init'].class.should == HashWithIndifferentAccess
344
+ pack['init'].should == {'1' => '2'}
345
+ pack['ivars']['@my_accessor'].should == 'Newt'
346
+ end
347
+
348
+ end
349
+
350
+ describe 'non-aquatic' do
351
+ before(:each) do
352
+ @struct = OpenStruct.new(
353
+ :gerbil => true,
354
+ :cat => 'yup, that too!',
355
+ :disaster => ['pow', 'blame', 'chase', 'spew']
356
+ )
357
+ @grounded = Grounded.new(
358
+ :openly_structured => @struct,
359
+ :hash_up => {:this => 'that'},
360
+ :arraynged => ['swimming', 'should', 'be easy', 'if you float']
361
+ )
362
+
363
+ end
364
+
365
+ describe 'OpenStructs' do
366
+ before(:each) do
367
+ @user.grab_bag = @struct
368
+ pack = @user._pack
369
+ @grab_bag = pack[:ivars][:@grab_bag]
370
+ end
371
+
372
+ it 'the key "class" should map to "OpenStruct"' do
373
+ @grab_bag['class'].should == 'OpenStruct'
374
+ end
375
+
376
+ it 'the key "ivars" should have the key "@table", a private variable' do
377
+ @grab_bag['ivars'].keys.should_not == ['@table']
378
+ end
379
+
380
+ it 'should initialize with the @table instance variable' do
381
+ init_keys = @grab_bag['init'].keys
382
+ init_keys.should include(':cat')
383
+ init_keys.should include(':disaster')
384
+ init_keys.should include(':gerbil')
385
+ @grab_bag['init'][':gerbil'].should == true
386
+ @grab_bag['init'][':cat'].should == 'yup, that too!'
387
+ @grab_bag['init'][':disaster'].should == {'class' => 'Array', 'init' => ['pow', 'blame', 'chase', 'spew']}
388
+ end
389
+ end
390
+
391
+ describe 'Uninherited classes with deep nesting' do
392
+ before(:each) do
393
+ @user.grab_bag = @grounded
394
+ pack = @user._pack
395
+ @grab_bag = pack[:ivars][:@grab_bag]
396
+ end
397
+
398
+ it 'the key "class" should map correctly to the class name' do
399
+ @grab_bag['class'].should == 'Grounded'
400
+ end
401
+
402
+ it 'should have ivars keys for all the ivars' do
403
+ keys = @grab_bag[:ivars].keys
404
+ keys.should include('@openly_structured')
405
+ keys.should include('@hash_up')
406
+ keys.should include('@arraynged')
407
+ end
408
+
409
+ it 'should correctly display the nested OpenStruct' do
410
+ user_2 = User.new(:grab_bag => @struct) # this has already been tested in the set above
411
+ user_2._pack[:ivars][:@grab_bag].should == @grab_bag[:ivars][:@openly_structured]
412
+ end
413
+ end
414
+
415
+ describe 'Classes inherited from Array' do
416
+ before(:each) do
417
+ @struct = OpenStruct.new(
418
+ :gerbil => true,
419
+ :cat => 'yup, that too!',
420
+ :disaster => ['pow', 'blame', 'chase', 'spew'],
421
+ :nipples => 'yes'
422
+ )
423
+ @strange_array = ArrayUdder['cat', 'octopus', @struct ]
424
+ @strange_array.udder # sets an instance variable
425
+ @user.grab_bag = @strange_array
426
+ pack = @user._pack
427
+ @grab_bag = pack[:ivars][:@grab_bag]
428
+ end
429
+
430
+ it 'should correctly map the class name' do
431
+ @grab_bag[:class].should == 'ArrayUdder'
432
+ end
433
+
434
+ it 'should store the instance variables' do
435
+ @grab_bag[:ivars].keys.should == ['@udder']
436
+ end
437
+
438
+ it 'should store the simple array values' do
439
+ @grab_bag[:init].should_not be_nil
440
+ @grab_bag[:init].class.should == Array
441
+ @grab_bag[:init].should include('cat')
442
+ @grab_bag[:init].should include('octopus')
443
+ end
444
+
445
+ it 'should store the more complex array values correctly' do
446
+ user_2 = User.new(:grab_bag => @struct) # this has already been tested in the set above
447
+ user_2._pack[:ivars][:@grab_bag].should == @grab_bag[:init].last
448
+ end
449
+ end
450
+
451
+ describe 'Classes inherited from Hash' do
452
+ before(:each) do
453
+ @struct = OpenStruct.new(
454
+ :gerbil => true,
455
+ :cat => 'yup, that too!',
456
+ :disaster => ['pow', 'blame', 'chase', 'spew'],
457
+ :nipples => 'yes'
458
+ )
459
+ @hash_derivative = CannedHash.new(
460
+ :ingredients => ['Corned Beef', 'Potatoes', 'Tin Can'],
461
+ :healthometer => false,
462
+ :random_struct => @struct
463
+ )
464
+ @hash_derivative.yum # sets an instance variable
465
+ @user.grab_bag = @hash_derivative
466
+ pack = @user._pack
467
+ @grab_bag = pack[:ivars][:@grab_bag]
468
+ end
469
+
470
+ it 'should correctly map the class name' do
471
+ @grab_bag[:class].should == 'CannedHash'
472
+ end
473
+
474
+ it 'should store the instance variables' do
475
+ @grab_bag[:ivars].keys.should == ['@yum']
476
+ end
477
+
478
+ it 'should store the simple hash values' do
479
+ @grab_bag[:init].should_not be_nil
480
+ @grab_bag[:init].class.should == HashWithIndifferentAccess
481
+
482
+ @grab_bag[:init].keys.should include('ingredients')
483
+ @grab_bag[:init].keys.should include('healthometer')
484
+ @grab_bag[:init].keys.should include('random_struct')
485
+ end
486
+
487
+ it 'should store the more complex hash values correctly' do
488
+ user_2 = User.new(:grab_bag => @struct) # this has already been tested in the set above
489
+ user_2._pack[:ivars][:@grab_bag].should == @grab_bag[:init][:random_struct]
490
+ end
491
+ end
492
+
493
+ describe 'Embedded IO:' do
494
+ before(:each) do
495
+ @file = File.new(File.dirname(__FILE__) + '/../store/couchdb/fixtures_and_data/image_attach.png')
496
+ @tempfile = Tempfile.new('temp.txt')
497
+ @tempfile.write('I am a tempfile!')
498
+ @tempfile.rewind
499
+ end
500
+
501
+ describe "File" do
502
+ it 'should have pack its class name as Aqua::FileStub' do
503
+ @user.grab_bag = @file
504
+ @user._pack
505
+ @user._pack[:ivars][:@grab_bag][:class].should == 'Aqua::FileStub'
506
+ end
507
+
508
+ it 'should have pack with an initialization key' do
509
+ @user.grab_bag = @file
510
+ pack = @user._pack
511
+ pack[:ivars][:@grab_bag][:init].should == '/FILE_image_attach.png'
512
+ end
513
+
514
+ it 'should add an attachment to the pack' do
515
+ @user.grab_bag = @file
516
+ pack = @user._pack
517
+ pack.attachments.size.should == 1
518
+ pack.attachments.get('image_attach.png').should == @file
519
+ end
520
+
521
+ it 'should stub content type and length' do
522
+ @user.grab_bag = @file
523
+ pack = @user._pack
524
+ pack[:ivars][:@grab_bag][:methods].should include('content_type', 'content_length')
525
+ pack[:ivars][:@grab_bag][:methods]['content_type'].should == 'image/png'
526
+ pack[:ivars][:@grab_bag][:methods]['content_length'].should == 26551
527
+ end
528
+ end
529
+
530
+ describe 'Tempfile' do
531
+ it 'should have pack its class name as Aqua::FileStub' do
532
+ @user.grab_bag = @tempfile
533
+ @user._pack
534
+ @user._pack[:ivars][:@grab_bag][:class].should == 'Aqua::FileStub'
535
+ end
536
+
537
+ it 'should have pack with an initialization key' do
538
+ @user.grab_bag = @tempfile
539
+ pack = @user._pack
540
+ pack[:ivars][:@grab_bag][:init].should == '/FILE_temp.txt'
541
+ end
542
+
543
+ it 'should add an attachment to the pack' do
544
+ @user.grab_bag = @tempfile
545
+ pack = @user._pack
546
+ pack.attachments.size.should == 1
547
+ pack.attachments.get('temp.txt').path.should == @tempfile.path
548
+ end
549
+ end
550
+ end
551
+ end
552
+ end
553
+ end
554
+ end
555
+
556
+ describe 'committing packed objects to the store' do
557
+ before(:each) do
558
+ User::Storage.database.delete_all
559
+ end
560
+
561
+ it 'commit! should not raise errors on successful save' do
562
+ lambda{ @user.commit! }.should_not raise_error
563
+ end
564
+
565
+ it 'commit! should raise error on failure' do
566
+ CouchDB.should_receive(:put).at_least(:once).and_return( CouchDB::Conflict )
567
+ lambda{ @user.commit! }.should raise_error
568
+ end
569
+
570
+ it 'commit! should assign an id back to the object' do
571
+ @user.commit!
572
+ @user.id.should_not be_nil
573
+ @user.id.should_not == @user.object_id
574
+ end
575
+
576
+ it 'commit! should assign the _rev to the parent object' do
577
+ @user.commit!
578
+ @user.instance_variable_get('@_rev').should_not be_nil
579
+ end
580
+
581
+ it 'commit! should save the record to CouchDB' do
582
+ @user.commit!
583
+ lambda{ CouchDB.get( "http://127.0.0.1:5984/aqua/#{@user.id}") }.should_not raise_error
584
+ end
585
+
586
+ it 'commit should save the record and return self' do
587
+ @user.commit.should == @user
588
+ end
589
+
590
+ it 'commit should not raise an error on falure' do
591
+ CouchDB.should_receive(:put).at_least(:once).and_return( CouchDB::Conflict )
592
+ lambda{ @user.commit }.should_not raise_error
593
+ end
594
+
595
+ it 'commit should return false on failure' do
596
+ CouchDB.should_receive(:put).at_least(:once).and_return( CouchDB::Conflict )
597
+ @user.commit.should == false
598
+ end
599
+
600
+ it 'should be able to update and commit again' do
601
+ @user.commit!
602
+ @user.grab_bag = {'1' => '2'}
603
+ lambda{ @user.commit! }.should_not raise_error
604
+ end
605
+ end
606
+
607
+ end