populate-me 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/Gemfile +3 -0
  4. data/LICENSE +20 -0
  5. data/README.md +655 -0
  6. data/Rakefile +14 -0
  7. data/example/config.ru +100 -0
  8. data/lib/populate_me.rb +2 -0
  9. data/lib/populate_me/admin.rb +157 -0
  10. data/lib/populate_me/admin/__assets__/css/asmselect.css +63 -0
  11. data/lib/populate_me/admin/__assets__/css/jquery-ui.min.css +6 -0
  12. data/lib/populate_me/admin/__assets__/css/main.css +244 -0
  13. data/lib/populate_me/admin/__assets__/img/help/children.png +0 -0
  14. data/lib/populate_me/admin/__assets__/img/help/create.png +0 -0
  15. data/lib/populate_me/admin/__assets__/img/help/delete.png +0 -0
  16. data/lib/populate_me/admin/__assets__/img/help/edit.png +0 -0
  17. data/lib/populate_me/admin/__assets__/img/help/form.png +0 -0
  18. data/lib/populate_me/admin/__assets__/img/help/list.png +0 -0
  19. data/lib/populate_me/admin/__assets__/img/help/login.png +0 -0
  20. data/lib/populate_me/admin/__assets__/img/help/logout.png +0 -0
  21. data/lib/populate_me/admin/__assets__/img/help/menu.png +0 -0
  22. data/lib/populate_me/admin/__assets__/img/help/overview.png +0 -0
  23. data/lib/populate_me/admin/__assets__/img/help/save.png +0 -0
  24. data/lib/populate_me/admin/__assets__/img/help/sort.png +0 -0
  25. data/lib/populate_me/admin/__assets__/img/help/sublist.png +0 -0
  26. data/lib/populate_me/admin/__assets__/js/asmselect.js +412 -0
  27. data/lib/populate_me/admin/__assets__/js/columnav.js +87 -0
  28. data/lib/populate_me/admin/__assets__/js/jquery-ui.min.js +7 -0
  29. data/lib/populate_me/admin/__assets__/js/main.js +388 -0
  30. data/lib/populate_me/admin/__assets__/js/mustache.js +578 -0
  31. data/lib/populate_me/admin/__assets__/js/sortable.js +2 -0
  32. data/lib/populate_me/admin/views/help.erb +94 -0
  33. data/lib/populate_me/admin/views/page.erb +189 -0
  34. data/lib/populate_me/api.rb +124 -0
  35. data/lib/populate_me/attachment.rb +186 -0
  36. data/lib/populate_me/document.rb +192 -0
  37. data/lib/populate_me/document_mixins/admin_adapter.rb +149 -0
  38. data/lib/populate_me/document_mixins/callbacks.rb +125 -0
  39. data/lib/populate_me/document_mixins/outcasting.rb +83 -0
  40. data/lib/populate_me/document_mixins/persistence.rb +95 -0
  41. data/lib/populate_me/document_mixins/schema.rb +198 -0
  42. data/lib/populate_me/document_mixins/typecasting.rb +70 -0
  43. data/lib/populate_me/document_mixins/validation.rb +44 -0
  44. data/lib/populate_me/file_system_attachment.rb +40 -0
  45. data/lib/populate_me/grid_fs_attachment.rb +103 -0
  46. data/lib/populate_me/mongo.rb +160 -0
  47. data/lib/populate_me/s3_attachment.rb +120 -0
  48. data/lib/populate_me/variation.rb +38 -0
  49. data/lib/populate_me/version.rb +4 -0
  50. data/populate-me.gemspec +34 -0
  51. data/test/helper.rb +37 -0
  52. data/test/test_admin.rb +183 -0
  53. data/test/test_api.rb +246 -0
  54. data/test/test_attachment.rb +167 -0
  55. data/test/test_document.rb +128 -0
  56. data/test/test_document_admin_adapter.rb +221 -0
  57. data/test/test_document_callbacks.rb +151 -0
  58. data/test/test_document_outcasting.rb +247 -0
  59. data/test/test_document_persistence.rb +83 -0
  60. data/test/test_document_schema.rb +280 -0
  61. data/test/test_document_typecasting.rb +128 -0
  62. data/test/test_grid_fs_attachment.rb +239 -0
  63. data/test/test_mongo.rb +324 -0
  64. data/test/test_s3_attachment.rb +281 -0
  65. data/test/test_variation.rb +91 -0
  66. data/test/test_version.rb +11 -0
  67. metadata +294 -0
@@ -0,0 +1,324 @@
1
+ require 'helper'
2
+ require 'populate_me/mongo'
3
+ # require 'mongo'
4
+
5
+ Mongo::Logger.logger.level = Logger::ERROR
6
+
7
+ MONGO = Mongo::Client.new([ '127.0.0.1:27017' ], :database => 'populate-me-test')
8
+ DB = MONGO.database
9
+ DB.drop
10
+ OTHER_MONGO = Mongo::Client.new([ '127.0.0.1:27017' ], :database => 'populate-me-test-other')
11
+ OTHER_DB = OTHER_MONGO.database
12
+ OTHER_DB.drop
13
+ PopulateMe::Mongo.set :db, DB
14
+
15
+
16
+ describe 'PopulateMe::Mongo' do
17
+ # PopulateMe::Mongo is the Mongo specific extention for
18
+ # Document.
19
+ #
20
+ # It contains what is specific to a Mongo
21
+ # database.
22
+
23
+ # parallelize_me!
24
+
25
+ class CatFish < PopulateMe::Mongo
26
+ field :name
27
+ end
28
+ module Paradise
29
+ class CatFish < PopulateMe::Mongo
30
+ field :name
31
+ end
32
+ end
33
+
34
+ it 'Includes Document Module' do
35
+ assert_equal "Cat Fish", CatFish.to_s
36
+ assert_equal "Fred", CatFish.new(name: "Fred").to_s
37
+ end
38
+
39
+ it "Has _id as persistent variable if set" do
40
+ assert_equal [:@name], CatFish.new(name: "hank").persistent_instance_variables
41
+ cf = CatFish.new(id: "bbbbb", name: "honk")
42
+ assert cf.persistent_instance_variables.include?(:@_id)
43
+ assert cf.persistent_instance_variables.include?(:@name)
44
+ end
45
+
46
+ describe 'Database connection' do
47
+
48
+ class NoMongoDB < PopulateMe::Mongo; set :db, nil; end
49
+
50
+ it 'Should raise if db is not set' do
51
+ assert_raises(PopulateMe::MissingMongoDBError) do
52
+ NoMongoDB.collection
53
+ end
54
+ end
55
+
56
+ it 'Should have db set by the parent class' do
57
+ assert_equal DB, CatFish.settings.db
58
+ end
59
+
60
+ it 'Can override db' do
61
+ CatFish.set :db, OTHER_DB
62
+ assert_equal OTHER_DB, CatFish.settings.db
63
+ CatFish.set :db, DB
64
+ assert_equal DB, CatFish.settings.db
65
+ end
66
+
67
+ it 'Should set DB collection to dasherized full class name by default' do
68
+ assert_equal "cat-fish", CatFish.collection_name
69
+ assert_equal "paradise--cat-fish", Paradise::CatFish.collection_name
70
+ end
71
+
72
+ it 'Finds collection in DB' do
73
+ assert_equal DB['cat-fish'].name, CatFish.collection.name
74
+ end
75
+
76
+ it 'Should set DB and collection name even for dynamically created classes' do
77
+ # Happens when an included module automatically happens a class.
78
+ # e.g. Slides for a Slideshow
79
+ CatFish.const_set("Item", Class.new(CatFish.superclass))
80
+ assert_equal "cat-fish--item", CatFish::Item.collection.name
81
+ end
82
+
83
+ end
84
+
85
+ describe 'Low level CRUD' do
86
+
87
+ class LowFish < PopulateMe::Mongo
88
+ field :name
89
+ field :lastname
90
+ end
91
+
92
+ before do
93
+ LowFish.collection.drop
94
+ end
95
+
96
+ it 'Should create' do
97
+ fred = LowFish.new(name: "Fred")
98
+ id = fred.perform_create
99
+ assert_equal id, fred.id
100
+ refute_nil LowFish.collection.find({'name'=>"Fred"}).first
101
+ LowFish.collection.delete_one({'name'=>'Fred'})
102
+ end
103
+
104
+ it 'Should create with custom id' do
105
+ dddd = LowFish.new(id: "dddd", name: "tom")
106
+ id = dddd.perform_create
107
+ assert_equal "dddd", id
108
+ end
109
+
110
+ it 'Should update' do
111
+ jason = LowFish.new(name: "jason")
112
+ jason.perform_create
113
+ jason.name = "billy"
114
+ jason.perform_update
115
+ assert_equal "billy", LowFish.collection.find({'_id'=> jason.id}).first['name']
116
+ end
117
+
118
+ it "Should get correct item" do
119
+ jackson_id = LowFish.new(name: "jackson").perform_create
120
+ assert_equal "jackson", LowFish.admin_get(jackson_id).name
121
+ assert_equal "jackson", LowFish.admin_get(jackson_id.to_s).name
122
+ assert_nil LowFish.admin_get("nonexistentid")
123
+
124
+ regular_fish_id = LowFish.new(id: 87, name: "regular").perform_create
125
+ # need to test with .to_s
126
+ assert_equal "regular", LowFish.admin_get(regular_fish_id).name
127
+ end
128
+
129
+ it "Should get multiple items" do
130
+ # Order is not respected
131
+ alpha_id = LowFish.new(name: "alpha").perform_create
132
+ beta_id = LowFish.new(name: "beta").perform_create
133
+ gamma_id = LowFish.new(name: "gamma").perform_create
134
+ items = LowFish.admin_get([alpha_id, beta_id, nil, alpha_id])
135
+ assert items.is_a?(Array)
136
+ assert_equal 2, items.count
137
+ refute_nil items.find{|i| i.id == alpha_id}
138
+ refute_nil items.find{|i| i.id == beta_id}
139
+ assert_nil items.find{|i| i.id == gamma_id}
140
+ end
141
+
142
+ it 'Should have the [] shortcut for admin_get' do
143
+ LowFish.collection.insert_one(_id: 42, name: "H2G2")
144
+ assert_equal LowFish[42], LowFish.admin_get(42)
145
+ end
146
+
147
+ it 'Should admin_find correctly' do
148
+ LowFish.collection.insert_one(_id: 10, name: "Arya")
149
+ LowFish.collection.insert_one(_id: 20, name: "Bran")
150
+ LowFish.collection.insert_one(_id: 30, name: "Arya")
151
+ LowFish.collection.insert_one(_id: 40, name: "Bran")
152
+ items = LowFish.admin_find
153
+ assert items.is_a?(Array)
154
+ assert_equal 4, items.count
155
+ assert_equal 10, items[0].id
156
+ items = LowFish.admin_find query: {name: 'Bran'}
157
+ assert items.is_a?(Array)
158
+ assert 2, items.count
159
+ assert_equal 20, items[0].id
160
+ end
161
+
162
+ it 'Should admin_find_first correctly' do
163
+ LowFish.collection.insert_one(_id: 10, name: "Arya")
164
+ LowFish.collection.insert_one(_id: 20, name: "Bran")
165
+ LowFish.collection.insert_one(_id: 30, name: "Arya")
166
+ LowFish.collection.insert_one(_id: 40, name: "Bran")
167
+ item = LowFish.admin_find_first
168
+ assert_equal 10, item.id
169
+ item = LowFish.admin_find_first query: {name: "Bran"}
170
+ assert_equal 20, item.id
171
+ end
172
+
173
+ it 'Should admin_find while turning fields option into a projection option' do
174
+ LowFish.collection.insert_one(_id: 42, name: "John", lastname: "Doe")
175
+ found = LowFish.admin_find(query: {lastname: "Doe"}, fields: ['_id', 'lastname'])[0]
176
+ assert_equal 42, found.id
177
+ assert_nil found.name
178
+ end
179
+
180
+ it 'Should delete' do
181
+ herbert = LowFish.new(name: "herbert")
182
+ herbert.perform_create
183
+ refute_nil LowFish.collection.find({"_id"=> herbert.id}).first
184
+ herbert.perform_delete
185
+ assert_nil LowFish.collection.find({"_id"=> herbert.id}).first
186
+ end
187
+
188
+ it 'Should not save to the @@documents class variable' do
189
+ LowFish.collection.insert_one(_id: 42, name: "H2G2")
190
+ assert_equal [], LowFish.documents
191
+ end
192
+
193
+ end
194
+
195
+ describe 'High level CRUD' do
196
+
197
+ it 'Should use callbacks' do
198
+ danny = CatFish.new(name: "danny")
199
+ assert_nil danny.id
200
+ assert danny.new?
201
+ danny.save
202
+ refute_nil danny.id
203
+ refute danny.new?
204
+ end
205
+
206
+ end
207
+
208
+ describe 'Default Sorting' do
209
+
210
+ class MongoSoldier < PopulateMe::Mongo
211
+ field :name
212
+ field :position
213
+ end
214
+
215
+ before do
216
+ MongoSoldier.collection.drop
217
+ MongoSoldier.new(name: 'Bob', position: 2).perform_create
218
+ MongoSoldier.new(name: 'Albert', position: 3).perform_create
219
+ MongoSoldier.new(name: 'Tony', position: 1).perform_create
220
+ end
221
+
222
+ it 'Uses Doc::sort_by to determine the order' do
223
+ assert_equal 'Albert', MongoSoldier.sort_by(:name).admin_find[0].name
224
+ assert_equal 'Tony', MongoSoldier.sort_by(:name,:desc).admin_find[0].name
225
+ assert_equal 1, MongoSoldier.sort_by(:position).admin_find[0].position
226
+ assert_raises(ArgumentError) do
227
+ MongoSoldier.sort_by(:name,0)
228
+ end
229
+ assert_raises(ArgumentError) do
230
+ MongoSoldier.sort_by(:namespace)
231
+ end
232
+ end
233
+
234
+ it 'Can write Mongo-specific sort if a Hash or an Array is passed' do
235
+ assert_equal 'Tony', MongoSoldier.sort_by([[:name, -1]]).admin_find[0].name
236
+ assert_equal 'Tony', MongoSoldier.sort_by({name: -1}).admin_find[0].name
237
+ end
238
+
239
+ end
240
+
241
+ describe 'Manual Sorting' do
242
+
243
+ class MongoChampion < PopulateMe::Mongo
244
+ field :position
245
+ field :reversed, direction: :desc
246
+ end
247
+
248
+ before do
249
+ MongoChampion.collection.drop
250
+ MongoChampion.new(id: 'a').perform_create
251
+ MongoChampion.new(id: 'b').perform_create
252
+ MongoChampion.new(id: 'c').perform_create
253
+ end
254
+
255
+ it 'Sets the indexes on the provided field' do
256
+ MongoChampion.set_indexes(:position,['b','a','c'])
257
+ assert_equal 1, MongoChampion.admin_get('a').position
258
+ assert_equal 0, MongoChampion.admin_get('b').position
259
+ assert_equal 2, MongoChampion.admin_get('c').position
260
+ end
261
+
262
+ it 'Sets the indexes taking direction into account' do
263
+ MongoChampion.set_indexes(:reversed,['b','a','c'])
264
+ assert_equal 1, MongoChampion.admin_get('a').reversed
265
+ assert_equal 2, MongoChampion.admin_get('b').reversed
266
+ assert_equal 0, MongoChampion.admin_get('c').reversed
267
+ end
268
+
269
+ it 'Sets the indexes correctly even if some ids are not string' do
270
+ MongoChampion.collection.drop
271
+ MongoChampion.new(id: 'a').perform_create
272
+ MongoChampion.new(id: BSON::ObjectId.from_string("507f1f77bcf86cd799439011")).perform_create
273
+ MongoChampion.new(id: BSON::ObjectId.from_string("507f191e810c19729de860ea")).perform_create
274
+ MongoChampion.set_indexes(:position,["507f1f77bcf86cd799439011",'a',"507f191e810c19729de860ea"])
275
+ assert_equal 0, MongoChampion.admin_get("507f1f77bcf86cd799439011").position
276
+ assert_equal 1, MongoChampion.admin_get('a').position
277
+ assert_equal 2, MongoChampion.admin_get("507f191e810c19729de860ea").position
278
+ end
279
+ end
280
+
281
+ describe '::admin_distinct' do
282
+
283
+ class MongoDistinction < PopulateMe::Mongo
284
+ attr_accessor :title, :age
285
+ end
286
+
287
+ before do
288
+ MongoDistinction.collection.drop
289
+ end
290
+
291
+ it 'Can list all distinct values' do
292
+ MongoDistinction.new(title: 'Lord').save
293
+ MongoDistinction.new(title: 'Lord').save
294
+ MongoDistinction.new.save
295
+ MongoDistinction.new(title: 'Chevalier').save
296
+ MongoDistinction.new(title: 'Baron').save
297
+ MongoDistinction.new(title: 'Baron').save
298
+ result = MongoDistinction.admin_distinct :title
299
+ assert_instance_of Array, result
300
+ assert_equal 3, result.size
301
+ assert_includes result, 'Lord'
302
+ assert_includes result, 'Chevalier'
303
+ assert_includes result, 'Baron'
304
+ end
305
+
306
+ it 'Can list all distinct values for a specific selector' do
307
+ MongoDistinction.new(title: 'Chevalier', age: 33).save
308
+ MongoDistinction.new(title: 'Chevalier', age: 34).save
309
+ MongoDistinction.new(title: 'Baron', age: 35).save
310
+ MongoDistinction.new(title: 'Baron', age: 36).save
311
+ result = MongoDistinction.admin_distinct :age, query: {title: 'Baron'}
312
+ assert_instance_of Array, result
313
+ assert_equal 2, result.size
314
+ assert_includes result, 35
315
+ assert_includes result, 36
316
+ end
317
+
318
+ end
319
+
320
+ end
321
+
322
+ DB.drop
323
+ OTHER_DB.drop
324
+
@@ -0,0 +1,281 @@
1
+ require 'helper'
2
+ require 'populate_me/document'
3
+ require 'populate_me/s3_attachment'
4
+
5
+ s3_resource = Aws::S3::Resource.new
6
+ s3_bucket = s3_resource.bucket(ENV['BUCKET'])
7
+ PopulateMe::Document.set :default_attachment_class, PopulateMe::S3Attachment
8
+ PopulateMe::S3Attachment.set :bucket, s3_bucket
9
+
10
+ describe 'PopulateMe::S3Attachment' do
11
+ # parallelize_me!
12
+
13
+ def check_if_public_read(object)
14
+ grant = object.acl.grants.find do |g|
15
+ g.grantee.uri=="http://acs.amazonaws.com/groups/global/AllUsers"
16
+ end
17
+ raise "grant not found" if grant.nil?
18
+ ['READ','FULL_CONTROL'].include? grant.permission
19
+ end
20
+
21
+ class S3Book < PopulateMe::Document
22
+ field :cover, type: :attachment, variations: [
23
+ PopulateMe::Variation.new_image_magick_job(:thumb, :gif, "-resize '300x'")
24
+ ]
25
+ field :content, type: :attachment, variations: [
26
+ PopulateMe::Variation.new(:upcase, :txt, lambda{ |src,dst|
27
+ Kernel.system "cat \"#{src}\" | tr 'a-z' 'A-Z' > \"#{dst}\""
28
+ })
29
+ ]
30
+ field :open_content, type: :attachment, url_prefix: 'open', variations: [
31
+ PopulateMe::Variation.new(:upcase, :txt, lambda{ |src,dst|
32
+ Kernel.system "cat \"#{src}\" | tr 'a-z' 'A-Z' > \"#{dst}\""
33
+ })
34
+ ]
35
+ end
36
+
37
+ class S3BookNoPrefix < PopulateMe::Document
38
+ set :s3_url_prefix, ''
39
+ field :content, type: :attachment, variations: [
40
+ PopulateMe::Variation.new(:upcase, :txt, lambda{ |src,dst|
41
+ Kernel.system "cat \"#{src}\" | tr 'a-z' 'A-Z' > \"#{dst}\""
42
+ })
43
+ ]
44
+ end
45
+
46
+ before do
47
+ s3_bucket.clear!
48
+ end
49
+
50
+ # Utils
51
+
52
+ it 'Returns URL with bucket url' do
53
+ book = S3Book.new cover: "candy.jpg"
54
+ assert_equal "#{s3_bucket.url}/candy.jpg", book.attachment(:cover).url
55
+ assert_equal "#{s3_bucket.url}/candy.thumb.gif", book.attachment(:cover).url(:thumb)
56
+ end
57
+
58
+ it 'Has nil URL when field is blank' do
59
+ book = S3Book.new
60
+ assert_nil book.attachment(:cover).url
61
+ end
62
+
63
+ it 'Has location root without attachee prefix' do
64
+ book = S3Book.new
65
+ refute_match book.attachment(:cover).attachee_prefix, book.attachment(:cover).location_root
66
+ end
67
+
68
+ # Create
69
+
70
+ it "Saves attachments on create with variations" do
71
+ book = S3Book.new
72
+
73
+ file = Tempfile.new('foo')
74
+ file.write('hello')
75
+ file.rewind
76
+
77
+ field_value = book.attachment(:content).create({
78
+ tempfile: file,
79
+ filename: 'story.txt',
80
+ type: 'text/plain'
81
+ })
82
+ assert_equal 'public/s-3-book/story.txt', field_value
83
+ assert s3_bucket.object('public/s-3-book/story.txt').exists?
84
+ assert s3_bucket.object('public/s-3-book/story.upcase.txt').exists?
85
+
86
+ s3file = s3_bucket.object('public/s-3-book/story.txt')
87
+ assert_equal 'text/plain', s3file.content_type
88
+ assert check_if_public_read(s3file)
89
+ assert_equal 's-3-book', s3file.metadata['parent_collection']
90
+ assert_equal 'hello', s3file.get.body.read
91
+
92
+ vars3file = s3_bucket.object('public/s-3-book/story.upcase.txt')
93
+ assert_equal 'text/plain', vars3file.content_type
94
+ assert check_if_public_read(vars3file)
95
+ assert_equal 's-3-book', vars3file.metadata['parent_collection']
96
+ assert_equal 'HELLO', vars3file.get.body.read
97
+
98
+ file.close
99
+ file.unlink
100
+ end
101
+
102
+ it "Can override the url_prefix at document class level" do
103
+ file = Tempfile.new('foo')
104
+ book = S3BookNoPrefix.new
105
+
106
+ field_value = book.attachment(:content).create({
107
+ tempfile: file,
108
+ filename: 'story.txt',
109
+ type: 'text/plain'
110
+ })
111
+
112
+ assert_equal 's-3-book-no-prefix/story.txt', field_value
113
+ assert s3_bucket.object('s-3-book-no-prefix/story.txt').exists?
114
+ assert s3_bucket.object('s-3-book-no-prefix/story.upcase.txt').exists?
115
+ end
116
+
117
+ it "Can override the url_prefix at field level" do
118
+ file = Tempfile.new('foo')
119
+ book = S3Book.new
120
+
121
+ field_value = book.attachment(:open_content).create({
122
+ tempfile: file,
123
+ filename: 'story.txt',
124
+ type: 'text/plain'
125
+ })
126
+
127
+ assert_equal 'open/s-3-book/story.txt', field_value
128
+ assert s3_bucket.object('open/s-3-book/story.txt').exists?
129
+ assert s3_bucket.object('open/s-3-book/story.upcase.txt').exists?
130
+ end
131
+
132
+ it 'Does not create 2 files with the same name' do
133
+ file = Tempfile.new('foo')
134
+
135
+ book = S3Book.new
136
+
137
+ field_value = book.attachment(:content).create({
138
+ tempfile: file,
139
+ filename: 'story.txt',
140
+ type: 'text/plain'
141
+ })
142
+
143
+ assert_equal 'public/s-3-book/story.txt', field_value
144
+
145
+ field_value = book.attachment(:content).create({
146
+ tempfile: file,
147
+ filename: 'story.txt',
148
+ type: 'text/plain'
149
+ })
150
+
151
+ assert_equal 'public/s-3-book/story-1.txt', field_value
152
+
153
+ field_value = book.attachment(:content).create({
154
+ tempfile: file,
155
+ filename: 'story.txt',
156
+ type: 'text/plain'
157
+ })
158
+
159
+ assert_equal 'public/s-3-book/story-2.txt', field_value
160
+
161
+ file.close
162
+ file.unlink
163
+ end
164
+
165
+ # Delete
166
+
167
+ it 'Is deletable when field is not blank' do
168
+ book = S3Book.new cover: "candy.jpg"
169
+ assert book.attachment(:cover).deletable?
170
+ end
171
+
172
+ it 'Is not deletable when field is blank' do
173
+ book = S3Book.new
174
+ refute book.attachment(:cover).deletable?
175
+ end
176
+
177
+ it 'Deletes all attachments' do
178
+ file = Tempfile.new('foo')
179
+
180
+ book = S3Book.new
181
+
182
+ field_value = book.attachment(:content).create({
183
+ tempfile: file,
184
+ filename: 'story.txt',
185
+ type: 'text/plain'
186
+ })
187
+ book.content = field_value
188
+
189
+ assert s3_bucket.object('public/s-3-book/story.txt').exists?
190
+ assert s3_bucket.object('public/s-3-book/story.upcase.txt').exists?
191
+
192
+ book.attachment(:content).delete_all
193
+
194
+ refute s3_bucket.object('public/s-3-book/story.txt').exists?
195
+ refute s3_bucket.object('public/s-3-book/story.upcase.txt').exists?
196
+
197
+ file.close
198
+ file.unlink
199
+ end
200
+
201
+ it 'Deletes one attachment at a time' do
202
+ file = Tempfile.new('foo')
203
+
204
+ book = S3Book.new
205
+
206
+ field_value = book.attachment(:content).create({
207
+ tempfile: file,
208
+ filename: 'story.txt',
209
+ type: 'text/plain'
210
+ })
211
+ book.content = field_value
212
+
213
+ assert s3_bucket.object('public/s-3-book/story.txt').exists?
214
+ assert s3_bucket.object('public/s-3-book/story.upcase.txt').exists?
215
+
216
+ book.attachment(:content).delete
217
+
218
+ refute s3_bucket.object('public/s-3-book/story.txt').exists?
219
+ assert s3_bucket.object('public/s-3-book/story.upcase.txt').exists?
220
+
221
+ book.attachment(:content).delete :upcase
222
+
223
+ refute s3_bucket.object('public/s-3-book/story.upcase.txt').exists?
224
+
225
+ file.close
226
+ file.unlink
227
+ end
228
+
229
+ # Update
230
+
231
+ it 'Deletes previous attachment when saving a new one' do
232
+ file = Tempfile.new('foo')
233
+ file.write('hello')
234
+ file.rewind
235
+
236
+ book = S3Book.new
237
+
238
+ field_value = book.attachment(:content).create({
239
+ tempfile: file,
240
+ filename: 'story.txt',
241
+ type: 'text/plain'
242
+ })
243
+ book.content = field_value
244
+
245
+ assert s3_bucket.object('public/s-3-book/story.txt').exists?
246
+ assert s3_bucket.object('public/s-3-book/story.upcase.txt').exists?
247
+
248
+ file.rewind
249
+ file.write('world')
250
+ file.rewind
251
+
252
+ field_value = book.attachment(:content).create({
253
+ tempfile: file,
254
+ filename: 'history.md',
255
+ type: 'text/markdown'
256
+ })
257
+ book.content = field_value
258
+
259
+ refute s3_bucket.object('public/s-3-book/story.txt').exists?
260
+ refute s3_bucket.object('public/s-3-book/story.upcase.txt').exists?
261
+ assert s3_bucket.object('public/s-3-book/history.md').exists?
262
+ assert s3_bucket.object('public/s-3-book/history.upcase.txt').exists?
263
+
264
+ s3file = s3_bucket.object('public/s-3-book/history.md')
265
+ assert_equal 'text/markdown', s3file.content_type
266
+ assert_equal 's-3-book', s3file.metadata['parent_collection']
267
+ assert_equal 'world', s3file.get.body.read
268
+
269
+ s3file = s3_bucket.object('public/s-3-book/history.upcase.txt')
270
+ assert_equal 'text/plain', s3file.content_type
271
+ assert_equal 's-3-book', s3file.metadata['parent_collection']
272
+ assert_equal 'WORLD', s3file.get.body.read
273
+
274
+ file.close
275
+ file.unlink
276
+ end
277
+
278
+ end
279
+
280
+ s3_bucket.clear!
281
+