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.
- data/.document +5 -0
- data/.gitignore +7 -0
- data/Aqua.gemspec +121 -0
- data/LICENCE_COUCHREST +176 -0
- data/LICENSE +20 -0
- data/README.rdoc +105 -0
- data/Rakefile +83 -0
- data/VERSION +1 -0
- data/lib/aqua.rb +101 -0
- data/lib/aqua/object/config.rb +43 -0
- data/lib/aqua/object/extensions/ar_convert.rb +0 -0
- data/lib/aqua/object/extensions/ar_style.rb +0 -0
- data/lib/aqua/object/extensions/property.rb +0 -0
- data/lib/aqua/object/extensions/validation.rb +0 -0
- data/lib/aqua/object/pack.rb +306 -0
- data/lib/aqua/object/query.rb +18 -0
- data/lib/aqua/object/stub.rb +122 -0
- data/lib/aqua/object/tank.rb +54 -0
- data/lib/aqua/object/unpack.rb +253 -0
- data/lib/aqua/store/couch_db/attachments.rb +183 -0
- data/lib/aqua/store/couch_db/couch_db.rb +151 -0
- data/lib/aqua/store/couch_db/database.rb +186 -0
- data/lib/aqua/store/couch_db/design_document.rb +57 -0
- data/lib/aqua/store/couch_db/http_client/adapter/rest_client.rb +53 -0
- data/lib/aqua/store/couch_db/http_client/rest_api.rb +62 -0
- data/lib/aqua/store/couch_db/server.rb +103 -0
- data/lib/aqua/store/couch_db/storage_methods.rb +405 -0
- data/lib/aqua/store/storage.rb +59 -0
- data/lib/aqua/support/initializers.rb +216 -0
- data/lib/aqua/support/mash.rb +144 -0
- data/lib/aqua/support/set.rb +23 -0
- data/lib/aqua/support/string_extensions.rb +121 -0
- data/spec/aqua_spec.rb +19 -0
- data/spec/object/config_spec.rb +58 -0
- data/spec/object/object_fixtures/array_udder.rb +5 -0
- data/spec/object/object_fixtures/canned_hash.rb +5 -0
- data/spec/object/object_fixtures/gerbilmiester.rb +18 -0
- data/spec/object/object_fixtures/grounded.rb +13 -0
- data/spec/object/object_fixtures/log.rb +19 -0
- data/spec/object/object_fixtures/persistent.rb +12 -0
- data/spec/object/object_fixtures/sugar.rb +4 -0
- data/spec/object/object_fixtures/user.rb +38 -0
- data/spec/object/pack_spec.rb +607 -0
- data/spec/object/query_spec.rb +27 -0
- data/spec/object/stub_spec.rb +51 -0
- data/spec/object/tank_spec.rb +61 -0
- data/spec/object/unpack_spec.rb +361 -0
- data/spec/spec.opts +3 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/store/couchdb/attachments_spec.rb +164 -0
- data/spec/store/couchdb/couch_db_spec.rb +104 -0
- data/spec/store/couchdb/database_spec.rb +161 -0
- data/spec/store/couchdb/design_document_spec.rb +43 -0
- data/spec/store/couchdb/fixtures_and_data/document_fixture.rb +3 -0
- data/spec/store/couchdb/fixtures_and_data/image_attach.png +0 -0
- data/spec/store/couchdb/server_spec.rb +96 -0
- data/spec/store/couchdb/storage_methods_spec.rb +408 -0
- data/utils/code_statistics.rb +134 -0
- data/utils/console +11 -0
- 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,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,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
|