populate-me 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+