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.
Files changed (136) hide show
  1. checksums.yaml +15 -0
  2. data/.document +5 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +7 -0
  5. data/Gemfile +24 -0
  6. data/Gemfile.lock +118 -0
  7. data/Gemfile_activemodel4 +24 -0
  8. data/Gemfile_activemodel4.lock +88 -0
  9. data/LICENSE.txt +20 -0
  10. data/README.markdown +360 -0
  11. data/Rakefile +93 -0
  12. data/VERSION +1 -0
  13. data/doc/.nojekyll +0 -0
  14. data/doc/Dynamoid.html +328 -0
  15. data/doc/Dynamoid/Adapter.html +1872 -0
  16. data/doc/Dynamoid/Adapter/AwsSdk.html +2101 -0
  17. data/doc/Dynamoid/Adapter/Local.html +1574 -0
  18. data/doc/Dynamoid/Associations.html +138 -0
  19. data/doc/Dynamoid/Associations/Association.html +847 -0
  20. data/doc/Dynamoid/Associations/BelongsTo.html +161 -0
  21. data/doc/Dynamoid/Associations/ClassMethods.html +766 -0
  22. data/doc/Dynamoid/Associations/HasAndBelongsToMany.html +167 -0
  23. data/doc/Dynamoid/Associations/HasMany.html +167 -0
  24. data/doc/Dynamoid/Associations/HasOne.html +161 -0
  25. data/doc/Dynamoid/Associations/ManyAssociation.html +1684 -0
  26. data/doc/Dynamoid/Associations/SingleAssociation.html +627 -0
  27. data/doc/Dynamoid/Components.html +242 -0
  28. data/doc/Dynamoid/Config.html +412 -0
  29. data/doc/Dynamoid/Config/Options.html +638 -0
  30. data/doc/Dynamoid/Criteria.html +138 -0
  31. data/doc/Dynamoid/Criteria/Chain.html +1471 -0
  32. data/doc/Dynamoid/Criteria/ClassMethods.html +105 -0
  33. data/doc/Dynamoid/Dirty.html +424 -0
  34. data/doc/Dynamoid/Dirty/ClassMethods.html +174 -0
  35. data/doc/Dynamoid/Document.html +1033 -0
  36. data/doc/Dynamoid/Document/ClassMethods.html +1116 -0
  37. data/doc/Dynamoid/Errors.html +125 -0
  38. data/doc/Dynamoid/Errors/ConditionalCheckFailedException.html +141 -0
  39. data/doc/Dynamoid/Errors/DocumentNotValid.html +221 -0
  40. data/doc/Dynamoid/Errors/Error.html +137 -0
  41. data/doc/Dynamoid/Errors/InvalidField.html +141 -0
  42. data/doc/Dynamoid/Errors/InvalidQuery.html +131 -0
  43. data/doc/Dynamoid/Errors/MissingRangeKey.html +141 -0
  44. data/doc/Dynamoid/Fields.html +686 -0
  45. data/doc/Dynamoid/Fields/ClassMethods.html +438 -0
  46. data/doc/Dynamoid/Finders.html +135 -0
  47. data/doc/Dynamoid/Finders/ClassMethods.html +943 -0
  48. data/doc/Dynamoid/IdentityMap.html +492 -0
  49. data/doc/Dynamoid/IdentityMap/ClassMethods.html +534 -0
  50. data/doc/Dynamoid/Indexes.html +321 -0
  51. data/doc/Dynamoid/Indexes/ClassMethods.html +369 -0
  52. data/doc/Dynamoid/Indexes/Index.html +1142 -0
  53. data/doc/Dynamoid/Middleware.html +115 -0
  54. data/doc/Dynamoid/Middleware/IdentityMap.html +264 -0
  55. data/doc/Dynamoid/Persistence.html +892 -0
  56. data/doc/Dynamoid/Persistence/ClassMethods.html +836 -0
  57. data/doc/Dynamoid/Validations.html +415 -0
  58. data/doc/_index.html +506 -0
  59. data/doc/class_list.html +53 -0
  60. data/doc/css/common.css +1 -0
  61. data/doc/css/full_list.css +57 -0
  62. data/doc/css/style.css +338 -0
  63. data/doc/file.LICENSE.html +73 -0
  64. data/doc/file.README.html +416 -0
  65. data/doc/file_list.html +58 -0
  66. data/doc/frames.html +28 -0
  67. data/doc/index.html +416 -0
  68. data/doc/js/app.js +214 -0
  69. data/doc/js/full_list.js +178 -0
  70. data/doc/js/jquery.js +4 -0
  71. data/doc/method_list.html +1144 -0
  72. data/doc/top-level-namespace.html +112 -0
  73. data/dynamoid-moda.gemspec +210 -0
  74. data/dynamoid.gemspec +208 -0
  75. data/lib/dynamoid.rb +46 -0
  76. data/lib/dynamoid/adapter.rb +267 -0
  77. data/lib/dynamoid/adapter/aws_sdk.rb +309 -0
  78. data/lib/dynamoid/associations.rb +106 -0
  79. data/lib/dynamoid/associations/association.rb +105 -0
  80. data/lib/dynamoid/associations/belongs_to.rb +44 -0
  81. data/lib/dynamoid/associations/has_and_belongs_to_many.rb +40 -0
  82. data/lib/dynamoid/associations/has_many.rb +39 -0
  83. data/lib/dynamoid/associations/has_one.rb +39 -0
  84. data/lib/dynamoid/associations/many_association.rb +191 -0
  85. data/lib/dynamoid/associations/single_association.rb +69 -0
  86. data/lib/dynamoid/components.rb +37 -0
  87. data/lib/dynamoid/config.rb +57 -0
  88. data/lib/dynamoid/config/options.rb +78 -0
  89. data/lib/dynamoid/criteria.rb +29 -0
  90. data/lib/dynamoid/criteria/chain.rb +326 -0
  91. data/lib/dynamoid/dirty.rb +47 -0
  92. data/lib/dynamoid/document.rb +199 -0
  93. data/lib/dynamoid/errors.rb +28 -0
  94. data/lib/dynamoid/fields.rb +138 -0
  95. data/lib/dynamoid/finders.rb +133 -0
  96. data/lib/dynamoid/identity_map.rb +96 -0
  97. data/lib/dynamoid/indexes.rb +69 -0
  98. data/lib/dynamoid/indexes/index.rb +103 -0
  99. data/lib/dynamoid/middleware/identity_map.rb +16 -0
  100. data/lib/dynamoid/persistence.rb +292 -0
  101. data/lib/dynamoid/validations.rb +36 -0
  102. data/spec/app/models/address.rb +13 -0
  103. data/spec/app/models/camel_case.rb +34 -0
  104. data/spec/app/models/car.rb +6 -0
  105. data/spec/app/models/magazine.rb +11 -0
  106. data/spec/app/models/message.rb +9 -0
  107. data/spec/app/models/nuclear_submarine.rb +5 -0
  108. data/spec/app/models/sponsor.rb +8 -0
  109. data/spec/app/models/subscription.rb +12 -0
  110. data/spec/app/models/tweet.rb +12 -0
  111. data/spec/app/models/user.rb +26 -0
  112. data/spec/app/models/vehicle.rb +7 -0
  113. data/spec/dynamoid/adapter/aws_sdk_spec.rb +376 -0
  114. data/spec/dynamoid/adapter_spec.rb +155 -0
  115. data/spec/dynamoid/associations/association_spec.rb +194 -0
  116. data/spec/dynamoid/associations/belongs_to_spec.rb +71 -0
  117. data/spec/dynamoid/associations/has_and_belongs_to_many_spec.rb +47 -0
  118. data/spec/dynamoid/associations/has_many_spec.rb +42 -0
  119. data/spec/dynamoid/associations/has_one_spec.rb +45 -0
  120. data/spec/dynamoid/associations_spec.rb +16 -0
  121. data/spec/dynamoid/config_spec.rb +27 -0
  122. data/spec/dynamoid/criteria/chain_spec.rb +210 -0
  123. data/spec/dynamoid/criteria_spec.rb +75 -0
  124. data/spec/dynamoid/dirty_spec.rb +57 -0
  125. data/spec/dynamoid/document_spec.rb +180 -0
  126. data/spec/dynamoid/fields_spec.rb +156 -0
  127. data/spec/dynamoid/finders_spec.rb +147 -0
  128. data/spec/dynamoid/identity_map_spec.rb +45 -0
  129. data/spec/dynamoid/indexes/index_spec.rb +104 -0
  130. data/spec/dynamoid/indexes_spec.rb +25 -0
  131. data/spec/dynamoid/persistence_spec.rb +301 -0
  132. data/spec/dynamoid/validations_spec.rb +36 -0
  133. data/spec/dynamoid_spec.rb +14 -0
  134. data/spec/spec_helper.rb +55 -0
  135. data/spec/support/with_partitioning.rb +15 -0
  136. metadata +363 -0
@@ -0,0 +1,155 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Dynamoid::Adapter do
4
+
5
+ def test_table; 'dynamoid_tests_TestTable'; end
6
+ let(:single_id){'123'}
7
+ let(:many_ids){%w(1 2)}
8
+
9
+ before(:all) do
10
+ described_class.create_table(test_table, :id) unless described_class.list_tables.include?(test_table)
11
+ end
12
+
13
+ it 'extends itself automatically' do
14
+ lambda {described_class.list_tables}.should_not raise_error
15
+ end
16
+
17
+ it 'raises NoMethodError if we try a method that is not on the child' do
18
+ lambda {described_class.foobar}.should raise_error(NoMethodError)
19
+ end
20
+
21
+ context 'without partioning' do
22
+ before(:all) do
23
+ @previous_value = Dynamoid::Config.partitioning
24
+ Dynamoid::Config.partitioning = false
25
+ end
26
+
27
+ after(:all) do
28
+ Dynamoid::Config.partitioning = @previous_value
29
+ end
30
+
31
+ it 'writes through the adapter' do
32
+ described_class.expects(:put_item).with(test_table, {:id => single_id}, nil).returns(true)
33
+ described_class.write(test_table, {:id => single_id})
34
+ end
35
+
36
+ it 'reads through the adapter for one ID' do
37
+ described_class.expects(:get_item).with(test_table, single_id, {}).returns(true)
38
+ described_class.read(test_table, single_id)
39
+ end
40
+
41
+ it 'reads through the adapter for many IDs' do
42
+ described_class.expects(:batch_get_item).with({test_table => many_ids}, {}).returns(true)
43
+ described_class.read(test_table, many_ids)
44
+ end
45
+
46
+ it 'delete through the adapter for one ID' do
47
+ described_class.expects(:delete_item).with(test_table, single_id, {}).returns(nil)
48
+ described_class.delete(test_table, single_id)
49
+ end
50
+
51
+ it 'deletes through the adapter for many IDs' do
52
+ described_class.expects(:batch_delete_item).with({test_table => many_ids}).returns(nil)
53
+ described_class.delete(test_table, many_ids)
54
+ end
55
+
56
+ it 'reads through the adapter for one ID and a range key' do
57
+ described_class.expects(:get_item).with(test_table, single_id, :range_key => 2.0).returns(true)
58
+ described_class.read(test_table, single_id, :range_key => 2.0)
59
+ end
60
+
61
+ it 'reads through the adapter for many IDs and a range key' do
62
+ described_class.expects(:batch_get_item).with({test_table => [['1', 2.0], ['2', 2.0]]}, {}).returns(true)
63
+ described_class.read(test_table, many_ids, :range_key => 2.0)
64
+ end
65
+
66
+ it 'deletes through the adapter for one ID and a range key' do
67
+ described_class.expects(:delete_item).with(test_table, single_id, :range_key => 2.0).returns(nil)
68
+ described_class.delete(test_table, single_id, :range_key => 2.0)
69
+ end
70
+
71
+ it 'deletes through the adapter for many IDs and a range key' do
72
+ described_class.expects(:batch_delete_item).with({test_table => [['1', 2.0], ['2', 2.0]]}).returns(nil)
73
+ described_class.delete(test_table, many_ids, :range_key => [2.0,2.0])
74
+ end
75
+ end
76
+
77
+ configured_with 'partitioning' do
78
+ let(:partition_range){0...Dynamoid::Config.partition_size}
79
+
80
+ it 'writes through the adapter' do
81
+ Random.expects(:rand).with(Dynamoid::Config.partition_size).once.returns(0)
82
+ described_class.write(test_table, {:id => 'testid'})
83
+
84
+ described_class.get_item(test_table, 'testid.0')[:id].should == 'testid.0'
85
+ described_class.get_item(test_table, 'testid.0')[:updated_at].should_not be_nil
86
+ end
87
+
88
+ it 'reads through the adapter for one ID' do
89
+ described_class.expects(:batch_get_item).with({test_table => partition_range.map{|n| "123.#{n}"}}, {}).returns({})
90
+ described_class.read(test_table, single_id)
91
+ end
92
+
93
+ it 'reads through the adapter for many IDs' do
94
+ described_class.expects(:batch_get_item).with({test_table => partition_range.map{|n| "1.#{n}"} + partition_range.map{|n| "2.#{n}"}}, {}).returns({})
95
+ described_class.read(test_table, many_ids)
96
+ end
97
+
98
+ it 'reads through the adapter for one ID and a range key' do
99
+ described_class.expects(:batch_get_item).with({test_table => partition_range.map{|n| ["123.#{n}", 2.0]}}, {}).returns({})
100
+ described_class.read(test_table, single_id, :range_key => 2.0)
101
+ end
102
+
103
+ it 'reads through the adapter for many IDs and a range key' do
104
+ described_class.expects(:batch_get_item).with({test_table => partition_range.map{|n| ["1.#{n}", 2.0]} + partition_range.map{|n| ["2.#{n}", 2.0]}}, {}).returns({})
105
+ described_class.read(test_table, many_ids, :range_key => 2.0)
106
+ end
107
+
108
+ it 'returns an ID with all partitions' do
109
+ described_class.id_with_partitions('1').should =~ partition_range.map{|n| "1.#{n}"}
110
+ end
111
+
112
+ it 'returns an ID and range key with all partitions' do
113
+ described_class.id_with_partitions([['1', 1.0]]).should =~ partition_range.map{|n| ["1.#{n}", 1.0]}
114
+ end
115
+
116
+ it 'returns a result for one partitioned element' do
117
+ @time = DateTime.now
118
+ @array =[{:id => '1.0', :updated_at => @time - 6.hours},
119
+ {:id => '1.1', :updated_at => @time - 3.hours},
120
+ {:id => '1.2', :updated_at => @time - 1.hour},
121
+ {:id => '1.3', :updated_at => @time - 6.hours},
122
+ {:id => '2.0', :updated_at => @time}]
123
+
124
+ described_class.result_for_partition(@array,test_table).should =~ [{:id => '1', :updated_at => @time - 1.hour},
125
+ {:id => '2', :updated_at => @time}]
126
+ end
127
+
128
+ it 'returns a valid original id and partition number' do
129
+ @id = "12345.387327.-sdf3"
130
+ @partition_number = "4"
131
+ described_class.get_original_id_and_partition("#{@id}.#{@partition_number}").should == [@id, @partition_number]
132
+ end
133
+
134
+ it 'delete through the adapter for one ID' do
135
+ described_class.expects(:batch_delete_item).with(test_table => partition_range.map{|n| "123.#{n}"}).returns(nil)
136
+ described_class.delete(test_table, single_id)
137
+ end
138
+
139
+ it 'deletes through the adapter for many IDs' do
140
+ described_class.expects(:batch_delete_item).with(test_table => partition_range.map{|n| "1.#{n}"} + partition_range.map{|n| "2.#{n}"}).returns(nil)
141
+ described_class.delete(test_table, many_ids)
142
+ end
143
+
144
+ it 'deletes through the adapter for one ID and a range key' do
145
+ described_class.expects(:batch_delete_item).with(test_table => partition_range.map{|n| ["123.#{n}", 2.0]}).returns(nil)
146
+ described_class.delete(test_table, single_id, :range_key => 2.0)
147
+ end
148
+
149
+ it 'deletes through the adapter for many IDs and a range key' do
150
+ described_class.expects(:batch_delete_item).with(test_table => partition_range.map{|n| ["1.#{n}", 2.0]} + partition_range.map{|n| ["2.#{n}", 2.0]}).returns(nil)
151
+ described_class.delete(test_table, many_ids, :range_key => [2.0,2.0])
152
+ end
153
+ end
154
+
155
+ end
@@ -0,0 +1,194 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe "Dynamoid::Associations::Association" do
4
+
5
+ before do
6
+ Subscription.create_table
7
+ @magazine = Magazine.create
8
+ end
9
+
10
+ it 'returns an empty array if there are no associations' do
11
+ @magazine.subscriptions.should be_empty
12
+ end
13
+
14
+ it 'adds an item to an association' do
15
+ @subscription = Subscription.create
16
+
17
+ @magazine.subscriptions << @subscription
18
+ @magazine.subscriptions.size.should == 1
19
+ @magazine.subscriptions.should include @subscription
20
+ end
21
+
22
+ it 'deletes an item from an association' do
23
+ @subscription = Subscription.create
24
+ @magazine.subscriptions << @subscription
25
+
26
+ @magazine.subscriptions.delete(@subscription)
27
+ @magazine.subscriptions.size.should == 0
28
+ end
29
+
30
+ it 'creates an item from an association' do
31
+ @subscription = @magazine.subscriptions.create
32
+
33
+ @subscription.class.should == Subscription
34
+ @magazine.subscriptions.size.should == 1
35
+ @magazine.subscriptions.should include @subscription
36
+ end
37
+
38
+ it 'returns the number of items in the association' do
39
+ @magazine.subscriptions.create
40
+ @magazine.subscriptions.size.should == 1
41
+
42
+ @second = @magazine.subscriptions.create
43
+ @magazine.subscriptions.size.should == 2
44
+
45
+ @magazine.subscriptions.delete(@second)
46
+ @magazine.subscriptions.size.should == 1
47
+
48
+ @magazine.subscriptions = []
49
+ @magazine.subscriptions.size.should == 0
50
+ end
51
+
52
+ it 'assigns directly via the equals operator' do
53
+ @subscription = Subscription.create
54
+ @magazine.subscriptions = [@subscription]
55
+
56
+ @magazine.subscriptions.should == [@subscription]
57
+ end
58
+
59
+ it 'assigns directly via the equals operator and reflects to the target association' do
60
+ @subscription = Subscription.create
61
+ @magazine.subscriptions = [@subscription]
62
+
63
+ @subscription.magazine.should == @magazine
64
+ end
65
+
66
+ it 'does not assign reflection association if the reflection association does not exist' do
67
+ @sponsor = Sponsor.create
68
+
69
+ @subscription = @sponsor.subscriptions.create
70
+ @subscription.should_not respond_to :sponsor
71
+ @subscription.should_not respond_to :sponsors
72
+ @subscription.should_not respond_to :sponsors_ids
73
+ @subscription.should_not respond_to :sponsor_ids
74
+ end
75
+
76
+ it 'deletes all items from the association' do
77
+ @magazine.subscriptions << Subscription.create
78
+ @magazine.subscriptions << Subscription.create
79
+ @magazine.subscriptions << Subscription.create
80
+
81
+ @magazine.subscriptions.size.should == 3
82
+
83
+ @magazine.subscriptions = nil
84
+ @magazine.subscriptions.size.should == 0
85
+ end
86
+
87
+ it 'uses where inside an association and returns a result' do
88
+ @included_subscription = @magazine.subscriptions.create(:length => 10)
89
+ @unincldued_subscription = @magazine.subscriptions.create(:length => 8)
90
+
91
+ @magazine.subscriptions.where(:length => 10).all.should == [@included_subscription]
92
+ end
93
+
94
+ it 'uses where inside an association and returns an empty set' do
95
+ @included_subscription = @magazine.subscriptions.create(:length => 10)
96
+ @unincldued_subscription = @magazine.subscriptions.create(:length => 8)
97
+
98
+ @magazine.subscriptions.where(:length => 6).all.should be_empty
99
+ end
100
+
101
+ it 'includes enumerable' do
102
+ @subscription1 = @magazine.subscriptions.create
103
+ @subscription2 = @magazine.subscriptions.create
104
+ @subscription3 = @magazine.subscriptions.create
105
+
106
+ @magazine.subscriptions.collect(&:id).should =~ [@subscription1.id, @subscription2.id, @subscription3.id]
107
+ end
108
+
109
+ it 'works for camel-cased associations' do
110
+ @magazine.camel_cases.create.class.should == CamelCase
111
+ end
112
+
113
+ it 'destroys associations' do
114
+ @subscription = Subscription.new
115
+ @magazine.subscriptions.expects(:target).returns([@subscription])
116
+ @subscription.expects(:destroy)
117
+
118
+ @magazine.subscriptions.destroy_all
119
+ end
120
+
121
+ it 'deletes associations' do
122
+ @subscription = Subscription.new
123
+ @magazine.subscriptions.expects(:target).returns([@subscription])
124
+ @subscription.expects(:delete)
125
+
126
+ @magazine.subscriptions.delete_all
127
+ end
128
+
129
+ # TODO This test is broken using the AWS SDK adapter.
130
+ #it 'returns the first and last record when they exist' do
131
+ # @subscription1 = @magazine.subscriptions.create
132
+ # @subscription2 = @magazine.subscriptions.create
133
+ # @subscription3 = @magazine.subscriptions.create
134
+ #
135
+ # @magazine.subscriptions.instance_eval { [first, last] }.should == [@subscription1, @subscription3]
136
+ #end
137
+
138
+ it 'replaces existing associations when using the setter' do
139
+ @subscription1 = @magazine.subscriptions.create
140
+ @subscription2 = @magazine.subscriptions.create
141
+ @subscription3 = Subscription.create
142
+
143
+ @subscription1.reload.magazine_ids.should_not be_nil
144
+ @subscription2.reload.magazine_ids.should_not be_nil
145
+
146
+ @magazine.subscriptions = @subscription3
147
+ @magazine.subscriptions_ids.should == Set[@subscription3.id]
148
+
149
+ @subscription1.reload.magazine_ids.should be_nil
150
+ @subscription2.reload.magazine_ids.should be_nil
151
+ @subscription3.reload.magazine_ids.should == Set[@magazine.id]
152
+ end
153
+
154
+ it 'destroys all objects and removes them from the association' do
155
+ @subscription1 = @magazine.subscriptions.create
156
+ @subscription2 = @magazine.subscriptions.create
157
+ @subscription3 = @magazine.subscriptions.create
158
+
159
+ @magazine.subscriptions.destroy_all
160
+
161
+ @magazine.subscriptions.should be_empty
162
+ Subscription.all.should be_empty
163
+ end
164
+
165
+ it 'deletes all objects and removes them from the association' do
166
+ @subscription1 = @magazine.subscriptions.create
167
+ @subscription2 = @magazine.subscriptions.create
168
+ @subscription3 = @magazine.subscriptions.create
169
+
170
+ @magazine.subscriptions.delete_all
171
+
172
+ @magazine.subscriptions.should be_empty
173
+ Subscription.all.should be_empty
174
+ end
175
+
176
+ it 'delegates class to the association object' do
177
+ @magazine.sponsor.class.should == nil.class
178
+ @magazine.sponsor.create
179
+ @magazine.sponsor.class.should == Sponsor
180
+
181
+ @magazine.subscriptions.class.should == Array
182
+ @magazine.subscriptions.create
183
+ @magazine.subscriptions.class.should == Array
184
+ end
185
+
186
+ it 'loads association one time only' do
187
+ @sponsor = @magazine.sponsor.create
188
+ @magazine.sponsor.expects(:find_target).once.returns(@sponsor)
189
+
190
+ @magazine.sponsor.id
191
+ @magazine.sponsor.id
192
+ end
193
+
194
+ end
@@ -0,0 +1,71 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe "Dynamoid::Associations::BelongsTo" do
4
+
5
+ context 'has many' do
6
+ before do
7
+ @subscription = Subscription.create
8
+ @camel_case = CamelCase.create
9
+ end
10
+
11
+ it 'determines nil if it has no associated record' do
12
+ @subscription.magazine.should be_nil
13
+ end
14
+
15
+ it 'determines target association correctly' do
16
+ @camel_case.magazine.send(:target_association).should == :camel_cases
17
+ end
18
+
19
+
20
+ it 'delegates equality to its source record' do
21
+ @magazine = @subscription.magazine.create
22
+
23
+ @subscription.magazine.should == @magazine
24
+ end
25
+
26
+ it 'associates has_many automatically' do
27
+ @magazine = @subscription.magazine.create
28
+
29
+ @magazine.subscriptions.should include @subscription
30
+
31
+ @magazine = Magazine.create
32
+ @user = @magazine.owner.create
33
+ @user.books.size.should == 1
34
+ @user.books.should include @magazine
35
+ end
36
+
37
+ it 'behaves like the object it is trying to be' do
38
+ @magazine = @subscription.magazine.create
39
+
40
+ @subscription.magazine.update_attribute(:title, 'Test Title')
41
+
42
+ Magazine.first.title.should == 'Test Title'
43
+ end
44
+ end
45
+
46
+ context 'has one' do
47
+ before do
48
+ @sponsor = Sponsor.create
49
+ @subscription = Subscription.create
50
+ end
51
+
52
+ it 'determins nil if it has no associated record' do
53
+ @sponsor.magazine.should be_nil
54
+ end
55
+
56
+ it 'delegates equality to its source record' do
57
+ @magazine = @sponsor.magazine.create
58
+
59
+ @sponsor.magazine.should == @magazine
60
+ end
61
+
62
+ it 'associates has_one automatically' do
63
+ @magazine = @sponsor.magazine.create
64
+
65
+ @magazine.sponsor.should == @sponsor
66
+
67
+ @user = @subscription.customer.create
68
+ @user.monthly.should == @subscription
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,47 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe "Dynamoid::Associations::HasAndBelongsToMany" do
4
+
5
+ before do
6
+ @subscription = Subscription.create
7
+ @camel_case = CamelCase.create
8
+ end
9
+
10
+ it 'determines equality from its records' do
11
+ @user = @subscription.users.create
12
+
13
+ @subscription.users.size.should == 1
14
+ @subscription.users.should include @user
15
+ end
16
+
17
+ it 'determines target association correctly' do
18
+ @subscription.users.send(:target_association).should == :subscriptions
19
+ @camel_case.subscriptions.send(:target_association).should == :camel_cases
20
+ end
21
+
22
+ it 'determines target attribute' do
23
+ @subscription.users.send(:target_attribute).should == :subscriptions_ids
24
+ end
25
+
26
+ it 'associates has_and_belongs_to_many automatically' do
27
+ @user = @subscription.users.create
28
+
29
+ @user.subscriptions.size.should == 1
30
+ @user.subscriptions.should include @subscription
31
+ @subscription.users.size.should == 1
32
+ @subscription.users.should include @user
33
+
34
+ @user = User.create
35
+ @follower = @user.followers.create
36
+ @follower.following.should include @user
37
+ @user.followers.should include @follower
38
+ end
39
+
40
+ it 'disassociates has_and_belongs_to_many automatically' do
41
+ @user = @subscription.users.create
42
+
43
+ @subscription.users.delete(@user)
44
+ @subscription.users.size.should == 0
45
+ @user.subscriptions.size.should == 0
46
+ end
47
+ end