shrine 2.3.1 → 2.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 27f5213f9cac952b0947848a6f9b518ca5ed6705
4
- data.tar.gz: d343abd428ddc64dec6d9a049509acdeebe90692
3
+ metadata.gz: b4039e8d8350766e0bbe1555e0039672f4224915
4
+ data.tar.gz: b8200b69674d031d15c82fd19e704eee55d049b9
5
5
  SHA512:
6
- metadata.gz: f33bf01e38fa6e854467121d0d92f640491977d234a1201c7c4187503e889965ba797e54f3d05d561e8862bbe32c82edf2cbda6e63ab1557f222ce225613322a
7
- data.tar.gz: 32351b7cb1fb3afd6bf31d206096fe034b038ed518950dbf82ca95cca8e4c718168ff4c50a5467c671e1d3a04ef7469e15c20ff61724edae416ebe31c7ffa0c3
6
+ metadata.gz: 0cf6ec31cb22d1396575720a5a8543f71875e9a5c9851ea1f8311aec34a5ef1dc1c83be64b32434dac3e825dd3b52c06bd9035eb15a02fe11572cbceb9584c45
7
+ data.tar.gz: 49ac2e4d1065b37733c82b7dacb8498db1784a2c7c2dadfb643b8d2348d9b8fea0ca84062dafa1dc7ce836ef142a101202b83abe2870003a7d803dd2230ace5c
data/README.md CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  Shrine is a toolkit for file attachments in Ruby applications.
4
4
 
5
- If you're new, you're encouraged to read the [introductory blog post] which
6
- explains the motivation behind Shrine.
5
+ If you're not sure why you should care, you're encouraged to read the
6
+ [motivation behind creating Shrine][motivation].
7
7
 
8
8
  ## Resources
9
9
 
@@ -14,7 +14,8 @@ explains the motivation behind Shrine.
14
14
 
15
15
  ## Quick start
16
16
 
17
- Add Shrine to the Gemfile and write an initializer:
17
+ Add Shrine to the Gemfile and write an initializer which sets up the storage and
18
+ loads the ORM plugin:
18
19
 
19
20
  ```rb
20
21
  gem "shrine"
@@ -25,16 +26,17 @@ require "shrine"
25
26
  require "shrine/storage/file_system"
26
27
 
27
28
  Shrine.storages = {
28
- cache: Shrine::Storage::FileSystem.new("public", prefix: "uploads/cache"),
29
- store: Shrine::Storage::FileSystem.new("public", prefix: "uploads/store"),
29
+ cache: Shrine::Storage::FileSystem.new("public", prefix: "uploads/cache"), # temporary
30
+ store: Shrine::Storage::FileSystem.new("public", prefix: "uploads/store"), # permanent
30
31
  }
31
32
 
32
33
  Shrine.plugin :sequel # :activerecord
33
34
  Shrine.plugin :cached_attachment_data # for forms
34
35
  ```
35
36
 
36
- Next write a migration to add a column which will hold attachment data, and run
37
- it:
37
+ Next decide how you will name the attachment attribute on your model, and run a
38
+ migration that adds an `<attachment>_data` text column, which Shrine will use
39
+ to store all information about the attachment:
38
40
 
39
41
  ```rb
40
42
  Sequel.migration do # class AddImageDataToPhotos < ActiveRecord::Migration
@@ -45,7 +47,7 @@ end # end
45
47
  ```
46
48
 
47
49
  Now you can create an uploader class for the type of files you want to upload,
48
- and make your model handle attachments:
50
+ and add the attachment attribute to your model which will accept files:
49
51
 
50
52
  ```rb
51
53
  class ImageUploader < Shrine
@@ -59,8 +61,11 @@ class Photo < Sequel::Model # ActiveRecord::Base
59
61
  end
60
62
  ```
61
63
 
62
- This creates an `image` attachment attribute which accepts files. Let's now
63
- add the form fields needed for attaching files:
64
+ Let's now add the form fields needed for attaching files. We need a file
65
+ field for choosing files, and a hidden field for retaining the uploaded file
66
+ in case of validation errors and [direct uploads]. Note that the file field
67
+ needs to go *after* the hidden field, so that attaching a new file can always
68
+ override whatever is in the hidden field.
64
69
 
65
70
  ```erb
66
71
  <form action="/photos" method="post" enctype="multipart/form-data">
@@ -75,17 +80,22 @@ add the form fields needed for attaching files:
75
80
  <% end %>
76
81
  ```
77
82
 
78
- Now assigning the request parameters in your router/controller will
79
- automatically handle the image attachment:
83
+ Note the `enctype="multipart/form-data"` HTML attribute, which is required for
84
+ submitting files through the form. The Rails form builder will automatically
85
+ generate it for you when you add a file field.
86
+
87
+ Now in your router/controller the attachment request parameter can be assigned
88
+ to the model like any other attribute. Note that for non-Rails apps you will
89
+ need to load the `rack_file` plugin which handles Rack's uploaded file hash.
80
90
 
81
91
  ```rb
82
92
  post "/photos" do
83
93
  Photo.create(params[:photo])
94
+ # ...
84
95
  end
85
96
  ```
86
97
 
87
- When a Photo is created with the image attached, you can display the image via
88
- its URL:
98
+ Finally, you can use the URL of the attached file to display it:
89
99
 
90
100
  ```erb
91
101
  <img src="<%= @photo.image_url %>">
@@ -93,7 +103,7 @@ its URL:
93
103
 
94
104
  ## Attachment
95
105
 
96
- When we assign an IO-like object to the record, Shrine will upload it to the
106
+ When we assign an IO object to the record, Shrine will upload it to the
97
107
  registered `:cache` storage, which acts as a temporary storage, and write the
98
108
  location, storage, and metadata of the uploaded file to a single
99
109
  `<attachment>_data` column:
@@ -112,13 +122,17 @@ The Shrine attachment module added the following methods to the `Photo` model:
112
122
  * `#image=` – caches the file and saves the result into `image_data`
113
123
  * `#image` – returns `Shrine::UploadedFile` instantiated from `image_data`
114
124
  * `#image_url` – calls `image.url` if attachment is present, otherwise returns nil
115
- * `#image_attacher` - instance of `Shrine::Attacher` which handles attaching
125
+ * `#image_attacher` returns instance of `Shrine::Attacher` which handles attaching
116
126
 
117
127
  In addition to assigning new files, you can also assign already cached files
118
128
  using their JSON representation:
119
129
 
120
130
  ```rb
121
- photo.image = '{"storage":"cache","id":"9260ea09d8effd.jpg","metadata":{...}}'
131
+ photo.image = '{
132
+ "storage": "cache",
133
+ "id": "9260ea09d8effd.jpg",
134
+ "metadata": { ... }
135
+ }'
122
136
  ```
123
137
 
124
138
  This allows Shrine to retain uploaded files in case of validation errors, and
@@ -161,6 +175,29 @@ class Movie < Sequel::Model
161
175
  end
162
176
  ```
163
177
 
178
+ ### Attacher
179
+
180
+ The model attachment interface under-the-hood just delegates to a
181
+ `Shrine::Attacher` object. If whether you don't want to add additional methods
182
+ to your model, prefer explicitness over callbacks, or use Shrine with custom
183
+ models, you can use `Shrine::Attacher` directly:
184
+
185
+ ```rb
186
+ attacher = ImageUploader::Attacher.new(photo, :image) # equivalent to `photo.image_attacher`
187
+
188
+ attacher.cache #=> #<ImageUploader @storage_key=:cache>
189
+ attacher.store #=> #<ImageUploader @storage_key=:store>
190
+
191
+ attacher.assign(file) # equivalent to `photo.image = file`
192
+ attacher.get # equivalent to `photo.image`
193
+ attacher.url # equivalent to `photo.image_url`
194
+
195
+ attacher.finalize # promotes cached file to store, deletes old attachment (after save callback)
196
+ attacher.destroy # deletes attachment (after destory callback)
197
+ ```
198
+
199
+ See [Using Attacher] guide for more details.
200
+
164
201
  ### Multiple files
165
202
 
166
203
  Sometimes we want to allow users to upload multiple files at once. This can be
@@ -179,11 +216,14 @@ into nested association attributes in the controller.
179
216
  ## Uploader
180
217
 
181
218
  "Uploaders" are subclasses of `Shrine`, and this is where we define all our
182
- attachment logic. Uploaders act as a wrappers around a storage, delegating all
183
- service-specific logic to the storage. They don't know anything about models
184
- and are stateless; they are only in charge of uploading, processing and
185
- deleting files.
219
+ attachment logic. Uploader objects act as a wrappers around a storage; they
220
+ don't know anything about models, and are stateless.
186
221
 
222
+ ```rb
223
+ class DocumentUploader < Shrine
224
+ # document uploading logic
225
+ end
226
+ ```
187
227
  ```rb
188
228
  uploader = DocumentUploader.new(:store)
189
229
  uploaded_file = uploader.upload(File.open("resume.pdf"))
@@ -191,6 +231,14 @@ uploaded_file #=> #<Shrine::UploadedFile>
191
231
  uploaded_file.to_json #=> '{"storage":"store","id":"0sdfllasfi842.pdf","metadata":{...}}'
192
232
  ```
193
233
 
234
+ The `Shrine#upload` method does the following:
235
+
236
+ * calls processing
237
+ * extracts metadata
238
+ * generates unique location
239
+ * uploads file(s) (this is where the storage is called)
240
+ * closes uploaded file(s)
241
+
194
242
  Shrine requires the input for uploading to be an IO-like object. So, `File`,
195
243
  `Tempfile` and `StringIO` instances are all valid inputs. The object doesn't
196
244
  have to be an actual IO, it's enough that it responds to: `#read(*args)`,
@@ -205,7 +253,7 @@ uploaded_file.url #=> "uploads/938kjsdf932.mp4"
205
253
  uploaded_file.metadata #=> {...}
206
254
  uploaded_file.download #=> #<Tempfile:/var/folders/k7/6zx6dx6x7ys3rv3srh0nyfj00000gn/T/20151004-74201-1t2jacf.mp4>
207
255
  uploaded_file.exists? #=> true
208
- uploaded_file.open { |io| ... }
256
+ uploaded_file.open { |io| io.read }
209
257
  uploaded_file.delete
210
258
  # ...
211
259
  ```
@@ -224,8 +272,8 @@ and any additional features are available via plugins. This way you can choose
224
272
  exactly what and how much Shrine does for you. See the [website] for a complete
225
273
  list of plugins.
226
274
 
227
- The plugin system respects inheritance, so you can choose which plugins will
228
- be applied to which uploaders:
275
+ The plugin system respects inheritance, so you can choose to load a plugin
276
+ globally or only for a specific uploader.
229
277
 
230
278
  ```rb
231
279
  Shrine.plugin :logging # enables logging for all uploaders
@@ -274,8 +322,8 @@ class ImageUploader < Shrine
274
322
  end
275
323
  ```
276
324
 
277
- Since here `io` is a cached `Shrine::UploadedFile`, we need to download it to
278
- a `File`, which is what image_processing recognizes.
325
+ Here the `io` is a cached `Shrine::UploadedFile`, so we need to download it to
326
+ a `File`, since this is what image_processing gem recognizes.
279
327
 
280
328
  ### Versions
281
329
 
@@ -305,11 +353,11 @@ class ImageUploader < Shrine
305
353
  end
306
354
  ```
307
355
 
308
- Being able to define processing on instance-level like this provides a lot of
309
- flexibility. For example, you can choose to process files in a certain order
310
- for maximum performance, and you can also add parallelization. It is
311
- recommended to load the `delete_raw` plugin for automatically deleting processed
312
- files after uploading.
356
+ By defining processing on instance-level Shrine gives you a lot of flexibility.
357
+ You could choose the processing order which yields the best performance, even
358
+ add parallelization, and when processing logic gets complex you could extract
359
+ everything into a PORO class. It is recommended to load the `delete_raw` plugin
360
+ so that processed files are automatically deleted after uploading.
313
361
 
314
362
  Each version will be saved to the attachment column, and the attachment getter
315
363
  will simply return a Hash of `Shrine::UploadedFile` objects:
@@ -356,6 +404,41 @@ class VideoUploader < Shrine
356
404
  end
357
405
  ```
358
406
 
407
+ ### Triggering processing
408
+
409
+ Whenever a file is uploaded by the attacher, an additional `:action` parameter
410
+ is added to the context, which holds a symbol name describing what the file was
411
+ uploaded for. For example, for caching files `action: :cache` will be sent, for
412
+ promoting `action: :store`, while for backing up attacher sends `action:
413
+ :backup`.
414
+
415
+ The argument to the `process` declaration is the name of that action. When
416
+ uploading via the uploader, you can add `:action` option with the value of the
417
+ processing block you want performed before uploading.
418
+
419
+ ```rb
420
+ uploader = ImageUploader.new(:store)
421
+ uploader.upload(file, action: :store) # performs processing defined under ":store"
422
+ ```
423
+
424
+ You can also define and call processing for a custom action:
425
+
426
+ ```rb
427
+ class ImageUploader < Shrine
428
+ process(:my_action) { |io, context| special_processing(io) }
429
+ end
430
+ ```
431
+ ```rb
432
+ uploader.upload(file, action: :my_action)
433
+ ```
434
+
435
+ Finally, you can also call defined processing directly, without uploading the
436
+ results, using `Shrine#process`.
437
+
438
+ ```rb
439
+ uploader.process(file, action: :my_action) # returns processed files without uploading
440
+ ```
441
+
359
442
  ## Context
360
443
 
361
444
  You may have noticed the `context` variable floating around as the second
@@ -371,57 +454,24 @@ to uploaded file, and can contain useful information depending on the situation:
371
454
  The `context` is useful for doing conditional processing, validation,
372
455
  generating location etc, and it is also used by some plugins internally.
373
456
 
374
- ## Validation
375
-
376
- Validations are registered inside a `Attacher.validate` block, and you can load
377
- the `validation_helpers` plugin to get some convenient file validation methods:
378
-
379
457
  ```rb
380
458
  class VideoUploader < Shrine
381
- plugin :validation_helpers
382
-
383
- Attacher.validate do
384
- validate_max_size 50*1024*1024, message: "is too large (max is 50 MB)"
385
- validate_mime_type_inclusion ["video/mp4"]
386
- end
387
- end
388
- ```
389
-
390
- ```rb
391
- trailer = Trailer.new
392
- trailer.video = File.open("matrix.mp4")
393
- trailer.valid? #=> false
394
- trailer.errors.to_hash #=> {video: ["is too large (max is 50 MB)"]}
395
- ```
396
-
397
- You can also do custom validations:
398
-
399
- ```rb
400
- class VideoUploader < Shrine
401
- Attacher.validate do
402
- errors << "is longer than 5 minutes" if get.duration > 300
459
+ process(:store) do |io, context|
460
+ trim_video(io, 300) if context[:record].guest?
403
461
  end
404
462
  end
405
463
  ```
406
464
 
407
- The `Attacher.validate` block is executed in context of a `Shrine::Attacher`
408
- instance:
465
+ The context is just a hash that is passed to the uploader methods. If you're
466
+ using the uploader directly, you can pass the context directly:
409
467
 
410
468
  ```rb
411
- class VideoUploader < Shrine
412
- Attacher.validate do
413
- self #=> #<Shrine::Attacher>
414
-
415
- get #=> #<Shrine::UploadedFile>
416
- record # the model instance
417
- errors # array of error messages for this file
418
- end
419
- end
469
+ uploader.upload(file, {foo: "bar"}) # passing context hash directly
420
470
  ```
421
471
 
422
472
  ## Metadata
423
473
 
424
- Shrine automatically extracts and stores general file metadata:
474
+ Shrine automatically extracts and stores available file metadata:
425
475
 
426
476
  ```rb
427
477
  photo = Photo.create(image: image)
@@ -480,6 +530,54 @@ photo.image.metadata["exif"]
480
530
  photo.image.exif
481
531
  ```
482
532
 
533
+ ## Validation
534
+
535
+ Validations are registered inside a `Attacher.validate` block, and you can load
536
+ the `validation_helpers` plugin to get some convenient file validation methods:
537
+
538
+ ```rb
539
+ class VideoUploader < Shrine
540
+ plugin :validation_helpers
541
+
542
+ Attacher.validate do
543
+ validate_max_size 50*1024*1024, message: "is too large (max is 50 MB)"
544
+ validate_mime_type_inclusion ["video/mp4"]
545
+ end
546
+ end
547
+ ```
548
+
549
+ ```rb
550
+ trailer = Trailer.new
551
+ trailer.video = File.open("matrix.mp4")
552
+ trailer.valid? #=> false
553
+ trailer.errors.to_hash #=> {video: ["is too large (max is 50 MB)"]}
554
+ ```
555
+
556
+ You can also do custom validations:
557
+
558
+ ```rb
559
+ class VideoUploader < Shrine
560
+ Attacher.validate do
561
+ errors << "is longer than 5 minutes" if get.duration > 300
562
+ end
563
+ end
564
+ ```
565
+
566
+ The `Attacher.validate` block is executed in context of a `Shrine::Attacher`
567
+ instance:
568
+
569
+ ```rb
570
+ class VideoUploader < Shrine
571
+ Attacher.validate do
572
+ self #=> #<Shrine::Attacher>
573
+
574
+ get #=> #<Shrine::UploadedFile>
575
+ record # the model instance
576
+ errors # array of error messages for this file
577
+ end
578
+ end
579
+ ```
580
+
483
581
  ## Locations
484
582
 
485
583
  Before Shrine uploads a file, it generates a random location for it. By
@@ -568,11 +666,22 @@ uploader = MyUploader.new(:store)
568
666
  uploader.upload(file, upload_options: {acl: "private"})
569
667
  ```
570
668
 
669
+ ### Clearing cache
670
+
671
+ From time to time you'll want to clean your temporary storage from old files.
672
+ Amazon S3 provides [a built-in solution][s3 lifecycle], and for FileSystem you
673
+ can put something like this in your Rake task:
674
+
675
+ ```rb
676
+ file_system = Shrine.storages[:cache]
677
+ file_system.clear!(older_than: Time.now - 7*24*60*60) # delete files older than 1 week
678
+ ```
679
+
571
680
  ## Direct uploads
572
681
 
573
- Shrine comes with a `direct_upload` plugin for asynchronous uploads to your
574
- app or an external service. It provides a [Roda] endpoint which you can mount
575
- in your app:
682
+ Shrine comes with a `direct_upload` plugin for asynchronous uploads to your app
683
+ or an external service like Amazon S3. It provides a [Roda] endpoint which you
684
+ can mount in your app:
576
685
 
577
686
  ```rb
578
687
  gem "roda"
@@ -589,7 +698,7 @@ end
589
698
  This endpoint provides the following routes:
590
699
 
591
700
  * `POST /images/cache/upload` - for direct uploads to your app
592
- * `GET /images/cache/presign` - for direct uploads to external service
701
+ * `GET /images/cache/presign` - for direct uploads to external service (e.g. Amazon S3)
593
702
 
594
703
  These routes can be used to asynchronously start caching the file the moment
595
704
  the user selects it, using JavaScript file upload libraries like
@@ -647,17 +756,6 @@ libraries are:
647
756
  * **Safety** – All of Shrine's code has been designed to take delayed storing
648
757
  into account, and concurrent requests are handled well.
649
758
 
650
- ## Clearing cache
651
-
652
- From time to time you'll want to clean your temporary storage from old files.
653
- Amazon S3 provides [a built-in solution](http://docs.aws.amazon.com/AmazonS3/latest/UG/lifecycle-configuration-bucket-no-versioning.html),
654
- and for FileSystem you can put something like this in your Rake task:
655
-
656
- ```rb
657
- file_system = Shrine.storages[:cache]
658
- file_system.clear!(older_than: Time.now - 7*24*60*60) # delete files older than 1 week
659
- ```
660
-
661
759
  ## On-the-fly processing
662
760
 
663
761
  Shrine allows you to define processing that will be performed on upload.
@@ -669,9 +767,26 @@ generic image servers.
669
767
  Shrine has integrations for many commercial on-the-fly processing services,
670
768
  including [Cloudinary], [Imgix] and [Uploadcare].
671
769
 
672
- If you don't want to use a commercial service, [Attache] is a great open-source
673
- image server. There isn't a Shrine integration written for it yet, but it
674
- should be fairly easy to write one.
770
+ If you don't want to use a commercial service, [Attache] and [Dragonfly] are
771
+ great open-source image servers. For Attache a Shrine integration is in
772
+ progress, while for Dragonfly it is not needed.
773
+
774
+ ## Chunked & Resumable uploads
775
+
776
+ When you're accepting large file uploads, you normally want to split it into
777
+ multiple chunks. This way if an upload fails, it is just for one chunk and can
778
+ be retried, while the previous chunks remain uploaded.
779
+
780
+ [Tus][tus] is an open protocol for resumable file uploads, which enables the
781
+ client and the server to achieve reliable file uploads, even on unstable
782
+ networks, with the possibility to resume the upload even after the browser is
783
+ closed or the device shut down. You can use a client library like
784
+ [tus-js-client] to upload the file to [tus-ruby-server], and attach the
785
+ uploaded file to a record using [shrine-url]. See [shrine-tus-demo] for an
786
+ example integration.
787
+
788
+ Another option might be to do chunked uploads directly to your storage service,
789
+ if the storage service supports it (e.g. Amazon S3 or Google Cloud Storage).
675
790
 
676
791
  ## Inspiration
677
792
 
@@ -704,7 +819,7 @@ The gem is available as open source under the terms of the [MIT License].
704
819
  [plugin system]: http://twin.github.io/the-plugin-system-of-sequel-and-roda/
705
820
  [MIT License]: http://opensource.org/licenses/MIT
706
821
  [ships with over 35 plugins]: http://shrinerb.com#plugins
707
- [introductory blog post]: http://twin.github.io/introducing-shrine/
822
+ [motivation]: https://twin.github.io/better-file-uploads-with-shrine-motivation/
708
823
  [FileSystem]: http://shrinerb.com/rdoc/classes/Shrine/Storage/FileSystem.html
709
824
  [S3]: http://shrinerb.com/rdoc/classes/Shrine/Storage/S3.html
710
825
  [External]: http://shrinerb.com#external
@@ -721,3 +836,11 @@ The gem is available as open source under the terms of the [MIT License].
721
836
  [Direct Uploads to S3]: http://shrinerb.com/rdoc/files/doc/direct_s3_md.html
722
837
  [website]: http://shrinerb.com
723
838
  [backgrounding libraries]: https://github.com/janko-m/shrine/wiki/Backgrounding-libraries
839
+ [tus]: http://tus.io
840
+ [tus-ruby-server]: https://github.com/janko-m/tus-ruby-server
841
+ [tus-js-client]: https://github.com/tus/tus-js-client
842
+ [shrine-tus-demo]: https://github.com/janko-m/shrine-tus-demo
843
+ [shrine-url]: https://github.com/janko-m/shrine-url
844
+ [s3 lifecycle]: http://docs.aws.amazon.com/AmazonS3/latest/UG/lifecycle-configuration-bucket-no-versioning.html
845
+ [Dragonfly]: http://markevans.github.io/dragonfly/
846
+ [Using Attacher]: http://shrinerb.com/rdoc/files/doc/attacher_md.html