shrine 2.1.1 → 2.2.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/README.md +214 -248
- data/doc/carrierwave.md +11 -22
- data/doc/changing_location.md +3 -3
- data/doc/creating_storages.md +48 -21
- data/doc/direct_s3.md +37 -29
- data/doc/paperclip.md +7 -9
- data/doc/refile.md +8 -11
- data/doc/regenerating_versions.md +14 -19
- data/lib/shrine.rb +53 -23
- data/lib/shrine/plugins/activerecord.rb +36 -15
- data/lib/shrine/plugins/add_metadata.rb +50 -0
- data/lib/shrine/plugins/backgrounding.rb +22 -13
- data/lib/shrine/plugins/backup.rb +4 -3
- data/lib/shrine/plugins/data_uri.rb +1 -1
- data/lib/shrine/plugins/delete_promoted.rb +1 -1
- data/lib/shrine/plugins/delete_raw.rb +1 -1
- data/lib/shrine/plugins/determine_mime_type.rb +12 -3
- data/lib/shrine/plugins/direct_upload.rb +55 -65
- data/lib/shrine/plugins/download_endpoint.rb +7 -3
- data/lib/shrine/plugins/logging.rb +1 -1
- data/lib/shrine/plugins/moving.rb +6 -5
- data/lib/shrine/plugins/processing.rb +50 -0
- data/lib/shrine/plugins/rack_file.rb +3 -0
- data/lib/shrine/plugins/recache.rb +8 -12
- data/lib/shrine/plugins/remove_invalid.rb +1 -1
- data/lib/shrine/plugins/restore_cached_data.rb +1 -3
- data/lib/shrine/plugins/sequel.rb +40 -19
- data/lib/shrine/plugins/versions.rb +22 -23
- data/lib/shrine/storage/file_system.rb +0 -5
- data/lib/shrine/storage/linter.rb +4 -9
- data/lib/shrine/storage/s3.rb +28 -13
- data/lib/shrine/version.rb +2 -2
- data/shrine.gemspec +3 -2
- metadata +24 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 06b571aff1653427247e09a1ec98f9e11a10eb34
|
4
|
+
data.tar.gz: cbdd5d75713e42ea5aa449eb1c0a604b2eae35a9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 44c88e4f5bb848bc8854cde7ff4eb0fd3209f3b829998cad60377bb975816d94fce356ed8728f1ff86152523aa0e735e07dd6c9d1e008000170d312565e799d1
|
7
|
+
data.tar.gz: 056383f1b82fb43ecd62d3a43923c9864323c8c70ace4532ee35e293d94c74ae86b190d83f2870b4b44f7ee005d7688e443bb783b34cc38a8976bfdb5b6c5fe3
|
data/README.md
CHANGED
@@ -59,7 +59,7 @@ class Photo < Sequel::Model # ActiveRecord::Base
|
|
59
59
|
end
|
60
60
|
```
|
61
61
|
|
62
|
-
|
62
|
+
And add attachment fields to the Photo form:
|
63
63
|
|
64
64
|
```erb
|
65
65
|
<form action="/photos" method="post" enctype="multipart/form-data">
|
@@ -75,220 +75,132 @@ Finally, you can add the attachment fields to your form:
|
|
75
75
|
<% end %>
|
76
76
|
```
|
77
77
|
|
78
|
-
|
78
|
+
Now when a Photo is created with the image attached, you can get the URL to
|
79
|
+
the image:
|
79
80
|
|
80
|
-
|
81
|
-
|
82
|
-
Here's an example showing how basic file upload works in Shrine:
|
83
|
-
|
84
|
-
```rb
|
85
|
-
require "shrine"
|
86
|
-
require "shrine/storage/file_system"
|
87
|
-
|
88
|
-
Shrine.storages[:file_system] = Shrine::Storage::FileSystem.new("uploads")
|
89
|
-
|
90
|
-
uploader = Shrine.new(:file_system)
|
91
|
-
|
92
|
-
uploaded_file = uploader.upload(File.open("movie.mp4"))
|
93
|
-
uploaded_file #=> #<Shrine::UploadedFile>
|
94
|
-
uploaded_file.data #=>
|
95
|
-
# {
|
96
|
-
# "storage" => "file_system",
|
97
|
-
# "id" => "9260ea09d8effd.mp4",
|
98
|
-
# "metadata" => {...},
|
99
|
-
# }
|
100
|
-
```
|
101
|
-
|
102
|
-
Let's see what's going on here:
|
103
|
-
|
104
|
-
First we registered the storage we want to use under a name. Storages are plain
|
105
|
-
Ruby classes which encapsulate file management on a particular service. We can
|
106
|
-
then instantiate `Shrine` as a wrapper around that storage. A call to `upload`
|
107
|
-
uploads the given file to the underlying storage.
|
108
|
-
|
109
|
-
The argument to `upload` needs to be an IO-like object. So, `File`, `Tempfile`
|
110
|
-
and `StringIO` are all valid arguments. The object doesn't have to be an actual
|
111
|
-
IO, though, it's enough that it responds to these 5 methods: `#read(*args)`,
|
112
|
-
`#size`, `#eof?`, `#rewind` and `#close`. `ActionDispatch::Http::UploadedFile`
|
113
|
-
is one such object, as well as `Shrine::UploadedFile` itself.
|
114
|
-
|
115
|
-
The result of uploading is a `Shrine::UploadedFile` object, which represents
|
116
|
-
the uploaded file on the storage. It is defined solely by its data hash. We can
|
117
|
-
do a lot with it:
|
118
|
-
|
119
|
-
```rb
|
120
|
-
uploaded_file.url #=> "uploads/938kjsdf932.mp4"
|
121
|
-
uploaded_file.metadata #=> {...}
|
122
|
-
uploaded_file.read #=> "..."
|
123
|
-
uploaded_file.exists? #=> true
|
124
|
-
uploaded_file.download #=> #<Tempfile:/var/folders/k7/6zx6dx6x7ys3rv3srh0nyfj00000gn/T/20151004-74201-1t2jacf.mp4>
|
125
|
-
uploaded_file.delete
|
126
|
-
# ...
|
81
|
+
```erb
|
82
|
+
<img src="<%= @photo.image_url %>">
|
127
83
|
```
|
128
84
|
|
129
85
|
## Attachment
|
130
86
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
First we need to register temporary and permanent storage which will be used
|
137
|
-
internally:
|
87
|
+
When we assign an IO-like object to the record, Shrine will upload it to the
|
88
|
+
registered `:cache` storage, which acts as a temporary storage, and write the
|
89
|
+
location/storage/metadata of the uploaded file to a single `<attachment>_data`
|
90
|
+
column:
|
138
91
|
|
139
92
|
```rb
|
140
|
-
|
141
|
-
|
93
|
+
photo = Photo.new
|
94
|
+
photo.image = File.open("waterfall.jpg")
|
95
|
+
photo.image_data #=> '{"storage":"cache","id":"9260ea09d8effd.jpg","metadata":{...}}'
|
142
96
|
|
143
|
-
|
144
|
-
|
145
|
-
store: Shrine::Storage::FileSystem.new("public", prefix: "uploads/store"),
|
146
|
-
}
|
97
|
+
photo.image #=> #<Shrine::UploadedFile>
|
98
|
+
photo.image_url #=> "/uploads/cache/9260ea09d8effd.jpg"
|
147
99
|
```
|
148
100
|
|
149
|
-
The
|
150
|
-
automatically (but that can be changed with the default_storage plugin). Next,
|
151
|
-
we create an uploader class specific to the type of attachment we want, so that
|
152
|
-
later we can have different uploading logic for different attachment types.
|
101
|
+
The Shrine attachment module added the following methods to the `Photo` model:
|
153
102
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
```
|
103
|
+
* `#image=` – caches the file and saves the result into `image_data`
|
104
|
+
* `#image` – returns `Shrine::UploadedFile` instantiated from `image_data`
|
105
|
+
* `#image_url` – calls `image.url` if attachment is present, otherwise returns nil
|
106
|
+
* `#image_attacher` - instance of `Shrine::Attacher` which handles attaching
|
159
107
|
|
160
|
-
|
161
|
-
module using the uploader and include it:
|
108
|
+
In addition to assigning new files, you can also assign already uploaded files:
|
162
109
|
|
163
110
|
```rb
|
164
|
-
|
165
|
-
include ImageUploader[:image] # requires "image_data" attribute
|
166
|
-
end
|
111
|
+
photo.image = '{"storage":"cache","id":"9260ea09d8effd.jpg","metadata":{...}}'
|
167
112
|
```
|
168
113
|
|
169
|
-
|
170
|
-
|
171
|
-
```rb
|
172
|
-
photo = Photo.new
|
173
|
-
photo.image = File.open("nature.jpg") # uploads the file to cache
|
174
|
-
photo.image #=> #<Shrine::UploadedFile>
|
175
|
-
photo.image_url #=> "/uploads/cache/9260ea09d8effd.jpg"
|
176
|
-
photo.image_data #=> "{\"storage\":\"cache\",\"id\":\"9260ea09d8effd.jpg\",\"metadata\":{...}}"
|
177
|
-
```
|
114
|
+
This allows Shrine to retain uploaded files in case of validation errors, and
|
115
|
+
handle [direct uploads], via the hidden form field.
|
178
116
|
|
179
|
-
The
|
180
|
-
|
117
|
+
The ORM plugin that we loaded will upload the attachment to permanent storage
|
118
|
+
(`:store`) when the record is saved, and delete the attachment when record
|
119
|
+
is destroyed:
|
181
120
|
|
182
121
|
```rb
|
183
|
-
|
184
|
-
|
185
|
-
Shrine[:image].instance_methods #=> [:image=, :image, :image_url, :image_attacher]
|
122
|
+
photo.image = File.open("waterfall.jpg")
|
123
|
+
photo.image_url #=> "/uploads/cache/0sdfllasfi842.jpg"
|
186
124
|
|
187
|
-
|
188
|
-
|
125
|
+
photo.save
|
126
|
+
photo.image_url #=> "/uploads/store/l02kladf8jlda.jpg"
|
189
127
|
|
190
|
-
|
191
|
-
|
192
|
-
Shrine::Attachment.new(:document)
|
128
|
+
photo.destroy
|
129
|
+
photo.image.exists? #=> false
|
193
130
|
```
|
194
131
|
|
195
|
-
|
196
|
-
|
197
|
-
* `#image_url` – calls `image.url` if attachment is present, otherwise returns nil.
|
198
|
-
|
199
|
-
This is how you should create a form for a `@photo`:
|
132
|
+
In these examples we used `image` as the name of the attachment, but we can
|
133
|
+
create attachment modules for any kind of attachments:
|
200
134
|
|
201
135
|
```rb
|
202
|
-
Shrine
|
136
|
+
class VideoUploader < Shrine
|
137
|
+
# video attachment logic
|
138
|
+
end
|
203
139
|
```
|
204
|
-
```
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
</form>
|
140
|
+
```rb
|
141
|
+
class Movie < Sequel::Model
|
142
|
+
include VideoUploader[:video] # uses "video_data" column
|
143
|
+
end
|
209
144
|
```
|
210
145
|
|
211
|
-
|
212
|
-
file persist in case of validation errors, and for direct uploads. Note that
|
213
|
-
the hidden field should always be *before* the file field.
|
146
|
+
## Uploader
|
214
147
|
|
215
|
-
|
216
|
-
|
148
|
+
"Uploaders" are subclasses of `Shrine`, and this is where we define all our
|
149
|
+
attachment logic. Uploaders act as a wrappers around a storage, delegating all
|
150
|
+
service-specific logic to the storage. They don't know anything about models
|
151
|
+
and are stateless; they are only in charge of uploading, processing and
|
152
|
+
deleting files.
|
217
153
|
|
218
154
|
```rb
|
219
|
-
|
155
|
+
uploader = DocumentUploader.new(:store)
|
156
|
+
uploaded_file = uploader.upload(File.open("resume.pdf"))
|
157
|
+
uploaded_file #=> #<Shrine::UploadedFile>
|
158
|
+
uploaded_file.to_json #=> '{"storage":"store","id":"0sdfllasfi842.pdf","metadata":{...}}'
|
220
159
|
```
|
221
160
|
|
222
|
-
|
161
|
+
Shrine requires the input for uploading to be an IO-like object. So, `File`,
|
162
|
+
`Tempfile` and `StringIO` instances are all valid inputs. The object doesn't
|
163
|
+
have to be an actual IO, it's enough that it responds to: `#read(*args)`,
|
164
|
+
`#size`, `#eof?`, `#rewind` and `#close`. `ActionDispatch::Http::UploadedFile`
|
165
|
+
is one such object, as well as `Shrine::UploadedFile` itself.
|
223
166
|
|
224
|
-
|
225
|
-
|
226
|
-
Sequel and ActiveRecord ORMs. It uses the `<attachment>_data` column for
|
227
|
-
storing data for uploaded files, so you'll need to add it in a migration.
|
167
|
+
The result of uploading is a `Shrine::UploadedFile` object, which represents
|
168
|
+
the uploaded file on the storage, and is defined by its underlying data hash.
|
228
169
|
|
229
170
|
```rb
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
include VideoUploader[:video]
|
238
|
-
end
|
171
|
+
uploaded_file.url #=> "uploads/938kjsdf932.mp4"
|
172
|
+
uploaded_file.metadata #=> {...}
|
173
|
+
uploaded_file.download #=> #<Tempfile:/var/folders/k7/6zx6dx6x7ys3rv3srh0nyfj00000gn/T/20151004-74201-1t2jacf.mp4>
|
174
|
+
uploaded_file.exists? #=> true
|
175
|
+
uploaded_file.open { |io| ... }
|
176
|
+
uploaded_file.delete
|
177
|
+
# ...
|
239
178
|
```
|
240
179
|
|
241
|
-
|
242
|
-
|
180
|
+
This is the same object that is returned when we access the attachment through
|
181
|
+
the record:
|
243
182
|
|
244
183
|
```rb
|
245
|
-
|
246
|
-
movie.video_url #=> "/uploads/cache/0sdfllasfi842.mp4"
|
247
|
-
|
248
|
-
movie.save
|
249
|
-
movie.video_url #=> "/uploads/store/l02kladf8jlda.mp4"
|
250
|
-
|
251
|
-
movie.destroy
|
252
|
-
movie.video.exists? #=> false
|
184
|
+
photo.image #=> #<Shrine::UploadedFile>
|
253
185
|
```
|
254
186
|
|
255
|
-
First the raw file is cached to temporary storage on assignment, then on saving
|
256
|
-
the cached file is uploaded to permanent storage. Destroying the record
|
257
|
-
destroys the attachment.
|
258
|
-
|
259
|
-
*NOTE: The record will first be saved with the cached attachment, and
|
260
|
-
afterwards (in an "after commit" hook) updated with the stored attachment. This
|
261
|
-
is done so that processing/storing isn't performed inside a database
|
262
|
-
transaction. If you're doing processing, there will be a period of time when
|
263
|
-
the record will be saved with an unprocessed attachment, so you may need to
|
264
|
-
account for that.*
|
265
|
-
|
266
187
|
## Processing
|
267
188
|
|
268
|
-
|
269
|
-
|
189
|
+
Shrine allows you to perform file processing in functional style; you receive
|
190
|
+
the original file as the input, and return processed files as the output.
|
270
191
|
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
end
|
277
|
-
```
|
278
|
-
|
279
|
-
Shrine's uploaders are stateless; the `#process` method is simply a function
|
280
|
-
which takes an input `io` and returns processed file(s) as output. Since it's
|
281
|
-
called for each upload, attaching the file will call it twice, first when
|
282
|
-
raw file is cached to temporary storage on assignment, then when cached file
|
283
|
-
is uploaded to permanent storage on saving. We usually want to process in the
|
284
|
-
latter phase (after file validations):
|
192
|
+
Processing can be performed whenever a file is uploaded. On attaching this
|
193
|
+
happens twice; first the raw file is cached to temporary storage ("cache"
|
194
|
+
action), then when the record is saved the cached file is "promoted" to
|
195
|
+
permanent storage ("store" action). We generally want to process on the "store"
|
196
|
+
action, because it happens after file validations and can be backgrounded.
|
285
197
|
|
286
198
|
```rb
|
287
199
|
class ImageUploader < Shrine
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
200
|
+
plugin :processing
|
201
|
+
|
202
|
+
process(:store) do |io, context|
|
203
|
+
# ...
|
292
204
|
end
|
293
205
|
end
|
294
206
|
```
|
@@ -303,53 +215,56 @@ require "image_processing/mini_magick"
|
|
303
215
|
|
304
216
|
class ImageUploader < Shrine
|
305
217
|
include ImageProcessing::MiniMagick
|
218
|
+
plugin :processing
|
306
219
|
|
307
|
-
|
308
|
-
|
309
|
-
resize_to_limit(io.download, 700, 700)
|
310
|
-
end
|
220
|
+
process(:store) do |io, context|
|
221
|
+
resize_to_limit(io.download, 700, 700)
|
311
222
|
end
|
312
223
|
end
|
313
224
|
```
|
314
225
|
|
315
226
|
Since here `io` is a cached `Shrine::UploadedFile`, we need to download it to
|
316
|
-
a
|
227
|
+
a `File`, which is what image_processing recognizes.
|
317
228
|
|
318
229
|
### Versions
|
319
230
|
|
320
|
-
|
321
|
-
|
322
|
-
|
231
|
+
Sometimes we want to generate multiple files as the result of processing. If
|
232
|
+
we're uploading images, we might want to store various thumbnails alongside the
|
233
|
+
original image. If we're uploading videos, we might want to save a screenshot
|
234
|
+
or transcode it into different formats.
|
235
|
+
|
236
|
+
To save multiple files, we just need to load the versions plugin, and then in
|
237
|
+
`#process` we can return a Hash of files:
|
323
238
|
|
324
239
|
```rb
|
325
240
|
require "image_processing/mini_magick"
|
326
241
|
|
327
242
|
class ImageUploader < Shrine
|
328
243
|
include ImageProcessing::MiniMagick
|
244
|
+
plugin :processing
|
329
245
|
plugin :versions
|
330
246
|
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
size_300 = resize_to_limit(size_500, 300, 300)
|
247
|
+
process(:store) do |io, context|
|
248
|
+
size_700 = resize_to_limit(io.download, 700, 700)
|
249
|
+
size_500 = resize_to_limit(size_700, 500, 500)
|
250
|
+
size_300 = resize_to_limit(size_500, 300, 300)
|
336
251
|
|
337
|
-
|
338
|
-
end
|
252
|
+
{large: size_700, medium: size_500, small: size_300}
|
339
253
|
end
|
340
254
|
end
|
341
255
|
```
|
342
256
|
|
343
|
-
Being able to define processing on instance
|
344
|
-
|
345
|
-
|
257
|
+
Being able to define processing on instance-level like this provides a lot of
|
258
|
+
flexibility. For example, you can choose to process files in a certain order
|
259
|
+
for maximum performance, and you can also add parallelization. It is
|
260
|
+
recommended to load the delete_raw plugin for automatically deleting processed
|
346
261
|
files after uploading.
|
347
262
|
|
348
|
-
|
349
|
-
|
263
|
+
Each version will be saved to the attachment column, and the attachment getter
|
264
|
+
will simply return a Hash of `Shrine::UploadedFile` objects:
|
350
265
|
|
351
266
|
```rb
|
352
|
-
photo.image
|
267
|
+
photo.image #=> {large: ..., medium: ..., small: ...}
|
353
268
|
|
354
269
|
# With the store_dimensions plugin
|
355
270
|
photo.image[:large].width #=> 700
|
@@ -360,39 +275,53 @@ photo.image[:small].width #=> 300
|
|
360
275
|
photo.image_url(:large) #=> "..."
|
361
276
|
```
|
362
277
|
|
363
|
-
|
278
|
+
### Custom processing
|
364
279
|
|
365
|
-
|
366
|
-
|
367
|
-
|
280
|
+
Your processing tool doesn't have to be in any way designed for Shrine
|
281
|
+
([image_processing] is a generic library), you only need to return processed
|
282
|
+
files as IO objects, e.g. `File` objects. Here's an example of processing a
|
283
|
+
video with [ffmpeg]:
|
368
284
|
|
369
285
|
```rb
|
370
|
-
|
371
|
-
|
372
|
-
|
286
|
+
require "streamio-ffmpeg"
|
287
|
+
|
288
|
+
class VideoUploader < Shrine
|
289
|
+
plugin :processing
|
290
|
+
plugin :versions
|
291
|
+
|
292
|
+
process(:store) do |io, context|
|
293
|
+
mov = io.download
|
294
|
+
video = Tempfile.new(["video", ".mp4"], binmode: true)
|
295
|
+
screenshot = Tempfile.new(["screenshot", ".jpg"], binmode: true)
|
296
|
+
|
297
|
+
movie = FFMPEG::Movie.new(mov.path)
|
298
|
+
movie.transcode(video.path)
|
299
|
+
movie.screenshot(screenshot.path)
|
300
|
+
|
301
|
+
mov.delete
|
302
|
+
|
303
|
+
{video: video, screenshot: screenshot}
|
373
304
|
end
|
374
305
|
end
|
375
306
|
```
|
376
|
-
```rb
|
377
|
-
photo = Photo.new
|
378
|
-
photo.image = File.open("image.jpg") # "cache"
|
379
|
-
photo.save # "store"
|
380
|
-
```
|
381
|
-
```
|
382
|
-
{:name=>:image, :record=>#<Photo:0x007fe1627f1138>, :phase=>:cache}
|
383
|
-
{:name=>:image, :record=>#<Photo:0x007fe1627f1138>, :phase=>:store}
|
384
|
-
```
|
385
307
|
|
386
|
-
|
387
|
-
is the model instance, in this case instance of `Photo`. Lastly, the `:phase`
|
388
|
-
is a symbol which indicates the purpose of the upload (by default there are
|
389
|
-
only `:cache` and `:store`, but some plugins add more of them).
|
308
|
+
## Context
|
390
309
|
|
391
|
-
|
392
|
-
|
393
|
-
|
310
|
+
You may have noticed the `context` variable floating around as the second
|
311
|
+
argument for processing. This variable is present all the way from input file
|
312
|
+
to uploaded file, and contains any additional information that can affect the
|
313
|
+
upload:
|
394
314
|
|
395
|
-
|
315
|
+
* `context[:record]` -- the model instance
|
316
|
+
* `context[:name]` -- attachment name on the model
|
317
|
+
* `context[:action]` -- identifier for the action being performed (`:cache`, `:store`, `:recache`, `:backup`, ...)
|
318
|
+
* `context[:version]` -- version name of the IO in the argument
|
319
|
+
* ...
|
320
|
+
|
321
|
+
The `context` is useful for doing conditional processing, validation,
|
322
|
+
generating location etc, and it is also used by some plugins internally.
|
323
|
+
|
324
|
+
## Validation
|
396
325
|
|
397
326
|
Validations are registered by calling `Attacher.validate`, and are best done
|
398
327
|
with the validation_helpers plugin:
|
@@ -424,6 +353,7 @@ Shrine automatically extracts and stores general file metadata:
|
|
424
353
|
|
425
354
|
```rb
|
426
355
|
photo = Photo.create(image: image)
|
356
|
+
|
427
357
|
photo.image.metadata #=>
|
428
358
|
# {
|
429
359
|
# "filename" => "nature.jpg",
|
@@ -458,20 +388,25 @@ photo.image.mime_type #=> "text/x-php"
|
|
458
388
|
|
459
389
|
### Custom metadata
|
460
390
|
|
461
|
-
You can also extract and store custom metadata
|
462
|
-
|
391
|
+
You can also extract and store completely custom metadata with the metadata
|
392
|
+
plugin:
|
463
393
|
|
464
394
|
```rb
|
395
|
+
require "mini_magick"
|
396
|
+
|
465
397
|
class ImageUploader < Shrine
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
398
|
+
plugin :add_metadata
|
399
|
+
|
400
|
+
add_metadata "exif" do |io, context|
|
401
|
+
MiniMagick::Image.new(io.path).exif
|
470
402
|
end
|
471
403
|
end
|
472
404
|
```
|
473
|
-
|
474
|
-
|
405
|
+
```rb
|
406
|
+
photo.image.metadata["exif"]
|
407
|
+
# or
|
408
|
+
photo.image.exif
|
409
|
+
```
|
475
410
|
|
476
411
|
## Locations
|
477
412
|
|
@@ -488,7 +423,7 @@ photo = Photo.create(image: File.open("nature.jpg"))
|
|
488
423
|
photo.image.id #=> "photo/34/image/34krtreds2df.jpg"
|
489
424
|
```
|
490
425
|
|
491
|
-
If you want to generate locations on your own,
|
426
|
+
If you want to generate locations on your own, you can override
|
492
427
|
`Shrine#generate_location`:
|
493
428
|
|
494
429
|
```rb
|
@@ -503,8 +438,8 @@ class ImageUploader < Shrine
|
|
503
438
|
end
|
504
439
|
```
|
505
440
|
|
506
|
-
Note that there should always be a random component in the location,
|
507
|
-
tracking
|
441
|
+
Note that there should always be a random component in the location, so that
|
442
|
+
dirty tracking is detected properly; you can use `Shrine#generate_uid`. Inside
|
508
443
|
`#generate_location` you can access the extracted metadata through
|
509
444
|
`context[:metadata]`.
|
510
445
|
|
@@ -512,13 +447,14 @@ When using the uploader directly, it's possible to bypass `#generate_location`
|
|
512
447
|
by passing a `:location`:
|
513
448
|
|
514
449
|
```rb
|
515
|
-
uploader =
|
450
|
+
uploader = MyUploader.new(:store)
|
516
451
|
file = File.open("nature.jpg")
|
517
452
|
uploader.upload(file, location: "some/specific/location.jpg")
|
518
453
|
```
|
519
454
|
|
520
455
|
## Storage
|
521
456
|
|
457
|
+
"Storages" are objects which know how to manage files on a particular service.
|
522
458
|
Other than [FileSystem], Shrine also ships with Amazon [S3] storage:
|
523
459
|
|
524
460
|
```rb
|
@@ -536,33 +472,33 @@ Shrine.storages[:store] = Shrine::Storage::S3.new(
|
|
536
472
|
```
|
537
473
|
|
538
474
|
```rb
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
475
|
+
photo = Photo.new(image: File.open("image.png"))
|
476
|
+
photo.image_url #=> "/uploads/cache/j4k343ui12ls9.png"
|
477
|
+
photo.save
|
478
|
+
photo.image_url #=> "https://my-bucket.s3.amazonaws.com/0943sf8gfk13.png"
|
543
479
|
```
|
544
480
|
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
capabilities, so versions are deleted with a single HTTP request.
|
481
|
+
Note that any options passed to `image_url` will be forwarded to the underlying
|
482
|
+
storage, see the documentation of the storage that you're using for which URL
|
483
|
+
options it supports.
|
549
484
|
|
550
|
-
|
551
|
-
many other Shrine storages available, see
|
485
|
+
You can see the full documentation for [FileSystem] and [S3] storages. There
|
486
|
+
are also many other Shrine storages available, see [External] section on the
|
487
|
+
website.
|
552
488
|
|
553
|
-
|
489
|
+
### Upload options
|
554
490
|
|
555
491
|
Many storages accept additional upload options, which you can pass via the
|
556
492
|
upload_options plugin, or manually when uploading:
|
557
493
|
|
558
494
|
```rb
|
559
|
-
uploader =
|
495
|
+
uploader = MyUploader.new(:store)
|
560
496
|
uploader.upload(file, upload_options: {acl: "private"})
|
561
497
|
```
|
562
498
|
|
563
499
|
## Direct uploads
|
564
500
|
|
565
|
-
Shrine comes with a direct_upload plugin which provides a [Roda] endpoint that
|
501
|
+
Shrine comes with a [direct_upload] plugin which provides a [Roda] endpoint that
|
566
502
|
accepts file uploads. This allows you to asynchronously start caching the file
|
567
503
|
the moment the user selects it via AJAX (e.g. using the [jQuery-File-Upload] JS
|
568
504
|
library).
|
@@ -572,12 +508,12 @@ Shrine.plugin :direct_upload # Provides a Roda endpoint
|
|
572
508
|
```
|
573
509
|
```rb
|
574
510
|
Rails.application.routes.draw do
|
575
|
-
mount VideoUploader::UploadEndpoint => "/
|
511
|
+
mount VideoUploader::UploadEndpoint => "/videos"
|
576
512
|
end
|
577
513
|
```
|
578
514
|
```js
|
579
515
|
$('[type="file"]').fileupload({
|
580
|
-
url: '/
|
516
|
+
url: '/videos/cache/upload',
|
581
517
|
paramName: 'file',
|
582
518
|
add: function(e, data) { /* Disable the submit button */ },
|
583
519
|
progress: function(e, data) { /* Add a nice progress bar */ },
|
@@ -585,10 +521,11 @@ $('[type="file"]').fileupload({
|
|
585
521
|
});
|
586
522
|
```
|
587
523
|
|
588
|
-
|
589
|
-
|
524
|
+
Along with the upload route, this endpoint also includes a route for generating
|
525
|
+
presigns for direct uploads to 3rd-party services like Amazon S3. See the
|
526
|
+
[direct_upload] plugin documentation for more details, as well as the
|
590
527
|
[Roda](https://github.com/janko-m/shrine-example)/[Rails](https://github.com/erikdahlstrand/shrine-rails-example)
|
591
|
-
example
|
528
|
+
example apps which demonstrate multiple uploads directly to S3.
|
592
529
|
|
593
530
|
## Backgrounding
|
594
531
|
|
@@ -619,9 +556,9 @@ class DeleteJob
|
|
619
556
|
end
|
620
557
|
```
|
621
558
|
|
622
|
-
The above puts all promoting (
|
623
|
-
background Sidekiq job. Obviously instead of Sidekiq
|
624
|
-
backgrounding library.
|
559
|
+
The above puts all promoting (uploading cached file to permanent storage) and
|
560
|
+
deleting of files into a background Sidekiq job. Obviously instead of Sidekiq
|
561
|
+
you can use any other backgrounding library.
|
625
562
|
|
626
563
|
The main advantages of Shrine's backgrounding support over other file upload
|
627
564
|
libraries are:
|
@@ -640,8 +577,8 @@ libraries are:
|
|
640
577
|
|
641
578
|
## Clearing cache
|
642
579
|
|
643
|
-
|
644
|
-
[a built-in solution](http://docs.aws.amazon.com/AmazonS3/latest/UG/lifecycle-configuration-bucket-no-versioning.html),
|
580
|
+
From time to time you'll want to clean your temporary storage from old files.
|
581
|
+
Amazon S3 provides [a built-in solution](http://docs.aws.amazon.com/AmazonS3/latest/UG/lifecycle-configuration-bucket-no-versioning.html),
|
645
582
|
and for FileSystem you can put something like this in your Rake task:
|
646
583
|
|
647
584
|
```rb
|
@@ -667,6 +604,21 @@ class ImageUploader < Shrine
|
|
667
604
|
end
|
668
605
|
```
|
669
606
|
|
607
|
+
## On-the-fly processing
|
608
|
+
|
609
|
+
Shrine allows you to define processing that will be performed on upload.
|
610
|
+
However, what if want to perform processing on-the-fly, only when the URL is
|
611
|
+
requested? Unlike Refile or Dragonfly, Shrine doesn't come with an image server
|
612
|
+
built in, instead it expects you to integrate any of the existing generic image
|
613
|
+
servers.
|
614
|
+
|
615
|
+
Shrine has integrations for many commercial on-the-fly processing services, so
|
616
|
+
you can use [shrine-cloudinary], [shrine-imgix] or [shrine-uploadcare].
|
617
|
+
|
618
|
+
If you don't want to use a commercial service, [Attache] is a great open-source
|
619
|
+
image server. There isn't a Shrine integration written for it yet, but it
|
620
|
+
should be fairly easy to write one.
|
621
|
+
|
670
622
|
## Inspiration
|
671
623
|
|
672
624
|
Shrine was heavily inspired by [Refile] and [Roda]. From Refile it borrows the
|
@@ -674,6 +626,13 @@ idea of "backends" (here named "storages"), attachment interface, and direct
|
|
674
626
|
uploads. From Roda it borrows the implementation of an extensible [plugin
|
675
627
|
system].
|
676
628
|
|
629
|
+
## Similar libraries
|
630
|
+
|
631
|
+
* Paperclip
|
632
|
+
* CarrierWave
|
633
|
+
* Dragonfly
|
634
|
+
* Refile
|
635
|
+
|
677
636
|
## License
|
678
637
|
|
679
638
|
The gem is available as open source under the terms of the [MIT License].
|
@@ -693,5 +652,12 @@ The gem is available as open source under the terms of the [MIT License].
|
|
693
652
|
[introductory blog post]: http://twin.github.io/introducing-shrine/
|
694
653
|
[FileSystem]: http://shrinerb.com/rdoc/classes/Shrine/Storage/FileSystem.html
|
695
654
|
[S3]: http://shrinerb.com/rdoc/classes/Shrine/Storage/S3.html
|
696
|
-
[
|
655
|
+
[External]: http://shrinerb.com#external
|
697
656
|
[`Shrine::UploadedFile`]: http://shrinerb.com/rdoc/classes/Shrine/Plugins/Base/FileMethods.html
|
657
|
+
[direct uploads]: #direct-uploads
|
658
|
+
[ffmpeg]: https://github.com/streamio/streamio-ffmpeg
|
659
|
+
[direct_upload]: http://shrinerb.com/rdoc/classes/Shrine/Plugins/DirectUpload.html
|
660
|
+
[shrine-cloudinary]: https://github.com/janko-m/shrine-cloudinary
|
661
|
+
[shrine-imgix]: https://github.com/janko-m/shrine-imgix
|
662
|
+
[shrine-uploadcare]: https://github.com/janko-m/shrine-uploadcare
|
663
|
+
[Attache]: https://github.com/choonkeat/attache
|