dynamoid-moda 0.7.1
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.
- checksums.yaml +15 -0
- data/.document +5 -0
- data/.rspec +1 -0
- data/.travis.yml +7 -0
- data/Gemfile +24 -0
- data/Gemfile.lock +118 -0
- data/Gemfile_activemodel4 +24 -0
- data/Gemfile_activemodel4.lock +88 -0
- data/LICENSE.txt +20 -0
- data/README.markdown +360 -0
- data/Rakefile +93 -0
- data/VERSION +1 -0
- data/doc/.nojekyll +0 -0
- data/doc/Dynamoid.html +328 -0
- data/doc/Dynamoid/Adapter.html +1872 -0
- data/doc/Dynamoid/Adapter/AwsSdk.html +2101 -0
- data/doc/Dynamoid/Adapter/Local.html +1574 -0
- data/doc/Dynamoid/Associations.html +138 -0
- data/doc/Dynamoid/Associations/Association.html +847 -0
- data/doc/Dynamoid/Associations/BelongsTo.html +161 -0
- data/doc/Dynamoid/Associations/ClassMethods.html +766 -0
- data/doc/Dynamoid/Associations/HasAndBelongsToMany.html +167 -0
- data/doc/Dynamoid/Associations/HasMany.html +167 -0
- data/doc/Dynamoid/Associations/HasOne.html +161 -0
- data/doc/Dynamoid/Associations/ManyAssociation.html +1684 -0
- data/doc/Dynamoid/Associations/SingleAssociation.html +627 -0
- data/doc/Dynamoid/Components.html +242 -0
- data/doc/Dynamoid/Config.html +412 -0
- data/doc/Dynamoid/Config/Options.html +638 -0
- data/doc/Dynamoid/Criteria.html +138 -0
- data/doc/Dynamoid/Criteria/Chain.html +1471 -0
- data/doc/Dynamoid/Criteria/ClassMethods.html +105 -0
- data/doc/Dynamoid/Dirty.html +424 -0
- data/doc/Dynamoid/Dirty/ClassMethods.html +174 -0
- data/doc/Dynamoid/Document.html +1033 -0
- data/doc/Dynamoid/Document/ClassMethods.html +1116 -0
- data/doc/Dynamoid/Errors.html +125 -0
- data/doc/Dynamoid/Errors/ConditionalCheckFailedException.html +141 -0
- data/doc/Dynamoid/Errors/DocumentNotValid.html +221 -0
- data/doc/Dynamoid/Errors/Error.html +137 -0
- data/doc/Dynamoid/Errors/InvalidField.html +141 -0
- data/doc/Dynamoid/Errors/InvalidQuery.html +131 -0
- data/doc/Dynamoid/Errors/MissingRangeKey.html +141 -0
- data/doc/Dynamoid/Fields.html +686 -0
- data/doc/Dynamoid/Fields/ClassMethods.html +438 -0
- data/doc/Dynamoid/Finders.html +135 -0
- data/doc/Dynamoid/Finders/ClassMethods.html +943 -0
- data/doc/Dynamoid/IdentityMap.html +492 -0
- data/doc/Dynamoid/IdentityMap/ClassMethods.html +534 -0
- data/doc/Dynamoid/Indexes.html +321 -0
- data/doc/Dynamoid/Indexes/ClassMethods.html +369 -0
- data/doc/Dynamoid/Indexes/Index.html +1142 -0
- data/doc/Dynamoid/Middleware.html +115 -0
- data/doc/Dynamoid/Middleware/IdentityMap.html +264 -0
- data/doc/Dynamoid/Persistence.html +892 -0
- data/doc/Dynamoid/Persistence/ClassMethods.html +836 -0
- data/doc/Dynamoid/Validations.html +415 -0
- data/doc/_index.html +506 -0
- data/doc/class_list.html +53 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +57 -0
- data/doc/css/style.css +338 -0
- data/doc/file.LICENSE.html +73 -0
- data/doc/file.README.html +416 -0
- data/doc/file_list.html +58 -0
- data/doc/frames.html +28 -0
- data/doc/index.html +416 -0
- data/doc/js/app.js +214 -0
- data/doc/js/full_list.js +178 -0
- data/doc/js/jquery.js +4 -0
- data/doc/method_list.html +1144 -0
- data/doc/top-level-namespace.html +112 -0
- data/dynamoid-moda.gemspec +210 -0
- data/dynamoid.gemspec +208 -0
- data/lib/dynamoid.rb +46 -0
- data/lib/dynamoid/adapter.rb +267 -0
- data/lib/dynamoid/adapter/aws_sdk.rb +309 -0
- data/lib/dynamoid/associations.rb +106 -0
- data/lib/dynamoid/associations/association.rb +105 -0
- data/lib/dynamoid/associations/belongs_to.rb +44 -0
- data/lib/dynamoid/associations/has_and_belongs_to_many.rb +40 -0
- data/lib/dynamoid/associations/has_many.rb +39 -0
- data/lib/dynamoid/associations/has_one.rb +39 -0
- data/lib/dynamoid/associations/many_association.rb +191 -0
- data/lib/dynamoid/associations/single_association.rb +69 -0
- data/lib/dynamoid/components.rb +37 -0
- data/lib/dynamoid/config.rb +57 -0
- data/lib/dynamoid/config/options.rb +78 -0
- data/lib/dynamoid/criteria.rb +29 -0
- data/lib/dynamoid/criteria/chain.rb +326 -0
- data/lib/dynamoid/dirty.rb +47 -0
- data/lib/dynamoid/document.rb +199 -0
- data/lib/dynamoid/errors.rb +28 -0
- data/lib/dynamoid/fields.rb +138 -0
- data/lib/dynamoid/finders.rb +133 -0
- data/lib/dynamoid/identity_map.rb +96 -0
- data/lib/dynamoid/indexes.rb +69 -0
- data/lib/dynamoid/indexes/index.rb +103 -0
- data/lib/dynamoid/middleware/identity_map.rb +16 -0
- data/lib/dynamoid/persistence.rb +292 -0
- data/lib/dynamoid/validations.rb +36 -0
- data/spec/app/models/address.rb +13 -0
- data/spec/app/models/camel_case.rb +34 -0
- data/spec/app/models/car.rb +6 -0
- data/spec/app/models/magazine.rb +11 -0
- data/spec/app/models/message.rb +9 -0
- data/spec/app/models/nuclear_submarine.rb +5 -0
- data/spec/app/models/sponsor.rb +8 -0
- data/spec/app/models/subscription.rb +12 -0
- data/spec/app/models/tweet.rb +12 -0
- data/spec/app/models/user.rb +26 -0
- data/spec/app/models/vehicle.rb +7 -0
- data/spec/dynamoid/adapter/aws_sdk_spec.rb +376 -0
- data/spec/dynamoid/adapter_spec.rb +155 -0
- data/spec/dynamoid/associations/association_spec.rb +194 -0
- data/spec/dynamoid/associations/belongs_to_spec.rb +71 -0
- data/spec/dynamoid/associations/has_and_belongs_to_many_spec.rb +47 -0
- data/spec/dynamoid/associations/has_many_spec.rb +42 -0
- data/spec/dynamoid/associations/has_one_spec.rb +45 -0
- data/spec/dynamoid/associations_spec.rb +16 -0
- data/spec/dynamoid/config_spec.rb +27 -0
- data/spec/dynamoid/criteria/chain_spec.rb +210 -0
- data/spec/dynamoid/criteria_spec.rb +75 -0
- data/spec/dynamoid/dirty_spec.rb +57 -0
- data/spec/dynamoid/document_spec.rb +180 -0
- data/spec/dynamoid/fields_spec.rb +156 -0
- data/spec/dynamoid/finders_spec.rb +147 -0
- data/spec/dynamoid/identity_map_spec.rb +45 -0
- data/spec/dynamoid/indexes/index_spec.rb +104 -0
- data/spec/dynamoid/indexes_spec.rb +25 -0
- data/spec/dynamoid/persistence_spec.rb +301 -0
- data/spec/dynamoid/validations_spec.rb +36 -0
- data/spec/dynamoid_spec.rb +14 -0
- data/spec/spec_helper.rb +55 -0
- data/spec/support/with_partitioning.rb +15 -0
- metadata +363 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
|
2
|
+
|
|
3
|
+
describe "Dynamoid::IdentityMap" do
|
|
4
|
+
before :each do
|
|
5
|
+
Dynamoid::Config.identity_map = true
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
after :each do
|
|
9
|
+
Dynamoid::Config.identity_map = false
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
context 'object identity' do
|
|
13
|
+
it 'maintains a single object' do
|
|
14
|
+
tweet = Tweet.create(:tweet_id => "x", :group => "one")
|
|
15
|
+
tweet1 = Tweet.where(:tweet_id => "x", :group => "one").first
|
|
16
|
+
tweet.should equal(tweet1)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
context 'cache' do
|
|
21
|
+
it 'uses cache' do
|
|
22
|
+
tweet = Tweet.create(:tweet_id => "x", :group => "one")
|
|
23
|
+
Dynamoid::Adapter.expects(:read).never
|
|
24
|
+
tweet1 = Tweet.find_by_id("x", :range_key => "one")
|
|
25
|
+
tweet.should equal(tweet1)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'clears cache on delete' do
|
|
29
|
+
tweet = Tweet.create(:tweet_id => "x", :group => "one")
|
|
30
|
+
tweet.delete
|
|
31
|
+
Tweet.find_by_id("x", :range_key => "one").should be_nil
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
context 'clear' do
|
|
36
|
+
it 'clears the identiy map' do
|
|
37
|
+
Tweet.create(:tweet_id => "x", :group => "one")
|
|
38
|
+
Tweet.create(:tweet_id => "x", :group => "two")
|
|
39
|
+
|
|
40
|
+
Tweet.identity_map.size.should eq(2)
|
|
41
|
+
Dynamoid::IdentityMap.clear
|
|
42
|
+
Tweet.identity_map.size.should eq(0)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
|
2
|
+
|
|
3
|
+
describe "Dynamoid::Indexes::Index" do
|
|
4
|
+
|
|
5
|
+
before do
|
|
6
|
+
@time = DateTime.now
|
|
7
|
+
@index = Dynamoid::Indexes::Index.new(User, [:password, :name], :range_key => :created_at)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it 'reorders keys alphabetically' do
|
|
11
|
+
@index.sort([:password, :name, :created_at]).should == [:created_at, :name, :password]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it 'assigns itself hash keys' do
|
|
15
|
+
@index.hash_keys.should == [:name, :password]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it 'assigns itself range keys' do
|
|
19
|
+
@index.range_keys.should == [:created_at]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it 'reorders its name automatically' do
|
|
23
|
+
@index.name.should == [:created_at, :name, :password]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it 'determines its own table name' do
|
|
27
|
+
@index.table_name.should == 'dynamoid_tests_index_user_created_ats_and_names_and_passwords'
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'raises an error if a field does not exist' do
|
|
31
|
+
lambda {@index = Dynamoid::Indexes::Index.new(User, [:password, :text])}.should raise_error(Dynamoid::Errors::InvalidField)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'returns values for indexes' do
|
|
35
|
+
@index.values(:name => 'Josh', :password => 'test123', :created_at => @time).should == {:hash_value => 'Josh.test123', :range_value => @time.to_f}
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it 'ignores values for fields that do not exist' do
|
|
39
|
+
@index.values(:name => 'Josh', :password => 'test123', :created_at => @time, :email => 'josh@joshsymonds.com').should == {:hash_value => 'Josh.test123', :range_value => @time.to_f}
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'substitutes a blank string for a hash value that does not exist' do
|
|
43
|
+
@index.values(:name => 'Josh', :created_at => @time).should == {:hash_value => 'Josh.', :range_value => @time.to_f}
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it 'substitutes a blank string if both hash values do not exist' do
|
|
47
|
+
@index.values(:created_at => @time).should == {:hash_value => '.', :range_value => @time.to_f}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it 'accepts values from an object instead of a hash' do
|
|
51
|
+
@user = User.new(:name => 'Josh', :password => 'test123', :created_at => @time)
|
|
52
|
+
|
|
53
|
+
@index.values(@user).should == {:hash_value => 'Josh.test123', :range_value => @time.to_f}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it 'accepts values from an object and returns changed values' do
|
|
57
|
+
@user = User.new(:name => 'Josh', :password => 'test123', :created_at => @time)
|
|
58
|
+
@user.name = 'Justin'
|
|
59
|
+
|
|
60
|
+
@index.values(@user).should == {:hash_value => 'Justin.test123', :range_value => @time.to_f}
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it 'saves an object to the index it is associated with' do
|
|
64
|
+
@index = Dynamoid::Indexes::Index.new(User, :name)
|
|
65
|
+
@user = User.new(:name => 'Josh', :password => 'test123', :created_at => @time, :id => 'test123')
|
|
66
|
+
|
|
67
|
+
@index.save(@user)
|
|
68
|
+
|
|
69
|
+
Dynamoid::Adapter.read("dynamoid_tests_index_user_names", 'Josh')[:ids].should == Set['test123']
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it 'saves an object to the index it is associated with with a range' do
|
|
73
|
+
@index = Dynamoid::Indexes::Index.new(User, :name, :range_key => :last_logged_in_at)
|
|
74
|
+
@user = User.create(:name => 'Josh', :last_logged_in_at => @time)
|
|
75
|
+
|
|
76
|
+
@index.save(@user)
|
|
77
|
+
|
|
78
|
+
Dynamoid::Adapter.read("dynamoid_tests_index_user_last_logged_in_ats_and_names", 'Josh', :range_key => @time.to_f)[:ids].should == Set[@user.id]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it 'deletes an object from the index it is associated with' do
|
|
82
|
+
@index = Dynamoid::Indexes::Index.new(User, :name)
|
|
83
|
+
@user = User.create(:name => 'Josh', :password => 'test123', :last_logged_in_at => @time, :id => 'test123')
|
|
84
|
+
|
|
85
|
+
@index.save(@user)
|
|
86
|
+
@index.delete(@user)
|
|
87
|
+
|
|
88
|
+
Dynamoid::Adapter.read("dynamoid_tests_index_user_names", 'Josh')[:ids].should be_nil
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it 'updates an object by removing it from its previous index and adding it to its new one' do
|
|
92
|
+
@index = Dynamoid::Indexes::Index.new(User, :name)
|
|
93
|
+
@user = User.create(:name => 'Josh', :password => 'test123', :last_logged_in_at => @time, :id => 'test123')
|
|
94
|
+
|
|
95
|
+
Dynamoid::Adapter.read("dynamoid_tests_index_user_names", 'Josh')[:ids].should == Set['test123']
|
|
96
|
+
Dynamoid::Adapter.read("dynamoid_tests_index_user_names", 'Justin').should be_nil
|
|
97
|
+
|
|
98
|
+
@user.update_attributes(:name => 'Justin')
|
|
99
|
+
|
|
100
|
+
Dynamoid::Adapter.read("dynamoid_tests_index_user_names", 'Josh')[:ids].should be_nil
|
|
101
|
+
Dynamoid::Adapter.read("dynamoid_tests_index_user_names", 'Justin')[:ids].should == Set['test123']
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
|
2
|
+
|
|
3
|
+
describe "Dynamoid::Indexes" do
|
|
4
|
+
|
|
5
|
+
it 'adds indexes to the index array' do
|
|
6
|
+
User.indexes.keys.should =~ [[:created_at], [:created_at, :name], [:email], [:email, :name], [:last_logged_in_at, :name], [:name]]
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
it 'saves indexes to their tables' do
|
|
10
|
+
@user = User.new(:name => 'Josh')
|
|
11
|
+
|
|
12
|
+
User.indexes.each {|k, v| v.expects(:save).with(@user).once.returns(true)}
|
|
13
|
+
|
|
14
|
+
@user.save
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'deletes indexes when the object is destroyed' do
|
|
18
|
+
@user = User.create(:name => 'Josh')
|
|
19
|
+
|
|
20
|
+
User.indexes.each {|k, v| v.expects(:delete).with(@user).once.returns(true)}
|
|
21
|
+
|
|
22
|
+
@user.destroy
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
end
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
|
2
|
+
|
|
3
|
+
describe "Dynamoid::Persistence" do
|
|
4
|
+
|
|
5
|
+
before do
|
|
6
|
+
Random.stubs(:rand).with(Dynamoid::Config.partition_size).returns(0)
|
|
7
|
+
@address = Address.new
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
context 'without AWS keys' do
|
|
11
|
+
unless ENV['ACCESS_KEY'] && ENV['SECRET_KEY']
|
|
12
|
+
before do
|
|
13
|
+
Dynamoid::Adapter.delete_table(Address.table_name) if Dynamoid::Adapter.list_tables.include?(Address.table_name)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'creates a table' do
|
|
17
|
+
Address.create_table(:table_name => Address.table_name)
|
|
18
|
+
|
|
19
|
+
Dynamoid::Adapter.list_tables.should include 'dynamoid_tests_addresses'
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it 'checks if a table already exists' do
|
|
23
|
+
Address.create_table(:table_name => Address.table_name)
|
|
24
|
+
|
|
25
|
+
Address.table_exists?(Address.table_name).should be_true
|
|
26
|
+
Address.table_exists?('crazytable').should be_false
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it 'assigns itself an id on save' do
|
|
32
|
+
@address.save
|
|
33
|
+
|
|
34
|
+
Dynamoid::Adapter.read("dynamoid_tests_addresses", @address.id)[:id].should == @address.id
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'prevents concurrent writes to tables with a lock_version' do
|
|
38
|
+
@address.save!
|
|
39
|
+
a1 = @address
|
|
40
|
+
a2 = Address.find(@address.id)
|
|
41
|
+
|
|
42
|
+
a1.city = 'Seattle'
|
|
43
|
+
a2.city = 'San Francisco'
|
|
44
|
+
|
|
45
|
+
a1.save!
|
|
46
|
+
expect { a2.save! }.to raise_exception(Dynamoid::Errors::ConditionalCheckFailedException)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
configured_with 'partitioning' do
|
|
50
|
+
it 'raises an error when attempting to use optimistic locking' do
|
|
51
|
+
expect { address.save! }.to raise_exception
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it 'assigns itself an id on save only if it does not have one' do
|
|
56
|
+
@address.id = 'test123'
|
|
57
|
+
@address.save
|
|
58
|
+
|
|
59
|
+
Dynamoid::Adapter.read("dynamoid_tests_addresses", 'test123').should_not be_empty
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it 'has a table name' do
|
|
63
|
+
Address.table_name.should == 'dynamoid_tests_addresses'
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it 'saves indexes along with itself' do
|
|
67
|
+
@user = User.new(:name => 'Josh')
|
|
68
|
+
|
|
69
|
+
@user.expects(:save_indexes).once.returns(true)
|
|
70
|
+
@user.save
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it 'deletes an item completely' do
|
|
74
|
+
@user = User.create(:name => 'Josh')
|
|
75
|
+
@user.destroy
|
|
76
|
+
|
|
77
|
+
Dynamoid::Adapter.read("dynamoid_tests_users", @user.id).should be_nil
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it 'keeps string attributes as strings' do
|
|
81
|
+
@user = User.new(:name => 'Josh')
|
|
82
|
+
@user.send(:dump)[:name].should == 'Josh'
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it 'dumps datetime attributes' do
|
|
86
|
+
@user = User.create(:name => 'Josh')
|
|
87
|
+
@user.send(:dump)[:name].should == 'Josh'
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it 'dumps integer attributes' do
|
|
91
|
+
@subscription = Subscription.create(:length => 10)
|
|
92
|
+
@subscription.send(:dump)[:length].should == 10
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it 'dumps set attributes' do
|
|
96
|
+
@subscription = Subscription.create(:length => 10)
|
|
97
|
+
@magazine = @subscription.magazine.create
|
|
98
|
+
|
|
99
|
+
@subscription.send(:dump)[:magazine_ids].should == Set[@magazine.id]
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it 'handles nil attributes properly' do
|
|
103
|
+
Address.undump(nil).should be_a(Hash)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
it 'dumps and undump a serialized field' do
|
|
107
|
+
@address.options = (hash = {:x => [1, 2], "foobar" => 3.14})
|
|
108
|
+
Address.undump(@address.send(:dump))[:options].should == hash
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
[true, false].each do |bool|
|
|
112
|
+
it "dumps a #{bool} boolean field" do
|
|
113
|
+
@address.deliverable = bool
|
|
114
|
+
Address.undump(@address.send(:dump))[:deliverable].should == bool
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
it 'raises on an invalid boolean value' do
|
|
119
|
+
expect do
|
|
120
|
+
@address.deliverable = true
|
|
121
|
+
data = @address.send(:dump)
|
|
122
|
+
data[:deliverable] = 'foo'
|
|
123
|
+
Address.undump(data)
|
|
124
|
+
end.to raise_error(ArgumentError)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
it 'loads a hash into a serialized field' do
|
|
128
|
+
hash = {foo: :bar}
|
|
129
|
+
Address.new(options: hash).options.should == hash
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
it 'loads attributes from a hash' do
|
|
133
|
+
@time = DateTime.now
|
|
134
|
+
@hash = {:name => 'Josh', :created_at => @time.to_f}
|
|
135
|
+
|
|
136
|
+
User.undump(@hash)[:name].should == 'Josh'
|
|
137
|
+
User.undump(@hash)[:created_at].to_f == @time.to_f
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it 'runs the before_create callback only once' do
|
|
141
|
+
CamelCase.any_instance.expects(:doing_before_create).once.returns(true)
|
|
142
|
+
|
|
143
|
+
CamelCase.create
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
it 'runs after save callbacks when doing #create' do
|
|
147
|
+
CamelCase.any_instance.expects(:doing_after_create).once.returns(true)
|
|
148
|
+
|
|
149
|
+
CamelCase.create
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
it 'runs after save callbacks when doing #save' do
|
|
153
|
+
CamelCase.any_instance.expects(:doing_after_create).once.returns(true)
|
|
154
|
+
|
|
155
|
+
CamelCase.new.save
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
it 'works with a HashWithIndifferentAccess' do
|
|
159
|
+
hash = ActiveSupport::HashWithIndifferentAccess.new("city" => "Atlanta")
|
|
160
|
+
|
|
161
|
+
lambda {Address.create(hash)}.should_not raise_error
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
context 'create' do
|
|
165
|
+
{
|
|
166
|
+
Tweet => ['with range', { :tweet_id => 1, :group => 'abc' }],
|
|
167
|
+
Message => ['without range', { :message_id => 1, :text => 'foo', :time => DateTime.now }]
|
|
168
|
+
}.each_pair do |clazz, fields|
|
|
169
|
+
it "checks for existence of an existing object #{fields[0]}" do
|
|
170
|
+
t1 = clazz.new(fields[1])
|
|
171
|
+
t2 = clazz.new(fields[1])
|
|
172
|
+
|
|
173
|
+
t1.save
|
|
174
|
+
expect do
|
|
175
|
+
t2.save!
|
|
176
|
+
end.to raise_exception Dynamoid::Errors::ConditionalCheckFailedException
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
context 'unknown fields' do
|
|
182
|
+
let(:clazz) do
|
|
183
|
+
Class.new do
|
|
184
|
+
include Dynamoid::Document
|
|
185
|
+
table :name => :addresses
|
|
186
|
+
|
|
187
|
+
field :city
|
|
188
|
+
field :options, :serialized
|
|
189
|
+
field :deliverable, :bad_type_specifier
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
it 'raises when undumping a column with an unknown field type' do
|
|
194
|
+
expect do
|
|
195
|
+
clazz.new(:deliverable => true) #undump is called here
|
|
196
|
+
end.to raise_error(ArgumentError)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
it 'raises when dumping a column with an unknown field type' do
|
|
200
|
+
doc = clazz.new
|
|
201
|
+
doc.deliverable = true
|
|
202
|
+
expect do
|
|
203
|
+
doc.dump
|
|
204
|
+
end.to raise_error(ArgumentError)
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
context 'update' do
|
|
209
|
+
|
|
210
|
+
before :each do
|
|
211
|
+
@tweet = Tweet.create(:tweet_id => 1, :group => 'abc', :count => 5, :tags => ['db', 'sql'], :user_name => 'john')
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
it 'runs before_update callbacks when doing #update' do
|
|
215
|
+
CamelCase.any_instance.expects(:doing_before_update).once.returns(true)
|
|
216
|
+
|
|
217
|
+
CamelCase.create(:color => 'blue').update do |t|
|
|
218
|
+
t.set(:color => 'red')
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
it 'runs after_update callbacks when doing #update' do
|
|
223
|
+
CamelCase.any_instance.expects(:doing_after_update).once.returns(true)
|
|
224
|
+
|
|
225
|
+
CamelCase.create(:color => 'blue').update do |t|
|
|
226
|
+
t.set(:color => 'red')
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
it 'support add/delete operation on a field' do
|
|
231
|
+
@tweet.update do |t|
|
|
232
|
+
t.add(:count => 3)
|
|
233
|
+
t.delete(:tags => ['db'])
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
@tweet.count.should eq(8)
|
|
237
|
+
@tweet.tags.to_a.should eq(['sql'])
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
it 'checks the conditions on update' do
|
|
241
|
+
@tweet.update(:if => { :count => 5 }) do |t|
|
|
242
|
+
t.add(:count => 3)
|
|
243
|
+
end.should be_true
|
|
244
|
+
|
|
245
|
+
@tweet.count.should eq(8)
|
|
246
|
+
|
|
247
|
+
@tweet.update(:if => { :count => 5 }) do |t|
|
|
248
|
+
t.add(:count => 3)
|
|
249
|
+
end.should be_false
|
|
250
|
+
|
|
251
|
+
@tweet.count.should eq(8)
|
|
252
|
+
|
|
253
|
+
expect {
|
|
254
|
+
@tweet.update!(:if => { :count => 5 }) do |t|
|
|
255
|
+
t.add(:count => 3)
|
|
256
|
+
end
|
|
257
|
+
}.to raise_error(Dynamoid::Errors::ConditionalCheckFailedException)
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
it 'prevents concurrent saves to tables with a lock_version' do
|
|
261
|
+
@address.save!
|
|
262
|
+
a2 = Address.find(@address.id)
|
|
263
|
+
a2.update! { |a| a.set(:city => "Chicago") }
|
|
264
|
+
|
|
265
|
+
expect do
|
|
266
|
+
@address.city = "Seattle"
|
|
267
|
+
@address.save!
|
|
268
|
+
end.to raise_error(Dynamoid::Errors::ConditionalCheckFailedException)
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
context 'delete' do
|
|
274
|
+
it 'deletes model with datetime range key' do
|
|
275
|
+
lambda {
|
|
276
|
+
msg = Message.create!(:message_id => 1, :time => DateTime.now, :text => "Hell yeah")
|
|
277
|
+
msg.destroy
|
|
278
|
+
}.should_not raise_error
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
context 'single table inheritance' do
|
|
283
|
+
let(:car) { Car.create(power_locks: false) }
|
|
284
|
+
let(:sub) { NuclearSubmarine.create(torpedoes: 5) }
|
|
285
|
+
|
|
286
|
+
it 'saves subclass objects in the parent table' do
|
|
287
|
+
c = car
|
|
288
|
+
Vehicle.find(c.id).should == c
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
it 'loads subclass item when querying the parent table' do
|
|
292
|
+
c = car
|
|
293
|
+
s = sub
|
|
294
|
+
|
|
295
|
+
Vehicle.all.to_a.tap { |v|
|
|
296
|
+
v.should include(c)
|
|
297
|
+
v.should include(s)
|
|
298
|
+
}
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
end
|