dynamoid 0.4.1 → 0.5.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.
- data/Dynamoid.gemspec +18 -8
- data/Gemfile +3 -1
- data/Gemfile.lock +48 -24
- data/README.markdown +6 -3
- data/VERSION +1 -1
- data/lib/dynamoid.rb +4 -0
- data/lib/dynamoid/adapter.rb +3 -3
- data/lib/dynamoid/adapter/aws_sdk.rb +19 -13
- data/lib/dynamoid/components.rb +5 -4
- data/lib/dynamoid/config.rb +7 -4
- data/lib/dynamoid/criteria/chain.rb +22 -13
- data/lib/dynamoid/dirty.rb +41 -0
- data/lib/dynamoid/document.rb +55 -26
- data/lib/dynamoid/errors.rb +5 -0
- data/lib/dynamoid/fields.rb +0 -5
- data/lib/dynamoid/finders.rb +60 -12
- data/lib/dynamoid/identity_map.rb +96 -0
- data/lib/dynamoid/indexes/index.rb +1 -1
- data/lib/dynamoid/middleware/identity_map.rb +16 -0
- data/lib/dynamoid/persistence.rb +45 -6
- data/spec/app/models/message.rb +9 -0
- data/spec/app/models/tweet.rb +3 -0
- data/spec/dynamoid/adapter/aws_sdk_spec.rb +34 -34
- data/spec/dynamoid/criteria/chain_spec.rb +17 -9
- data/spec/dynamoid/criteria_spec.rb +8 -2
- data/spec/dynamoid/dirty_spec.rb +49 -0
- data/spec/dynamoid/document_spec.rb +5 -1
- data/spec/dynamoid/fields_spec.rb +47 -17
- data/spec/dynamoid/finders_spec.rb +27 -11
- data/spec/dynamoid/identity_map_spec.rb +45 -0
- data/spec/dynamoid/indexes/index_spec.rb +0 -2
- data/spec/dynamoid/persistence_spec.rb +65 -29
- data/spec/dynamoid_spec.rb +4 -0
- data/spec/spec_helper.rb +21 -25
- metadata +54 -18
- data/lib/dynamoid/adapter/local.rb +0 -196
- data/spec/dynamoid/adapter/local_spec.rb +0 -242
@@ -87,9 +87,13 @@ describe "Dynamoid::Document" do
|
|
87
87
|
|
88
88
|
Address.first.update_attributes(:city => 'Chicago')
|
89
89
|
|
90
|
-
@address.city.should be_nil
|
91
90
|
@address.reload.city.should == 'Chicago'
|
92
91
|
end
|
92
|
+
|
93
|
+
it 'reloads document with range key' do
|
94
|
+
tweet = Tweet.create(:tweet_id => 'x', :group => 'abc')
|
95
|
+
tweet.reload.group.should == 'abc'
|
96
|
+
end
|
93
97
|
|
94
98
|
it 'has default table options' do
|
95
99
|
@address = Address.create
|
@@ -9,34 +9,34 @@ describe "Dynamoid::Fields" do
|
|
9
9
|
it 'declares read attributes' do
|
10
10
|
@address.city.should be_nil
|
11
11
|
end
|
12
|
-
|
12
|
+
|
13
13
|
it 'declares write attributes' do
|
14
|
-
@address.city = 'Chicago'
|
14
|
+
@address.city = 'Chicago'
|
15
15
|
@address.city.should == 'Chicago'
|
16
16
|
end
|
17
|
-
|
17
|
+
|
18
18
|
it 'declares a query attribute' do
|
19
19
|
@address.city?.should be_false
|
20
|
-
|
20
|
+
|
21
21
|
@address.city = 'Chicago'
|
22
|
-
|
22
|
+
|
23
23
|
@address.city?.should be_true
|
24
24
|
end
|
25
|
-
|
25
|
+
|
26
26
|
it 'automatically declares id' do
|
27
27
|
lambda {@address.id}.should_not raise_error
|
28
28
|
end
|
29
|
-
|
29
|
+
|
30
30
|
it 'automatically declares and fills in created_at and updated_at' do
|
31
31
|
@address.save
|
32
|
-
|
32
|
+
|
33
33
|
@address = @address.reload
|
34
34
|
@address.created_at.should_not be_nil
|
35
35
|
@address.created_at.class.should == DateTime
|
36
36
|
@address.updated_at.should_not be_nil
|
37
37
|
@address.updated_at.class.should == DateTime
|
38
38
|
end
|
39
|
-
|
39
|
+
|
40
40
|
context 'with a saved address' do
|
41
41
|
before do
|
42
42
|
@address = Address.create
|
@@ -46,35 +46,35 @@ describe "Dynamoid::Fields" do
|
|
46
46
|
it 'should write an attribute correctly' do
|
47
47
|
@address.write_attribute(:city, 'Chicago')
|
48
48
|
end
|
49
|
-
|
49
|
+
|
50
50
|
it 'should write an attribute with an alias' do
|
51
51
|
@address[:city] = 'Chicago'
|
52
52
|
end
|
53
|
-
|
53
|
+
|
54
54
|
it 'should read a written attribute' do
|
55
55
|
@address.write_attribute(:city, 'Chicago')
|
56
56
|
@address.read_attribute(:city).should == 'Chicago'
|
57
57
|
end
|
58
|
-
|
58
|
+
|
59
59
|
it 'should read a written attribute with the alias' do
|
60
60
|
@address.write_attribute(:city, 'Chicago')
|
61
61
|
@address[:city].should == 'Chicago'
|
62
62
|
end
|
63
|
-
|
63
|
+
|
64
64
|
it 'should update all attributes' do
|
65
65
|
@address.expects(:save).once.returns(true)
|
66
66
|
@address.update_attributes(:city => 'Chicago')
|
67
67
|
@address[:city].should == 'Chicago'
|
68
68
|
@address.id.should == @original_id
|
69
69
|
end
|
70
|
-
|
70
|
+
|
71
71
|
it 'should update one attribute' do
|
72
72
|
@address.expects(:save).once.returns(true)
|
73
73
|
@address.update_attribute(:city, 'Chicago')
|
74
74
|
@address[:city].should == 'Chicago'
|
75
75
|
@address.id.should == @original_id
|
76
76
|
end
|
77
|
-
|
77
|
+
|
78
78
|
it 'adds in dirty methods for attributes' do
|
79
79
|
@address.city = 'Chicago'
|
80
80
|
@address.save
|
@@ -83,7 +83,7 @@ describe "Dynamoid::Fields" do
|
|
83
83
|
|
84
84
|
@address.city_was.should == 'Chicago'
|
85
85
|
end
|
86
|
-
|
86
|
+
|
87
87
|
it 'returns all attributes' do
|
88
88
|
Address.attributes.should == {:id=>{:type=>:string}, :created_at=>{:type=>:datetime}, :updated_at=>{:type=>:datetime}, :city=>{:type=>:string}, :options=>{:type=>:serialized}}
|
89
89
|
end
|
@@ -93,5 +93,35 @@ describe "Dynamoid::Fields" do
|
|
93
93
|
Dynamoid.logger.expects(:warn).with(regexp_matches(/city field has a length of 66000/))
|
94
94
|
Address.new city: ("Ten chars " * 6_600)
|
95
95
|
end
|
96
|
-
|
96
|
+
|
97
|
+
|
98
|
+
context 'default values for fields' do
|
99
|
+
before do
|
100
|
+
@clazz = Class.new do
|
101
|
+
include Dynamoid::Document
|
102
|
+
|
103
|
+
field :name, :string, :default => 'x'
|
104
|
+
field :uid, :integer, :default => lambda { 42 }
|
105
|
+
|
106
|
+
def self.name
|
107
|
+
'Document'
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
|
112
|
+
@doc = @clazz.new
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'returns default value' do
|
116
|
+
@doc.name.should eq('x')
|
117
|
+
@doc.uid.should eq(42)
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'should save default value' do
|
121
|
+
@doc.save!
|
122
|
+
@doc.reload.name.should eq('x')
|
123
|
+
@doc.uid.should eq(42)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
97
127
|
end
|
@@ -8,24 +8,24 @@ describe "Dynamoid::Finders" do
|
|
8
8
|
|
9
9
|
it 'finds an existing address' do
|
10
10
|
found = Address.find(@address.id)
|
11
|
-
|
11
|
+
|
12
12
|
found.should == @address
|
13
13
|
found.city.should == 'Chicago'
|
14
14
|
end
|
15
|
-
|
15
|
+
|
16
16
|
it 'is not a new object' do
|
17
17
|
found = Address.find(@address.id)
|
18
|
-
|
18
|
+
|
19
19
|
found.new_record.should_not be_true
|
20
20
|
end
|
21
|
-
|
21
|
+
|
22
22
|
it 'returns nil when nothing is found' do
|
23
23
|
Address.find('1234').should be_nil
|
24
24
|
end
|
25
|
-
|
25
|
+
|
26
26
|
it 'finds multiple ids' do
|
27
27
|
@address2 = Address.create(:city => 'Illinois')
|
28
|
-
|
28
|
+
|
29
29
|
Address.find(@address.id, @address2.id).should include @address, @address2
|
30
30
|
end
|
31
31
|
|
@@ -33,12 +33,12 @@ describe "Dynamoid::Finders" do
|
|
33
33
|
Dynamoid::Adapter.expects(:get_item).with { |table_name, key, options| options[:consistent_read] == true }
|
34
34
|
Address.find('x', :consistent_read => true)
|
35
35
|
end
|
36
|
-
|
36
|
+
|
37
37
|
context 'with users' do
|
38
38
|
before do
|
39
39
|
User.create_indexes
|
40
40
|
end
|
41
|
-
|
41
|
+
|
42
42
|
it 'finds using method_missing for attributes' do
|
43
43
|
array = Address.find_by_city('Chicago')
|
44
44
|
|
@@ -83,7 +83,7 @@ describe "Dynamoid::Finders" do
|
|
83
83
|
|
84
84
|
array.should be_empty
|
85
85
|
end
|
86
|
-
|
86
|
+
|
87
87
|
it 'finds using method_missing for a single attribute and no results' do
|
88
88
|
@user1 = User.create(:name => 'Josh', :email => 'josh@joshsymonds.com')
|
89
89
|
@user2 = User.create(:name => 'Justin', :email => 'justin@joshsymonds.com')
|
@@ -108,12 +108,28 @@ describe "Dynamoid::Finders" do
|
|
108
108
|
|
109
109
|
array.should == [@user]
|
110
110
|
end
|
111
|
-
|
111
|
+
|
112
112
|
it 'should return an empty array when fields exist but nothing is found' do
|
113
113
|
array = User.find_all_by_password('Test')
|
114
|
-
|
114
|
+
|
115
115
|
array.should be_empty
|
116
116
|
end
|
117
117
|
|
118
118
|
end
|
119
|
+
|
120
|
+
context 'find_all' do
|
121
|
+
it 'should return a array of users' do
|
122
|
+
users = (1..10).map { User.create }
|
123
|
+
User.find_all(users.map(&:id)).should eq(users)
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'should return a array of tweets' do
|
127
|
+
tweets = (1..10).map { |i| Tweet.create(:tweet_id => "#{i}", :group => "group_#{i}") }
|
128
|
+
Tweet.find_all(tweets.map { |t| [t.tweet_id, t.group] }).should eq(tweets)
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'should return an empty array' do
|
132
|
+
User.find_all([]).should eq([])
|
133
|
+
end
|
134
|
+
end
|
119
135
|
end
|
@@ -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
|
@@ -57,8 +57,6 @@ describe "Dynamoid::Indexes::Index" do
|
|
57
57
|
@user = User.new(:name => 'Josh', :password => 'test123', :created_at => @time)
|
58
58
|
@user.name = 'Justin'
|
59
59
|
|
60
|
-
@index.values(@user, true).should == {:hash_value => 'Josh.test123', :range_value => @time.to_f}
|
61
|
-
|
62
60
|
@index.values(@user).should == {:hash_value => 'Justin.test123', :range_value => @time.to_f}
|
63
61
|
end
|
64
62
|
|
@@ -12,7 +12,7 @@ describe "Dynamoid::Persistence" do
|
|
12
12
|
before do
|
13
13
|
Dynamoid::Adapter.delete_table(Address.table_name) if Dynamoid::Adapter.list_tables.include?(Address.table_name)
|
14
14
|
end
|
15
|
-
|
15
|
+
|
16
16
|
it 'creates a table' do
|
17
17
|
Address.create_table(:table_name => Address.table_name)
|
18
18
|
|
@@ -27,57 +27,57 @@ describe "Dynamoid::Persistence" do
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
end
|
30
|
-
|
30
|
+
|
31
31
|
it 'assigns itself an id on save' do
|
32
32
|
@address.save
|
33
|
-
|
33
|
+
|
34
34
|
Dynamoid::Adapter.read("dynamoid_tests_addresses", @address.id)[:id].should == @address.id
|
35
35
|
end
|
36
|
-
|
36
|
+
|
37
37
|
it 'assigns itself an id on save only if it does not have one' do
|
38
38
|
@address.id = 'test123'
|
39
39
|
@address.save
|
40
|
-
|
40
|
+
|
41
41
|
Dynamoid::Adapter.read("dynamoid_tests_addresses", 'test123').should_not be_empty
|
42
42
|
end
|
43
|
-
|
43
|
+
|
44
44
|
it 'has a table name' do
|
45
45
|
Address.table_name.should == 'dynamoid_tests_addresses'
|
46
46
|
end
|
47
|
-
|
47
|
+
|
48
48
|
it 'saves indexes along with itself' do
|
49
49
|
@user = User.new(:name => 'Josh')
|
50
|
-
|
50
|
+
|
51
51
|
@user.expects(:save_indexes).once.returns(true)
|
52
52
|
@user.save
|
53
53
|
end
|
54
|
-
|
54
|
+
|
55
55
|
it 'deletes an item completely' do
|
56
56
|
@user = User.create(:name => 'Josh')
|
57
57
|
@user.destroy
|
58
|
-
|
58
|
+
|
59
59
|
Dynamoid::Adapter.read("dynamoid_tests_users", @user.id).should be_nil
|
60
60
|
end
|
61
|
-
|
61
|
+
|
62
62
|
it 'keeps string attributes as strings' do
|
63
63
|
@user = User.new(:name => 'Josh')
|
64
64
|
@user.send(:dump)[:name].should == 'Josh'
|
65
65
|
end
|
66
|
-
|
66
|
+
|
67
67
|
it 'dumps datetime attributes' do
|
68
68
|
@user = User.create(:name => 'Josh')
|
69
69
|
@user.send(:dump)[:name].should == 'Josh'
|
70
70
|
end
|
71
|
-
|
71
|
+
|
72
72
|
it 'dumps integer attributes' do
|
73
73
|
@subscription = Subscription.create(:length => 10)
|
74
74
|
@subscription.send(:dump)[:length].should == 10
|
75
75
|
end
|
76
|
-
|
76
|
+
|
77
77
|
it 'dumps set attributes' do
|
78
78
|
@subscription = Subscription.create(:length => 10)
|
79
79
|
@magazine = @subscription.magazine.create
|
80
|
-
|
80
|
+
|
81
81
|
@subscription.send(:dump)[:magazine_ids].should == Set[@magazine.id]
|
82
82
|
end
|
83
83
|
|
@@ -94,11 +94,11 @@ describe "Dynamoid::Persistence" do
|
|
94
94
|
hash = {foo: :bar}
|
95
95
|
Address.new(options: hash).options.should == hash
|
96
96
|
end
|
97
|
-
|
97
|
+
|
98
98
|
it 'loads attributes from a hash' do
|
99
99
|
@time = DateTime.now
|
100
100
|
@hash = {:name => 'Josh', :created_at => @time.to_f}
|
101
|
-
|
101
|
+
|
102
102
|
User.undump(@hash)[:name].should == 'Josh'
|
103
103
|
User.undump(@hash)[:created_at].to_f == @time.to_f
|
104
104
|
end
|
@@ -120,21 +120,57 @@ describe "Dynamoid::Persistence" do
|
|
120
120
|
|
121
121
|
CamelCase.new.save
|
122
122
|
end
|
123
|
-
|
124
|
-
it 'tracks previous changes on save or update' do
|
125
|
-
@address.city = 'Chicago'
|
126
|
-
@address.save
|
127
|
-
|
128
|
-
@address.city = 'San Francisco'
|
129
|
-
@address.save
|
130
|
-
|
131
|
-
@address.city_was.should == 'Chicago'
|
132
|
-
end
|
133
|
-
|
123
|
+
|
134
124
|
it 'works with a HashWithIndifferentAccess' do
|
135
125
|
hash = ActiveSupport::HashWithIndifferentAccess.new("city" => "Atlanta")
|
136
|
-
|
126
|
+
|
137
127
|
lambda {Address.create(hash)}.should_not raise_error
|
138
128
|
end
|
139
129
|
|
130
|
+
context 'update' do
|
131
|
+
|
132
|
+
before :each do
|
133
|
+
@tweet = Tweet.create(:tweet_id => 1, :group => 'abc', :count => 5, :tags => ['db', 'sql'], :user_name => 'john')
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'support add/delete operation on a field' do
|
137
|
+
@tweet.update do |t|
|
138
|
+
t.add(:count => 3)
|
139
|
+
t.delete(:tags => ['db'])
|
140
|
+
end
|
141
|
+
|
142
|
+
@tweet.count.should eq(8)
|
143
|
+
@tweet.tags.to_a.should eq(['sql'])
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'checks the conditions on update' do
|
147
|
+
@tweet.update(:if => { :count => 5 }) do |t|
|
148
|
+
t.add(:count => 3)
|
149
|
+
end.should be_true
|
150
|
+
|
151
|
+
@tweet.count.should eq(8)
|
152
|
+
|
153
|
+
@tweet.update(:if => { :count => 5 }) do |t|
|
154
|
+
t.add(:count => 3)
|
155
|
+
end.should be_false
|
156
|
+
|
157
|
+
@tweet.count.should eq(8)
|
158
|
+
|
159
|
+
expect {
|
160
|
+
@tweet.update!(:if => { :count => 5 }) do |t|
|
161
|
+
t.add(:count => 3)
|
162
|
+
end
|
163
|
+
}.to raise_error(Dynamoid::Errors::ConditionalCheckFailedException)
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
context 'delete' do
|
169
|
+
it 'deletes model with datetime range key' do
|
170
|
+
lambda {
|
171
|
+
msg = Message.create!(:message_id => 1, :time => DateTime.now, :text => "Hell yeah")
|
172
|
+
msg.destroy
|
173
|
+
}.should_not raise_error
|
174
|
+
end
|
175
|
+
end
|
140
176
|
end
|