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