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
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