simply_stored 0.1.4

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 (121) hide show
  1. data/lib/simply_stored/class_methods_base.rb +31 -0
  2. data/lib/simply_stored/couch/belongs_to.rb +117 -0
  3. data/lib/simply_stored/couch/ext/couch_potato.rb +16 -0
  4. data/lib/simply_stored/couch/has_many.rb +148 -0
  5. data/lib/simply_stored/couch/has_one.rb +93 -0
  6. data/lib/simply_stored/couch/validations.rb +74 -0
  7. data/lib/simply_stored/couch/views/array_property_view_spec.rb +22 -0
  8. data/lib/simply_stored/couch/views.rb +1 -0
  9. data/lib/simply_stored/couch.rb +278 -0
  10. data/lib/simply_stored/instance_methods.rb +143 -0
  11. data/lib/simply_stored/simpledb/associations.rb +196 -0
  12. data/lib/simply_stored/simpledb/attributes.rb +173 -0
  13. data/lib/simply_stored/simpledb/storag.rb +85 -0
  14. data/lib/simply_stored/simpledb/validations.rb +88 -0
  15. data/lib/simply_stored/simpledb.rb +212 -0
  16. data/lib/simply_stored/storage.rb +93 -0
  17. data/lib/simply_stored.rb +9 -0
  18. data/test/custom_views_test.rb +33 -0
  19. data/test/fixtures/couch.rb +182 -0
  20. data/test/fixtures/simpledb/item.rb +11 -0
  21. data/test/fixtures/simpledb/item_daddy.rb +8 -0
  22. data/test/fixtures/simpledb/log_item.rb +3 -0
  23. data/test/fixtures/simpledb/namespace_bar.rb +5 -0
  24. data/test/fixtures/simpledb/namespace_foo.rb +7 -0
  25. data/test/fixtures/simpledb/protected_item.rb +3 -0
  26. data/test/simply_stored_couch_test.rb +1684 -0
  27. data/test/simply_stored_simpledb_test.rb +1341 -0
  28. data/test/test_helper.rb +22 -0
  29. data/test/vendor/dhaka-2.2.1/lib/dhaka/dot/dot.rb +29 -0
  30. data/test/vendor/dhaka-2.2.1/lib/dhaka/evaluator/evaluator.rb +133 -0
  31. data/test/vendor/dhaka-2.2.1/lib/dhaka/grammar/closure_hash.rb +15 -0
  32. data/test/vendor/dhaka-2.2.1/lib/dhaka/grammar/grammar.rb +240 -0
  33. data/test/vendor/dhaka-2.2.1/lib/dhaka/grammar/grammar_symbol.rb +27 -0
  34. data/test/vendor/dhaka-2.2.1/lib/dhaka/grammar/precedence.rb +19 -0
  35. data/test/vendor/dhaka-2.2.1/lib/dhaka/grammar/production.rb +36 -0
  36. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/accept_actions.rb +36 -0
  37. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/alphabet.rb +21 -0
  38. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/compiled_lexer.rb +46 -0
  39. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/dfa.rb +121 -0
  40. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/lexeme.rb +32 -0
  41. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/lexer.rb +70 -0
  42. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/lexer_run.rb +78 -0
  43. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/regex_grammar.rb +392 -0
  44. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/regex_parser.rb +2010 -0
  45. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/regex_tokenizer.rb +14 -0
  46. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/specification.rb +96 -0
  47. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/state.rb +68 -0
  48. data/test/vendor/dhaka-2.2.1/lib/dhaka/lexer/state_machine.rb +37 -0
  49. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/action.rb +55 -0
  50. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/channel.rb +58 -0
  51. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/compiled_parser.rb +51 -0
  52. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/conflict.rb +54 -0
  53. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/item.rb +42 -0
  54. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parse_result.rb +50 -0
  55. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parse_tree.rb +66 -0
  56. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parser.rb +165 -0
  57. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parser_methods.rb +11 -0
  58. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parser_run.rb +39 -0
  59. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/parser_state.rb +74 -0
  60. data/test/vendor/dhaka-2.2.1/lib/dhaka/parser/token.rb +22 -0
  61. data/test/vendor/dhaka-2.2.1/lib/dhaka/runtime.rb +51 -0
  62. data/test/vendor/dhaka-2.2.1/lib/dhaka/tokenizer/tokenizer.rb +190 -0
  63. data/test/vendor/dhaka-2.2.1/lib/dhaka.rb +62 -0
  64. data/test/vendor/dhaka-2.2.1/test/all_tests.rb +5 -0
  65. data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_evaluator.rb +64 -0
  66. data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_evaluator_test.rb +43 -0
  67. data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_grammar.rb +41 -0
  68. data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_grammar_test.rb +9 -0
  69. data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_test_methods.rb +9 -0
  70. data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_tokenizer.rb +39 -0
  71. data/test/vendor/dhaka-2.2.1/test/arithmetic/arithmetic_tokenizer_test.rb +38 -0
  72. data/test/vendor/dhaka-2.2.1/test/arithmetic_precedence/arithmetic_precedence_evaluator.rb +43 -0
  73. data/test/vendor/dhaka-2.2.1/test/arithmetic_precedence/arithmetic_precedence_grammar.rb +24 -0
  74. data/test/vendor/dhaka-2.2.1/test/arithmetic_precedence/arithmetic_precedence_grammar_test.rb +30 -0
  75. data/test/vendor/dhaka-2.2.1/test/arithmetic_precedence/arithmetic_precedence_lexer_specification.rb +23 -0
  76. data/test/vendor/dhaka-2.2.1/test/arithmetic_precedence/arithmetic_precedence_parser_test.rb +33 -0
  77. data/test/vendor/dhaka-2.2.1/test/brackets/bracket_grammar.rb +23 -0
  78. data/test/vendor/dhaka-2.2.1/test/brackets/bracket_tokenizer.rb +22 -0
  79. data/test/vendor/dhaka-2.2.1/test/brackets/brackets_test.rb +28 -0
  80. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_driver.rb +46 -0
  81. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_driver_test.rb +276 -0
  82. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_evaluator.rb +284 -0
  83. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_evaluator_test.rb +38 -0
  84. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_grammar.rb +104 -0
  85. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_lexer.rb +109 -0
  86. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_lexer_specification.rb +37 -0
  87. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_lexer_test.rb +58 -0
  88. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_parser.rb +879 -0
  89. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_parser_test.rb +55 -0
  90. data/test/vendor/dhaka-2.2.1/test/chittagong/chittagong_test.rb +170 -0
  91. data/test/vendor/dhaka-2.2.1/test/core/another_lalr_but_not_slr_grammar.rb +20 -0
  92. data/test/vendor/dhaka-2.2.1/test/core/compiled_parser_test.rb +44 -0
  93. data/test/vendor/dhaka-2.2.1/test/core/dfa_test.rb +170 -0
  94. data/test/vendor/dhaka-2.2.1/test/core/evaluator_test.rb +22 -0
  95. data/test/vendor/dhaka-2.2.1/test/core/grammar_test.rb +83 -0
  96. data/test/vendor/dhaka-2.2.1/test/core/lalr_but_not_slr_grammar.rb +19 -0
  97. data/test/vendor/dhaka-2.2.1/test/core/lexer_test.rb +139 -0
  98. data/test/vendor/dhaka-2.2.1/test/core/malformed_grammar.rb +7 -0
  99. data/test/vendor/dhaka-2.2.1/test/core/malformed_grammar_test.rb +8 -0
  100. data/test/vendor/dhaka-2.2.1/test/core/nullable_grammar.rb +21 -0
  101. data/test/vendor/dhaka-2.2.1/test/core/parse_result_test.rb +44 -0
  102. data/test/vendor/dhaka-2.2.1/test/core/parser_state_test.rb +24 -0
  103. data/test/vendor/dhaka-2.2.1/test/core/parser_test.rb +131 -0
  104. data/test/vendor/dhaka-2.2.1/test/core/precedence_grammar.rb +17 -0
  105. data/test/vendor/dhaka-2.2.1/test/core/precedence_grammar_test.rb +9 -0
  106. data/test/vendor/dhaka-2.2.1/test/core/rr_conflict_grammar.rb +21 -0
  107. data/test/vendor/dhaka-2.2.1/test/core/simple_grammar.rb +22 -0
  108. data/test/vendor/dhaka-2.2.1/test/core/sr_conflict_grammar.rb +16 -0
  109. data/test/vendor/dhaka-2.2.1/test/dhaka_test_helper.rb +17 -0
  110. data/test/vendor/dhaka-2.2.1/test/fake_logger.rb +17 -0
  111. data/test/vendor/simplerdb-0.2/lib/simplerdb/client_exception.rb +10 -0
  112. data/test/vendor/simplerdb-0.2/lib/simplerdb/db.rb +146 -0
  113. data/test/vendor/simplerdb-0.2/lib/simplerdb/query_language.rb +266 -0
  114. data/test/vendor/simplerdb-0.2/lib/simplerdb/server.rb +33 -0
  115. data/test/vendor/simplerdb-0.2/lib/simplerdb/servlet.rb +191 -0
  116. data/test/vendor/simplerdb-0.2/lib/simplerdb.rb +3 -0
  117. data/test/vendor/simplerdb-0.2/test/functional_test.rb +81 -0
  118. data/test/vendor/simplerdb-0.2/test/query_evaluator_test.rb +73 -0
  119. data/test/vendor/simplerdb-0.2/test/query_parser_test.rb +64 -0
  120. data/test/vendor/simplerdb-0.2/test/simplerdb_test.rb +80 -0
  121. metadata +182 -0
@@ -0,0 +1,1341 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/test_helper")
2
+
3
+ require File.dirname(__FILE__) + "/fixtures/simpledb/item_daddy.rb"
4
+ require File.dirname(__FILE__) + "/fixtures/simpledb/item.rb"
5
+ require File.dirname(__FILE__) + "/fixtures/simpledb/namespace_foo.rb"
6
+ require File.dirname(__FILE__) + "/fixtures/simpledb/namespace_bar.rb"
7
+ require File.dirname(__FILE__) + "/fixtures/simpledb/log_item.rb"
8
+ require File.dirname(__FILE__) + "/fixtures/simpledb/protected_item.rb"
9
+
10
+ require 'simplerdb/server'
11
+ old_stderr = $stderr
12
+ $stderr = File.open("/dev/null","w")
13
+
14
+ $server ||= SimplerDB::Server.new(8087)
15
+ $thread ||= Thread.new do
16
+ trap(:INT) {
17
+ exit
18
+ }
19
+ $server.start
20
+ end
21
+
22
+ $stderr = old_stderr
23
+
24
+
25
+ class SimplyStoredTest < Test::Unit::TestCase
26
+ context "The simply stored base class" do
27
+ setup do
28
+ ItemDaddy.instance_eval do
29
+ has_one :item, :clear => :nullify
30
+ end
31
+
32
+ ItemDaddy.instance_eval do
33
+ has_many :items, :clear => :nullify
34
+ end
35
+
36
+ SimplyStored::Simple.aws_access_key = 'foo'
37
+ SimplyStored::Simple.aws_secret_access_key = 'bar'
38
+ RightAws::ActiveSdb.establish_connection(SimplyStored::Simple.aws_access_key, SimplyStored::Simple.aws_secret_access_key, :server => 'localhost', :port => '8087', :protocol => 'http', :logger => Logger.new('/dev/null'))
39
+ Item.create_domain
40
+ ItemDaddy.create_domain
41
+ Namespace::Foo.create_domain
42
+ Namespace::Bar.create_domain
43
+ LogItem.create_domain
44
+ ProtectedItem.create_domain
45
+ @item = Item.new(:foo_attribute => 'a', :bar_attribute_array => 'goog')
46
+ end
47
+
48
+ should "use UUIDTools::UUID.random_create to generate the ID" do
49
+ UUIDTools::UUID.expects(:random_create).returns('foo')
50
+ item = create_item
51
+ assert_equal 'foo', item.id
52
+ end
53
+
54
+ context "attributes" do
55
+ should "set the given attributes" do
56
+ @item.set_attributes(:foo_attribute => '99', :bar_attribute_array => ['1', '2', '3'])
57
+ assert_equal '99', @item.foo_attribute
58
+ assert_equal ['1', '2', '3'], @item.bar_attribute_array
59
+ end
60
+
61
+ should "not nil the other attributes" do
62
+ @item.bar_attribute_array = ['99']
63
+ @item.set_attributes(:foo_attribute => '66')
64
+ assert_equal ['99'], @item.bar_attribute_array
65
+ end
66
+ end
67
+
68
+ context "simpledb getter and setter" do
69
+
70
+ should "simpledb_attribute_getter" do
71
+ assert @item.respond_to?(:foo_attribute)
72
+ @item['foo_attribute'] = 'abc'
73
+ assert_equal 'abc', @item.foo_attribute
74
+ end
75
+
76
+ should "getter when empty" do
77
+ @item['bar_attribute_array'] = []
78
+ @item['foo_attribute'] = nil
79
+ assert_equal nil, @item.foo_attribute
80
+ assert_equal [], @item.bar_attribute_array
81
+ end
82
+
83
+ should "getter when empty after setting using the setter" do
84
+ @item.bar_attribute_array = []
85
+ @item.foo_attribute = nil
86
+ assert_equal nil, @item.foo_attribute
87
+ assert_equal [], @item.bar_attribute_array
88
+ end
89
+
90
+ should "attribute setter" do
91
+ assert @item.respond_to?(:foo_attribute=)
92
+ @item.foo_attribute = 5
93
+ assert_equal 5, @item['foo_attribute'].first
94
+ assert_equal 5, @item.foo_attribute
95
+ end
96
+
97
+ should "attribute definition adds to internal attribute list" do
98
+ ['foo_attribute', 'item_daddy_id', 'bar_attribute_array', 'updated_at', 'created_at', 'integer_field'].each do |attr|
99
+ assert @item.class.instance_variable_get("@_defined_attributes").include?(attr)
100
+ end
101
+ end
102
+
103
+ should "array_getter" do
104
+ assert @item.respond_to?(:bar_attribute_array)
105
+ @item['bar_attribute_array'] = ['abc']
106
+ assert_equal ['abc'], @item.bar_attribute_array
107
+ end
108
+
109
+ should "array_setter" do
110
+ assert @item.respond_to?(:bar_attribute_array=)
111
+ @item.bar_attribute_array = 5
112
+ assert_equal [5], @item['bar_attribute_array']
113
+ assert_equal [5], @item.bar_attribute_array
114
+
115
+ @item.bar_attribute_array << 7
116
+ assert_equal [5,7], @item.bar_attribute_array
117
+ end
118
+
119
+ should "array_setter when given an empty array" do
120
+ @item['bar_attribute_array'] = []
121
+ assert_equal [], @item.bar_attribute_array
122
+ @item.bar_attribute_array << 7
123
+ assert_equal [7], @item.bar_attribute_array
124
+
125
+ @item.bar_attribute_array << 11
126
+ assert_equal [7,11], @item.bar_attribute_array
127
+ end
128
+
129
+ context "belongs_to" do
130
+
131
+ should "getter" do
132
+ assert @item.respond_to?(:item_daddy)
133
+
134
+ @item[:item_daddy_id] = 'the_id_of_item'
135
+ ItemDaddy.expects(:find).with('the_id_of_item', {:auto_load => true}).returns('foo')
136
+ assert_equal 'foo', @item.item_daddy
137
+ end
138
+
139
+ should "setter" do
140
+ assert @item.respond_to?(:item_daddy=)
141
+
142
+ item_daddy = ItemDaddy.new
143
+ item_daddy.expects(:id).returns('item_daddy_id')
144
+ @item.expects(:item_daddy_id=).with('item_daddy_id')
145
+
146
+ @item.item_daddy = item_daddy
147
+ end
148
+
149
+ should "check the class when setting" do
150
+ assert_raise(ArgumentError, 'expected ItemDaddy got String') do
151
+ @item.item_daddy = 'foo'
152
+ end
153
+ end
154
+
155
+ should "use the cache when getting" do
156
+ @item[:item_daddy_id] = 'the_id_of_item'
157
+ ItemDaddy.expects(:find).with('the_id_of_item', {:auto_load => true}).returns('foo').times(1)
158
+ assert_equal 'foo', @item.item_daddy
159
+ assert_equal 'foo', @item.item_daddy
160
+ end
161
+
162
+ should "set the cache when setting" do
163
+ @item.instance_variable_set("@_cached_belongs_to_item_daddy", 'foo')
164
+ assert_equal 'foo', @item.item_daddy
165
+
166
+ daddy = ItemDaddy.new('id' => 'the-ID')
167
+ @item.expects(:item_daddy_id=)
168
+ @item.expects(:instance_variable_set).with("@_cached_belongs_to_item_daddy", daddy)
169
+
170
+ @item.item_daddy = daddy
171
+ end
172
+
173
+ should "not hit the database when the id column is empty" do
174
+ ItemDaddy.expects(:find).never
175
+ @item.item_daddy_id = []
176
+ @item.item_daddy
177
+ end
178
+ end
179
+
180
+ context "has_one" do
181
+
182
+ should "getter" do
183
+ daddy = ItemDaddy.new(:id => 'daddy_id')
184
+ assert daddy.respond_to?(:item)
185
+
186
+ Item.expects(:send).with(:find_by_item_daddy_id, 'daddy_id', {:auto_load => true}).returns('a')
187
+ assert_equal 'a', daddy.item
188
+ end
189
+
190
+ should "use the cache when getting" do
191
+ daddy = ItemDaddy.new(:id => 'daddy_id')
192
+ Item.expects(:send).with(:find_by_item_daddy_id, 'daddy_id', {:auto_load => true}).returns('a').times(1)
193
+ assert_equal 'a', daddy.item
194
+ assert_equal 'a', daddy.item
195
+ assert_equal 'a', daddy.item
196
+ end
197
+
198
+ should "setter" do
199
+ daddy = ItemDaddy.new(:id => 'daddy_id')
200
+ assert daddy.respond_to?(:item=)
201
+ item = Item.new
202
+ item.expects(:item_daddy_id=).with('daddy_id')
203
+
204
+ daddy.item = item
205
+ end
206
+
207
+ should "set the cache when setting" do
208
+ daddy = ItemDaddy.new(:id => 'daddy_id')
209
+ item = Item.new
210
+ item.expects(:item_daddy_id=).with('daddy_id')
211
+
212
+ daddy.item = item
213
+ assert_equal item, daddy.instance_variable_get("@_cached_has_one_item")
214
+ end
215
+
216
+ should "check the class" do
217
+ daddy = ItemDaddy.new(:id => 'daddy_id')
218
+ assert_raise(ArgumentError, 'expected Item got String') do
219
+ daddy.item = 'foo'
220
+ end
221
+ end
222
+
223
+ should "clear relations when depending:nullify" do
224
+
225
+ ItemDaddy.instance_eval do
226
+ has_one :item, :clear => :nullify
227
+ end
228
+
229
+ daddy = ItemDaddy.new(:id => 'daddy_id')
230
+ old_item = Item.new
231
+ old_item.stubs(:item_daddy_id).returns('daddy_id')
232
+ old_item.expects(:item_daddy_id=).with(nil)
233
+ daddy.expects(:item).returns(old_item)
234
+
235
+ new_item = Item.new
236
+ new_item.stubs(:item_daddy_id=)
237
+ daddy.item = new_item
238
+ end
239
+
240
+ should "delete relations when depending:destroy" do
241
+
242
+ ItemDaddy.instance_eval do
243
+ has_one :item, :clear => :destroy
244
+ end
245
+
246
+ daddy = ItemDaddy.new(:id => 'daddy_id')
247
+ old_item = Item.new
248
+ old_item.stubs(:item_daddy_id).returns('daddy_id')
249
+ old_item.expects(:delete)
250
+ daddy.expects(:item).returns(old_item)
251
+
252
+ new_item = Item.new
253
+ new_item.stubs(:item_daddy_id=)
254
+ daddy.item = new_item
255
+ end
256
+
257
+ end
258
+
259
+ context "has_many" do
260
+
261
+ should "getter" do
262
+ daddy = ItemDaddy.new(:id => 'daddy_id')
263
+ assert daddy.respond_to?(:items)
264
+
265
+ Item.expects(:send).with(:find_all_by_item_daddy_id, 'daddy_id', {:auto_load => true}).returns(['a'])
266
+ assert_equal ['a'], daddy.items
267
+ end
268
+
269
+ should "use the cache when getting" do
270
+ daddy = ItemDaddy.new(:id => 'daddy_id')
271
+ Item.expects(:send).with(:find_all_by_item_daddy_id, 'daddy_id', {:auto_load => true}).returns(['a']).times(1)
272
+ assert_equal ['a'], daddy.items
273
+ assert_equal ['a'], daddy.items
274
+ assert_equal ['a'], daddy.items
275
+ end
276
+
277
+ should "setter" do
278
+ daddy = ItemDaddy.new(:id => 'daddy_id')
279
+ assert daddy.respond_to?(:add_item)
280
+ assert daddy.respond_to?(:remove_item)
281
+ assert daddy.respond_to?(:remove_all_items)
282
+ end
283
+
284
+ should "add_item" do
285
+ daddy = ItemDaddy.new(:id => 'daddy_id_')
286
+ item = Item.new
287
+ item.expects(:item_daddy_id=).with('daddy_id_')
288
+
289
+ assert_equal [], daddy.items
290
+ daddy.add_item(item)
291
+ assert_equal [item], daddy.items
292
+ assert_equal [item], daddy.instance_variable_get("@_cached_has_many_items")
293
+ end
294
+
295
+ should "check the class when using add_item" do
296
+ daddy = ItemDaddy.new(:id => 'daddy_id')
297
+ assert_raise(ArgumentError, 'excepted Item got String') do
298
+ daddy.add_item('foo')
299
+ end
300
+ end
301
+
302
+ should "save when adding an item" do
303
+ @item.expects(:save).with(false)
304
+
305
+ daddy = ItemDaddy.new(:id => 'daddy_id')
306
+ daddy.add_item(@item)
307
+ end
308
+
309
+ should "remove an item" do
310
+ daddy = ItemDaddy.new(:id => 'daddy_id')
311
+ item = Item.new
312
+ item.expects(:item_daddy_id=).with(nil)
313
+ item.expects(:item_daddy_id).returns('daddy_id')
314
+ item.expects(:save).with(false)
315
+
316
+ daddy.instance_variable_set("@_cached_has_many_items", [item])
317
+ assert_equal [item], daddy.items
318
+ daddy.remove_item(item)
319
+ assert_equal [], daddy.items
320
+ assert_equal [], daddy.instance_variable_get("@_cached_has_many_items")
321
+ end
322
+
323
+ should "check ownership when removing" do
324
+ daddy = ItemDaddy.new(:id => 'daddy_id')
325
+ my_item = Item.new
326
+ my_item.expects(:item_daddy_id=).with(nil)
327
+ my_item.expects(:item_daddy_id).returns('daddy_id')
328
+
329
+ other_item = Item.new
330
+ other_item.expects(:item_daddy_id=).never
331
+ other_item.expects(:item_daddy_id).returns('not_daddys_id')
332
+
333
+ assert_nothing_raised do
334
+ daddy.remove_item(my_item)
335
+ end
336
+
337
+ assert_raise(ArgumentError, 'cannot remove as not mine') do
338
+ daddy.remove_item(other_item)
339
+ end
340
+ end
341
+
342
+ should "remove all items" do
343
+ daddy = ItemDaddy.new(:id => 'daddy_id')
344
+ item_1 = Item.new
345
+ item_1.expects(:item_daddy_id).returns('daddy_id')
346
+ item_1.expects(:item_daddy_id=).with(nil)
347
+ item_2 = Item.new
348
+ item_2.expects(:item_daddy_id).returns('daddy_id')
349
+ item_2.expects(:item_daddy_id=).with(nil)
350
+
351
+ Item.expects(:find_all_by_item_daddy_id).with('daddy_id').returns([item_1, item_2])
352
+
353
+ daddy.remove_all_items
354
+
355
+ assert_equal [], daddy.instance_variable_get("@_cached_has_many_items")
356
+ end
357
+
358
+ should "delete relations if depending:destroy" do
359
+ ItemDaddy.instance_eval do
360
+ has_many :items, :clear => :destroy
361
+ end
362
+
363
+ daddy = ItemDaddy.new(:id => 'daddy_id')
364
+ item = Item.new
365
+ item.expects(:delete)
366
+ item.expects(:item_daddy_id).returns('daddy_id')
367
+
368
+ daddy.remove_item(item)
369
+ assert_equal [], daddy.instance_variable_get("@_cached_has_many_items")
370
+ end
371
+
372
+ end
373
+ end
374
+
375
+ context "attribute proctection against mass assignment" do
376
+
377
+ context "when using attr_protected" do
378
+ setup do
379
+ ProtectedItem.instance_eval do
380
+ @_accessible_attributes ||= []
381
+ attr_protected :a, :b
382
+ end
383
+ end
384
+
385
+ should "not allow to set with mass assignment using attributes=" do
386
+ item = ProtectedItem.new
387
+ item.attributes = {:a => 'a', :c => 'c'}
388
+ assert_equal 'c', item.c
389
+ assert_nil item.a
390
+ end
391
+
392
+ should "not allow to set with mass assignment using attributes= - ignore string vs. symbol" do
393
+ item = ProtectedItem.new
394
+ item.attributes = {'a' => 'a', 'c' => 'c'}
395
+ assert_equal 'c', item.c
396
+ assert_nil item.a
397
+ end
398
+
399
+ should "not allow to set with mass assignment using the constructor" do
400
+ item = ProtectedItem.new(:a => 'a', :c => 'c')
401
+ assert_equal 'c', item.c
402
+ assert_nil item.a
403
+ end
404
+
405
+ should "allow to set with mass assignment using update_attributes" do
406
+ item = ProtectedItem.new
407
+ item.update_attributes(:a => 'a', :c => 'c')
408
+ item.reload
409
+ assert_equal 'c', item.c
410
+ assert_equal 'a', item.a
411
+ end
412
+
413
+ should "not allow to set with mass assignment using set_attributes" do
414
+ item = ProtectedItem.new
415
+ item.set_attributes(:a => 'a', :c => 'c')
416
+ assert_equal 'c', item.c
417
+ assert_nil item.a
418
+ end
419
+ end
420
+
421
+ context "attr_accessible" do
422
+ setup do
423
+ ProtectedItem.instance_eval do
424
+ @_protected_attributes ||= []
425
+ attr_accessible :c
426
+ end
427
+ end
428
+
429
+ should "not allow to set with mass assignment using attributes=" do
430
+ item = ProtectedItem.new
431
+ item.attributes = {:a => 'a', :c => 'c'}
432
+ assert_equal 'c', item.c
433
+ assert_nil item.a
434
+ end
435
+
436
+ should "not allow to set with mass assignment using the constructor" do
437
+ item = ProtectedItem.new(:a => 'a', :c => 'c')
438
+ assert_equal 'c', item.c
439
+ assert_nil item.a
440
+ end
441
+
442
+ should "allow to set with mass assignment using update_attributes" do
443
+ item = ProtectedItem.new
444
+ item.update_attributes(:a => 'a', :c => 'c')
445
+ item.reload
446
+ assert_equal 'c', item.c
447
+ assert_equal 'a', item.a
448
+ end
449
+
450
+ should "not allow to set with mass assignment using set_attributes" do
451
+ item = ProtectedItem.new
452
+ item.set_attributes(:a => 'a', :c => 'c')
453
+ assert_equal 'c', item.c
454
+ assert_nil item.a
455
+ end
456
+ end
457
+
458
+ end
459
+
460
+ context "validation and errors" do
461
+
462
+ should "get errors" do
463
+ @item.send(:add_error, 'foo', 'bar')
464
+ assert_equal [['foo', 'bar']], @item.errors
465
+ end
466
+
467
+ should "valid_if_errors" do
468
+ @item.stubs(:errors).returns([true])
469
+ assert !@item.valid?
470
+ end
471
+
472
+ should "valid_if_no_errors" do
473
+ @item.stubs(:errors).returns([])
474
+ assert @item.valid?
475
+ end
476
+
477
+ should "clear_errors" do
478
+ @item.instance_variable_set("@errors", [true])
479
+ assert !@item.errors.blank?
480
+ @item.send(:clear_errors)
481
+ assert @item.errors.blank?
482
+ end
483
+
484
+ should "valid_calls_validate" do
485
+ @item.expects(:validate)
486
+ @item.valid?
487
+ end
488
+
489
+ should "clears errors on a successful save" do
490
+ @item.expects(:valid?).returns(true)
491
+ @item.expects(:active_sdb_save).returns(true)
492
+ @item.expects(:clear_errors).returns(true)
493
+
494
+ assert @item.save
495
+ end
496
+
497
+ should "clear errors when valid? is true" do
498
+ @item.instance_variable_set("@errors", [:am, 'foo'])
499
+ assert @item.valid?
500
+ assert_equal [], @item.instance_variable_get("@errors")
501
+ end
502
+
503
+ should "fail the save when there's validation errors" do
504
+ @item.instance_variable_set("@errors", [:am, 'foo'])
505
+ @item.stubs(:clear_errors)
506
+ @item.expects(:active_sdb_save).never
507
+ assert !@item.save
508
+ end
509
+
510
+ should "more_attributes_than_allowed" do
511
+ @item[:foo] = 'bar'
512
+ assert !@item.valid?, @item.attributes.inspect
513
+ assert_equal [['foo', 'is unknown and should not be set']], @item.errors
514
+ end
515
+
516
+ should "more_attributes_than_allowed_does_not_check_id" do
517
+ @item[:id] = '90834980324kjndfaslkjadsfp89'
518
+ assert @item.valid?, @item.errors.inspect
519
+ end
520
+
521
+ should "require presence" do
522
+ daddy = ItemDaddy.new
523
+ daddy.name = nil
524
+ assert !daddy.valid?
525
+
526
+ daddy.name = 'foo'
527
+ assert daddy.valid?
528
+ end
529
+
530
+ should "require inclusion" do
531
+ @item.foo_attribute = 'd'
532
+ assert !@item.valid?
533
+
534
+ @item.foo_attribute = 'c'
535
+ assert @item.valid?
536
+ end
537
+
538
+ should "require inclusion and allow_blank" do
539
+ Item.instance_eval do
540
+ require_inclusion_of :foo_attribute, ['a', 'b', 'c'], :allow_blank => true
541
+ end
542
+
543
+ item = Item.new(:bar_attribute_array => ['goog'], :foo_attribute => nil)
544
+ assert item.valid?, item.errors.inspect
545
+
546
+ Item.instance_eval do
547
+ require_inclusion_of :foo_attribute, ['a', 'b', 'c'], :allow_blank => false
548
+ end
549
+
550
+ assert !item.valid?, item.errors.inspect
551
+ end
552
+
553
+ should "require inclusion should work with array attribute" do
554
+ @item.bar_attribute_array = ['foobar']
555
+ assert !@item.valid?
556
+
557
+ @item.bar_attribute_array = ['goog']
558
+ assert @item.valid?
559
+ end
560
+
561
+ should "save_without_validations" do
562
+ item = Item.new(:foo_attribute => nil)
563
+ assert !item.valid?
564
+ assert !item.save
565
+ assert item.save(false)
566
+ end
567
+
568
+ context "format validation" do
569
+ setup do
570
+ Item.instance_eval do
571
+ simpledb_string :format_attribute
572
+ require_format_of :format_attribute, /a/, :allow_blank => true
573
+ end
574
+
575
+ @item = Item.new(:format_attribute => nil, :foo_attribute => 'a')
576
+ end
577
+
578
+ should "check format" do
579
+ @item.format_attribute = 'b'
580
+ assert !@item.valid?
581
+
582
+ @item.format_attribute = 'c'
583
+ assert !@item.valid?
584
+
585
+ @item.format_attribute = 'a'
586
+ assert @item.valid?, @item.errors.inspect
587
+
588
+ @item.format_attribute = 'abc'
589
+ assert @item.valid?
590
+ end
591
+
592
+ should "allow nil" do
593
+ @item.format_attribute = 'b'
594
+ assert !@item.valid?
595
+
596
+ @item.format_attribute = nil
597
+ assert @item.valid?, @item.errors.inspect
598
+ end
599
+
600
+ end
601
+ end
602
+
603
+ context "callbacks" do
604
+
605
+ should "not call callbacks if none are defined" do
606
+ @item.stubs(:active_sdb_save).returns(true)
607
+ @item.stubs(:respond_to?).returns(false)
608
+
609
+ @item.expects(:before_save).never
610
+ @item.expects(:after_save).never
611
+ assert @item.save
612
+ end
613
+
614
+ should "call callbacks if none are defined" do
615
+ @item.stubs(:active_sdb_save).returns(true)
616
+
617
+ def @item.before_save
618
+ end
619
+
620
+ def @item.after_save
621
+ end
622
+
623
+ @item.expects(:before_save)
624
+ @item.expects(:after_save)
625
+ assert @item.save
626
+ end
627
+
628
+ should "call after_create and before_create callbacks if we have a new record" do
629
+ @item.stubs(:new_record?).returns(true)
630
+ @item.stubs(:active_sdb_save).returns(true)
631
+
632
+ def @item.before_create
633
+ end
634
+
635
+ def @item.after_create
636
+ end
637
+
638
+ @item.expects(:before_create)
639
+ @item.expects(:after_create)
640
+ assert @item.save
641
+ end
642
+
643
+ should "not call after_create and before_create callbacks if we have an existing record" do
644
+ @item.stubs(:new_record?).returns(false)
645
+ @item.stubs(:active_sdb_save).returns(true)
646
+
647
+ def @item.before_create
648
+ end
649
+
650
+ def @item.after_create
651
+ end
652
+
653
+ @item.expects(:before_create).never
654
+ @item.expects(:after_create).never
655
+ assert @item.save
656
+ end
657
+
658
+ should "call before_validation before the validation if defined" do
659
+ def @item.before_validation
660
+ end
661
+
662
+ @item.expects(:before_validation)
663
+ assert @item.valid?
664
+ end
665
+
666
+ should "not call before_validation if it is not defined" do
667
+ @item.expects(:respond_to?).with(:before_validation).returns(false)
668
+
669
+ @item.expects(:before_validation).never
670
+ assert @item.valid?
671
+ end
672
+
673
+ should "before_delete_callback_not_called_if_no_before_delete_defined" do
674
+ @item.id = '5'
675
+ assert !@item.respond_to?(:before_delete)
676
+ assert_nothing_raised do
677
+ @item.delete
678
+ end
679
+ end
680
+
681
+ should "before_delete_callback" do
682
+ item_with_callback = Item.create(:foo_attribute => '123', :bar_attribute_array => ['a', 'b'], :id => 5)
683
+ def item_with_callback.before_delete
684
+ 'foo'
685
+ end
686
+ item_with_callback.expects(:before_delete)
687
+
688
+ item_with_callback.delete
689
+ end
690
+
691
+ should "after_delete_callback_not_called_if_no_before_delete_defined" do
692
+ @item.id = '5'
693
+ assert !@item.respond_to?(:after_delete)
694
+ assert_nothing_raised do
695
+ @item.delete
696
+ end
697
+ end
698
+
699
+ should "after_delete_callback" do
700
+ item_with_callback = Item.create(:foo_attribute => '123', :bar_attribute_array => ['a', 'b'], :id => 5)
701
+ def item_with_callback.after_delete
702
+ 'foo'
703
+ end
704
+ item_with_callback.expects(:after_delete)
705
+
706
+ item_with_callback.delete
707
+ end
708
+
709
+ end
710
+
711
+ context "timestamps" do
712
+
713
+ should "timestamps" do
714
+ item = Item.new(:foo_attribute => 'a', :bar_attribute_array => ['goog'])
715
+ assert_nil item.updated_at
716
+ assert_nil item.created_at
717
+
718
+ time = Time.now
719
+ Time.stubs(:now).returns(time)
720
+
721
+ assert item.save, item.errors.inspect
722
+ assert_equal time.utc, item.updated_at
723
+ assert_equal time.utc, item.created_at
724
+ end
725
+
726
+ should "timestamps_when_saving_without_validations" do
727
+ item = Item.new(:foo_attribute => 'a', :bar_attribute_array => ['goog'])
728
+ assert_nil item.updated_at
729
+ assert_nil item.created_at
730
+
731
+ time = Time.now
732
+ Time.stubs(:now).returns(time)
733
+
734
+ assert item.save(false)
735
+ assert_equal time.utc, item.updated_at
736
+ assert_equal time.utc, item.created_at
737
+ end
738
+
739
+ should "timestamps_when_existing_record" do
740
+ item = Item.new(:foo_attribute => 'a', :bar_attribute_array => ['goog'])
741
+ item.stubs(:new_record?).returns(false)
742
+ assert_nil item.updated_at
743
+ assert_nil item.created_at
744
+
745
+ time = Time.now
746
+ Time.stubs(:now).returns(time)
747
+
748
+ assert item.save, item.errors.inspect
749
+ assert_equal time.utc, item.updated_at
750
+ assert_nil item.created_at
751
+ end
752
+
753
+ should "timestamps_when_validation_fails" do
754
+ item = Item.new(:foo_attribute => 'a', :bar_attribute_array => ['goog'])
755
+ item.expects(:valid?).returns(false)
756
+ assert_nil item.updated_at
757
+ assert_nil item.created_at
758
+
759
+ assert !item.save
760
+ assert_nil item.updated_at
761
+ assert_nil item.created_at
762
+ end
763
+
764
+ should "timestamp_saves_representation_as_integer" do
765
+ @item.updated_at = Time.local(2004, 1, 12, 9, 7, 59)
766
+ assert_equal ['20040112090759'], @item['updated_at']
767
+ end
768
+
769
+ should "timestamp_loads_from_representation_as_integer" do
770
+ @item['updated_at'] = '20040112090759'
771
+ assert_equal Time.local(2004, 1, 12, 10, 7, 59).utc, @item.updated_at
772
+ end
773
+ end
774
+
775
+ context "integer attributes" do
776
+
777
+ should "save representation padded" do
778
+ @item.integer_field = 91
779
+ assert_equal ['0000000000000091'], @item['integer_field']
780
+ end
781
+
782
+ should "load representation padded" do
783
+ @item['integer_field'] = '0000000000000091'
784
+ assert_equal 91, @item.integer_field
785
+ end
786
+ end
787
+
788
+ context "find helper" do
789
+
790
+ should "all" do
791
+ Item.expects(:find).with(:all, instance_of(Hash))
792
+ Item.all
793
+ end
794
+
795
+ should "all_with_options" do
796
+ Item.expects(:find).with do |arg, options|
797
+ arg == :all && options[:auto_load] == true
798
+ end
799
+ Item.all(:auto_load => true)
800
+ end
801
+
802
+ should "first" do
803
+ Item.expects(:find).with(:first, instance_of(Hash))
804
+ Item.first
805
+ end
806
+
807
+ should "first_with_options" do
808
+ Item.expects(:find).with do |arg, options|
809
+ arg == :first && options[:auto_load] == true
810
+ end
811
+ Item.first(:auto_load => true)
812
+ end
813
+ end
814
+
815
+ context "when finding an object" do
816
+ should "should raise RecordNotFound when the object wasn't found" do
817
+ assert_raise(SimplyStored::RecordNotFound) do
818
+ Item.find("bla")
819
+ end
820
+ end
821
+
822
+ should "should retry x-times before raising RecordNotFound" do
823
+ item = create_item
824
+ seq = sequence("find")
825
+ Item.expects(:find_from_ids).with([item.id], {:auto_load => true}).raises(RightAws::ActiveSdb::ActiveSdbError, "Couldn't find Item with ID").in_sequence(seq)
826
+ Item.expects(:find_from_ids).with([item.id], {:auto_load => true}).returns([item]).in_sequence(seq)
827
+ assert_nothing_raised do
828
+ assert Item.find(item.id)
829
+ end
830
+ end
831
+
832
+ should "should sleep more and more on each subsequent retry" do
833
+ item = create_item
834
+ seq = sequence("find")
835
+ Item.expects(:find_from_ids).with([item.id],{:auto_load => true}).raises(RightAws::ActiveSdb::ActiveSdbError, "Couldn't find Item with ID").in_sequence(seq)
836
+ Item.expects(:find_from_ids).with([item.id],{:auto_load => true}).raises(RightAws::ActiveSdb::ActiveSdbError, "Couldn't find Item with ID").in_sequence(seq)
837
+ Item.expects(:find_from_ids).with([item.id],{:auto_load => true}).raises(RightAws::ActiveSdb::ActiveSdbError, "Couldn't find Item with ID").in_sequence(seq)
838
+
839
+ sleepy = sequence("sleep")
840
+ Item.expects(:sleep).with(0.5).in_sequence(sleepy)
841
+ Item.expects(:sleep).with(1.0).in_sequence(sleepy)
842
+ assert_raise(SimplyStored::RecordNotFound) do
843
+ Item.find(item.id)
844
+ end
845
+ end
846
+
847
+ should "retry on system error" do
848
+ item = create_item
849
+ seq = sequence("find")
850
+ item.stubs(:sleep)
851
+ Item.expects(:find_from_ids).with([item.id], {:auto_load => true}).raises(Rightscale::AwsError, "RequestThrottled").in_sequence(seq)
852
+ Item.expects(:find_from_ids).with([item.id], {:auto_load => true}).raises(Rightscale::AwsError, "RequestThrottled").in_sequence(seq)
853
+ Item.expects(:find_from_ids).with([item.id], {:auto_load => true}).returns([item]).in_sequence(seq)
854
+ assert_nothing_raised do
855
+ assert Item.find(item.id)
856
+ end
857
+ end
858
+
859
+ should "eventually raise an error on system error" do
860
+ item = create_item
861
+ item.stubs(:sleep)
862
+ Item.expects(:find_from_ids).with([item.id], {:auto_load => true}).raises(Rightscale::AwsError, "RequestThrottled").times(3)
863
+ assert_raise(SimplyStored::Error) do
864
+ Item.find(item.id)
865
+ end
866
+ end
867
+
868
+ should "not retry on client error" do
869
+ item = create_item
870
+ Item.expects(:find_from_ids).with([item.id], {:auto_load => true}).raises(Rightscale::AwsError, "NoSuchDomain")
871
+ assert_raise(SimplyStored::Error) do
872
+ Item.find(item.id)
873
+ end
874
+ end
875
+
876
+ should "should auto load attributes by default" do
877
+ item = create_item
878
+ item = Item.find(item.id)
879
+ assert item.attributes.size > 1
880
+ end
881
+ end
882
+
883
+ context "when saving an instance" do
884
+ context "when saving fails due to errors" do
885
+ should "retry save when a system error was raised by simpledb" do
886
+ item = create_item
887
+ sleepy = sequence("sleep")
888
+ item.expects(:sleep).with(0.5).in_sequence(sleepy)
889
+ item.expects(:sleep).with(1.0).in_sequence(sleepy)
890
+
891
+ raiser = sequence("exception")
892
+ item.expects(:active_sdb_save).raises(Rightscale::AwsError, "RequestThrottled").in_sequence(raiser)
893
+ item.expects(:active_sdb_save).raises(Rightscale::AwsError, "RequestThrottled").in_sequence(raiser)
894
+ item.expects(:active_sdb_save).returns(true).in_sequence(raiser)
895
+
896
+ item.save
897
+ end
898
+
899
+ should "not retry when error was a client error" do
900
+ item = create_item
901
+ item.expects(:sleep).never
902
+ item.expects(:active_sdb_save).raises(Rightscale::AwsError, "Ron Telesky")
903
+ assert_raise(SimplyStored::Error) {item.save}
904
+ end
905
+
906
+ should "raise an error when the world has gone bad" do
907
+ item = create_item
908
+ item.expects(:active_sdb_save).times(3).raises(Rightscale::AwsError, "RequestThrottled")
909
+ assert_raise(SimplyStored::Error) {item.save}
910
+ end
911
+ end
912
+ end
913
+
914
+ context "when deleting an instance" do
915
+ should "actually delete the instance" do
916
+ @item.save
917
+ @item.delete
918
+ @item.reload
919
+ assert @item.attributes.empty?
920
+ end
921
+
922
+ should "retry on system error" do
923
+ item = create_item
924
+ sleepy = sequence("sleep")
925
+ item.expects(:sleep).with(0.5).in_sequence(sleepy)
926
+ item.expects(:sleep).with(1.0).in_sequence(sleepy)
927
+
928
+ raiser = sequence("exception")
929
+ item.expects(:active_sdb_delete).raises(Rightscale::AwsError, "RequestThrottled").in_sequence(raiser)
930
+ item.expects(:active_sdb_delete).raises(Rightscale::AwsError, "RequestThrottled").in_sequence(raiser)
931
+ item.expects(:active_sdb_delete).returns(true).in_sequence(raiser)
932
+
933
+ item.delete
934
+ end
935
+
936
+ should "not retry on client error" do
937
+ item = create_item
938
+ item.expects(:sleep).never
939
+
940
+ item.expects(:active_sdb_delete).raises(Rightscale::AwsError, "NoSuchDomain")
941
+
942
+ assert_raise(SimplyStored::Error) do
943
+ item.delete
944
+ end
945
+ end
946
+
947
+ should "eventually raise an error" do
948
+ item = create_item
949
+ item.stubs(:sleep)
950
+
951
+ item.expects(:active_sdb_delete).raises(Rightscale::AwsError, "NoSuchDomain")
952
+
953
+ assert_raise(SimplyStored::Error) do
954
+ item.delete
955
+ end
956
+ end
957
+
958
+ end
959
+
960
+ context "reload" do
961
+ should "return itself" do
962
+ item = create_item
963
+ assert item.reload.is_a?(Item)
964
+ assert_equal item, item.reload
965
+ end
966
+ end
967
+
968
+ context "ActiveRecord compatability" do
969
+ should "have a to_param helper" do
970
+ item = create_item
971
+ item.id = 9912
972
+ assert item.save
973
+ assert_equal "9912", item.id
974
+ assert_equal "9912", item.to_param
975
+ end
976
+ end
977
+
978
+ context "when updating attributes" do
979
+ setup do
980
+ @item = create_item
981
+ end
982
+
983
+ should "use save_attributes" do
984
+ @item.expects(:active_sdb_save_attributes).with('foo_attribute' => 'b')
985
+
986
+ @item.update_attributes('foo_attribute' => 'b')
987
+ end
988
+
989
+ should "nil a nil attribute" do
990
+ @item.update_attributes('foo_attribute' => 'hi')
991
+ assert_equal 'hi', @item.reload.foo_attribute
992
+
993
+ @item.update_attributes('foo_attribute' => nil)
994
+ assert_nil @item.reload.foo_attribute
995
+ end
996
+
997
+ should "nil an empty string attribute" do
998
+ @item.update_attributes('foo_attribute' => 'hi')
999
+ assert_equal 'hi', @item.reload.foo_attribute
1000
+
1001
+ @item.update_attributes('foo_attribute' => '')
1002
+ assert_nil @item.reload.foo_attribute
1003
+ end
1004
+
1005
+ should "retry on system error" do
1006
+ sleepy = sequence("sleep")
1007
+ @item.expects(:sleep).with(0.5).in_sequence(sleepy)
1008
+ @item.expects(:sleep).with(1.0).in_sequence(sleepy)
1009
+
1010
+ raiser = sequence("exception")
1011
+ @item.expects(:active_sdb_save_attributes).raises(Rightscale::AwsError, "RequestThrottled").in_sequence(raiser)
1012
+ @item.expects(:active_sdb_save_attributes).raises(Rightscale::AwsError, "RequestThrottled").in_sequence(raiser)
1013
+ @item.expects(:active_sdb_save_attributes).returns(true).in_sequence(raiser)
1014
+
1015
+ @item.update_attributes(:foo_attribute => 'a')
1016
+ end
1017
+
1018
+ should "not retry on client error" do
1019
+ @item.expects(:sleep).never
1020
+
1021
+ @item.expects(:active_sdb_save_attributes).raises(Rightscale::AwsError, "NoSuchDomain")
1022
+
1023
+ assert_raise(SimplyStored::Error) do
1024
+ @item.update_attributes(:foo_attribute => 'a')
1025
+ end
1026
+ end
1027
+
1028
+ should "eventually raise an error on system error" do
1029
+ item = create_item
1030
+ item.stubs(:sleep)
1031
+
1032
+ item.expects(:active_sdb_save_attributes).raises(Rightscale::AwsError, "RequestThrottled").times(3)
1033
+
1034
+ assert_raise(SimplyStored::Error) do
1035
+ item.update_attributes(:foo_attribute => 'a')
1036
+ end
1037
+ end
1038
+
1039
+ end
1040
+
1041
+ context "namespaced classes" do
1042
+
1043
+ setup do
1044
+ ItemDaddy.instance_eval do
1045
+ belongs_to 'namespace__foo'
1046
+ end
1047
+ @foo = Namespace::Foo.new
1048
+ assert @foo.save
1049
+ end
1050
+
1051
+ should "be able to adress them in belongs_to" do
1052
+ daddy = ItemDaddy.new(:name => 'abc')
1053
+ daddy.namespace__foo = @foo
1054
+ assert daddy.save
1055
+ end
1056
+
1057
+ should "be able to call a has_many relation" do
1058
+ daddy = ItemDaddy.new(:name => 'abc')
1059
+ daddy.namespace__foo = @foo
1060
+ assert daddy.save
1061
+
1062
+ assert_equal daddy.id, ItemDaddy.find_by_namespace__foo_id(@foo.id).id
1063
+ assert_equal [daddy].map(&:id), @foo.item_daddys.map(&:id)
1064
+ end
1065
+
1066
+ should "need special prefix inside namespace" do
1067
+ bar = Namespace::Bar.new
1068
+ assert bar.save
1069
+ @foo.namespace__bar = bar
1070
+ assert @foo.save
1071
+ end
1072
+ end
1073
+
1074
+ context "attributes longer than 1024 bytes" do
1075
+ setup do
1076
+ Item.instance_eval do
1077
+ simpledb_string :very_long
1078
+ end
1079
+ @item = create_item
1080
+ @item.very_long = '*' * 1600
1081
+ assert_equal 1600, @item.very_long.size
1082
+ end
1083
+
1084
+ should "add partitioned attributes to the class metadata"
1085
+
1086
+ should "not raise an error" do
1087
+ assert_nothing_raised do
1088
+ @item.save
1089
+ end
1090
+ end
1091
+
1092
+ should "split up the string into numbered attributes by 1024 bytes" do
1093
+ @item.save
1094
+ assert_equal 1024, @item["very_long_0"].first.size
1095
+ assert_equal 576, @item["very_long_1"].first.size
1096
+ end
1097
+
1098
+ should "store all characters and re-retrieve them on load" do
1099
+ @item.save
1100
+ @item.reload
1101
+ assert_equal 1600, @item.very_long.size
1102
+ end
1103
+
1104
+ should "survive several reloads" do
1105
+ 5.times do
1106
+ @item.save
1107
+ @item.reload
1108
+ end
1109
+ assert_equal 1600, @item.very_long.size
1110
+ assert_equal 1600, @item["very_long"].first.size
1111
+ end
1112
+
1113
+ should "unset the original attribute after saving" do
1114
+ @item.save
1115
+ assert_equal nil, @item["very_long"]
1116
+ end
1117
+
1118
+ should "still make the attribute available through its accessor" do
1119
+ @item.save
1120
+ assert_not_nil @item.very_long
1121
+ end
1122
+ end
1123
+
1124
+ context "with attachments" do
1125
+ should "add a class method to specify an attachment" do
1126
+ assert SimplyStored::Simple.respond_to?(:has_s3_attachment)
1127
+ end
1128
+
1129
+ should 'store the options in a class accessor' do
1130
+ assert LogItem.respond_to?(:_s3_options)
1131
+ assert_not_nil LogItem._s3_options
1132
+ end
1133
+
1134
+ should "include an accessor for the _attachments" do
1135
+ assert LogItem.public_instance_methods.grep(/_s3_attachments/)
1136
+ end
1137
+
1138
+ should "add accessors to the including class" do
1139
+ @log_item = LogItem.new
1140
+ assert @log_item.respond_to?(:log_data)
1141
+ assert @log_item.respond_to?(:log_data=)
1142
+ end
1143
+
1144
+ should "return the assigned value with the accessor" do
1145
+ @log_item = LogItem.new
1146
+ @log_item.log_data = "Yay! It logged!"
1147
+ assert_equal "Yay! It logged!", @log_item.log_data
1148
+ end
1149
+
1150
+ should "mark the attachment as changed when assigned a new value" do
1151
+ @log_item = LogItem.new
1152
+ @log_item.log_data = "Yay! It logged!"
1153
+ assert @log_item._s3_attachments[:log_data][:dirty]
1154
+ end
1155
+
1156
+ should "not include the assigned value to the simpledb attributes" do
1157
+ @log_item = LogItem.new
1158
+ @log_item.log_data = "Yay! It logged!"
1159
+ assert_nil @log_item.attributes[:log_data]
1160
+ end
1161
+
1162
+ should "raise an error when no bucket was specified" do
1163
+ assert_raise(ArgumentError, "No bucket name specified") do
1164
+ LogItem.class_eval do
1165
+ has_s3_attachment :bla
1166
+ end
1167
+ end
1168
+ end
1169
+
1170
+ context "with s3 interaction" do
1171
+ setup do
1172
+ LogItem.instance_variable_set(:@_s3_connection, nil)
1173
+
1174
+ bucket = stub(:bckt) do
1175
+ stubs(:put).returns(true)
1176
+ stubs(:get).returns(true)
1177
+ end
1178
+
1179
+ @bucket = bucket
1180
+
1181
+ @s3 = stub(:s3) do
1182
+ stubs(:bucket).returns(bucket)
1183
+ end
1184
+
1185
+ RightAws::S3.stubs(:new).returns @s3
1186
+ @log_item = LogItem.new
1187
+ end
1188
+
1189
+ context "when saving the attachment" do
1190
+ should "fetch the collection" do
1191
+ @log_item.log_data = "Yay! It logged!"
1192
+ RightAws::S3.expects(:new).with('abcdef', 'secret!', :multi_thread => true).returns(@s3)
1193
+ @log_item.save
1194
+ end
1195
+
1196
+ should "upload the file" do
1197
+ @log_item.log_data = "Yay! It logged!"
1198
+ @bucket.expects(:put).with(anything, "Yay! It logged!", {}, anything)
1199
+ @log_item.save
1200
+ end
1201
+
1202
+ should "use the specified bucket" do
1203
+ @log_item.log_data = "Yay! It logged!"
1204
+ LogItem._s3_options[:log_data][:bucket] = 'mybucket'
1205
+ @s3.expects(:bucket).with('mybucket').returns(@bucket)
1206
+ @log_item.save
1207
+ end
1208
+
1209
+ should "create the bucket if it doesn't exist" do
1210
+ @log_item.log_data = "Yay! log me"
1211
+ LogItem._s3_options[:log_data][:bucket] = 'mybucket'
1212
+
1213
+ @s3.expects(:bucket).with('mybucket').returns(nil)
1214
+ @s3.expects(:bucket).with('mybucket', true, 'private').returns(@bucket)
1215
+ @log_item.save
1216
+ end
1217
+
1218
+ should "raise an error if the bucket is not ours" do
1219
+ @log_item.log_data = "Yay! log me too"
1220
+ LogItem._s3_options[:log_data][:bucket] = 'mybucket'
1221
+
1222
+ @s3.expects(:bucket).with('mybucket').returns(nil)
1223
+ @s3.expects(:bucket).with('mybucket', true, 'private').raises(RightAws::AwsError, 'BucketAlreadyExists: The requested bucket name is not available. The bucket namespace is shared by all users of the system. Please select a different name and try again')
1224
+
1225
+ assert_raise(ArgumentError) do
1226
+ @log_item.save
1227
+ end
1228
+ end
1229
+
1230
+ should "not upload the attachment when it hasn't been changed" do
1231
+ @bucket.expects(:put).never
1232
+ @log_item.save
1233
+ end
1234
+
1235
+ should "set the permissions to private by default" do
1236
+ class PrivateLogItem < SimplyStored::Simple
1237
+ has_s3_attachment :log_data, :bucket => 'mybucket'
1238
+ end
1239
+ PrivateLogItem.create_domain
1240
+
1241
+ @bucket.expects(:put).with(anything, anything, {}, 'private')
1242
+ @log_item = PrivateLogItem.new
1243
+
1244
+ @log_item.log_data = 'Yay!'
1245
+ @log_item.save
1246
+ end
1247
+
1248
+ should "set the permissions to whatever's specified in the options for the attachment" do
1249
+ @log_item.save
1250
+ old_perms = LogItem._s3_options[:log_data][:permissions]
1251
+ LogItem._s3_options[:log_data][:permissions] = 'public-read'
1252
+ @bucket.expects(:put).with(anything, anything, {}, 'public-read')
1253
+ @log_item.log_data = 'Yay!'
1254
+ @log_item.save
1255
+ LogItem._s3_options[:log_data][:permissions] = old_perms
1256
+ end
1257
+
1258
+ should "use the full class name and the id as key" do
1259
+ @log_item.save
1260
+ @bucket.expects(:put).with("log_items/log_data/#{@log_item.id}", 'Yay!', {}, anything)
1261
+ @log_item.log_data = 'Yay!'
1262
+ @log_item.save
1263
+ end
1264
+
1265
+ should "mark the attachment as not dirty after uploading" do
1266
+ @log_item.log_data = 'Yay!'
1267
+ @log_item.save
1268
+ assert !@log_item.instance_variable_get(:@_s3_attachments)[:log_data][:dirty]
1269
+ end
1270
+
1271
+ should 'store the attachment when the validations succeeded' do
1272
+ @log_item.log_data = 'Yay!'
1273
+ @log_item.stubs(:valid?).returns(true)
1274
+ @bucket.expects(:put)
1275
+ @log_item.save
1276
+ end
1277
+
1278
+ should "not store the attachment when the validations failed" do
1279
+ @log_item.log_data = 'Yay!'
1280
+ @log_item.stubs(:valid?).returns(false)
1281
+ @bucket.expects(:put).never
1282
+ @log_item.save
1283
+ end
1284
+
1285
+ should "save the attachment status" do
1286
+ @log_item.save
1287
+ @log_item.attributes["log_data_attachments"]
1288
+ end
1289
+
1290
+ should "save generate the url for the attachment" do
1291
+ @log_item._s3_options[:log_data][:bucket] = 'bucket-for-monsieur'
1292
+ @log_item._s3_options[:log_data][:permissions] = 'public-read'
1293
+ @log_item.save
1294
+ assert_equal "http://bucket-for-monsieur.s3.amazonaws.com/#{@log_item.s3_attachment_key(:log_data)}", @log_item.log_data_url
1295
+ end
1296
+
1297
+ should "add a short-lived access key for private attachments" do
1298
+ @log_item._s3_options[:log_data][:permissions] = 'private'
1299
+ @log_item.save
1300
+ assert @log_item.log_data_url.include?("https://bucket-for-monsieur.s3.amazonaws.com:443/#{@log_item.s3_attachment_key(:log_data)}")
1301
+ assert @log_item.log_data_url.include?("Signature=")
1302
+ assert @log_item.log_data_url.include?("Expires=")
1303
+ end
1304
+
1305
+ should "serialize data other than strings to json" do
1306
+ @log_item.log_data = ['one log entry', 'and another one']
1307
+ @bucket.expects(:put).with(anything, '["one log entry","and another one"]', {}, anything)
1308
+ @log_item.save
1309
+ end
1310
+ end
1311
+
1312
+ context "when fetching the data" do
1313
+ should "fetch the data from s3 and set the attachment attribute" do
1314
+ @log_item.instance_variable_set(:@_s3_attachments, {})
1315
+ @bucket.expects(:get).with("log_items/log_data/#{@log_item.id}").returns("Yay!")
1316
+ assert_equal "Yay!", @log_item.log_data
1317
+ end
1318
+
1319
+ should "not mark the the attachment as dirty" do
1320
+ @log_item.instance_variable_set(:@_s3_attachments, {})
1321
+ @bucket.expects(:get).with("log_items/log_data/#{@log_item.id}").returns("Yay!")
1322
+ @log_item.log_data
1323
+ assert !@log_item._s3_attachments[:log_data][:dirty]
1324
+ end
1325
+
1326
+ should "not try to fetch the attachment if the value is already set" do
1327
+ @log_item.log_data = "Yay!"
1328
+ @bucket.expects(:get).never
1329
+ assert_equal "Yay!", @log_item.log_data
1330
+ end
1331
+ end
1332
+ end
1333
+ end
1334
+ end
1335
+
1336
+ def create_item
1337
+ i = Item.new(:foo_attribute => 'a', :bar_attribute_array => ['goog'])
1338
+ assert i.save
1339
+ i
1340
+ end
1341
+ end