spec/object/pack_spec.rb in aqua-0.1.6 vs spec/object/pack_spec.rb in aqua-0.2.0

- old
+ new

@@ -3,133 +3,26 @@ Aqua.set_storage_engine('CouchDB') # to initialize CouchDB CouchDB = Aqua::Store::CouchDB unless defined?( CouchDB ) describe Aqua::Pack do - before(:each) do - @time = Time.now - @date = Date.parse('12/23/1969') - @log = Log.new( :message => "Hello World! This is a log entry", :created_at => Time.now ) - @user = User.new( - :username => 'kane', - :name => ['Kane', 'Baccigalupi'], - :dob => @date, - :created_at => @time, - :log => @log, - :password => 'my secret!' - ) - - def pack_grab_bag( value ) - @user.grab_bag = value - @user._pack[:ivars][:@grab_bag] - end - end - describe 'packing classes' do - it 'should pack class variables' - it 'should pack class level instance variables' - it 'should pack class definition' - it 'should save all the class details to the design document' - it 'should package views/finds in the class and save them to the design document\'s view attribute' - it 'should be saved into the design document' - end + def pack_grab_bag( value ) + @user.grab_bag = value + @user._pack[:ivars][:@grab_bag] + end + + describe 'translator' do + # the translator packs the object + # then the translator passes back the attachments and the externals back to the pack/storage document + end - describe 'external saves and stubs' do + describe 'hiding attributes' do before(:each) do - User::Storage.database.delete_all - @graeme = User.new(:username => 'graeme', :name => ['Graeme', 'Nelson']) - @user.other_user = @graeme - @pack = @user._pack - end + build_user_ivars + end - describe 'packing' do - it 'should pack a stubbed object representation under __pack[:stubs]' do - @pack[:stubs].size.should == 1 - other_user_pack = @pack[:stubs].first - other_user_pack[:class].should == "User" - other_user_pack[:id].should == @graeme - end - - it 'should pack the values of a stubbed methods' do - other_user_pack = @pack[:stubs].first - other_user_pack[:methods].size.should == 1 - other_user_pack[:methods][:username].should == 'graeme' - end - - it "should pack a stub of an object with embed=>false" do - sugar = Sugar.new - sugar.sweetness = Sugar.new - lambda {sugar._pack}.should_not raise_error - end - - it 'should pack an array of stubbed methods' do - User.configure_aqua( :embed => {:stub => [:username, :name] } ) - @user = User.new( - :username => 'kane', - :name => ['Kane', 'Baccigalupi'], - :dob => @date, - :created_at => @time, - :log => @log, - :password => 'my secret!', - :other_user => @graeme - ) - - @pack = @user._pack - other_user_pack = @pack[:stubs].first - other_user_pack[:methods].size.should == 2 - other_user_pack[:methods][:username].should == 'graeme' - other_user_pack[:methods][:name].should == ['Graeme', 'Nelson'] - - # reseting the User model, and @user instance - User.configure_aqua( :embed => {:stub => :username } ) - end - - it 'should pack the object itself with the class "Aqua::Stub"' do - @pack[:ivars][:@other_user][:class].should == "Aqua::Stub" - end - - it 'should pack the object itself with a reference to the __pack[:stubs] object' do - @pack[:ivars][:@other_user][:init].should == "/STUB_0" - end - end - - describe 'commiting' do - it 'should commit external objects' do - @user.commit! - db_docs = CouchDB::Database.new.documents - db_docs['total_rows'].should == 2 - end - - it 'should save the id to the stub after commiting' do - @user.commit! - doc = CouchDB.get( "http://127.0.0.1:5984/aqua/#{@user.id}" ) - doc["stubs"].first["id"].class.should == String - end - - it 'should log a warning if an external object doesn\'t commit' do - @graeme.should_receive(:commit).and_return(false) - @user.commit! - @user._warnings.size.should == 1 - @user._warnings.first.should match(/unable to save/i) - end - - it 'should log a warning and save the id if an object has an id' do - @graeme.commit! - @graeme.should_receive(:commit).and_return(false) - @user.commit! - @user._warnings.size.should == 1 - @user._warnings.first.should match(/unable to save latest/i) - doc = CouchDB.get( "http://127.0.0.1:5984/aqua/#{@user.id}" ) - doc["stubs"].first["id"].class.should == String - end - - it 'should rollback external commits if the parent object doesn\'t save' - end - - end - - describe 'hiding attributes' do it 'should add a class method for designating hidden instance variables' do User.should respond_to( :hide_attributes ) end it 'class method should hide instance variables designated by the user as hidden' do @@ -146,421 +39,144 @@ @user.visible_attr.should_not include('@_store') @user.class._hidden_attributes.should include('@_store') end it 'should not pack hidden variables' do - @pack = @user._pack - @pack[:ivars].keys.should_not include("@password") + pack = @user._pack + pack[:ivars].keys.should_not include("@password") end - end + end - describe 'packing up object instances:' do - before(:each) do - @pack = @user._pack - end - - it 'should save its class name as an attribute on the pack document' do - @pack[:class].should == 'User' + describe 'embed/stub reporting' do + before(:each) do + build_user_ivars end - - it 'should add an :id accessor if :id is not already an instance method' do - @user.should respond_to(:id=) + + describe '_embedded?' do + it 'should be true for embedded objects' do + @log._embedded?.should == true + end + it 'should be false for stubbed object with methods' do + @user._embedded?.should == false + end + + it 'should be false without stubbed methods, where :embed configuration is false' do + p = Persistent.new + p._embedded?.should == false + end end - it 'should pack an id if an id is present' # TODO - - it 'should not pack an id if an id is not present' do - @pack.id.should be_nil + describe '_stubbed_methods' do + it 'should be an empty for embedded objects (with no possible stubbed methods)' do + @log._stubbed_methods.should == [] + end + + it 'should be an array the stub string or symbol method passed into configuration' do + @user._stubbed_methods.should == [:username] + end + + it 'should be the array of the stub methods passed into configuration' do + User.configure_aqua :embed => { :stub => [:username, :name] } + @user._stubbed_methods.should == [:username, :name] + User.configure_aqua :embed => { :stub => :username } # resetting user model + end end + + end + + # Most of the serious packing tests are in packer_spec + # This is just to test/double-check that everything is working right withing an aquatic object + describe "nesting" do + before(:each) do + build_user_ivars + end + + describe 'embedded aquatics' do + it 'should pack an embedded object internally' do + @pack[:ivars]['@log'].should == { + 'class' => 'Log', + 'ivars' => {'@message' => @message, '@created_at' => Aqua::Translator.pack_object( @time ).pack } + } + end + end - it 'should pack the _rev if it is present' do + describe 'externals' do + before(:each) do + User::Storage.database.delete_all + end + + it 'should stub an external object' do + @pack[:ivars]['@other_user'].should == { + 'class' => 'Aqua::Stub', + 'init' => {'methods' => {'username' => 'strictnine' }, "class"=>"User", "id"=>""} + } + end + + it 'should commit the external when saving the base object' do + @user.commit! + @other_user.id.should_not be_nil + @other_user.id.should_not == @other_user.object_id + end + + it 'should commit external objects' do + @user.should_receive(:_commit_externals) + @user.commit! + end + + it 'should update the stubbed object id correctly' do + @user.instance_eval "_commit_externals" + @other_user.id.should_not be_empty + pack = @user._pack + pack[:ivars]['@other_user']['init']['id'].should == @other_user.id + end + + it 'should save the document with the correct external ids' do + @user.commit! + document = User._get_store( @user.id ) + document[:ivars]['@other_user']['init']['id'].should_not be_empty + end + + describe 'transactions' do + it 'should rollback all externals if an one external fails to commit' + it 'should rollback all externals if the base object fails to commit' + end + end + end + + describe 'pack ids and revs' do + before(:each) do + build_user_ivars + end + + it 'should have a _rev if it is present in the base object' do @user.instance_variable_set("@_rev", '1-2222222') pack = @user._pack pack[:_rev].should == '1-2222222' - end + end - describe 'instance variables, ' do - describe 'hashes' - it 'should be in a hash-like object with the key :ivars' do - @pack[:ivars].should_not be_nil - @pack[:ivars].should respond_to(:keys) - end - - it 'should save symbol keys differently that string keys' do - @user.name = {:first => 'Kane', 'last' => 'Baccigalupi'} - pack = @user._pack - pack[:ivars][:@name][:init].keys.sort.should == [':first', 'last'] - end - - describe 'basic ivars types' do - it 'should pack strings as strings' do - @pack[:ivars][:@username].should == 'kane' - end + it 'should not have a _rev of nil if _rev is not provided in the base' do + @pack[:_rev].should == nil + end - it 'should pack an array of strings as a hash with the :class "Array" and :init as the original array' do - @pack[:ivars][:@name].should == {'class' => 'Array', 'init' => ['Kane', 'Baccigalupi']} - end - end - - describe 'objects: ' do - # TODO: http://www.ruby-doc.org/core/ - # make sure all the basic types work - - describe 'Time' do - it 'should save as a hash with the class and to_s as the value' do - time_hash = @pack[:ivars][:@created_at] - time_hash['class'].should == 'Time' - time_hash['init'].class.should == String - end - - it 'the value should be reconstitutable with Time.parse' do - # comparing times directly never works for me. It is probably a micro second issue or something - @time.to_s.should == Time.parse( @pack[:ivars][:@created_at]['init'] ).to_s - end - end - - describe 'true and false' do - it 'should save as self' do - @user.grab_bag = true - pack = @user._pack - pack[:ivars][:@grab_bag].should == true - - @user.grab_bag = false - pack = @user._pack - pack[:ivars][:@grab_bag].should == false - end - end - - describe 'Date' do - it 'should save as a hash with the class and to_s as the value' do - time_hash = @pack[:ivars][:@dob] - time_hash['class'].should == 'Date' - time_hash['init'].class.should == String - end - - it 'the value should be reconstitutable with Date.parse' do - @date.should == Date.parse( @pack[:ivars][:@dob]['init'] ) - end - - it 'should not pack internally used ivars as specified by the class' do - @pack[:ivars][:@dob][:ivars].keys.should_not include('@sg', '@of', '@ajd') - end - end - - describe 'Numbers' do - it 'should pack Fixnums with correct class and value' do - pack = pack_grab_bag( 42 ) - pack[:class].should == 'Fixnum' - pack[:init].should == '42' - end - - it 'should pack Bignums with correct class and value' do - pack = pack_grab_bag( 123456789123456789 ) - pack[:class].should == 'Bignum' - pack[:init].should == '123456789123456789' - end - - it 'should pack Floats with correct class and value' do - pack = pack_grab_bag( 3.2 ) - pack[:class].should == 'Float' - pack[:init].should == '3.2' - end - - it 'should pack Rationals with the correct class and values' do - pack = pack_grab_bag( Rational( 1, 17 ) ) - pack[:class].should == 'Rational' - pack[:init].should == ['1', '17'] - end - - end - - describe 'hashes with object as keys' do - 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 - @user.name = {'first' => 'Kane', 'last' => 'Baccigalupi'} - pack = @user._pack - pack[:ivars][:@name].should == {'class' => 'Hash', 'init' => {'first' => 'Kane', 'last' => 'Baccigalupi'} } - end - - it 'should pack a numeric object key' do - pack = pack_grab_bag( {1 => 'first', 2 => 'second'} ) - keys = pack[:init].keys - keys.should include( '/OBJECT_0', '/OBJECT_1' ) - user_pack = @user.instance_variable_get("@__pack") - user_pack[:keys].size.should == 2 - user_pack[:keys].first['class'].should == 'Fixnum' - end - - it 'should pack a more complex object as a key' do - struct = OpenStruct.new( :gerbil => true ) - pack = pack_grab_bag( { struct => 'first'} ) - keys = pack[:init].keys - keys.should include( '/OBJECT_0' ) - user_pack = @user.instance_variable_get("@__pack") - user_pack[:keys].size.should == 1 - user_pack[:keys].first['class'].should == 'OpenStruct' - end - end - - describe 'embeddable aquatic' do - it 'aquatic objects should have packing instructions in the form of #_embed_me' do - @user._embed_me.should == {'stub' => :username } - Log.new._embed_me.should == true - User.configure_aqua( :embed => {:stub => [:username, :name] } ) - @user._embed_me.should == { 'stub' => [:username, :name] } - # reset for future tests - User.configure_aqua( :embed => {:stub => :username } ) - end + it 'should initially have an id of nil' do + @pack[:_rev].should == nil + end + + it 'should pack the id if it exists in the base' do + @user.id = 'my_id' + pack = @user._pack + pack[:_id].should == 'my_id' + end + end - it 'should save their ivars correctly' do - @pack[:ivars][:@log].keys.should include('ivars') - @pack[:ivars][:@log]['ivars'].keys.should == ['@created_at', '@message'] - @pack[:ivars][:@log]['ivars']['@message'].should == "Hello World! This is a log entry" - end - - it 'should correctly pack Array derivatives' do - class Arrayed < Array - aquatic - attr_accessor :my_accessor - end - arrayish = Arrayed['a', 'b', 'c', 'd'] - arrayish.my_accessor = 'Newt' - pack = arrayish._pack - pack.keys.should include('class', 'init', 'ivars') - pack['init'].class.should == Array - pack['init'].should == ['a', 'b', 'c', 'd'] - pack['ivars']['@my_accessor'].should == 'Newt' - end - - it 'should correctly pack Hash derivative' do - class Hashed < Hash - aquatic - attr_accessor :my_accessor - end - hashish = Hashed.new - hashish['1'] = '2' - hashish.my_accessor = 'Newt' - pack = hashish._pack - pack.keys.should include('class', 'init', 'ivars') - pack['init'].class.should == HashWithIndifferentAccess - pack['init'].should == {'1' => '2'} - pack['ivars']['@my_accessor'].should == 'Newt' - end - - end - - describe 'non-aquatic' do - before(:each) do - @struct = OpenStruct.new( - :gerbil => true, - :cat => 'yup, that too!', - :disaster => ['pow', 'blame', 'chase', 'spew'] - ) - @grounded = Grounded.new( - :openly_structured => @struct, - :hash_up => {:this => 'that'}, - :arraynged => ['swimming', 'should', 'be easy', 'if you float'] - ) - - end - - describe 'OpenStructs' do - before(:each) do - @user.grab_bag = @struct - pack = @user._pack - @grab_bag = pack[:ivars][:@grab_bag] - end - - it 'the key "class" should map to "OpenStruct"' do - @grab_bag['class'].should == 'OpenStruct' - end - - it 'the key "ivars" should have the key "@table", a private variable' do - @grab_bag['ivars'].keys.should_not == ['@table'] - end - - it 'should initialize with the @table instance variable' do - init_keys = @grab_bag['init'].keys - init_keys.should include(':cat') - init_keys.should include(':disaster') - init_keys.should include(':gerbil') - @grab_bag['init'][':gerbil'].should == true - @grab_bag['init'][':cat'].should == 'yup, that too!' - @grab_bag['init'][':disaster'].should == {'class' => 'Array', 'init' => ['pow', 'blame', 'chase', 'spew']} - end - end - - describe 'Uninherited classes with deep nesting' do - before(:each) do - @user.grab_bag = @grounded - pack = @user._pack - @grab_bag = pack[:ivars][:@grab_bag] - end - - it 'the key "class" should map correctly to the class name' do - @grab_bag['class'].should == 'Grounded' - end - - it 'should have ivars keys for all the ivars' do - keys = @grab_bag[:ivars].keys - keys.should include('@openly_structured') - keys.should include('@hash_up') - keys.should include('@arraynged') - end - - it 'should correctly display the nested OpenStruct' do - user_2 = User.new(:grab_bag => @struct) # this has already been tested in the set above - user_2._pack[:ivars][:@grab_bag].should == @grab_bag[:ivars][:@openly_structured] - end - end - - describe 'Classes inherited from Array' do - before(:each) do - @struct = OpenStruct.new( - :gerbil => true, - :cat => 'yup, that too!', - :disaster => ['pow', 'blame', 'chase', 'spew'], - :nipples => 'yes' - ) - @strange_array = ArrayUdder['cat', 'octopus', @struct ] - @strange_array.udder # sets an instance variable - @user.grab_bag = @strange_array - pack = @user._pack - @grab_bag = pack[:ivars][:@grab_bag] - end - - it 'should correctly map the class name' do - @grab_bag[:class].should == 'ArrayUdder' - end - - it 'should store the instance variables' do - @grab_bag[:ivars].keys.should == ['@udder'] - end - - it 'should store the simple array values' do - @grab_bag[:init].should_not be_nil - @grab_bag[:init].class.should == Array - @grab_bag[:init].should include('cat') - @grab_bag[:init].should include('octopus') - end - - it 'should store the more complex array values correctly' do - user_2 = User.new(:grab_bag => @struct) # this has already been tested in the set above - user_2._pack[:ivars][:@grab_bag].should == @grab_bag[:init].last - end - end - - describe 'Classes inherited from Hash' do - before(:each) do - @struct = OpenStruct.new( - :gerbil => true, - :cat => 'yup, that too!', - :disaster => ['pow', 'blame', 'chase', 'spew'], - :nipples => 'yes' - ) - @hash_derivative = CannedHash.new( - :ingredients => ['Corned Beef', 'Potatoes', 'Tin Can'], - :healthometer => false, - :random_struct => @struct - ) - @hash_derivative.yum # sets an instance variable - @user.grab_bag = @hash_derivative - pack = @user._pack - @grab_bag = pack[:ivars][:@grab_bag] - end - - it 'should correctly map the class name' do - @grab_bag[:class].should == 'CannedHash' - end - - it 'should store the instance variables' do - @grab_bag[:ivars].keys.should == ['@yum'] - end - - it 'should store the simple hash values' do - @grab_bag[:init].should_not be_nil - @grab_bag[:init].class.should == HashWithIndifferentAccess - - @grab_bag[:init].keys.should include('ingredients') - @grab_bag[:init].keys.should include('healthometer') - @grab_bag[:init].keys.should include('random_struct') - end - - it 'should store the more complex hash values correctly' do - user_2 = User.new(:grab_bag => @struct) # this has already been tested in the set above - user_2._pack[:ivars][:@grab_bag].should == @grab_bag[:init][:random_struct] - end - end - - describe 'Embedded IO:' do - before(:each) do - @file = File.new(File.dirname(__FILE__) + '/../store/couchdb/fixtures_and_data/image_attach.png') - @tempfile = Tempfile.new('temp.txt') - @tempfile.write('I am a tempfile!') - @tempfile.rewind - end - - describe "File" do - it 'should have pack its class name as Aqua::FileStub' do - @user.grab_bag = @file - @user._pack - @user._pack[:ivars][:@grab_bag][:class].should == 'Aqua::FileStub' - end - - it 'should have pack with an initialization key' do - @user.grab_bag = @file - pack = @user._pack - pack[:ivars][:@grab_bag][:init].should == '/FILE_image_attach.png' - end - - it 'should add an attachment to the pack' do - @user.grab_bag = @file - pack = @user._pack - pack.attachments.size.should == 1 - pack.attachments.get('image_attach.png').should == @file - end - - it 'should stub content type and length' do - @user.grab_bag = @file - pack = @user._pack - pack[:ivars][:@grab_bag][:methods].should include('content_type', 'content_length') - pack[:ivars][:@grab_bag][:methods]['content_type'].should == 'image/png' - pack[:ivars][:@grab_bag][:methods]['content_length'].should == 26551 - end - end - - describe 'Tempfile' do - it 'should have pack its class name as Aqua::FileStub' do - @user.grab_bag = @tempfile - @user._pack - @user._pack[:ivars][:@grab_bag][:class].should == 'Aqua::FileStub' - end - - it 'should have pack with an initialization key' do - @user.grab_bag = @tempfile - pack = @user._pack - pack[:ivars][:@grab_bag][:init].should == '/FILE_temp.txt' - end - - it 'should add an attachment to the pack' do - @user.grab_bag = @tempfile - pack = @user._pack - pack.attachments.size.should == 1 - pack.attachments.get('temp.txt').path.should == @tempfile.path - end - end - end - end - end - end - end - - describe 'committing packed objects to the store' do - before(:each) do + describe 'commit' do + before(:each) do + build_user_ivars User::Storage.database.delete_all end - it 'commit! should not raise errors on successful save' do + it 'commit! should not raise errors on successful save' do lambda{ @user.commit! }.should_not raise_error end it 'commit! should raise error on failure' do CouchDB.should_receive(:put).at_least(:once).and_return( CouchDB::Conflict ) @@ -576,11 +192,11 @@ it 'commit! should assign the _rev to the parent object' do @user.commit! @user.instance_variable_get('@_rev').should_not be_nil end - it 'commit! should save the record to CouchDB' do + it 'commit! should save the record to CouchDB' do @user.commit! lambda{ CouchDB.get( "http://127.0.0.1:5984/aqua/#{@user.id}") }.should_not raise_error end it 'commit should save the record and return self' do @@ -595,13 +211,17 @@ it 'commit should return false on failure' do CouchDB.should_receive(:put).at_least(:once).and_return( CouchDB::Conflict ) @user.commit.should == false end - it 'should be able to update and commit again' do + it 'should be able to update and commit again without conflict' do @user.commit! @user.grab_bag = {'1' => '2'} lambda{ @user.commit! }.should_not raise_error end end - + + describe 'classes' do + it 'should have a separate database' + end + end