shrine 3.0.0.beta3 → 3.0.0.rc
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of shrine might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/README.md +33 -5
- data/doc/advantages.md +8 -3
- data/doc/changing_derivatives.md +11 -5
- data/doc/changing_location.md +11 -5
- data/doc/creating_storages.md +13 -4
- data/doc/direct_s3.md +1 -1
- data/doc/metadata.md +38 -14
- data/doc/plugins/backgrounding.md +34 -51
- data/doc/plugins/mirroring.md +12 -4
- data/doc/plugins/pretty_location.md +11 -0
- data/doc/processing.md +32 -13
- data/doc/release_notes/3.0.0.md +90 -43
- data/doc/storage/file_system.md +9 -0
- data/doc/storage/s3.md +9 -0
- data/doc/upgrading_to_3.md +38 -11
- data/lib/shrine.rb +1 -1
- data/lib/shrine/plugins/pretty_location.rb +9 -1
- data/lib/shrine/plugins/remote_url.rb +3 -3
- data/lib/shrine/storage/file_system.rb +15 -8
- data/lib/shrine/storage/linter.rb +34 -7
- data/lib/shrine/storage/memory.rb +6 -2
- data/lib/shrine/storage/s3.rb +10 -0
- data/lib/shrine/version.rb +1 -1
- data/shrine.gemspec +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 99f93b258aea07a618b1f35bf08f6b8394dea5d0c6301ed2f96cc72ec12371a4
|
4
|
+
data.tar.gz: ac05d107593d54ce6260d5159c8484f03c24ea0e9df4026b615f79fd5bc04a92
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 640d3a4f07816d7f4a20d385ac1249cd5cdbc46f320a6b8d377338b4ceaa724f5ce1e84782391fd2af6be1419aecd61474a36f03bcffb41c6600f6d999b0be0f
|
7
|
+
data.tar.gz: fa93fe25894f9a1125a852e438ca5ecd20518e206418d464afbcf59d8576959e373a3084ff133ef2c8aff062e1a4ba93d64841e19cc14fdc256f87d8871dcb4c
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
## 3.0.0.rc (2019-09-28)
|
2
|
+
|
3
|
+
* `core` – Add `Storage#delete_prefixed` method for deleting all files in specified directory (@jrochkind)
|
4
|
+
|
5
|
+
* `linter` – Return `true` in `Storage::Linter#call` so that it can be used with `assert` (@jrochkind)
|
6
|
+
|
7
|
+
* `linter` – Allow `Storage::Linter` to accept a key that will be used for testing nonexistent file (@janko)
|
8
|
+
|
9
|
+
* `core` – Infer file extension from `filename` metadata (@janko)
|
10
|
+
|
11
|
+
* `pretty_location` – Add `:class_underscore` option for underscoring class name (@Uysim)
|
12
|
+
|
13
|
+
* Update `down` dependency to `~> 5.0` (@janko)
|
14
|
+
|
1
15
|
## 3.0.0.beta3 (2019-09-25)
|
2
16
|
|
3
17
|
* `multi_cache` – Add new plugin for whitelisting additional temporary storages (@janko, @jrochkind)
|
data/README.md
CHANGED
@@ -34,6 +34,7 @@ If you're curious how it compares to other file attachment libraries, see the [A
|
|
34
34
|
- [IO abstraction](#io-abstraction)
|
35
35
|
* [Uploaded file](#uploaded-file)
|
36
36
|
* [Attachment](#attachment)
|
37
|
+
- [Temporary storage](#temporary-storage)
|
37
38
|
* [Attacher](#attacher)
|
38
39
|
* [Plugin system](#plugin-system)
|
39
40
|
* [Metadata](#metadata)
|
@@ -421,6 +422,20 @@ photo.update(image: new_file) # changes the attachment and deletes previous
|
|
421
422
|
photo.update(image: nil) # removes the attachment and deletes previous
|
422
423
|
```
|
423
424
|
|
425
|
+
### Temporary storage
|
426
|
+
|
427
|
+
Shrine uses temporary storage to enable retaining uploaded files across form
|
428
|
+
redisplays and for [direct uploads](#direct-uploads), but you can disable this
|
429
|
+
behaviour and have files go straight to permanent storage:
|
430
|
+
|
431
|
+
```rb
|
432
|
+
Shrine.plugin :model, cache: false
|
433
|
+
```
|
434
|
+
```rb
|
435
|
+
photo.image = File.open("waterfall.jpg")
|
436
|
+
photo.image.storage_key #=> :store
|
437
|
+
```
|
438
|
+
|
424
439
|
## Attacher
|
425
440
|
|
426
441
|
The model attachment attributes and callbacks added by `Shrine::Attachment`
|
@@ -848,12 +863,21 @@ choice][Backgrounding Libraries]:
|
|
848
863
|
|
849
864
|
```rb
|
850
865
|
Shrine.plugin :backgrounding
|
851
|
-
Shrine::Attacher.promote_block
|
852
|
-
|
866
|
+
Shrine::Attacher.promote_block do
|
867
|
+
PromoteJob.perform_async(self.class.name, record.class.name, record.id, name, file_data)
|
868
|
+
end
|
869
|
+
Shrine::Attacher.destroy_block do
|
870
|
+
DestroyJob.perform_async(self.class.name, data)
|
871
|
+
end
|
853
872
|
```
|
854
873
|
```rb
|
855
|
-
class PromoteJob
|
856
|
-
|
874
|
+
class PromoteJob
|
875
|
+
include Sidekiq::Worker
|
876
|
+
|
877
|
+
def perform(attacher_class, record_class, record.id, name, file_data)
|
878
|
+
attacher_class = Object.const_get(attacher_class)
|
879
|
+
record = Object.const_get(record_class).find(record_id) # if using Active Record
|
880
|
+
|
857
881
|
attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
|
858
882
|
attacher.atomic_promote
|
859
883
|
rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
|
@@ -862,8 +886,12 @@ class PromoteJob < ActiveJob::Base
|
|
862
886
|
end
|
863
887
|
```
|
864
888
|
```rb
|
865
|
-
class DestroyJob
|
889
|
+
class DestroyJob
|
890
|
+
include Sidekiq::Worker
|
891
|
+
|
866
892
|
def perform(attacher_class, data)
|
893
|
+
attacher_class = Object.const_get(attacher_class)
|
894
|
+
|
867
895
|
attacher = attacher_class.from_data(data)
|
868
896
|
attacher.destroy
|
869
897
|
end
|
data/doc/advantages.md
CHANGED
@@ -310,12 +310,17 @@ library][backgrounding libraries].
|
|
310
310
|
|
311
311
|
```rb
|
312
312
|
Shrine::Attacher.promote_block do
|
313
|
-
PromoteJob.
|
313
|
+
PromoteJob.perform_async(self.class.name, record.class.name, record.id, name, file_data)
|
314
314
|
end
|
315
315
|
```
|
316
316
|
```rb
|
317
|
-
class PromoteJob
|
318
|
-
|
317
|
+
class PromoteJob
|
318
|
+
include Sidekiq::Worker
|
319
|
+
|
320
|
+
def perform(attacher_class, record_class, record_id, name, file_data)
|
321
|
+
attacher_class = Object.const_get(attacher_class)
|
322
|
+
record = Object.const_get(record_class).find(record_id) # if using Active Record
|
323
|
+
|
319
324
|
attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
|
320
325
|
attacher.create_derivatives # perform processing
|
321
326
|
attacher.atomic_promote
|
data/doc/changing_derivatives.md
CHANGED
@@ -288,17 +288,23 @@ Photo.find_each do |photo|
|
|
288
288
|
|
289
289
|
next unless attacher.stored?
|
290
290
|
|
291
|
-
MakeChangeJob.
|
292
|
-
attacher.class,
|
293
|
-
attacher.record,
|
291
|
+
MakeChangeJob.perform_async(
|
292
|
+
attacher.class.name,
|
293
|
+
attacher.record.class.name,
|
294
|
+
attacher.record.id,
|
294
295
|
attacher.name,
|
295
296
|
attacher.file_data,
|
296
297
|
)
|
297
298
|
end
|
298
299
|
```
|
299
300
|
```rb
|
300
|
-
class MakeChangeJob
|
301
|
-
|
301
|
+
class MakeChangeJob
|
302
|
+
include Sidekiq::Worker
|
303
|
+
|
304
|
+
def perform(attacher_class, record_class, record_id, name, file_data)
|
305
|
+
attacher_class = Object.const_get(attacher_class)
|
306
|
+
record = Object.const_get(record_class).find(record_id) # if using Active Record
|
307
|
+
|
302
308
|
attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
|
303
309
|
# ... make our change ...
|
304
310
|
end
|
data/doc/changing_location.md
CHANGED
@@ -76,17 +76,23 @@ Photo.find_each do |photo|
|
|
76
76
|
|
77
77
|
next unless attacher.stored? # move only attachments uploaded to permanent storage
|
78
78
|
|
79
|
-
MoveFilesJob.
|
80
|
-
attacher.class,
|
81
|
-
attacher.record,
|
79
|
+
MoveFilesJob.perform_async(
|
80
|
+
attacher.class.name,
|
81
|
+
attacher.record.class.name,
|
82
|
+
attacher.record.id,
|
82
83
|
attacher.name,
|
83
84
|
attacher.file_data,
|
84
85
|
)
|
85
86
|
end
|
86
87
|
```
|
87
88
|
```rb
|
88
|
-
class MoveFilesJob
|
89
|
-
|
89
|
+
class MoveFilesJob
|
90
|
+
include Sidekiq::Worker
|
91
|
+
|
92
|
+
def perform(attacher_class, record_class, record_id, name, file_data)
|
93
|
+
attacher_class = Object.const_get(attacher_class)
|
94
|
+
record = Object.const_get(record_class).find(record_id) # if using Active Record
|
95
|
+
|
90
96
|
attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
|
91
97
|
old_attacher = attacher.dup
|
92
98
|
|
data/doc/creating_storages.md
CHANGED
@@ -206,15 +206,24 @@ The storage can support additional options to customize how the presign will be
|
|
206
206
|
generated, those can be forwarded via the `:presign_options` option on the
|
207
207
|
`presign_endpoint` plugin.
|
208
208
|
|
209
|
-
## Clear
|
209
|
+
## Delete Prefixed and Clear
|
210
210
|
|
211
|
-
|
212
|
-
|
213
|
-
|
211
|
+
There are two methods that are not currently used by shrine, but which it's good
|
212
|
+
for storages to provide to allow client code to delete files from storage. If
|
213
|
+
storages provide these conventional methods, then clients can delete files using
|
214
|
+
consistent API for any storage.
|
215
|
+
|
216
|
+
`#clear!` deletes all files from storage, and `#delete_prefixed` will delete all
|
217
|
+
files in a given directory/prefix/path. While not strictly required for shrine storage
|
218
|
+
service functionality, storages should usually implement if possible.
|
214
219
|
|
215
220
|
```rb
|
216
221
|
class MyStorage
|
217
222
|
# ...
|
223
|
+
def delete_prefixed(prefix_path)
|
224
|
+
# deletes all files under the supplied argument prefix
|
225
|
+
end
|
226
|
+
|
218
227
|
def clear!
|
219
228
|
# deletes all files in the storage
|
220
229
|
end
|
data/doc/direct_s3.md
CHANGED
@@ -310,7 +310,7 @@ Shrine.plugin :backgrounding
|
|
310
310
|
|
311
311
|
Shrine::Attacher.promote_block do
|
312
312
|
# tells a Sidekiq worker to perform in 3 seconds
|
313
|
-
PromoteJob.perform_in(3, self.class, record.class, record.id, name, file_data)
|
313
|
+
PromoteJob.perform_in(3, self.class.name, record.class.name, record.id, name, file_data)
|
314
314
|
end
|
315
315
|
```
|
316
316
|
|
data/doc/metadata.md
CHANGED
@@ -255,11 +255,19 @@ plugin uses internally):
|
|
255
255
|
```rb
|
256
256
|
Shrine.plugin :refresh_metadata # allow re-extracting metadata
|
257
257
|
Shrine.plugin :backgrounding
|
258
|
-
|
258
|
+
|
259
|
+
Shrine::Attacher.promote_block do
|
260
|
+
PromoteJob.perform_async(self.class.name, record.class.name, record.id, name, file_data)
|
261
|
+
end
|
259
262
|
```
|
260
263
|
```rb
|
261
|
-
class PromoteJob
|
262
|
-
|
264
|
+
class PromoteJob
|
265
|
+
include Sidekiq::Worker
|
266
|
+
|
267
|
+
def perform(attacher_class, record_class, record_id, name, file_data)
|
268
|
+
attacher_class = Object.const_get(attacher_class)
|
269
|
+
record = Object.const_get(record_class).find(record_id) # if using Active Record
|
270
|
+
|
263
271
|
attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
|
264
272
|
attacher.refresh_metadata!
|
265
273
|
attacher.atomic_promote
|
@@ -270,16 +278,22 @@ end
|
|
270
278
|
You can also extract metadata in the background separately from promotion:
|
271
279
|
|
272
280
|
```rb
|
273
|
-
MetadataJob.
|
274
|
-
attacher.class,
|
275
|
-
attacher.record,
|
281
|
+
MetadataJob.perform_async(
|
282
|
+
attacher.class.name,
|
283
|
+
attacher.record.class.name,
|
284
|
+
attacher.record.id,
|
276
285
|
attacher.name,
|
277
286
|
attacher.file_data,
|
278
287
|
)
|
279
288
|
```
|
280
289
|
```rb
|
281
|
-
class MetadataJob
|
282
|
-
|
290
|
+
class MetadataJob
|
291
|
+
include Sidekiq::Worker
|
292
|
+
|
293
|
+
def perform(attacher_class, record_class, record_id, name, file_data)
|
294
|
+
attacher_class = Object.const_get(attacher_class)
|
295
|
+
record = Object.const_get(record_class).find(record_id) # if using Active Record
|
296
|
+
|
283
297
|
attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
|
284
298
|
attacher.refresh_metadata!
|
285
299
|
attacher.atomic_persist
|
@@ -308,9 +322,14 @@ class MyUploader < Shrine
|
|
308
322
|
end
|
309
323
|
```
|
310
324
|
```rb
|
311
|
-
class MetadataJob
|
312
|
-
|
313
|
-
|
325
|
+
class MetadataJob
|
326
|
+
include Sidekiq::Worker
|
327
|
+
|
328
|
+
def perform(attacher_class, record_class, record_id, name, file_data)
|
329
|
+
attacher_class = Object.const_get(attacher_class)
|
330
|
+
record = Object.const_get(record_class).find(record_id) # if using Active Record
|
331
|
+
|
332
|
+
attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
|
314
333
|
attacher.refresh_metadata!(background: true)
|
315
334
|
attacher.atomic_persist
|
316
335
|
end
|
@@ -324,9 +343,14 @@ promotion, you can wrap both in an `UploadedFile#open` block to make
|
|
324
343
|
sure the file content is retrieved from the storage only once.
|
325
344
|
|
326
345
|
```rb
|
327
|
-
class PromoteJob
|
328
|
-
|
329
|
-
|
346
|
+
class PromoteJob
|
347
|
+
include Sidekiq::Worker
|
348
|
+
|
349
|
+
def perform(attacher_class, record_class, record_id, name, file_data)
|
350
|
+
attacher_class = Object.const_get(attacher_class)
|
351
|
+
record = Object.const_get(record_class).find(record_id) # if using Active Record
|
352
|
+
|
353
|
+
attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
|
330
354
|
|
331
355
|
attacher.file.open do
|
332
356
|
attacher.refresh_metadata!
|
@@ -15,12 +15,21 @@ jobs:
|
|
15
15
|
|
16
16
|
```rb
|
17
17
|
# register backgrounding blocks for all uploaders
|
18
|
-
Shrine::Attacher.promote_block
|
19
|
-
|
18
|
+
Shrine::Attacher.promote_block do
|
19
|
+
PromoteJob.perform_async(self.class.name, record.class.name, record.id, name, file_data)
|
20
|
+
end
|
21
|
+
Shrine::Attacher.destroy_block do
|
22
|
+
DestroyJob.perform_async(self.class.name, data)
|
23
|
+
end
|
20
24
|
```
|
21
25
|
```rb
|
22
|
-
class PromoteJob
|
23
|
-
|
26
|
+
class PromoteJob
|
27
|
+
include Sidekiq::Worker
|
28
|
+
|
29
|
+
def perform(attacher_class, record_class, record_id, name, file_data)
|
30
|
+
attacher_class = Object.const_get(attacher_class)
|
31
|
+
record = Object.const_get(record_class).find(record_id) # if using Active Record
|
32
|
+
|
24
33
|
attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
|
25
34
|
attacher.atomic_promote
|
26
35
|
rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
|
@@ -29,8 +38,12 @@ class PromoteJob < ActiveJob::Base
|
|
29
38
|
end
|
30
39
|
```
|
31
40
|
```rb
|
32
|
-
class DestroyJob
|
41
|
+
class DestroyJob
|
42
|
+
include Sidekiq::Worker
|
43
|
+
|
33
44
|
def perform(attacher_class, data)
|
45
|
+
attacher_class = Object.const_get(attacher_class)
|
46
|
+
|
34
47
|
attacher = attacher_class.from_data(data)
|
35
48
|
attacher.destroy
|
36
49
|
end
|
@@ -43,8 +56,12 @@ backgrounding blocks only for a specific uploader:
|
|
43
56
|
```rb
|
44
57
|
class MyUploader < Shrine
|
45
58
|
# register backgrounding blocks only for this uploader
|
46
|
-
Attacher.promote_block
|
47
|
-
|
59
|
+
Attacher.promote_block do
|
60
|
+
PromoteJob.perform_async(self.class.name, record.class.name, record.id, name, file_data)
|
61
|
+
end
|
62
|
+
Attacher.destroy_block do
|
63
|
+
DestroyJob.perform_async(self.class.name, data)
|
64
|
+
end
|
48
65
|
end
|
49
66
|
```
|
50
67
|
|
@@ -101,17 +118,18 @@ also use the explicit version by declaring an attacher argument:
|
|
101
118
|
|
102
119
|
```rb
|
103
120
|
Shrine::Attacher.promote_block do |attacher|
|
104
|
-
PromoteJob.
|
105
|
-
attacher.class,
|
106
|
-
attacher.record,
|
121
|
+
PromoteJob.perform_async(
|
122
|
+
attacher.class.name,
|
123
|
+
attacher.record.class.name,
|
124
|
+
attacher.record.id,
|
107
125
|
attacher.name,
|
108
126
|
attacher.file_data,
|
109
127
|
)
|
110
128
|
end
|
111
129
|
|
112
130
|
Shrine::Attacher.destroy_block do |attacher|
|
113
|
-
DestroyJob.
|
114
|
-
attacher.class,
|
131
|
+
DestroyJob.perform_async(
|
132
|
+
attacher.class.name,
|
115
133
|
attacher.data,
|
116
134
|
)
|
117
135
|
end
|
@@ -122,9 +140,10 @@ flexibility:
|
|
122
140
|
|
123
141
|
```rb
|
124
142
|
photo.image_attacher.promote_block do |attacher|
|
125
|
-
PromoteJob.
|
126
|
-
attacher.class,
|
127
|
-
attacher.record,
|
143
|
+
PromoteJob.perform_async(
|
144
|
+
attacher.class.name,
|
145
|
+
attacher.record.class.name,
|
146
|
+
attacher.record.id,
|
128
147
|
attacher.name,
|
129
148
|
attacher.file_data,
|
130
149
|
current_user.id, # pass arguments known at the controller level
|
@@ -135,42 +154,6 @@ photo.image = file
|
|
135
154
|
photo.save # executes the promote block above
|
136
155
|
```
|
137
156
|
|
138
|
-
## Other backgrounding libraries
|
139
|
-
|
140
|
-
If you're not using Active Job for backgrounding, you might need to retrieve
|
141
|
-
records and resolve constants from job arguments manually:
|
142
|
-
|
143
|
-
```rb
|
144
|
-
# register backgrounding blocks for all uploaders
|
145
|
-
Shrine::Attacher.promote_block { PromoteJob.perform_async(self.class, record.class, record.id, name, file_data) }
|
146
|
-
Shrine::Attacher.destroy_block { DestroyJob.perform_async(self.class, data) }
|
147
|
-
```
|
148
|
-
```rb
|
149
|
-
class PromoteJob
|
150
|
-
include Sidekiq::Worker
|
151
|
-
|
152
|
-
def perform(attacher_class, record_class, record_id, name, file_data)
|
153
|
-
attacher_class = Object.const_get(attacher_class)
|
154
|
-
record = Object.const_get(record_class).find(record_id) # if using Active Record
|
155
|
-
|
156
|
-
attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
|
157
|
-
attacher.atomic_promote
|
158
|
-
rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
|
-
class DestroyJob
|
163
|
-
include Sidekiq::Worker
|
164
|
-
|
165
|
-
def perform(attacher_class, data)
|
166
|
-
attacher_class = Object.const_get(attacher_class)
|
167
|
-
|
168
|
-
attacher = attacher_class.from_data(data)
|
169
|
-
attacher.destroy
|
170
|
-
end
|
171
|
-
end
|
172
|
-
```
|
173
|
-
|
174
157
|
[backgrounding]: /lib/shrine/plugins/backgrounding.rb
|
175
158
|
[derivatives]: /doc/plugins/derivatives.md#readme
|
176
159
|
[atomic_helpers]: /doc/plugins/atomic_helpers.md#readme
|
data/doc/plugins/mirroring.md
CHANGED
@@ -49,24 +49,32 @@ You can have mirroring performed in a background job:
|
|
49
49
|
|
50
50
|
```rb
|
51
51
|
Shrine.mirror_upload_block do |file|
|
52
|
-
MirrorUploadJob.
|
52
|
+
MirrorUploadJob.perform_async(file.shrine_class.name, file.data)
|
53
53
|
end
|
54
54
|
|
55
55
|
Shrine.mirror_delete_block do |file|
|
56
|
-
MirrorDeleteJob.
|
56
|
+
MirrorDeleteJob.perform_async(file.shrine_class.name, file.data)
|
57
57
|
end
|
58
58
|
```
|
59
59
|
```rb
|
60
|
-
class MirrorUploadJob
|
60
|
+
class MirrorUploadJob
|
61
|
+
include Sidekiq::Worker
|
62
|
+
|
61
63
|
def perform(shrine_class, file_data)
|
64
|
+
shrine_class = Object.const_get(shrine_class)
|
65
|
+
|
62
66
|
file = shrine_class.uploaded_file(file_data)
|
63
67
|
file.mirror_upload
|
64
68
|
end
|
65
69
|
end
|
66
70
|
```
|
67
71
|
```rb
|
68
|
-
class MirrorDeleteJob
|
72
|
+
class MirrorDeleteJob
|
73
|
+
include Sidekiq::Worker
|
74
|
+
|
69
75
|
def perform(shrine_class, file_data)
|
76
|
+
shrine_class = Object.const_get(shrine_class)
|
77
|
+
|
70
78
|
file = shrine_class.uploaded_file(file_data)
|
71
79
|
file.mirror_delete
|
72
80
|
end
|
@@ -40,6 +40,17 @@ plugin :pretty_location, identifier: :email
|
|
40
40
|
# "user/foo@bar.com/profile_picture/493g82jf23.jpg"
|
41
41
|
```
|
42
42
|
|
43
|
+
By default, the class name will be only downcased. We can also have the class
|
44
|
+
name underscored with the `:class_underscore` option:
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
plugin :pretty_location
|
48
|
+
# "blogpost/aa357797-5845-451b-8662-08eecdc9f762/image/493g82jf23.jpg"
|
49
|
+
|
50
|
+
plugin :pretty_location, class_underscore: :true
|
51
|
+
# "blog_post/aa357797-5845-451b-8662-08eecdc9f762/image/493g82jf23.jpg"
|
52
|
+
```
|
53
|
+
|
43
54
|
For a more custom identifier logic, you can overwrite the method
|
44
55
|
`#generate_location` and call `#pretty_location` with the identifier you have
|
45
56
|
calculated.
|
data/doc/processing.md
CHANGED
@@ -82,11 +82,18 @@ promotion:
|
|
82
82
|
|
83
83
|
```rb
|
84
84
|
Shrine.plugin :backgrounding
|
85
|
-
Shrine::Attacher.promote_block
|
85
|
+
Shrine::Attacher.promote_block do
|
86
|
+
PromoteJob.perform_async(self.class.name, record.class.name, record.id, name, file_data)
|
87
|
+
end
|
86
88
|
```
|
87
89
|
```rb
|
88
|
-
class PromoteJob
|
89
|
-
|
90
|
+
class PromoteJob
|
91
|
+
include Sidekiq::Worker
|
92
|
+
|
93
|
+
def perform(attacher_class, record_class, record_id, name, file_data)
|
94
|
+
attacher_class = Object.const_get(attacher_class)
|
95
|
+
record = Object.const_get(record_class).find(record_id) # if using Active Record
|
96
|
+
|
90
97
|
attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
|
91
98
|
attacher.create_derivatives # calls derivatives processor
|
92
99
|
attacher.atomic_promote
|
@@ -102,16 +109,22 @@ Derivatives don't need to be created as part of the attachment flow, you can
|
|
102
109
|
create them at any point after promotion:
|
103
110
|
|
104
111
|
```rb
|
105
|
-
DerivativesJob.
|
106
|
-
attacher.class,
|
107
|
-
attacher.record,
|
112
|
+
DerivativesJob.perform_async(
|
113
|
+
attacher.class.name,
|
114
|
+
attacher.record.class.name,
|
115
|
+
attacher.record.id,
|
108
116
|
attacher.name,
|
109
117
|
attacher.file_data,
|
110
118
|
)
|
111
119
|
```
|
112
120
|
```rb
|
113
|
-
class DerivativesJob
|
114
|
-
|
121
|
+
class DerivativesJob
|
122
|
+
include Sidekiq::Worker
|
123
|
+
|
124
|
+
def perform(attacher_class, record_class, record_id, name, file_data)
|
125
|
+
attacher_class = Object.const_get(attacher_class)
|
126
|
+
record = Object.const_get(record_class).find(record_id) # if using Active Record
|
127
|
+
|
115
128
|
attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
|
116
129
|
attacher.create_derivatives # calls derivatives processor
|
117
130
|
attacher.atomic_persist
|
@@ -144,9 +157,10 @@ end
|
|
144
157
|
```
|
145
158
|
```rb
|
146
159
|
ImageUploader::THUMBNAILS.each_key do |derivative_name|
|
147
|
-
DerivativeJob.
|
148
|
-
attacher.class,
|
149
|
-
attacher.record,
|
160
|
+
DerivativeJob.perform_async(
|
161
|
+
attacher.class.name,
|
162
|
+
attacher.record.class.name,
|
163
|
+
attacher.record.id,
|
150
164
|
attacher.name,
|
151
165
|
attacher.file_data,
|
152
166
|
derivative_name,
|
@@ -154,8 +168,13 @@ ImageUploader::THUMBNAILS.each_key do |derivative_name|
|
|
154
168
|
end
|
155
169
|
```
|
156
170
|
```rb
|
157
|
-
class DerivativeJob
|
158
|
-
|
171
|
+
class DerivativeJob
|
172
|
+
include Sidekiq::Worker
|
173
|
+
|
174
|
+
def perform(attacher_class, record_class, record_id, name, file_data, derivative_name)
|
175
|
+
attacher_class = Object.const_get(attacher_class)
|
176
|
+
record = Object.const_get(record_class).find(record_id) # if using Active Record
|
177
|
+
|
159
178
|
attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
|
160
179
|
attacher.create_derivatives(name: derivative_name)
|
161
180
|
attacher.atomic_persist do |reloaded_attacher|
|
data/doc/release_notes/3.0.0.md
CHANGED
@@ -135,22 +135,35 @@ how to upgrade.
|
|
135
135
|
|
136
136
|
```rb
|
137
137
|
Shrine.plugin :backgrounding
|
138
|
-
Shrine::Attacher.promote_block
|
139
|
-
|
138
|
+
Shrine::Attacher.promote_block do
|
139
|
+
PromoteJob.perform_async(self.class.name, record.class.name, record.id, name, file_data)
|
140
|
+
end
|
141
|
+
Shrine::Attacher.destroy_block do
|
142
|
+
DestroyJob.perform_async(self.class.name, data)
|
143
|
+
end
|
140
144
|
```
|
141
145
|
```rb
|
142
|
-
class PromoteJob
|
143
|
-
|
146
|
+
class PromoteJob
|
147
|
+
include Sidekiq::Worker
|
148
|
+
|
149
|
+
def perform(attacher_class, record_class, record.id, name, file_data)
|
150
|
+
attacher_class = Object.const_get(attacher_class)
|
151
|
+
record = Object.const_get(record_class).find(record_id) # if using Active Record
|
152
|
+
|
144
153
|
attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
|
145
|
-
attacher.atomic_promote
|
154
|
+
attacher.atomic_promote
|
146
155
|
rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
|
147
|
-
# attachment has changed or record has been deleted, nothing to do
|
156
|
+
# attachment has changed or the record has been deleted, nothing to do
|
148
157
|
end
|
149
158
|
end
|
150
159
|
```
|
151
160
|
```rb
|
152
|
-
class DestroyJob
|
161
|
+
class DestroyJob
|
162
|
+
include Sidekiq::Worker
|
163
|
+
|
153
164
|
def perform(attacher_class, data)
|
165
|
+
attacher_class = Object.const_get(attacher_class)
|
166
|
+
|
154
167
|
attacher = attacher_class.from_data(data)
|
155
168
|
attacher.destroy
|
156
169
|
end
|
@@ -170,9 +183,10 @@ how to upgrade.
|
|
170
183
|
photo = Photo.new(photo_params)
|
171
184
|
|
172
185
|
photo.image_attacher.promote_block do |attacher|
|
173
|
-
PromoteJob.
|
174
|
-
attacher.class,
|
175
|
-
attacher.record,
|
186
|
+
PromoteJob.perform_async(
|
187
|
+
attacher.class.name,
|
188
|
+
attacher.record.class.name,
|
189
|
+
attacher.record.id,
|
176
190
|
attacher.name,
|
177
191
|
attacher.file_data,
|
178
192
|
current_user.id, # <== parameters from the controller
|
@@ -196,16 +210,22 @@ how to upgrade.
|
|
196
210
|
implement metadata extraction in the background in a concurrency-safe way:
|
197
211
|
|
198
212
|
```rb
|
199
|
-
MetadataJob.
|
200
|
-
attacher.class,
|
201
|
-
attacher.record,
|
213
|
+
MetadataJob.perform_async(
|
214
|
+
attacher.class.name,
|
215
|
+
attacher.record.class.name,
|
216
|
+
attacher.record.id,
|
202
217
|
attacher.name,
|
203
218
|
attacher.file_data,
|
204
219
|
)
|
205
220
|
```
|
206
221
|
```rb
|
207
|
-
class MetadataJob
|
208
|
-
|
222
|
+
class MetadataJob
|
223
|
+
include Sidekiq::Worker
|
224
|
+
|
225
|
+
def perform(attacher_class, record_class, record_id, name, file_data)
|
226
|
+
attacher_class = Object.const_get(attacher_class)
|
227
|
+
record = Object.const_get(record_class).find(record_id) # if using Active Record
|
228
|
+
|
209
229
|
attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
|
210
230
|
attacher.refresh_metadata! # extract metadata
|
211
231
|
attacher.atomic_persist # persist if attachment hasn't changed
|
@@ -215,7 +235,7 @@ how to upgrade.
|
|
215
235
|
end
|
216
236
|
```
|
217
237
|
|
218
|
-
## Other
|
238
|
+
## Other new plugins
|
219
239
|
|
220
240
|
* The new [`mirroring`][mirroring] plugin has been added for replicating
|
221
241
|
uploads and deletes to other storages.
|
@@ -272,6 +292,8 @@ how to upgrade.
|
|
272
292
|
photo.image.storage_key #=> :store (permanent storage)
|
273
293
|
```
|
274
294
|
|
295
|
+
## Other features
|
296
|
+
|
275
297
|
* New `Shrine.download_response` method has been added to the
|
276
298
|
`download_endpoint` plugin for generating file response from the controller.
|
277
299
|
|
@@ -314,10 +336,11 @@ how to upgrade.
|
|
314
336
|
```rb
|
315
337
|
class Photo
|
316
338
|
include ImageUploader::Attachment(:image)
|
317
|
-
|
318
|
-
image_attacher #=> #<ImageUploader::Attacher ...>
|
319
339
|
end
|
320
340
|
```
|
341
|
+
```rb
|
342
|
+
Photo.image_attacher #=> #<ImageUploader::Attacher ...>
|
343
|
+
```
|
321
344
|
|
322
345
|
* The attachment data serializer is now configurable (by default `JSON`
|
323
346
|
standard library is used):
|
@@ -382,6 +405,17 @@ how to upgrade.
|
|
382
405
|
|
383
406
|
## Other Improvements
|
384
407
|
|
408
|
+
### Core improvements
|
409
|
+
|
410
|
+
* Shrine now works again with MRI 2.3.
|
411
|
+
|
412
|
+
* The memory storage from the [shrine-memory] gem has been merged into core.
|
413
|
+
|
414
|
+
```rb
|
415
|
+
# Gemfile
|
416
|
+
gem "shrine-memory" # this can be removed
|
417
|
+
```
|
418
|
+
|
385
419
|
* The `Attacher#assign` method now accepts cached file data as a Hash.
|
386
420
|
|
387
421
|
```rb
|
@@ -396,15 +430,36 @@ how to upgrade.
|
|
396
430
|
|
397
431
|
* The temporary storage doesn't need to be defined anymore if it's not used.
|
398
432
|
|
399
|
-
*
|
433
|
+
* Any changes to `Shrine.storages` will now be applied to existing `Shrine` and
|
434
|
+
`Attacher` instances.
|
435
|
+
|
436
|
+
* When copying the S3 object to another location, any specified upload options
|
437
|
+
will now be applied.
|
438
|
+
|
439
|
+
* Deprecation of passing unknown options to `FileSystem#open` has been
|
440
|
+
reverted. This allows users to continue using `FileSystem` storage in tests
|
441
|
+
as a mock storage.
|
442
|
+
|
443
|
+
* The `Shrine#upload` method now infers file extension from `filename` metadata,
|
444
|
+
making possible to use `filename` to specify file extension.
|
400
445
|
|
401
446
|
```rb
|
402
|
-
|
403
|
-
|
447
|
+
file = uploader.upload(StringIO.new("some text"), metadata: { "filename" => "file.txt" })
|
448
|
+
file.id #=> "2a2467ee6acbc5cb.txt"
|
404
449
|
```
|
405
450
|
|
406
|
-
*
|
407
|
-
|
451
|
+
* The `Shrine.opts` hash is now deep-copied on subclassing. This allows plugins
|
452
|
+
to freely mutate hashes and arrays in `Shrine.opts`, knowing they won't be
|
453
|
+
shared across subclasses.
|
454
|
+
|
455
|
+
* The `down` dependency has been updated to `~> 5.0`.
|
456
|
+
|
457
|
+
### Plugin improvements
|
458
|
+
|
459
|
+
* The `activerecord` plugin now works with Active Record 3.
|
460
|
+
|
461
|
+
* Callback code from `activerecord` and `sequel` plugin has been moved into
|
462
|
+
attacher methods, allowing the user to override them.
|
408
463
|
|
409
464
|
* The `url_options` plugin now allows you to override URL options by deleting
|
410
465
|
them.
|
@@ -448,6 +503,9 @@ how to upgrade.
|
|
448
503
|
* The `Derivation#upload` method from `derivation_endpoint` plugin now accepts
|
449
504
|
any IO-like object.
|
450
505
|
|
506
|
+
* The `derivation_endpoint` plugin doesn't re-open `File` objects returned in
|
507
|
+
derivation block anymore.
|
508
|
+
|
451
509
|
* The `instrumentation` plugin now instruments `UploadedFile#open` calls as a
|
452
510
|
new `open.shrine` event. `UploadedFile#download` is still instrumented as
|
453
511
|
`download.shrine`.
|
@@ -462,29 +520,19 @@ how to upgrade.
|
|
462
520
|
* Any options passed to `Attacher#attach_cached` are now forwarded to metadata
|
463
521
|
extraction when `restore_cached_data` plugin is loaded.
|
464
522
|
|
465
|
-
* Deprecation of passing unknown options to `FileSystem#open` has been
|
466
|
-
reverted. This allows users to continue using `FileSystem` storage in tests
|
467
|
-
as a mock storage.
|
468
|
-
|
469
|
-
* The `activerecord` plugin now works with Active Record 3.
|
470
|
-
|
471
|
-
* Shrine now works again with MRI 2.3.
|
472
|
-
|
473
523
|
* The `infer_extension` plugin now works correctly with `pretty_location`
|
474
524
|
plugin when `pretty_location` was loaded after `infer_extension`.
|
475
525
|
|
476
|
-
* The `
|
477
|
-
|
478
|
-
|
479
|
-
* Any changes to `Shrine.storages` will now be applied to existing `Shrine` and
|
480
|
-
`Attacher` instances.
|
526
|
+
* The `pretty_location` plugin now accepts `:class_underscore` option for
|
527
|
+
underscoring class names.
|
481
528
|
|
482
|
-
|
483
|
-
|
529
|
+
```rb
|
530
|
+
plugin :pretty_location
|
531
|
+
# "blogpost/aa357797-5845-451b-8662-08eecdc9f762/image/493g82jf23.jpg"
|
484
532
|
|
485
|
-
|
486
|
-
|
487
|
-
|
533
|
+
plugin :pretty_location, class_underscore: :true
|
534
|
+
# "blog_post/aa357797-5845-451b-8662-08eecdc9f762/image/493g82jf23.jpg"
|
535
|
+
```
|
488
536
|
|
489
537
|
## Backwards compatibility
|
490
538
|
|
@@ -636,8 +684,7 @@ how to upgrade.
|
|
636
684
|
|
637
685
|
### S3 API
|
638
686
|
|
639
|
-
* The support for `aws-sdk` 2.x and `aws-sdk-s3`
|
640
|
-
removed.
|
687
|
+
* The support for `aws-sdk` 2.x and `aws-sdk-s3` < 1.14 has been removed.
|
641
688
|
|
642
689
|
* `S3#open` now raises `Shrine::FileNotFound` exception when S3 object doesn't
|
643
690
|
exist on the bucket.
|
data/doc/storage/file_system.md
CHANGED
@@ -74,6 +74,15 @@ You can retrieve path to the file using `#path`:
|
|
74
74
|
storage.path("image.jpg") #=> #<Pathname:public/image.jpg>
|
75
75
|
```
|
76
76
|
|
77
|
+
## Deleting prefixed
|
78
|
+
|
79
|
+
If you want to delete all files in some directory, you can use
|
80
|
+
`FileSystem#delete_prefixed`:
|
81
|
+
|
82
|
+
```rb
|
83
|
+
storage.delete_prefixed("some_directory") # deletes all files in "some_directory/"
|
84
|
+
```
|
85
|
+
|
77
86
|
## Clearing cache
|
78
87
|
|
79
88
|
If you're using FileSystem as cache, you will probably want to periodically
|
data/doc/storage/s3.md
CHANGED
@@ -259,6 +259,15 @@ To use Amazon S3's [Transfer Acceleration] feature, set
|
|
259
259
|
Shrine::Storage::S3.new(use_accelerate_endpoint: true, **other_options)
|
260
260
|
```
|
261
261
|
|
262
|
+
## Deleting prefixed
|
263
|
+
|
264
|
+
If you want to delete all objects in some prefix, you can use
|
265
|
+
`S3#delete_prefixed`:
|
266
|
+
|
267
|
+
```rb
|
268
|
+
s3.delete_prefixed("some_prefix") # deletes all objects in "some_prefix/"
|
269
|
+
```
|
270
|
+
|
262
271
|
## Clearing cache
|
263
272
|
|
264
273
|
If you're using S3 as a cache, you will probably want to periodically delete
|
data/doc/upgrading_to_3.md
CHANGED
@@ -161,12 +161,21 @@ The `backgrounding` plugin has been rewritten in Shrine 3.0 and has a new API.
|
|
161
161
|
|
162
162
|
```rb
|
163
163
|
Shrine.plugin :backgrounding
|
164
|
-
Shrine::Attacher.promote_block
|
165
|
-
|
164
|
+
Shrine::Attacher.promote_block do
|
165
|
+
PromoteJob.perform_async(self.class.name, record.class.name, record.id, name, file_data)
|
166
|
+
end
|
167
|
+
Shrine::Attacher.destroy_block do
|
168
|
+
DestroyJob.perform_async(self.class.name, data)
|
169
|
+
end
|
166
170
|
```
|
167
171
|
```rb
|
168
|
-
class PromoteJob
|
169
|
-
|
172
|
+
class PromoteJob
|
173
|
+
include Sidekiq::Worker
|
174
|
+
|
175
|
+
def perform(attacher_class, record_class, record_id, name, file_data)
|
176
|
+
attacher_class = Object.const_get(attacher_class)
|
177
|
+
record = Object.const_get(record_class).find(record_id) # if using Active Record
|
178
|
+
|
170
179
|
attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
|
171
180
|
attacher.atomic_promote
|
172
181
|
rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
|
@@ -175,8 +184,12 @@ class PromoteJob < ActiveJob::Base
|
|
175
184
|
end
|
176
185
|
```
|
177
186
|
```rb
|
178
|
-
class DestroyJob
|
187
|
+
class DestroyJob
|
188
|
+
include Sidekiq::Worker
|
189
|
+
|
179
190
|
def perform(attacher_class, data)
|
191
|
+
attacher_class = Object.const_get(attacher_class)
|
192
|
+
|
180
193
|
attacher = attacher_class.from_data(data)
|
181
194
|
attacher.destroy
|
182
195
|
end
|
@@ -191,16 +204,21 @@ both argument formats, and then switch to the new one once the jobs with old
|
|
191
204
|
format have been drained.
|
192
205
|
|
193
206
|
```rb
|
194
|
-
class PromoteJob
|
207
|
+
class PromoteJob
|
208
|
+
include Sidekiq::Worker
|
209
|
+
|
195
210
|
def perform(*args)
|
196
211
|
if args.one?
|
197
212
|
file_data, (record_class, record_id), name, shrine_class =
|
198
213
|
args.first.values_at("attachment", "record", "name", "shrine_class")
|
199
214
|
|
200
|
-
record = Object.const_get(record_class).find(record_id)
|
215
|
+
record = Object.const_get(record_class).find(record_id) # if using Active Record
|
201
216
|
attacher_class = Object.const_get(shrine_class)::Attacher
|
202
217
|
else
|
203
|
-
attacher_class,
|
218
|
+
attacher_class, record_class, record_id, name, file_data = args
|
219
|
+
|
220
|
+
attacher_class = Object.const_get(attacher_class)
|
221
|
+
record = Object.const_get(record_class).find(record_id) # if using Active Record
|
204
222
|
end
|
205
223
|
|
206
224
|
attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
|
@@ -211,7 +229,9 @@ class PromoteJob < ActiveJob::Base
|
|
211
229
|
and
|
212
230
|
```
|
213
231
|
```rb
|
214
|
-
class DestroyJob
|
232
|
+
class DestroyJob
|
233
|
+
include Sidekiq::Worker
|
234
|
+
|
215
235
|
def perform(*args)
|
216
236
|
if args.one?
|
217
237
|
data, shrine_class = args.first.values_at("attachment", "shrine_class")
|
@@ -220,6 +240,8 @@ class DestroyJob < ActiveJob::Base
|
|
220
240
|
attacher_class = Object.const_get(shrine_class)::Attacher
|
221
241
|
else
|
222
242
|
attacher_class, data = args
|
243
|
+
|
244
|
+
attacher_class = Object.const_get(attacher_class)
|
223
245
|
end
|
224
246
|
|
225
247
|
attacher = attacher_class.from_data(data)
|
@@ -393,8 +415,13 @@ If you're using the `backgrounding` plugin, you can trigger derivatives
|
|
393
415
|
creation in the `PromoteJob` instead of the controller:
|
394
416
|
|
395
417
|
```rb
|
396
|
-
class PromoteJob
|
397
|
-
|
418
|
+
class PromoteJob
|
419
|
+
include Sidekiq::Worker
|
420
|
+
|
421
|
+
def perform(attacher_class, record_class, record.id, name, file_data)
|
422
|
+
attacher_class = Object.const_get(attacher_class)
|
423
|
+
record = Object.const_get(record_class).find(record_id) # if using Active Record
|
424
|
+
|
398
425
|
attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
|
399
426
|
attacher.create_derivatives # call derivatives processor
|
400
427
|
attacher.atomic_promote
|
data/lib/shrine.rb
CHANGED
@@ -273,7 +273,7 @@ class Shrine
|
|
273
273
|
# Generates a basic location for an uploaded file
|
274
274
|
def basic_location(io, metadata:)
|
275
275
|
extension = ".#{io.extension}" if io.is_a?(UploadedFile) && io.extension
|
276
|
-
extension ||= File.extname(
|
276
|
+
extension ||= File.extname(metadata["filename"].to_s).downcase
|
277
277
|
basename = generate_uid(io)
|
278
278
|
|
279
279
|
basename + extension
|
@@ -34,9 +34,17 @@ class Shrine
|
|
34
34
|
record.public_send(opts[:pretty_location][:identifier])
|
35
35
|
end
|
36
36
|
|
37
|
+
def transform_class_name(class_name)
|
38
|
+
if opts[:pretty_location][:class_underscore]
|
39
|
+
class_name.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').gsub(/([a-z])([A-Z])/, '\1_\2').downcase
|
40
|
+
else
|
41
|
+
class_name.downcase
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
37
45
|
def record_namespace(record)
|
38
46
|
class_name = record.class.name or return
|
39
|
-
parts = class_name.
|
47
|
+
parts = transform_class_name(class_name).split("::")
|
40
48
|
|
41
49
|
if separator = opts[:pretty_location][:namespace]
|
42
50
|
parts.join(separator)
|
@@ -66,12 +66,12 @@ class Shrine
|
|
66
66
|
|
67
67
|
def download_remote_url(url, options)
|
68
68
|
opts[:remote_url][:downloader].call(url, options)
|
69
|
-
rescue Down::NotFound
|
70
|
-
fail DownloadError, "remote file not found"
|
71
69
|
rescue Down::TooLarge
|
72
70
|
fail DownloadError, "remote file too large"
|
71
|
+
rescue Down::Error
|
72
|
+
fail DownloadError, "remote file not found"
|
73
73
|
rescue DownloadError
|
74
|
-
fail
|
74
|
+
fail # re-raise
|
75
75
|
end
|
76
76
|
|
77
77
|
# Sends a `remote_url.shrine` event for instrumentation plugin.
|
@@ -70,6 +70,16 @@ class Shrine
|
|
70
70
|
path(id).exist?
|
71
71
|
end
|
72
72
|
|
73
|
+
# If #prefix is not present, returns a path composed of #directory and
|
74
|
+
# the given `id`. If #prefix is present, it excludes the #directory part
|
75
|
+
# from the returned path (e.g. #directory can be set to "public" folder).
|
76
|
+
# Both cases accept a `:host` value which will be prefixed to the
|
77
|
+
# generated path.
|
78
|
+
def url(id, host: nil, **options)
|
79
|
+
path = (prefix ? relative_path(id) : path(id)).to_s
|
80
|
+
host ? host + path : path
|
81
|
+
end
|
82
|
+
|
73
83
|
# Delets the file, and by default deletes the containing directory if
|
74
84
|
# it's empty.
|
75
85
|
def delete(id)
|
@@ -79,14 +89,11 @@ class Shrine
|
|
79
89
|
rescue Errno::ENOENT
|
80
90
|
end
|
81
91
|
|
82
|
-
#
|
83
|
-
#
|
84
|
-
#
|
85
|
-
|
86
|
-
|
87
|
-
def url(id, host: nil, **options)
|
88
|
-
path = (prefix ? relative_path(id) : path(id)).to_s
|
89
|
-
host ? host + path : path
|
92
|
+
# Deletes the specified directory on the filesystem.
|
93
|
+
#
|
94
|
+
# file_system.delete_prefixed("somekey/derivatives")
|
95
|
+
def delete_prefixed(delete_prefix)
|
96
|
+
FileUtils.rm_rf directory.join(delete_prefix)
|
90
97
|
end
|
91
98
|
|
92
99
|
# Deletes all files from the #directory. If a block is passed in, deletes
|
@@ -1,5 +1,3 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
1
|
require "shrine"
|
4
2
|
|
5
3
|
require "forwardable"
|
@@ -30,19 +28,32 @@ class Shrine
|
|
30
28
|
new(*args).call
|
31
29
|
end
|
32
30
|
|
33
|
-
def initialize(storage, action: :error)
|
34
|
-
@storage
|
35
|
-
@action
|
31
|
+
def initialize(storage, action: :error, nonexisting: "nonexisting")
|
32
|
+
@storage = storage
|
33
|
+
@action = action
|
34
|
+
@nonexisting = nonexisting
|
36
35
|
end
|
37
36
|
|
38
37
|
def call(io_factory = default_io_factory)
|
39
|
-
storage.upload(io_factory.call, id = "foo"
|
38
|
+
storage.upload(io_factory.call, id = "foo", {})
|
40
39
|
|
41
40
|
lint_open(id)
|
42
41
|
lint_exists(id)
|
43
42
|
lint_url(id)
|
44
43
|
lint_delete(id)
|
45
44
|
|
45
|
+
if storage.respond_to?(:delete_prefixed)
|
46
|
+
storage.upload(io_factory.call, id1 = "a/a/a")
|
47
|
+
storage.upload(io_factory.call, id2 = "a/a/b")
|
48
|
+
storage.upload(io_factory.call, id3 = "a/aaa/a")
|
49
|
+
|
50
|
+
lint_delete_prefixed(prefix: "a/a",
|
51
|
+
expect_deleted: [id1, id2],
|
52
|
+
expect_remaining: [id3])
|
53
|
+
|
54
|
+
storage.delete(id3)
|
55
|
+
end
|
56
|
+
|
46
57
|
if storage.respond_to?(:clear!)
|
47
58
|
storage.upload(io_factory.call, id = "quux".dup)
|
48
59
|
lint_clear(id)
|
@@ -51,6 +62,8 @@ class Shrine
|
|
51
62
|
if storage.respond_to?(:presign)
|
52
63
|
lint_presign(id)
|
53
64
|
end
|
65
|
+
|
66
|
+
true
|
54
67
|
end
|
55
68
|
|
56
69
|
def lint_open(id)
|
@@ -60,7 +73,7 @@ class Shrine
|
|
60
73
|
opened.close
|
61
74
|
|
62
75
|
begin
|
63
|
-
storage.open(
|
76
|
+
storage.open(@nonexisting, {})
|
64
77
|
error :open, "should raise an exception on nonexisting file"
|
65
78
|
rescue Shrine::FileNotFound
|
66
79
|
rescue => exception
|
@@ -100,6 +113,20 @@ class Shrine
|
|
100
113
|
error :presign, "result should include :url key" unless data.to_h.key?(:url)
|
101
114
|
end
|
102
115
|
|
116
|
+
def lint_delete_prefixed(prefix:, expect_deleted:, expect_remaining:)
|
117
|
+
storage.delete_prefixed(prefix)
|
118
|
+
|
119
|
+
expect_deleted.each do |key|
|
120
|
+
next unless storage.exists?(key)
|
121
|
+
error :delete_prefixed, "#{key} still #exists? after #clear_prefix('a/a/')"
|
122
|
+
end
|
123
|
+
|
124
|
+
expect_remaining.each do |key|
|
125
|
+
next if storage.exists?(key)
|
126
|
+
error :delete_prefixed, "#{key} doesn't #exists? but should after #clear_prefix('a/a/')"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
103
130
|
private
|
104
131
|
|
105
132
|
attr_reader :storage
|
@@ -26,12 +26,16 @@ class Shrine
|
|
26
26
|
store.key?(id)
|
27
27
|
end
|
28
28
|
|
29
|
+
def url(id, *)
|
30
|
+
"memory://#{id}"
|
31
|
+
end
|
32
|
+
|
29
33
|
def delete(id)
|
30
34
|
store.delete(id)
|
31
35
|
end
|
32
36
|
|
33
|
-
def
|
34
|
-
"
|
37
|
+
def delete_prefixed(delete_prefix)
|
38
|
+
store.delete_if { |k, _v| k.start_with?(delete_prefix + "/") }
|
35
39
|
end
|
36
40
|
|
37
41
|
def clear!
|
data/lib/shrine/storage/s3.rb
CHANGED
@@ -205,6 +205,16 @@ class Shrine
|
|
205
205
|
object(id).delete
|
206
206
|
end
|
207
207
|
|
208
|
+
# Deletes objects at keys starting with the specified prefix.
|
209
|
+
#
|
210
|
+
# s3.delete_prefixed("somekey/derivatives")
|
211
|
+
def delete_prefixed(delete_prefix)
|
212
|
+
# We need to make sure to combine with storage prefix, and
|
213
|
+
# that it ends in '/' cause S3 can be squirrely about matching interior.
|
214
|
+
absolute_prefix = [*prefix, delete_prefix + "/"].join("/")
|
215
|
+
bucket.objects(prefix: absolute_prefix).batch_delete!
|
216
|
+
end
|
217
|
+
|
208
218
|
# If block is given, deletes all objects from the storage for which the
|
209
219
|
# block evaluates to true. Otherwise deletes all objects from the storage.
|
210
220
|
#
|
data/lib/shrine/version.rb
CHANGED
data/shrine.gemspec
CHANGED
@@ -33,7 +33,7 @@ direct uploads for fully asynchronous user experience.
|
|
33
33
|
gem.files = Dir["README.md", "LICENSE.txt", "CHANGELOG.md", "lib/**/*.rb", "shrine.gemspec", "doc/**/*.md"]
|
34
34
|
gem.require_path = "lib"
|
35
35
|
|
36
|
-
gem.add_dependency "down", "~>
|
36
|
+
gem.add_dependency "down", "~> 5.0"
|
37
37
|
gem.add_dependency "content_disposition", "~> 1.0"
|
38
38
|
|
39
39
|
# general testing helpers
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shrine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.0.0.
|
4
|
+
version: 3.0.0.rc
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Janko Marohnić
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-09-
|
11
|
+
date: 2019-09-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: down
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '5.0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '5.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: content_disposition
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|