aqua 0.1.6 → 0.2.0

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 (37) hide show
  1. data/.gitignore +2 -1
  2. data/Aqua.gemspec +14 -11
  3. data/Rakefile +1 -1
  4. data/VERSION +1 -1
  5. data/lib/aqua.rb +5 -7
  6. data/lib/aqua/object/config.rb +2 -3
  7. data/lib/aqua/object/initializers.rb +309 -0
  8. data/lib/aqua/object/pack.rb +56 -132
  9. data/lib/aqua/object/query.rb +30 -2
  10. data/lib/aqua/object/stub.rb +60 -95
  11. data/lib/aqua/object/tank.rb +1 -0
  12. data/lib/aqua/object/translator.rb +313 -0
  13. data/lib/aqua/object/unpack.rb +26 -227
  14. data/lib/aqua/store/couch_db/couch_db.rb +1 -0
  15. data/lib/aqua/store/couch_db/database.rb +1 -1
  16. data/lib/aqua/store/couch_db/design_document.rb +126 -2
  17. data/lib/aqua/store/couch_db/result_set.rb +36 -0
  18. data/lib/aqua/store/couch_db/storage_methods.rb +182 -17
  19. data/lib/aqua/store/storage.rb +4 -48
  20. data/lib/aqua/support/mash.rb +2 -3
  21. data/lib/aqua/support/set.rb +4 -16
  22. data/spec/object/object_fixtures/array_udder.rb +1 -1
  23. data/spec/object/object_fixtures/persistent.rb +0 -2
  24. data/spec/object/pack_spec.rb +137 -517
  25. data/spec/object/query_spec.rb +36 -6
  26. data/spec/object/stub_spec.rb +10 -9
  27. data/spec/object/translator_packing_spec.rb +402 -0
  28. data/spec/object/translator_unpacking_spec.rb +262 -0
  29. data/spec/object/unpack_spec.rb +162 -320
  30. data/spec/spec_helper.rb +18 -0
  31. data/spec/store/couchdb/design_document_spec.rb +148 -7
  32. data/spec/store/couchdb/result_set_spec.rb +95 -0
  33. data/spec/store/couchdb/storage_methods_spec.rb +150 -10
  34. metadata +13 -9
  35. data/lib/aqua/support/initializers.rb +0 -216
  36. data/spec/object/object_fixtures/grounded.rb +0 -13
  37. data/spec/object/object_fixtures/sugar.rb +0 -4
@@ -9,6 +9,24 @@ def require_fixtures
9
9
  Dir[ File.dirname(__FILE__) + "/object/object_fixtures/**/*.rb" ].each do |file|
10
10
  require file
11
11
  end
12
+ end
13
+
14
+ def build_user_ivars
15
+ @time = Time.now
16
+ @date = Date.parse('12/23/1969')
17
+ @message = "Hello World! This is a log entry"
18
+ @log = Log.new( :message => @message, :created_at => @time ) # embedded object
19
+ @other_user = User.new( :username => 'strictnine', :name => ['What', 'Ever'] ) # stubbed objects
20
+ @user = User.new(
21
+ :username => 'kane',
22
+ :name => ['Kane', 'Baccigalupi'],
23
+ :dob => @date,
24
+ :created_at => @time,
25
+ :log => @log,
26
+ :password => 'my secret!',
27
+ :other_user => @other_user
28
+ )
29
+ @pack = @user._pack
12
30
  end
13
31
 
14
32
 
@@ -7,18 +7,18 @@ CouchDB = Aqua::Store::CouchDB unless defined?( CouchDB )
7
7
  Database = CouchDB::Database unless defined?( Database )
8
8
  Server = CouchDB::Server unless defined?( Server )
9
9
  Design = CouchDB::DesignDocument unless defined?( Design )
10
+ ResultSet = CouchDB::ResultSet unless defined?( ResultSet )
11
+
12
+ require File.dirname(__FILE__) + '/fixtures_and_data/document_fixture' # Document ... a Mash with the collection of methods
10
13
 
11
14
  describe CouchDB::DesignDocument do
12
15
  before(:each) do
13
16
  Aqua::Storage.database.delete_all
17
+ @name = 'User'
18
+ @design = Design.new(:name => @name)
14
19
  end
15
20
 
16
21
  describe 'new and create' do
17
- before(:each) do
18
- @name = 'User'
19
- @design = Design.new(:name => @name)
20
- end
21
-
22
22
  it 'should require a name to build the uri' do
23
23
  design = Design.new
24
24
  lambda{ design.uri }.should raise_error
@@ -35,9 +35,150 @@ describe CouchDB::DesignDocument do
35
35
  end
36
36
  end
37
37
 
38
+ it 'should get a design document by name' do
39
+ @design.save!
40
+ lambda{ Design.get( @name ) }.should_not raise_error
41
+ end
42
+
38
43
  describe 'views' do
44
+ before(:each) do
45
+ ResultSet.document_class = Document
46
+ end
47
+
48
+ it 'should be a Hash-like object' do
49
+ @design.views.should == Mash.new
50
+ end
39
51
 
40
- end
41
-
52
+ describe '<<, add, add!' do
53
+ describe 'string as argument' do
54
+ it 'should add a view with the right name' do
55
+ @design << 'my_attribute'
56
+ @design.views.keys.should == ['my_attribute']
57
+ end
58
+
59
+ it 'should autogenerate a generic map' do
60
+ @design << 'my_attribute'
61
+ @design.views[:my_attribute][:map].should match(/function\(doc\)/)
62
+ @design.views[:my_attribute][:map].should match(/emit/)
63
+ end
64
+
65
+ it 'should not autogenerate a reduce function' do
66
+ @design << 'my_attribute'
67
+ @design.views[:my_attribute][:reduce].should be_nil
68
+ end
69
+ end
70
+
71
+ describe 'hash options as argument' do
72
+ it 'should add a view named with the options name key' do
73
+ @design << {:name => 'my_attribute'}
74
+ @design.views.keys.should == ['my_attribute']
75
+ end
76
+
77
+ it 'should autogenerate a generic map' do
78
+ @design << {:name => 'my_attribute'}
79
+ @design.views[:my_attribute][:map].should match(/function\(doc\)/)
80
+ @design.views[:my_attribute][:map].should match(/emit/)
81
+ end
82
+
83
+ it 'should autogenerate a generic map with class constraints' do
84
+ @design << {:name => 'my_docs', :class_constraint => Document}
85
+ @design.views[:my_docs][:map].should match(/doc\['type'\] == 'Document'/)
86
+ end
87
+
88
+ it 'should autogenerate a generic map and insert preformed class constraints' do
89
+ @design << {:name => 'user', :class_constraint => "doc['class'] == 'User'" }
90
+ @design.views[:user][:map].should match(/doc\['class'\] == 'User'/)
91
+ end
92
+
93
+ it 'should not autogenerate a reduce function' do
94
+ @design << {:name => 'my_attribute'}
95
+ @design.views[:my_attribute][:reduce].should be_nil
96
+ end
97
+
98
+ it 'should apply a map option when provided' do
99
+ @design << {:name => 'my_attribute', :map => 'not the generic'}
100
+ @design.views[:my_attribute][:map].should == 'not the generic'
101
+ end
102
+
103
+ it 'should apply a reduce option when provided' do
104
+ @design << {:name => 'my_attribute', :reduce => 'I exist!'}
105
+ @design.views[:my_attribute][:map].should match(/function\(doc\)/)
106
+ @design.views[:my_attribute][:map].should match(/emit/)
107
+ @design.views[:my_attribute][:reduce].should == 'I exist!'
108
+ end
109
+ end
110
+
111
+ it 'add should act like <<' do
112
+ @design.add :name => 'my_attribute', :map => 'not the generic'
113
+ @design.views[:my_attribute][:map].should == 'not the generic'
114
+ end
115
+
116
+ it 'add! should save after adding the view' do
117
+ @design.add! :name => 'my_attribute', :map => 'not the generic'
118
+ lambda{ Design.get( @design.name ) }.should_not raise_error
119
+ design = Design.get( @design.name )
120
+ design.views.keys.should include( 'my_attribute' )
121
+ end
122
+ end
42
123
 
124
+ describe 'query' do
125
+ before(:each) do
126
+ (1..10).each do |num|
127
+ Document.create!( :index => num )
128
+ end
129
+ @design << :index
130
+ @design.save!
131
+ end
132
+
133
+ it 'should query by saved view' do
134
+ lambda{ @design.query( :index ) }.should_not raise_error
135
+ end
136
+
137
+ it 'should return a number of rows corresponding to all the documents in the query' do
138
+ @docs = @design.query( :index )
139
+ @docs.size.should == 10
140
+ end
141
+
142
+ it 'should return the documents themselves by default' do
143
+ @docs = @design.query( :index )
144
+ @docs.first.keys.should include( 'index' )
145
+ @docs.first.class.should == Document
146
+ end
147
+
148
+ it 'should limit query results' do
149
+ @docs = @design.query( :index, :limit => 2 )
150
+ @docs.size.should == 2
151
+ end
152
+
153
+ it 'should offset query results' do
154
+ @docs = @design.query( :index, :limit => 2, :offset => 2)
155
+ @docs.size.should == 2
156
+ @docs.first[:index].should == 3
157
+ end
158
+
159
+ it 'should put in descending order' do
160
+ @docs = @design.query( :index, :order => :desc )
161
+ @docs.first[:index].should == 10
162
+ end
163
+
164
+ it 'should select a range' do
165
+ @docs = @design.query( :index, :range => [2,4])
166
+ @docs.size.should == 3
167
+ @docs.first[:index].should == 2
168
+ end
169
+
170
+ it 'should select a range with descending order' do
171
+ @docs = @design.query( :index, :range => [2,4], :order => :descending )
172
+ @docs.size.should == 3
173
+ @docs.first[:index].should == 4
174
+ @docs[2][:index].should == 2
175
+ end
176
+
177
+ it 'should select by exact key' do
178
+ @docs = @design.query( :index, :equals => 3 )
179
+ @docs.size.should == 1
180
+ @docs.first[:index].should == 3
181
+ end
182
+ end
183
+ end
43
184
  end
@@ -0,0 +1,95 @@
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
+ Design = CouchDB::DesignDocument unless defined?( Design )
9
+ ResultSet = CouchDB::ResultSet unless defined?( ResultSet )
10
+
11
+ require File.dirname(__FILE__) + '/fixtures_and_data/document_fixture' # Document ... a Mash with the collection of methods
12
+ class Docintalk < Document
13
+ def talk
14
+ 'Hello, I am a Docintalk'
15
+ end
16
+ end
17
+
18
+ describe ResultSet do
19
+ before(:each) do
20
+ ResultSet.document_class = nil # resets for all theses tests
21
+ # These are sample returns from couchdb for a view query
22
+ # the first is when the include_document=true is omitted
23
+ # the next is when it is included
24
+ @no_docs = {
25
+ "rows"=>[
26
+ {"id"=>"b7e4623f506c437bae2b517d169b3c88", "value"=>nil, "key"=>1},
27
+ {"id"=>"43d650760fdda785a3bd20056d53b3e0", "value"=>nil, "key"=>2},
28
+ {"id"=>"b638586d7d71267e54af9420ee803a7c", "value"=>nil, "key"=>3},
29
+ {"id"=>"c0f8a9d683fb1576dc1da943d9bf2251", "value"=>nil, "key"=>4},
30
+ {"id"=>"11d12976059170f360df222bc097834c", "value"=>nil, "key"=>5},
31
+ {"id"=>"d9270a9bdc7128f02c4b9ccdc10e6b18", "value"=>nil, "key"=>6},
32
+ {"id"=>"c85c7d08e240c1798a7db11b0756581c", "value"=>nil, "key"=>7},
33
+ {"id"=>"721ec0ec55c5d16719ae438855528575", "value"=>nil, "key"=>8},
34
+ {"id"=>"13d2140c707196af4b4e53fe73a0ab0b", "value"=>nil, "key"=>9},
35
+ {"id"=>"b139683cf575446edb4245eba245d907", "value"=>nil, "key"=>10}
36
+ ],
37
+ "offset"=>0,
38
+ "total_rows"=>10
39
+ }
40
+ @with_docs = {
41
+ "rows"=>[
42
+ {"doc"=>{"_id"=>"9d749a8a532294c22ccbf16873f50ead", "_rev"=>"1-567432465", "index"=>2}, "id"=>"9d749a8a532294c22ccbf16873f50ead", "value"=>nil, "key"=>1},
43
+ {"doc"=>{"_id"=>"b1394da1469eae862cca661da238b951", "_rev"=>"1-2128259075", "index"=>3}, "id"=>"b1394da1469eae862cca661da238b951", "value"=>nil, "key"=>2},
44
+ {"doc"=>{"_id"=>"a1b7b978b20649cb5fd648cd510e0243", "_rev"=>"1-3326711422", "index"=>4}, "id"=>"a1b7b978b20649cb5fd648cd510e0243", "value"=>nil, "key"=>3},
45
+ {"doc"=>{"_id"=>"92b69da432d658da6dc69a5c611065ca", "_rev"=>"1-2590331142", "index"=>5}, "id"=>"92b69da432d658da6dc69a5c611065ca", "value"=>nil, "key"=>4},
46
+ {"doc"=>{"_id"=>"f3b257b6d9bc60062147db98175a809b", "_rev"=>"1-2121844908", "index"=>6}, "id"=>"f3b257b6d9bc60062147db98175a809b", "value"=>nil, "key"=>5},
47
+ ],
48
+ "offset"=>1,
49
+ "total_rows"=>5
50
+ }
51
+ @docless = ResultSet.new( @no_docs )
52
+ @docfull = ResultSet.new( @with_docs )
53
+ end
54
+
55
+ it 'should have a default document class accessor' do
56
+ ResultSet.should respond_to(:document_class)
57
+ ResultSet.should respond_to(:document_class=)
58
+ end
59
+
60
+ it 'should have an offset' do
61
+ @docless.offset.should == 0
62
+ @docfull.offset.should == 1
63
+ end
64
+
65
+ it 'should have a total' do
66
+ @docless.total.should == 10
67
+ @docfull.total.should == 5
68
+ end
69
+
70
+ it 'should have rows' do
71
+ @docless.rows.should == @no_docs['rows']
72
+ @docfull.rows.should == @with_docs['rows']
73
+ end
74
+
75
+ it 'keys should be the array accessible content of the set when docs are not included' do
76
+ @docless.first.should == @no_docs['rows'].first['key']
77
+ end
78
+
79
+ it 'docs should be the array accessible content of the set, when available' do
80
+ @docfull.first.should == @with_docs['rows'].first['doc']
81
+ end
82
+
83
+ it 'it should convert docs to the default document class if no instance level document class is available' do
84
+ ResultSet.document_class = Document
85
+ docs = ResultSet.new( @with_docs )
86
+ docs.first.class.should == Document
87
+ end
88
+
89
+ it 'should use instance level document class when available' do
90
+ docs = ResultSet.new( @with_docs, Docintalk )
91
+ docs.document_class.should == Docintalk
92
+ docs.first.class.should == Docintalk
93
+ end
94
+
95
+ end
@@ -4,10 +4,10 @@ Aqua.set_storage_engine('CouchDB') # to initialize the Aqua::Store namespace
4
4
  require File.dirname(__FILE__) + '/fixtures_and_data/document_fixture' # Document ... a Mash with the collection of methods
5
5
 
6
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 )
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
11
 
12
12
  describe 'CouchDB::StorageMethods' do
13
13
  before(:each) do
@@ -17,6 +17,9 @@ describe 'CouchDB::StorageMethods' do
17
17
  :more => "my big stuff"
18
18
  }
19
19
  @doc = Document.new( @params )
20
+ # the line below is neccessary in the full suite, but not when the file is run on it's own ??
21
+ CouchDB.put( 'http://127.0.0.1:5984/aqua' ) unless @doc.class.database.exists?
22
+ @doc.class.database.delete_all
20
23
  end
21
24
 
22
25
  describe 'initialization' do
@@ -148,10 +151,6 @@ describe 'CouchDB::StorageMethods' do
148
151
  end
149
152
 
150
153
  describe 'save/create' do
151
- before(:each) do
152
- @doc.delete if @doc.exists?
153
- end
154
-
155
154
  it 'saving should create a document in the database' do
156
155
  @doc.save
157
156
  lambda{ Aqua::Store::CouchDB.get( @doc.uri ) }.should_not raise_error
@@ -213,9 +212,43 @@ describe 'CouchDB::StorageMethods' do
213
212
  @doc[:noodle] = 'spaghetti'
214
213
  @doc.reload
215
214
  @doc[:noodle].should be_nil
215
+ end
216
+
217
+ it 'should create using find_or_create' do
218
+ lambda{ Document.get( @params[:id] ) }.should raise_error
219
+ doc = Document.find_or_create( @params[:id] )
220
+ lambda{ Document.get( @params[:id] ) }.should_not raise_error
216
221
  end
217
222
  end
218
223
 
224
+ describe 'getting' do
225
+ it 'should get a document from its id' do
226
+ @doc.save!
227
+ lambda{ Document.get( @doc.id ) }.should_not raise_error
228
+ end
229
+
230
+ it 'returned document should have an id' do
231
+ @doc.save!
232
+ document = Document.get( @doc.id )
233
+ document.id.should == @doc.id
234
+ end
235
+
236
+ it 'returned document should have an id even if not explicitly set' do
237
+ doc = Document.new(:this => 'that')
238
+ doc.save!
239
+ retrieved = Document.get( doc.id )
240
+ retrieved.id.should_not be_nil
241
+ retrieved.id.should_not be_empty
242
+ end
243
+
244
+ it 'returned document should have a rev' do
245
+ @doc.save!
246
+ document = Document.get( @doc.id )
247
+ document.rev.should_not be_nil
248
+ end
249
+
250
+ end
251
+
219
252
  describe 'deleting' do
220
253
  before(:each) do
221
254
  @doc.delete if @doc.exists?
@@ -401,8 +434,115 @@ describe 'CouchDB::StorageMethods' do
401
434
 
402
435
  end
403
436
 
404
- describe 'indexing/views' do
405
- it 'should '
437
+ describe 'design document' do
438
+ it 'should not have a design document if there is no design_name' do
439
+ Document.design_name.should be_nil
440
+ Document.design_document.should be_nil
441
+ end
442
+
443
+ it 'should create a design document if there is a design_name but the design document exists' do
444
+ Document.design_name = 'User'
445
+ lambda{ CouchDB::DesignDocument.get( 'User' ) }.should raise_error
446
+ Document.design_document.should_not be_nil
447
+ lambda{ CouchDB::DesignDocument.get( 'User') }.should_not raise_error
448
+ end
449
+
450
+ it 'should retrieve a design document if there is a design_name and the design document exists' do
451
+ CouchDB::DesignDocument.create!( :name => 'User' )
452
+ # ensures that the record exists, before the real test
453
+ lambda{ CouchDB::DesignDocument.get( 'User') }.should_not raise_error
454
+ Document.design_document.should_not be_nil
455
+ end
456
+ end
457
+
458
+ describe 'indexing & queries' do
459
+ Document.class_eval { attr_accessor( :my_field ) }
460
+
461
+ it 'should have a class method "indexes" that stores map names related to the class' do
462
+ Document.indexes.should == []
463
+ end
464
+
465
+ describe 'index_on' do
466
+ before(:each) do
467
+ Document.index_on(:my_field)
468
+ end
469
+
470
+ it 'should add text and symbol entries to indexes' do
471
+ Document.indexes.size.should == 2
472
+ Document.indexes.should include( :my_field )
473
+ Document.indexes.should include( 'my_field' )
474
+ end
475
+
476
+ it 'should not duplicate an index name' do
477
+ # previous tests have already added it to the array
478
+ Document.indexes.size.should == 2
479
+ end
480
+
481
+ it 'should add a view to the design document' do
482
+ design = Document.design_document(true)
483
+ design.views.should include( :my_field )
484
+ end
485
+ end
486
+
487
+ describe 'queries' do
488
+ before(:each) do
489
+ Document.parent_class = 'Document'
490
+ Document.index_on(:my_field)
491
+ Document.index_on_ivar( :my_ivar )
492
+ (1..5).each do |number|
493
+ Document.create!(
494
+ :my_field => number * 5,
495
+ :ivars => {
496
+ '@my_ivar' => number + 5
497
+ },
498
+ :class => 'Document'
499
+ )
500
+ end
501
+ end
502
+
503
+ it 'should query full documents for an index' do
504
+ docs = Document.query(:my_field)
505
+ docs.each{ |r| r.class.should == Document }
506
+ docs.size.should == 5
507
+ end
508
+
509
+ it 'should query ivar indexes correctly' do
510
+ docs = Document.query(:my_ivar)
511
+ docs.each{ |r| r.class.should == Document }
512
+ docs.size.should == 5
513
+ end
514
+
515
+ it 'should query only the index value for an index' do
516
+ docs = Document.query(:my_field, :select => 'index only')
517
+ docs.each{ |r| r.class.should == Fixnum }
518
+ end
519
+
520
+ it 'should generate a calculated/reduced view for an index the first time it is called' do
521
+ Document.count(:my_field)
522
+ Document.design_document.views.should include( :my_field_count )
523
+ end
524
+
525
+ it 'should count an index' do
526
+ Document.count(:my_field).should == 5
527
+ end
528
+
529
+ it 'should sum an index' do
530
+ Document.sum(:my_field).should == 75
531
+ end
532
+
533
+ it 'should average an index' do
534
+ Document.average(:my_field).should == 15
535
+ end
536
+
537
+ it 'should get the minimum of an index' do
538
+ Document.min(:my_field).should == 5
539
+ end
540
+
541
+ it 'should get the maximum of an index' do
542
+ Document.max(:my_field).should == 25
543
+ end
544
+
545
+ end
406
546
  end
407
547
 
408
548
  end