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,43 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ Aqua.set_storage_engine('CouchDB') # to initialize the Aqua::Store namespace
4
+
5
+ # Conveniences for typing with tests ...
6
+ CouchDB = Aqua::Store::CouchDB unless defined?( CouchDB )
7
+ Database = CouchDB::Database unless defined?( Database )
8
+ Server = CouchDB::Server unless defined?( Server )
9
+ Design = CouchDB::DesignDocument unless defined?( Design )
10
+
11
+ describe CouchDB::DesignDocument do
12
+ before(:each) do
13
+ Aqua::Storage.database.delete_all
14
+ end
15
+
16
+ describe 'new and create' do
17
+ before(:each) do
18
+ @name = 'User'
19
+ @design = Design.new(:name => @name)
20
+ end
21
+
22
+ it 'should require a name to build the uri' do
23
+ design = Design.new
24
+ lambda{ design.uri }.should raise_error
25
+ lambda{ @design.uri }.should_not raise_error
26
+ end
27
+
28
+ it 'should build the correct uri' do
29
+ @design.uri.should == 'http://127.0.0.1:5984/aqua/_design/User'
30
+ end
31
+
32
+ it 'should save' do
33
+ lambda{ @design.save! }.should_not raise_error
34
+ lambda{ CouchDB.get( @design.uri ) }.should_not raise_error
35
+ end
36
+ end
37
+
38
+ describe 'views' do
39
+
40
+ end
41
+
42
+
43
+ end
@@ -0,0 +1,3 @@
1
+ class Document < Mash
2
+ include Aqua::Store::CouchDB::StorageMethods
3
+ end
@@ -0,0 +1,96 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ Aqua.set_storage_engine('CouchDB') # to initialize the Aqua::Store namespace
4
+ Server = Aqua::Store::CouchDB::Server unless defined?( Server )
5
+
6
+ describe 'Aqua::Store::CouchDB::Server' do
7
+ before(:each) do
8
+ @server = Server.new
9
+ end
10
+
11
+ before(:all) do
12
+ Server.new.delete_all
13
+ end
14
+
15
+ describe 'initialization' do
16
+ it 'should have a default uri "http://127.0.0.1:5984"' do
17
+ @server.uri.should == 'http://127.0.0.1:5984'
18
+ end
19
+
20
+ it 'should have a settable uri' do
21
+ server = Server.new(:server => 'http://newhost.com:5984')
22
+ server.uri.should == 'http://newhost.com:5984'
23
+ end
24
+
25
+ it 'should have a database prefix for namespacing the collection of persistance databases' do
26
+ @server.namespace.should == 'aqua'
27
+ end
28
+
29
+ it 'should have a settable namespace' do
30
+ server = Server.new(:namespace => 'not_aqua')
31
+ server.namespace.should == 'not_aqua'
32
+ end
33
+
34
+ it 'should escape alpha-numeric, plus hyphen and underscore, characters from the namespace' do
35
+ server = Server.new(:namespace => '&not_!kosher*%')
36
+ server.namespace.should == 'not_kosher'
37
+ end
38
+
39
+ it 'should escape :: in the namespace and substitute with __' do
40
+ server = Server.new(:namespace => 'not::kosher')
41
+ server.namespace.should == 'not__kosher'
42
+ end
43
+ end
44
+
45
+ describe 'general couchdb managment features' do
46
+ it 'should retain a set of uuids to prevent collision' do
47
+ token = @server.next_uuid( 2 )
48
+ @server.next_uuid.should_not == token
49
+ Aqua::Store::CouchDB.should_receive(:get).and_return({'uuids' => []}) # because we have run out of uuids on the last request
50
+ @server.next_uuid
51
+ end
52
+
53
+ it 'should get couchdb info' do
54
+ info = @server.info #{"couchdb"=>"Welcome", "version"=>"0.9.0"}
55
+ info.class.should == Hash
56
+ info['couchdb'].should == 'Welcome'
57
+ end
58
+
59
+ it 'should restart the couchdb server' do
60
+ Aqua::Store::CouchDB.should_receive(:post).with("#{@server.uri}/_restart" )
61
+ @server.restart!
62
+ end
63
+ end
64
+
65
+ describe 'managing databases' do
66
+ before(:all) do
67
+ Server.new.delete_all # this is kind of circular testing here ...
68
+ Server.new.databases.size.should == 0
69
+ end
70
+
71
+ it 'should have a convenience method for creating databases' do
72
+ @server.database!('first')
73
+ Aqua::Store::CouchDB::Database.new('first').should be_exists
74
+ end
75
+
76
+ it 'should show all_databases related to this server as an array' do
77
+ @server.database!('second')
78
+ dbs = @server.databases
79
+ dbs.class.should == Array
80
+ dbs.size.should == 2
81
+ dbs.first.class.should == Aqua::Store::CouchDB::Database
82
+ end
83
+
84
+ it 'should show default database in the list of databases' do
85
+ Aqua::Store::CouchDB::Database.create!
86
+ @server.databases.size.should == 3
87
+ end
88
+
89
+ it 'should delete_all! databases for the namespace' do
90
+ @server.databases.size.should == 3
91
+ @server.delete_all!
92
+ @server.databases.size.should == 0
93
+ end
94
+ end
95
+
96
+ end
@@ -0,0 +1,408 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ Aqua.set_storage_engine('CouchDB') # to initialize the Aqua::Store namespace
4
+ require File.dirname(__FILE__) + '/fixtures_and_data/document_fixture' # Document ... a Mash with the collection of methods
5
+
6
+ # Conveniences for typing with tests ...
7
+ CouchDB = Aqua::Store::CouchDB unless defined?( CouchDB )
8
+ Database = CouchDB::Database unless defined?( Database )
9
+ Server = CouchDB::Server unless defined?( Server)
10
+ Attachments = CouchDB::Attachments unless defined?( Attachments )
11
+
12
+ describe 'CouchDB::StorageMethods' do
13
+ before(:each) do
14
+ @params = {
15
+ :id => 'my_slug/thaz-right',
16
+ :rev => "shouldn't change yo!",
17
+ :more => "my big stuff"
18
+ }
19
+ @doc = Document.new( @params )
20
+ end
21
+
22
+ describe 'initialization' do
23
+ it 'should initialize with a hash of values accessible by symbol or string' do
24
+ @doc[:more].should == 'my big stuff'
25
+ @doc['more'].should == 'my big stuff'
26
+ end
27
+
28
+ it 'should set the id with the initialization hash' do
29
+ @doc.id.should == 'my_slug/thaz-right'
30
+ end
31
+
32
+ it 'should escape the id' do
33
+ @doc[:_id].should == 'my_slug%2Fthaz-right'
34
+ end
35
+
36
+ it 'should not set the rev and it should discard those keys' do
37
+ @doc.rev.should == nil
38
+ @doc[:rev].should == nil
39
+ end
40
+ end
41
+
42
+ describe 'core attributes' do
43
+
44
+ describe 'revisions' do
45
+ before(:each) do
46
+ @doc.delete if @doc.exists?
47
+ end
48
+
49
+ it 'should be an empty array for a new record' do
50
+ @doc.revisions.should == []
51
+ end
52
+
53
+ it 'should have one value after the document is saved' do
54
+ @doc.save!
55
+ @doc.revisions.size.should == 1
56
+ @doc.revisions.first.should == @doc[:_rev]
57
+ end
58
+
59
+ it 'should continue adding revisions with each save' do
60
+ @doc.save!
61
+ @doc['new_attr'] = 'my new attribute, yup!'
62
+ @doc.save!
63
+ @doc.revisions.size.should == 2
64
+ end
65
+ end
66
+
67
+ it 'rev should not be publicly settable' do
68
+ lambda{ @doc.rev = 'my_rev' }.should raise_error
69
+ end
70
+
71
+ describe 'changing the id, post save' do
72
+ before(:each) do
73
+ @doc.database.delete_all
74
+ @doc.save!
75
+ @doc.id = 'something/new_and_fresh'
76
+ end
77
+
78
+ it 'should change the id' do
79
+ @doc.id.should == 'something/new_and_fresh'
80
+ end
81
+
82
+ it 'should change the _id' do
83
+ @doc[:_id].should == 'something%2Fnew_and_fresh'
84
+ end
85
+
86
+ it 'should successfully save' do
87
+ lambda{ @doc.save! }.should_not raise_error
88
+ @doc.retrieve['id'].should == 'something/new_and_fresh'
89
+ end
90
+
91
+ it 'should delete earlier versions on save' do
92
+ @doc.save!
93
+ lambda{ CouchDB.get( "#{@doc.database.uri}/aqua/my_slug%2Fthaz-right") }.should raise_error
94
+ end
95
+ end
96
+
97
+ end
98
+
99
+ describe 'database' do
100
+ before(:each) do
101
+ @doc.delete
102
+ end
103
+
104
+ it 'should have a database per class' do
105
+ Document.database.should_not be_nil
106
+ Document.database.uri.should == 'http://127.0.0.1:5984/aqua'
107
+ end
108
+
109
+ it 'should not be nil' do
110
+ @doc.database.should_not be_nil
111
+ end
112
+
113
+ it 'should have the default database uri by default' do
114
+ @doc.database.uri.should == 'http://127.0.0.1:5984/aqua'
115
+ end
116
+
117
+ it 'should be settable' do
118
+ @doc.database = Database.create('my_class')
119
+ @doc.database.uri.should == 'http://127.0.0.1:5984/aqua_my_class'
120
+ end
121
+
122
+ it 'should depend on database strategies set for storage\'s parent class' do
123
+ pending( 'Have to create some CouchDB database strategies. Also have to make parent classes configurable.')
124
+ end
125
+ end
126
+
127
+ describe 'uri' do
128
+ before(:each) do
129
+ @doc.delete if @doc.exists?
130
+ end
131
+
132
+ it 'should have use the default database uri by default with the document id' do
133
+ @doc.uri.should == 'http://127.0.0.1:5984/aqua/my_slug%2Fthaz-right'
134
+ end
135
+
136
+ it 'should reflect the non-default database name' do
137
+ @doc.database = Database.create('my_class')
138
+ @doc.uri.should == 'http://127.0.0.1:5984/aqua_my_class/my_slug%2Fthaz-right'
139
+ end
140
+
141
+ it 'should use a server generated uuid for the id if an id is not provided' do
142
+ params = @params.dup
143
+ params.delete(:id)
144
+ doc = Document.new( params )
145
+ doc.uri.should match(/\A#{doc.database.uri}\/[a-f0-9]*\z/)
146
+ end
147
+
148
+ end
149
+
150
+ describe 'save/create' do
151
+ before(:each) do
152
+ @doc.delete if @doc.exists?
153
+ end
154
+
155
+ it 'saving should create a document in the database' do
156
+ @doc.save
157
+ lambda{ Aqua::Store::CouchDB.get( @doc.uri ) }.should_not raise_error
158
+ end
159
+
160
+ it 'save should return itself if it worked' do
161
+ return_value = @doc.save
162
+ return_value.class.should == Document
163
+ return_value.id.should == @doc.id
164
+ end
165
+
166
+ it 'saving should return false if it did not work' do
167
+ @doc.save
168
+ @doc[:_rev] = nil # should cause a conflict error HTTP 409 in couchdb
169
+ lambda{ @doc.save }.should_not raise_error
170
+ @doc.save.should == false
171
+ end
172
+
173
+ it 'saving should update the "id" and "rev"' do
174
+ doc_id = @doc.id
175
+ doc_rev = @doc.rev
176
+ @doc.save
177
+ @doc[:_id].should_not == doc_id
178
+ @doc.rev.should_not == doc_rev
179
+ end
180
+
181
+ it 'save! should raise an error on failure when creating' do
182
+ @doc.save
183
+ @doc[:_rev] = nil # should cause a conflict error HTTP 409 in couchdb
184
+ lambda{ @doc.save! }.should raise_error
185
+ end
186
+
187
+ it 'create should return itself when successful' do
188
+ doc = Document.create(@params)
189
+ doc.class.should == Document
190
+ doc.rev.should_not be_nil
191
+ end
192
+
193
+ it '#new? should be true for unsaved documents' do
194
+ @doc.should be_new
195
+ end
196
+
197
+ it '#new? should be false after a document has been saved' do
198
+ @doc.save!
199
+ @doc.should_not be_new
200
+ end
201
+
202
+ it 'should #exists? if it has been saved to CouchDB' do
203
+ @doc.save!
204
+ @doc.should be_exists
205
+ end
206
+
207
+ it 'should not #exists? if the document is new' do
208
+ @doc.should_not be_exists
209
+ end
210
+
211
+ it 'should reload' do
212
+ @doc.save!
213
+ @doc[:noodle] = 'spaghetti'
214
+ @doc.reload
215
+ @doc[:noodle].should be_nil
216
+ end
217
+ end
218
+
219
+ describe 'deleting' do
220
+ before(:each) do
221
+ @doc.delete if @doc.exists?
222
+ end
223
+
224
+ it 'should #delete a record' do
225
+ @doc.save!
226
+ @doc.delete
227
+ @doc.should_not be_exists
228
+ end
229
+
230
+ it 'should return true on successful #delete' do
231
+ @doc.save!
232
+ @doc.delete.should == true
233
+ end
234
+
235
+ it 'should return false when it fails' do
236
+ @doc.save!
237
+ CouchDB.should_receive(:delete).and_raise( CouchDB::Conflict )
238
+ @doc.delete.should == false
239
+ end
240
+
241
+ it 'should raise an error on failure when #delete! is used' do
242
+ @doc.save!
243
+ CouchDB.should_receive(:delete).and_raise( CouchDB::Conflict )
244
+ lambda { @doc.delete! }.should raise_error
245
+ end
246
+ end
247
+
248
+ describe 'updating' do
249
+ before(:each) do
250
+ @doc.delete if @doc.exists?
251
+ end
252
+
253
+ it 'saving after a change should change the revision number' do
254
+ @doc.save
255
+ rev = @doc.rev
256
+ _id = @doc[:_id]
257
+ id = @doc[:id]
258
+ @doc['more'] = 'less ... really'
259
+ @doc['newness'] = 'overrated'
260
+ @doc.save
261
+ @doc.id.should == id
262
+ @doc[:_id].should == _id
263
+ @doc.rev.should_not == rev
264
+ end
265
+
266
+ it 'saving after a change should retain changed data' do
267
+ @doc.save
268
+ @doc['more'] = 'less ... really'
269
+ @doc['newness'] = 'overrated'
270
+ @doc.save
271
+
272
+ @doc['more'].should == 'less ... really'
273
+ @doc['newness'].should == 'overrated'
274
+ @doc.retrieve['more'].should == 'less ... really'
275
+ end
276
+ end
277
+
278
+ describe 'attachments' do
279
+ before(:each) do
280
+ @file = File.new( File.dirname( __FILE__ ) + '/fixtures_and_data/image_attach.png' )
281
+ end
282
+
283
+ it 'should have an accessor for storing attachments' do
284
+ @doc.attachments.should == Attachments.new( @doc )
285
+ end
286
+
287
+ it 'should add attachments' do
288
+ @doc.attachments.add(:my_file, @file)
289
+ @doc.attachments[:my_file].should == @file
290
+ end
291
+
292
+ it 'should pack attachments' do
293
+ @doc.attachments.add(:my_file, @file)
294
+ @doc.attachments.add("dup.png", @file)
295
+ pack = @doc.attachments.pack
296
+ pack.keys.should include('my_file', 'dup.png')
297
+ end
298
+
299
+ it 'should pack attachments to key _attachments on save' do
300
+ @doc.delete! if @doc.exists?
301
+ @doc.attachments.add(:my_file, @file)
302
+ @doc.attachments.add("dup.png", @file)
303
+ pack = @doc.attachments.pack
304
+ @doc.save!
305
+ @doc[:_attachments].should == pack
306
+ end
307
+
308
+ it 'should pack attachments before save' do
309
+ @doc.delete! if @doc.exists?
310
+
311
+ @doc.attachments.add(:my_file, @file)
312
+ @doc.attachments.add("dup.png", @file)
313
+ pack = @doc.attachments.pack
314
+
315
+ @doc.attachments.should_receive( :pack ).and_return( pack )
316
+ @doc.commit
317
+ end
318
+
319
+ it 'should pack attachments before save' do
320
+ @doc.delete! if @doc.exists?
321
+
322
+ @doc.attachments.add(:my_file, @file)
323
+ @doc.attachments.add("dup.png", @file)
324
+ pack = @doc.attachments.pack
325
+
326
+ @doc.attachments.should_receive( :pack ).and_return( pack )
327
+ @doc.commit
328
+ end
329
+
330
+ it 'should be correctly formed in database' do
331
+ @doc.delete! if @doc.exists?
332
+
333
+ @doc.attachments.add(:my_file, @file)
334
+ @doc.attachments.add("dup.png", @file)
335
+ @doc.commit
336
+ @doc.reload
337
+
338
+ @doc[:_attachments]['dup.png']['content_type'].should == 'image/png'
339
+ @doc[:_attachments]['dup.png']['stub'].should == true
340
+ (@doc[:_attachments]['my_file']['length'] > 0).should == true
341
+ @doc[:_attachments]['my_file']['content_type'].should == 'image/png'
342
+ @doc[:_attachments]['my_file']['stub'].should == true
343
+ (@doc[:_attachments]['my_file']['length'] > 0).should == true
344
+ end
345
+
346
+ it 'should be retrievable by a url' do
347
+ @doc.delete! if @doc.exists?
348
+
349
+ @doc.attachments.add(:my_file, @file)
350
+ @doc.attachments.add("dup.png", @file)
351
+ @doc.commit
352
+
353
+ url = @doc.attachments.uri_for('dup.png')
354
+ lambda{ CouchDB.get( url, true ) }.should_not raise_error
355
+ end
356
+
357
+ it 'should save and retrieve the data correctly' do
358
+ @doc.delete! if @doc.exists?
359
+
360
+ @doc.attachments.add(:my_file, @file)
361
+ @doc.attachments.add("dup.png", @file)
362
+ @doc.commit
363
+
364
+ data = @file.read
365
+ data.should_not be_nil
366
+ data.should_not be_empty
367
+
368
+ file = @doc.attachments.get!( :my_file )
369
+ file.read.should == data
370
+ end
371
+
372
+ it 'should save and stream the data correctly' do
373
+ @doc.delete! if @doc.exists?
374
+
375
+ @doc.attachments.add(:my_file, @file)
376
+ @doc.attachments.add("dup.png", @file)
377
+ @doc.commit
378
+
379
+ data = @file.read
380
+ data.should_not be_nil
381
+ data.should_not be_empty
382
+
383
+ streamed = @doc.attachments.get!( :my_file, true )
384
+ streamed.should == data
385
+ end
386
+
387
+ it 'should have a class accessor for attachments' do
388
+ @doc.delete! if @doc.exists?
389
+
390
+ @doc.attachments.add(:my_file, @file)
391
+ @doc.attachments.add("dup.png", @file)
392
+ @doc.commit
393
+
394
+ data = @file.read
395
+ data.should_not be_nil
396
+ data.should_not be_empty
397
+
398
+ attachment = Document.attachment( @doc.id, 'my_file' )
399
+ attachment.read.should == data
400
+ end
401
+
402
+ end
403
+
404
+ describe 'indexing/views' do
405
+ it 'should '
406
+ end
407
+
408
+ end