shrine 2.12.0 → 2.13.0
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 +30 -0
- data/README.md +153 -41
- data/doc/advantages.md +96 -106
- data/doc/attacher.md +55 -18
- data/doc/design.md +16 -5
- data/doc/direct_s3.md +132 -113
- data/doc/metadata.md +82 -27
- data/doc/multiple_files.md +76 -33
- data/doc/processing.md +2 -11
- data/doc/testing.md +2 -2
- data/lib/shrine.rb +18 -10
- data/lib/shrine/plugins/determine_mime_type.rb +6 -1
- data/lib/shrine/plugins/download_endpoint.rb +3 -0
- data/lib/shrine/plugins/infer_extension.rb +25 -10
- data/lib/shrine/plugins/module_include.rb +1 -1
- data/lib/shrine/plugins/presign_endpoint.rb +10 -8
- data/lib/shrine/plugins/rack_file.rb +8 -0
- data/lib/shrine/plugins/store_dimensions.rb +1 -1
- data/lib/shrine/plugins/upload_endpoint.rb +8 -4
- data/lib/shrine/plugins/versions.rb +1 -2
- data/lib/shrine/storage/file_system.rb +6 -4
- data/lib/shrine/storage/s3.rb +89 -82
- data/lib/shrine/version.rb +1 -1
- data/shrine.gemspec +3 -2
- metadata +22 -8
data/doc/metadata.md
CHANGED
@@ -14,8 +14,8 @@ uploaded_file.metadata #=>
|
|
14
14
|
# }
|
15
15
|
```
|
16
16
|
|
17
|
-
|
18
|
-
any IO object.
|
17
|
+
Under the hood `Shrine#extract_metadata` is called, which you can also use
|
18
|
+
directly to extract metadata from any IO object.
|
19
19
|
|
20
20
|
```rb
|
21
21
|
uploader.extract_metadata(io) #=>
|
@@ -26,6 +26,15 @@ uploader.extract_metadata(io) #=>
|
|
26
26
|
# }
|
27
27
|
```
|
28
28
|
|
29
|
+
Note that you can also manually add or override metadata on upload by passing
|
30
|
+
the `:metadata` option to `Shrine#upload`:
|
31
|
+
|
32
|
+
```rb
|
33
|
+
uploaded_file = uploader.upload(file, metadata: { "filename" => "Matrix[1999].mp4", "foo" => "bar" })
|
34
|
+
uploaded_file.original_filename #=> "Matrix[1999].mp4"
|
35
|
+
uploaded_file.metadata["foo"] #=> "bar"
|
36
|
+
```
|
37
|
+
|
29
38
|
## MIME type
|
30
39
|
|
31
40
|
By default, the `mime_type` metadata will be copied over from the
|
@@ -108,7 +117,7 @@ uploaded_file.metadata["exif"] #=> {...}
|
|
108
117
|
uploaded_file.exif #=> {...}
|
109
118
|
```
|
110
119
|
|
111
|
-
|
120
|
+
Or, if you're uploading videos, you might want to extract some video-specific
|
112
121
|
meatadata:
|
113
122
|
|
114
123
|
```rb
|
@@ -162,17 +171,31 @@ photo = Photo.new(image: file)
|
|
162
171
|
photo.image_type #=> "image/jpeg"
|
163
172
|
```
|
164
173
|
|
165
|
-
##
|
166
|
-
|
167
|
-
When
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
174
|
+
## Direct uploads
|
175
|
+
|
176
|
+
When attaching files that were uploaded directly to the cloud or a [tus
|
177
|
+
server], Shrine won't automatically extract metadata from them, instead it will
|
178
|
+
copy any existing metadata that was set on the client side. The reason why this
|
179
|
+
is the default behaviour is because extracting the metadata would require (at
|
180
|
+
least partially) retrieving file content from the storage, which could
|
181
|
+
potentially be expensive depending on the storage and the type of metadata
|
182
|
+
being extracted.
|
183
|
+
|
184
|
+
If you're just attaching files uploaded directly to local disk, there wouldn't
|
185
|
+
be any additional performance penalty for extracting metadata. However, if
|
186
|
+
you're attaching files uploaded directly to a cloud service like S3, retrieving
|
187
|
+
file content for metadata extraction would require an HTTP download. If you're
|
188
|
+
using just the `determine_mime_type` plugin, only a small portion of the file
|
189
|
+
will be downloaded, so the performance impact might not be so big. But in other
|
190
|
+
cases you might have to download the whole file.
|
191
|
+
|
192
|
+
There are two ways of extracting metadata from directly uploaded files. If you
|
193
|
+
want metadata to be automatically extracted on assignment (which is useful if
|
194
|
+
you want to validate the extracted metadata or have it immediately available),
|
195
|
+
you can load the `restore_cached_data` plugin:
|
173
196
|
|
174
197
|
```rb
|
175
|
-
Shrine.plugin :restore_cached_data # extract metadata from cached files on assingment
|
198
|
+
Shrine.plugin :restore_cached_data # automatically extract metadata from cached files on assingment
|
176
199
|
```
|
177
200
|
```rb
|
178
201
|
photo.image = '{"id":"ks9elsd.jpg","storage":"cache","metadata":{}}' # metadata is extracted
|
@@ -184,25 +207,56 @@ photo.image.metadata #=>
|
|
184
207
|
# }
|
185
208
|
```
|
186
209
|
|
187
|
-
|
188
|
-
|
189
|
-
`restore_cached_data` plugin
|
190
|
-
download the whole file from the storage, instead, it will open a connection to
|
191
|
-
the storage, and the metadata analyzers will download how much of the file they
|
192
|
-
need. Most MIME type analyzers and the FastImage dimensions analyzer need only
|
193
|
-
the first few kilobytes.
|
194
|
-
|
195
|
-
You can also extract metadata from an uploaded file explicitly using the
|
196
|
-
`refresh_metadata` plugin (which the `restore_cached_data` plugin uses
|
197
|
-
internally).
|
210
|
+
On the other hand, if you're using backgrounding, you can extract metadata
|
211
|
+
during background promotion using the `refresh_metadata` plugin (which the
|
212
|
+
`restore_cached_data` plugin uses internally):
|
198
213
|
|
199
214
|
```rb
|
200
215
|
Shrine.plugin :refresh_metadata
|
201
216
|
```
|
202
217
|
```rb
|
203
|
-
|
204
|
-
|
205
|
-
|
218
|
+
class MyUploader < Shrine
|
219
|
+
plugin :processing
|
220
|
+
|
221
|
+
# this will be called in the background if using backgrounding plugin
|
222
|
+
process(:store) do |io, context|
|
223
|
+
io.refresh_metadata! # extracts metadata and updates `io.metadata`
|
224
|
+
io
|
225
|
+
end
|
226
|
+
end
|
227
|
+
```
|
228
|
+
|
229
|
+
If you have metadata that is cheap to extract in the foreground, but also have
|
230
|
+
additional metadata that can be extracted asynchronously, you can combine the
|
231
|
+
two approaches. For example, if you're attaching video files, you might want to
|
232
|
+
extract MIME type upfront and video-specific metadata in a background job, which
|
233
|
+
can be done as follows (provided that `backgrounding` plugin is used):
|
234
|
+
|
235
|
+
```rb
|
236
|
+
Shrine.plugin :restore_cached_data
|
237
|
+
```
|
238
|
+
```rb
|
239
|
+
class MyUploader < Shrine
|
240
|
+
plugin :determine_mime_type # this will be called in the foreground
|
241
|
+
plugin :processing
|
242
|
+
|
243
|
+
# this will be called in the background if using backgrounding plugin
|
244
|
+
process(:store) do |io, context|
|
245
|
+
additional_metadata = io.download do |file|
|
246
|
+
# example of metadata extraction
|
247
|
+
movie = FFMPEG::Movie.new(file.path) # uses the streamio-ffmpeg gem
|
248
|
+
|
249
|
+
{ "duration" => movie.duration,
|
250
|
+
"bitrate" => movie.bitrate,
|
251
|
+
"resolution" => movie.resolution,
|
252
|
+
"frame_rate" => movie.frame_rate }
|
253
|
+
end
|
254
|
+
|
255
|
+
io.metadata.merge!(additional_metadata)
|
256
|
+
|
257
|
+
io
|
258
|
+
end
|
259
|
+
end
|
206
260
|
```
|
207
261
|
|
208
262
|
[`file`]: http://linux.die.net/man/1/file
|
@@ -210,4 +264,5 @@ uploaded_file.metadata #=> {"filename"=>"nature.jpg","size"=>532894,"mime_type"=
|
|
210
264
|
[Marcel]: https://github.com/basecamp/marcel
|
211
265
|
[FastImage]: https://github.com/sdsykes/fastimage
|
212
266
|
[MiniMagick]: https://github.com/minimagick/minimagick
|
213
|
-
[ruby-vips]: https://github.com/
|
267
|
+
[ruby-vips]: https://github.com/libvips/ruby-vips
|
268
|
+
[tus server]: https://github.com/janko-m/tus-ruby-server
|
data/doc/multiple_files.md
CHANGED
@@ -65,7 +65,7 @@ Let's create a table for the main resource and attachments, and add a foreign
|
|
65
65
|
key in the attachment table for the main table:
|
66
66
|
|
67
67
|
```rb
|
68
|
-
# Sequel
|
68
|
+
# with Sequel:
|
69
69
|
Sequel.migration do
|
70
70
|
change do
|
71
71
|
create_table :albums do
|
@@ -81,7 +81,7 @@ Sequel.migration do
|
|
81
81
|
end
|
82
82
|
end
|
83
83
|
|
84
|
-
# Active Record
|
84
|
+
# with Active Record:
|
85
85
|
class CreateAlbumsAndPhotos < ActiveRecord::Migration
|
86
86
|
def change
|
87
87
|
create_table :albums do |t|
|
@@ -102,12 +102,12 @@ In the Photo model, create a Shrine attachment attribute named `image`
|
|
102
102
|
(`:image` matches the `_data` column prefix above):
|
103
103
|
|
104
104
|
```rb
|
105
|
-
# Sequel
|
105
|
+
# with Sequel:
|
106
106
|
class Photo < Sequel::Model
|
107
107
|
include ImageUploader::Attachment.new(:image)
|
108
108
|
end
|
109
109
|
|
110
|
-
# Active Record
|
110
|
+
# with Active Record:
|
111
111
|
class Photo < ActiveRecord::Base
|
112
112
|
include ImageUploader::Attachment.new(:image)
|
113
113
|
end
|
@@ -121,7 +121,7 @@ relationship to the photos table, and allow it to directly accept attributes
|
|
121
121
|
for the associated photo records by enabling nested attributes:
|
122
122
|
|
123
123
|
```rb
|
124
|
-
# Sequel
|
124
|
+
# with Sequel:
|
125
125
|
class Album < Sequel::Model
|
126
126
|
one_to_many :photos
|
127
127
|
plugin :association_dependencies, photos: :destroy # destroy photos when album is destroyed
|
@@ -130,7 +130,7 @@ class Album < Sequel::Model
|
|
130
130
|
nested_attributes :photos, destroy: true
|
131
131
|
end
|
132
132
|
|
133
|
-
# Active Record
|
133
|
+
# with Active Record:
|
134
134
|
class Album < ActiveRecord::Base
|
135
135
|
has_many :photos, dependent: :destroy
|
136
136
|
accepts_nested_attributes_for :photos, allow_destroy: true
|
@@ -141,6 +141,7 @@ Documentation on nested attributes:
|
|
141
141
|
|
142
142
|
* [`Sequel::Model.nested_attributes`]
|
143
143
|
* [`ActiveRecord::Base.accepts_nested_attributes_for`]
|
144
|
+
* [`Mongoid::Document.accepts_nested_attributes_for`]
|
144
145
|
|
145
146
|
### 3. Create the View
|
146
147
|
|
@@ -165,7 +166,7 @@ form @album, action: "/photos", enctype: "multipart/form-data" do |f|
|
|
165
166
|
end
|
166
167
|
|
167
168
|
# with Rails form builder:
|
168
|
-
form_for @album do |f|
|
169
|
+
form_for @album, html: { enctype: "multipart/form-data" } do |f|
|
169
170
|
f.text_field :title
|
170
171
|
f.fields_for :photos do |p| # adds new `album[photos_attributes]` parameter
|
171
172
|
p.hidden_field :image, value: p.object.cached_image_data
|
@@ -179,37 +180,69 @@ end
|
|
179
180
|
|
180
181
|
In your controller you should still be able to assign all the attributes to the
|
181
182
|
album, just remember to whitelist the new parameter for the nested attributes,
|
182
|
-
in this case `photos_attributes`.
|
183
|
+
in this case `album[photos_attributes]`.
|
183
184
|
|
184
|
-
###
|
185
|
+
### 4a. Form upload
|
185
186
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
187
|
+
If you would like to avoid writing JavaScript, you can have selected files
|
188
|
+
uploaded via the form, and then in your controller you can assign them in the
|
189
|
+
format of nested attributes. You'll want to merge these newly uploaded photos
|
190
|
+
with the existing photos params that will come from the nested form fields.
|
191
|
+
This could look something like this:
|
192
|
+
|
193
|
+
```rb
|
194
|
+
# In your controller:
|
195
|
+
|
196
|
+
# transform the list of uploaded files into a photos attributes hash
|
197
|
+
new_photos_attributes = params[:files].inject({}) do |hash, file|
|
198
|
+
hash.merge!(SecureRandom.hex => { image: file })
|
199
|
+
end
|
200
|
+
|
201
|
+
# merge new photos attributes with existing (`album_params` is whitelisted `params[:album]`)
|
202
|
+
photos_attributes = album_params[:photos_attributes].to_h.merge(new_photos_attributes)
|
203
|
+
album_attributes = album_params.merge(photos_attributes: photos_attributes)
|
204
|
+
|
205
|
+
# create the album with photos
|
206
|
+
Album.create(album_params)
|
207
|
+
```
|
208
|
+
|
209
|
+
In this case you need to make sure your form tag has
|
210
|
+
`enctype="multipart/form-data"` attributes specified, otherwise form upload
|
211
|
+
won't succeed.
|
212
|
+
|
213
|
+
### 4b. Direct upload
|
214
|
+
|
215
|
+
Alternatively, you can have selected files immediately uploaded to a direct
|
216
|
+
upload endpoint, as soon as they're selected. It's recommended to use [Uppy]
|
217
|
+
for handling client side file uploads. There are two methods of implementing
|
218
|
+
direct uploads: to your app using the [`upload_endpoint`] plugin, or directly
|
219
|
+
to a storage like S3 using the [`presign_endpoint`] plugin. See the
|
220
|
+
walkthroughs for setting up simple [direct app uploads] and [direct S3
|
221
|
+
uploads], as well as the [Roda][roda demo] or [Rails][rails demo] demo app
|
222
|
+
which implement multiple file uploads.
|
191
223
|
|
192
224
|
Once files are uploaded asynchronously, you can dynamically insert photo
|
193
|
-
attachment fields for the `image` attribute
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
`album[photos_attributes][<idx>]`, where `<idx>` is any incrementing integer
|
200
|
-
(e.g. you can use the current UNIX timestamp).
|
225
|
+
attachment fields for the `image` attachment attribute into the form, where the
|
226
|
+
hidden field is filled with uploaded file data in JSON format, just like when
|
227
|
+
doing single direct uploads. The attachment field names should be namespaced
|
228
|
+
according to the convention that the nested attributes feature expects. In this
|
229
|
+
case it should be `album[photos_attributes][<uid>]`, where `<uid>` is any
|
230
|
+
unique string.
|
201
231
|
|
202
232
|
```rb
|
203
233
|
# naming format in which photos fields should be generated and submitted
|
204
234
|
album[photos_attributes][11111][image] = '{"id":"38k25.jpg","storage":"cache","metadata":{...}}'
|
205
235
|
album[photos_attributes][29323][image] = '{"id":"sg0fg.jpg","storage":"cache","metadata":{...}}'
|
206
236
|
album[photos_attributes][34820][image] = '{"id":"041jd.jpg","storage":"cache","metadata":{...}}'
|
207
|
-
# ...
|
208
237
|
```
|
209
238
|
|
210
|
-
|
211
|
-
|
212
|
-
|
239
|
+
In your controller you don't need to make any changes to handle the nested
|
240
|
+
photos attributes (other than allowing them), your ORM will take care of
|
241
|
+
creating, updating, or removing the associated photos.
|
242
|
+
|
243
|
+
```rb
|
244
|
+
Album.create(album_params) # or `params[:album]`
|
245
|
+
```
|
213
246
|
|
214
247
|
### 5. Adding Validations
|
215
248
|
|
@@ -229,28 +262,38 @@ class ImageUploader < Shrine
|
|
229
262
|
end
|
230
263
|
```
|
231
264
|
```rb
|
232
|
-
# Sequel
|
265
|
+
# with Sequel:
|
233
266
|
class Album < Sequel::Model
|
234
267
|
# ... (nested_attributes already enables validating associated photos) ...
|
235
268
|
end
|
236
269
|
|
237
|
-
# ActiveRecord
|
270
|
+
# with ActiveRecord:
|
238
271
|
class Album < ActiveRecord::Base
|
239
272
|
# ...
|
240
273
|
validates_associated :photos
|
241
274
|
end
|
242
275
|
```
|
243
276
|
|
244
|
-
|
277
|
+
Note that by default only metadata set on the client side will be available for
|
278
|
+
validations. Shrine will not automatically run metadata extraction for directly
|
279
|
+
uploaded files, as this would require (at least partially) downloading each
|
280
|
+
file from storage, which could be an unwanted performance penalty. If you want
|
281
|
+
metadata to be extracted on the server side, you can load the
|
282
|
+
`restore_cached_data` plugin which will make metadata extraction run on
|
283
|
+
assignment, or you can extract the metadata manually (e.g. in a background job)
|
284
|
+
using the `refresh_metadata` plugin.
|
285
|
+
|
286
|
+
### Conclusion
|
245
287
|
|
246
288
|
Now we have a simple interface for accepting multiple attachments, which
|
247
|
-
internally uses nested attributes to create multiple
|
248
|
-
with a single attachment. After creation you can also
|
249
|
-
update and delete existing ones, which the nested
|
250
|
-
for free.
|
289
|
+
internally uses the nested attributes feature of your ORM to create multiple
|
290
|
+
associated records, each with a single attachment. After creation you can also
|
291
|
+
add new attachments, or update and delete existing ones, which the nested
|
292
|
+
attributes feature gives you for free.
|
251
293
|
|
252
294
|
[`Sequel::Model.nested_attributes`]: http://sequel.jeremyevans.net/rdoc-plugins/classes/Sequel/Plugins/NestedAttributes.html
|
253
295
|
[`ActiveRecord::Base.accepts_nested_attributes_for`]: http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
|
296
|
+
[`Mongoid::Document.accepts_nested_attributes_for`]: https://docs.mongodb.com/mongoid/master/tutorials/mongoid-nested-attributes/
|
254
297
|
[`upload_endpoint`]: https://shrinerb.com/rdoc/classes/Shrine/Plugins/UploadEndpoint.html
|
255
298
|
[`presign_endpoint`]: https://shrinerb.com/rdoc/classes/Shrine/Plugins/PresignEndpoint.html
|
256
299
|
[Uppy]: https://uppy.io
|
data/doc/processing.md
CHANGED
@@ -253,15 +253,6 @@ generic gem.
|
|
253
253
|
To demonstrate, here is an example of transcoding videos using
|
254
254
|
[streamio-ffmpeg]:
|
255
255
|
|
256
|
-
```sh
|
257
|
-
$ brew install ffmpeg
|
258
|
-
```
|
259
|
-
|
260
|
-
```rb
|
261
|
-
# Gemfile
|
262
|
-
gem "streamio-ffmpeg"
|
263
|
-
```
|
264
|
-
|
265
256
|
```rb
|
266
257
|
require "streamio-ffmpeg"
|
267
258
|
|
@@ -404,8 +395,8 @@ photo.image.url(width: 100, height: 100, crop: :fit)
|
|
404
395
|
[`ImageProcessing::MiniMagick`]: https://github.com/janko-m/image_processing/blob/master/doc/minimagick.md
|
405
396
|
[ImageMagick]: https://www.imagemagick.org
|
406
397
|
[GraphicsMagick]: http://www.graphicsmagick.org
|
407
|
-
[libvips]: http://
|
408
|
-
[Why is libvips quick]: https://github.com/
|
398
|
+
[libvips]: http://libvips.github.io/libvips/
|
399
|
+
[Why is libvips quick]: https://github.com/libvips/libvips/wiki/Why-is-libvips-quick
|
409
400
|
[ImageOptim.com]: https://imageoptim.com/api
|
410
401
|
[Down]: https://github.com/janko-m/down
|
411
402
|
[HTTP.rb]: https://github.com/httprb/http
|
data/doc/testing.md
CHANGED
@@ -54,8 +54,8 @@ Shrine.storages = {
|
|
54
54
|
}
|
55
55
|
```
|
56
56
|
|
57
|
-
If you're using AWS S3 storage,
|
58
|
-
|
57
|
+
If you're using AWS S3 storage, you can use [Minio] (explained below) instead
|
58
|
+
of S3, both in test and development environment. Alternatively, you can [stub
|
59
59
|
aws-sdk-s3 requests][aws-sdk-ruby stubs] in tests.
|
60
60
|
|
61
61
|
### Minio
|
data/lib/shrine.rb
CHANGED
@@ -13,7 +13,8 @@ class Shrine
|
|
13
13
|
# Raised when a file is not a valid IO.
|
14
14
|
class InvalidFile < Error
|
15
15
|
def initialize(io, missing_methods)
|
16
|
-
super "#{io.inspect} is not a valid IO object (it doesn't respond to
|
16
|
+
super "#{io.inspect} is not a valid IO object (it doesn't respond to \
|
17
|
+
#{missing_methods.map{|m, _|"##{m}"}.join(", ")})"
|
17
18
|
end
|
18
19
|
end
|
19
20
|
|
@@ -218,7 +219,7 @@ class Shrine
|
|
218
219
|
|
219
220
|
# The main method for uploading files. Takes an IO-like object and an
|
220
221
|
# optional context hash (used internally by Shrine::Attacher). It calls
|
221
|
-
# user-defined #process, and
|
222
|
+
# user-defined #process, and afterwards it calls #store. The `io` is
|
222
223
|
# closed after upload.
|
223
224
|
def upload(io, context = {})
|
224
225
|
io = processed(io, context) || io
|
@@ -311,7 +312,10 @@ class Shrine
|
|
311
312
|
# file that was uploaded.
|
312
313
|
def _store(io, context)
|
313
314
|
_enforce_io(io)
|
315
|
+
|
314
316
|
metadata = get_metadata(io, context)
|
317
|
+
metadata = metadata.merge(context[:metadata]) if context[:metadata]
|
318
|
+
|
315
319
|
location = get_location(io, context.merge(metadata: metadata))
|
316
320
|
|
317
321
|
put(io, context.merge(location: location, metadata: metadata))
|
@@ -527,21 +531,22 @@ class Shrine
|
|
527
531
|
# already cached file as a JSON string, otherwise it assumes that it's
|
528
532
|
# an IO object and uploads it to the temporary storage. The cached file
|
529
533
|
# is then written to the attachment attribute in the JSON format.
|
530
|
-
def assign(value)
|
534
|
+
def assign(value, **options)
|
531
535
|
if value.is_a?(String)
|
532
536
|
return if value == "" || !cache.uploaded?(uploaded_file(value))
|
533
537
|
assign_cached(uploaded_file(value))
|
534
538
|
else
|
535
|
-
uploaded_file = cache!(value, action: :cache) if value
|
539
|
+
uploaded_file = cache!(value, action: :cache, **options) if value
|
536
540
|
set(uploaded_file)
|
537
541
|
end
|
538
542
|
end
|
539
543
|
|
540
|
-
# Accepts a Shrine::UploadedFile object and writes
|
544
|
+
# Accepts a Shrine::UploadedFile object and writes it to the attachment
|
541
545
|
# attribute. It then runs file validations, and records that the
|
542
546
|
# attachment has changed.
|
543
547
|
def set(uploaded_file)
|
544
|
-
|
548
|
+
file = get
|
549
|
+
@old = file unless uploaded_file == file
|
545
550
|
_set(uploaded_file)
|
546
551
|
validate
|
547
552
|
end
|
@@ -601,7 +606,8 @@ class Shrine
|
|
601
606
|
# Deletes the current attachment, typically called after destroying the
|
602
607
|
# record.
|
603
608
|
def destroy
|
604
|
-
|
609
|
+
file = get
|
610
|
+
_delete(file, action: :destroy) if file && !cache.uploaded?(file)
|
605
611
|
end
|
606
612
|
|
607
613
|
# Delegates to #delete!, overriden for backgrounding.
|
@@ -617,12 +623,14 @@ class Shrine
|
|
617
623
|
|
618
624
|
# Returns true if attachment is present and cached.
|
619
625
|
def cached?
|
620
|
-
|
626
|
+
file = get
|
627
|
+
file && cache.uploaded?(file)
|
621
628
|
end
|
622
629
|
|
623
630
|
# Returns true if attachment is present and stored.
|
624
631
|
def stored?
|
625
|
-
|
632
|
+
file = get
|
633
|
+
file && store.uploaded?(file)
|
626
634
|
end
|
627
635
|
|
628
636
|
# Returns a Shrine::UploadedFile instantiated from the data written to
|
@@ -681,7 +689,7 @@ class Shrine
|
|
681
689
|
set(cached_file)
|
682
690
|
end
|
683
691
|
|
684
|
-
# Writes the uploaded file the attachment attribute. Overriden in ORM
|
692
|
+
# Writes the uploaded file to the attachment attribute. Overriden in ORM
|
685
693
|
# plugins to additionally save the model instance.
|
686
694
|
def update(uploaded_file)
|
687
695
|
_set(uploaded_file)
|