shrine 3.1.0 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +82 -0
  3. data/README.md +11 -4
  4. data/doc/advantages.md +4 -4
  5. data/doc/attacher.md +2 -2
  6. data/doc/carrierwave.md +24 -12
  7. data/doc/changing_derivatives.md +1 -1
  8. data/doc/changing_location.md +6 -5
  9. data/doc/design.md +134 -85
  10. data/doc/direct_s3.md +26 -0
  11. data/doc/external/articles.md +57 -45
  12. data/doc/external/extensions.md +41 -35
  13. data/doc/external/misc.md +23 -8
  14. data/doc/getting_started.md +156 -85
  15. data/doc/metadata.md +80 -44
  16. data/doc/multiple_files.md +1 -1
  17. data/doc/paperclip.md +28 -9
  18. data/doc/plugins/add_metadata.md +112 -35
  19. data/doc/plugins/atomic_helpers.md +41 -3
  20. data/doc/plugins/backgrounding.md +12 -2
  21. data/doc/plugins/column.md +36 -7
  22. data/doc/plugins/default_url.md +6 -3
  23. data/doc/plugins/derivatives.md +83 -44
  24. data/doc/plugins/download_endpoint.md +5 -5
  25. data/doc/plugins/dynamic_storage.md +1 -1
  26. data/doc/plugins/entity.md +12 -4
  27. data/doc/plugins/form_assign.md +5 -5
  28. data/doc/plugins/included.md +25 -5
  29. data/doc/plugins/infer_extension.md +9 -0
  30. data/doc/plugins/instrumentation.md +1 -1
  31. data/doc/plugins/metadata_attributes.md +1 -0
  32. data/doc/plugins/mirroring.md +1 -1
  33. data/doc/plugins/model.md +8 -3
  34. data/doc/plugins/persistence.md +10 -1
  35. data/doc/plugins/remote_url.md +6 -1
  36. data/doc/plugins/remove_invalid.md +9 -1
  37. data/doc/plugins/sequel.md +1 -1
  38. data/doc/plugins/store_dimensions.md +10 -0
  39. data/doc/plugins/type_predicates.md +96 -0
  40. data/doc/plugins/upload_endpoint.md +1 -1
  41. data/doc/plugins/upload_options.md +1 -1
  42. data/doc/plugins/url_options.md +4 -4
  43. data/doc/plugins/validation.md +14 -4
  44. data/doc/plugins/versions.md +7 -7
  45. data/doc/processing.md +287 -123
  46. data/doc/refile.md +9 -9
  47. data/doc/release_notes/2.8.0.md +1 -1
  48. data/doc/release_notes/3.0.0.md +1 -1
  49. data/doc/release_notes/3.2.0.md +96 -0
  50. data/doc/release_notes/3.2.1.md +31 -0
  51. data/doc/release_notes/3.2.2.md +14 -0
  52. data/doc/release_notes/3.3.0.md +105 -0
  53. data/doc/release_notes/3.4.0.md +35 -0
  54. data/doc/securing_uploads.md +2 -2
  55. data/doc/storage/memory.md +19 -0
  56. data/doc/storage/s3.md +104 -77
  57. data/doc/testing.md +12 -2
  58. data/doc/upgrading_to_3.md +99 -53
  59. data/lib/shrine.rb +9 -8
  60. data/lib/shrine/attacher.rb +20 -10
  61. data/lib/shrine/attachment.rb +2 -2
  62. data/lib/shrine/plugins.rb +22 -0
  63. data/lib/shrine/plugins/activerecord.rb +3 -3
  64. data/lib/shrine/plugins/add_metadata.rb +20 -5
  65. data/lib/shrine/plugins/backgrounding.rb +2 -2
  66. data/lib/shrine/plugins/default_url.rb +1 -1
  67. data/lib/shrine/plugins/derivation_endpoint.rb +13 -8
  68. data/lib/shrine/plugins/derivatives.rb +59 -30
  69. data/lib/shrine/plugins/determine_mime_type.rb +5 -3
  70. data/lib/shrine/plugins/entity.rb +12 -11
  71. data/lib/shrine/plugins/instrumentation.rb +12 -18
  72. data/lib/shrine/plugins/mirroring.rb +8 -8
  73. data/lib/shrine/plugins/model.rb +3 -3
  74. data/lib/shrine/plugins/presign_endpoint.rb +16 -4
  75. data/lib/shrine/plugins/pretty_location.rb +1 -1
  76. data/lib/shrine/plugins/processing.rb +1 -1
  77. data/lib/shrine/plugins/refresh_metadata.rb +2 -2
  78. data/lib/shrine/plugins/remote_url.rb +3 -3
  79. data/lib/shrine/plugins/remove_attachment.rb +5 -0
  80. data/lib/shrine/plugins/remove_invalid.rb +10 -5
  81. data/lib/shrine/plugins/sequel.rb +1 -1
  82. data/lib/shrine/plugins/store_dimensions.rb +4 -2
  83. data/lib/shrine/plugins/type_predicates.rb +113 -0
  84. data/lib/shrine/plugins/upload_endpoint.rb +10 -5
  85. data/lib/shrine/plugins/upload_options.rb +2 -2
  86. data/lib/shrine/plugins/url_options.rb +2 -2
  87. data/lib/shrine/plugins/validation.rb +9 -7
  88. data/lib/shrine/storage/linter.rb +4 -4
  89. data/lib/shrine/storage/memory.rb +5 -3
  90. data/lib/shrine/storage/s3.rb +117 -38
  91. data/lib/shrine/version.rb +1 -1
  92. data/shrine.gemspec +8 -8
  93. metadata +42 -34
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 30bd30989579217202b2c51b9a00abe778af7cbab75fc9df78052bc074ce9405
4
- data.tar.gz: 5c718df903c203e06ea44afa9c9017fa2cf01a8924c99e86a87be52f52ce0233
3
+ metadata.gz: 9c012576836f3a56efa91be436999395123342c4650f76f152ef64808ce82ddb
4
+ data.tar.gz: 5be0e63b923ba28f838b4f692758af3eb975c937c4b63453acc629aab0240eac
5
5
  SHA512:
6
- metadata.gz: 27f896211caba27686d5988750ce214ec0b31e70e9bbcf8a7d325952a5eee2e4c3293d539bb3074d4384964723bda09edd1acfcdcd387d82a4ef7373ff5787f0
7
- data.tar.gz: 3d06d57d2eb1fee1cada71dfa4092a23c4b0bf4d5df8cf76e2fbf2eb4eaf5c9477f333ef241772d917456ebfb3067ad0ce7adc6becc49e15c9129720842cd5b3
6
+ metadata.gz: da5eb6be96c3cacb0575e124b4238c038709d44f33085a5bfb4ae6cb1702d079ba0c7f1ca775828812c6d8cacd1b75aa5979c564dbe0e1ea52fea0036c0f577e
7
+ data.tar.gz: fe12c86f5d826581e4ecb1b5e1951ca1330c8d6d288d19001badd79dab646013a2e57faf51d6cf7369669515636dda3058bf42a2128ea04934f7b611a53e97dd
data/CHANGELOG.md CHANGED
@@ -1,3 +1,85 @@
1
+ ## 3.4.0 (2021-06-14)
2
+
3
+ * `base` – Fix passing options to `Shrine.Attachment` on Ruby 3.0 (@lucianghinda)
4
+
5
+ * `determine_mime_type` – Return correct `image/svg+xml` MIME type for SVGs with `:fastimage` analyzer (@Bandes)
6
+
7
+ * `activerecord` – Fix keyword argument warning when adding errors with options (@janko)
8
+
9
+ * `entity` – Make `Attacher#read` method public (@janko)
10
+
11
+ * `entity` – Reset attachment dirty tracking in `Attacher#reload` (@janko)
12
+
13
+ * `activerecord` – Don't load the attacher on `ActiveRecord::Base#reload` if it hasn't yet been initialized (@janko)
14
+
15
+ * `sequel` – Don't load the attacher on `Sequel::Model#reload` if it hasn't yet been initialized (@janko)
16
+
17
+ ## 3.3.0 (2020-10-04)
18
+
19
+ * `s3` - Support new `Aws::S3::EncryptionV2::Client` for client-side encryption (@janko)
20
+
21
+ * `derivation_endpoint` – Reduce possibility of timing attacks when comparing signatures (@esparta)
22
+
23
+ * `derivatives` – Avoid downloading the attached file when calling default no-op processor (@janko)
24
+
25
+ * `derivatives` – Add `:download` processor setting for skipping downloading source file (@jrochkind, @janko)
26
+
27
+ * `derivatives` – Copy non-file source IO objects into local file before passing them to the processor (@jrochkind)
28
+
29
+ * `sequel` – Call `Attacher#reload` in `Sequel::Model#reload`, which keeps rest of attacher state (@janko, @jrochkind)
30
+
31
+ * `activerecord` – Call `Attacher#reload` in `ActiveRecord::Base#reload`, which keeps rest of attacher state (@janko, @jrochkind)
32
+
33
+ * `add_metadata` – Add `:skip_nil` option for excluding metadata keys whose values are nil (@renchap)
34
+
35
+ * `store_dimensions` – Add `:auto_extraction` option for disabling automatically extracting dimensions on upload (@renchap)
36
+
37
+ * `mirroring` – Forward original upload options when mirroring upload (@corneverbruggen)
38
+
39
+ * `derivation_endpoint` – Apply `version` URL option in derivation endpoint (@janko)
40
+
41
+ * `remove_attachment` – Delete removed file if a new file was attached right after removal (@janko)
42
+
43
+ * `upload_endpoint` – Fix `Shrine.upload_response` not working in a Rails controller (@pldavid2)
44
+
45
+ * `presign_endpoint` – Add `OPTIONS` route that newer versions of Uppy check (@janko)
46
+
47
+ * `derivatives` – Add `:create_on_promote` option for auto-creating derivatives on promotion (@janko)
48
+
49
+ * `s3` – Add back support for client-side encryption (@janko)
50
+
51
+ * `memory` – Ensure `Memory#open` returns content in original encoding (@jrochkind)
52
+
53
+ ## 3.2.2 (2020-08-05)
54
+
55
+ * `s3` – Fix `S3#open` not working on aws-sdk-core 3.104 and above (@janko)
56
+
57
+ ## 3.2.1 (2020-01-12)
58
+
59
+ * `derivation_endpoint` – Use `Rack::Files` constant on Rack >= 2.1 (@janko)
60
+
61
+ * Fix Ruby 2.7 warnings regarding separation of positional and keyword arguments (@janko)
62
+
63
+ * `s3` – Make `S3#open` handle empty S3 objects (@janko)
64
+
65
+ ## 3.2.0 (2019-12-17) [[release notes]](https://shrinerb.com/docs/release_notes/3.2.0)
66
+
67
+ * `validation` – Run validation on `Attacher#attach` & `Attacher#attach_cached` instead of `Attacher#change` (@janko)
68
+
69
+ * `remove_invalid` – Activate also when `Attacher#validate` is run manually (@janko)
70
+
71
+ * `remove_invalid` – Fix incompatibility with `derivatives` plugin (@janko)
72
+
73
+ * `type_predicates` – Add new plugin with convenient `UploadedFile` predicate methods based on MIME type (@janko)
74
+
75
+ * `core` – Allow assigning back current attached file data (@janko)
76
+
77
+ * `derivatives` – Fix `:derivative` value inconsistency when derivatives are being promoted (@janko)
78
+
79
+ * `add_metadata` – Add `#add_metadata` method for adding metadata to uploaded files (@janko)
80
+
81
+ * `derivatives` – Add `:io` and `:attacher` values to instrumentation event payload (@janko)
82
+
1
83
  ## 3.1.0 (2019-11-15) [[release notes]](https://shrinerb.com/docs/release_notes/3.1.0)
2
84
 
3
85
  * `default_storage` – Coerce storage key to symbol in `Attacher#cache_key` & `Attacher#store_key` (@janko)
data/README.md CHANGED
@@ -8,7 +8,7 @@ Shrine is a toolkit for handling file attachments in Ruby applications. Some hig
8
8
  * **Memory friendly** – streaming uploads and [downloads][Retrieving Uploads] make it work great with large files
9
9
  * **Cloud storage** – store files on [disk][FileSystem], [AWS S3][S3], [Google Cloud][GCS], [Cloudinary] and others
10
10
  * **Persistence integrations** – works with [Sequel], [ActiveRecord], [ROM], [Hanami] and [Mongoid] and others
11
- * **Flexible processing** – generate thumbnails [up front] or [on-the-fly] using [ImageMagick] or [libvips]
11
+ * **Flexible processing** – generate thumbnails [eagerly] or [on-the-fly] using [ImageMagick] or [libvips]
12
12
  * **Metadata validation** – [validate files][validation] based on [extracted metadata][metadata]
13
13
  * **Direct uploads** – upload asynchronously [to your app][simple upload] or [to the cloud][presigned upload] using [Uppy]
14
14
  * **Resumable uploads** – make large file uploads [resumable][resumable upload] on [S3][uppy-s3_multipart] or [tus][tus-ruby-server]
@@ -57,8 +57,9 @@ Next, add the `<name>_data` column to the table you want to attach files to. For
57
57
  an "image" attachment on a `photos` table this would be an `image_data` column:
58
58
 
59
59
  ```
60
- $ rails generate migration add_image_data_to_photos image_data:text
60
+ $ rails generate migration add_image_data_to_photos image_data:text # or :jsonb
61
61
  ```
62
+ If using `jsonb` consider adding a [gin index] for fast key-value pair searchability within `image_data`.
62
63
 
63
64
  Now create an uploader class (which you can put in `app/uploaders`) and
64
65
  register the attachment on your model:
@@ -127,6 +128,10 @@ system.
127
128
  * Refile
128
129
  * Active Storage
129
130
 
131
+ ## Contributing
132
+
133
+ Please refer to the [contributing page][Contributing].
134
+
130
135
  ## Code of Conduct
131
136
 
132
137
  Everyone interacting in the Shrine project’s codebases, issue trackers, and
@@ -149,8 +154,8 @@ The gem is available as open source under the terms of the [MIT License].
149
154
  [ROM]: https://github.com/shrinerb/shrine-rom
150
155
  [Hanami]: https://github.com/katafrakt/hanami-shrine
151
156
  [Mongoid]: https://github.com/shrinerb/shrine-mongoid
152
- [up front]: https://shrinerb.com/docs/getting-started#processing-up-front
153
- [on-the-fly]: https://shrinerb.com/docs/getting-started#processing-on-the-fly
157
+ [eagerly]: https://shrinerb.com/docs/getting-started#eager-processing
158
+ [on-the-fly]: https://shrinerb.com/docs/getting-started#on-the-fly-processing
154
159
  [ImageMagick]: https://github.com/janko/image_processing/blob/master/doc/minimagick.md#readme
155
160
  [libvips]: https://github.com/janko/image_processing/blob/master/doc/vips.md#readme
156
161
  [validation]: https://shrinerb.com/docs/validation
@@ -170,3 +175,5 @@ The gem is available as open source under the terms of the [MIT License].
170
175
  [Roda]: https://github.com/jeremyevans/roda
171
176
  [CoC]: /CODE_OF_CONDUCT.md
172
177
  [MIT License]: /LICENSE.txt
178
+ [Contributing]: https://github.com/shrinerb/shrine/blob/master/CONTRIBUTING.md
179
+ [gin index]: https://www.postgresql.org/docs/current/datatype-json.html#JSON-INDEXING
data/doc/advantages.md CHANGED
@@ -93,7 +93,7 @@ low-level abstractions that give you the flexibility to build your own flow.
93
93
  ```rb
94
94
  uploaded_file = ImageUploader.upload(image, :store) # metadata extraction, upload location generation
95
95
  uploaded_file.id #=> "44ccafc10ce6a4ff22829e8f579ee6b9.jpg"
96
- uplaoded_file.metadata #=> { ... extracted metadata ... }
96
+ uploaded_file.metadata #=> { ... extracted metadata ... }
97
97
 
98
98
  data = uploaded_file.to_json # serialization
99
99
  # ...
@@ -157,14 +157,14 @@ end
157
157
 
158
158
  ## Processing
159
159
 
160
- Most file attachment libraries provide either processing files up front
161
- (Paperclip, CarrierWave) or on-the-fly (Dragonfly, Refile, Active Storage).
160
+ Most file attachment libraries allow you to process files either "eagerly"
161
+ (Paperclip, CarrierWave) or "on-the-fly" (Dragonfly, Refile, Active Storage).
162
162
  However, each approach is suitable for different requirements. For instance,
163
163
  while on-the-fly processing is suitable for fast processing (image thumbnails,
164
164
  document previews), longer running processing (video transcoding, raw images)
165
165
  should be moved into a background job.
166
166
 
167
- That's why Shrine supports both [up front][derivatives] and
167
+ That's why Shrine supports both [eager][derivatives] and
168
168
  [on-the-fly][derivation_endpoint] processing. For example, if you're handling
169
169
  image uploads, you can choose to either generate a set of pre-defined
170
170
  thumbnails during attachment:
data/doc/attacher.md CHANGED
@@ -130,8 +130,8 @@ attacher.attach_cached(file)
130
130
 
131
131
  # sets cached file
132
132
  attacher.attach_cached('{"id":"asdf.jpg","storage":"cache","metadata":{...}}')
133
- attacher.attach_cached("id" => "asdf.jpg", "storage" => "cache", "metadata" => { ... })
134
- attacher.attach_cached(id: "asdf.jpg", storage: "cache", metadata: { ... })
133
+ attacher.attach_cached({ "id" => "asdf.jpg", "storage" => "cache", "metadata" => { ... } })
134
+ attacher.attach_cached({ id: "asdf.jpg", storage: "cache", metadata: { ... } })
135
135
 
136
136
  # unsets attached file
137
137
  attacher.attach_cached(nil)
data/doc/carrierwave.md CHANGED
@@ -1,5 +1,5 @@
1
1
  ---
2
- title: Shrine for CarrierWave Users
2
+ title: Upgrading from CarrierWave
3
3
  ---
4
4
 
5
5
  This guide is aimed at helping CarrierWave users transition to Shrine, and it
@@ -322,19 +322,22 @@ module CarrierwaveShrineSynchronization
322
322
 
323
323
  private
324
324
 
325
- # If you'll be using `:prefix` on your Shrine storage, make sure to
326
- # subtract it from the path assigned as `:id`.
327
325
  def shrine_file(uploader)
328
326
  name = uploader.mounted_as
329
327
  filename = read_attribute(name)
330
- path = uploader.store_path(filename)
328
+ location = uploader.store_path(filename)
329
+ location = location.sub(%r{^#{storage.prefix}/}, "") if storage.prefix
331
330
 
332
331
  Shrine.uploaded_file(
333
332
  storage: :store,
334
- id: path,
333
+ id: location,
335
334
  metadata: { "filename" => filename },
336
335
  )
337
336
  end
337
+
338
+ def storage
339
+ Shrine.storages[:store]
340
+ end
338
341
  end
339
342
  ```
340
343
  ```rb
@@ -476,16 +479,25 @@ photo.image.original_filename #=> "avatar.jpg"
476
479
 
477
480
  #### `#store_dir`, `#cache_dir`
478
481
 
479
- Shrine here provides a `#generate_location` method, which is triggered for all
480
- storages:
482
+ Shrine here provides a single `#generate_location` method that's triggered for
483
+ all storages:
481
484
 
482
485
  ```rb
483
486
  class ImageUploader < Shrine
484
- def generate_location(io, record: nil, **)
485
- "#{record.class}/#{record.id}/#{io.original_filename}"
487
+ def generate_location(io, record: nil, name: nil, **)
488
+ [ storage_key,
489
+ record && record.class.name.underscore,
490
+ record && record.id,
491
+ super,
492
+ io.original_filename ].compact.join("/")
486
493
  end
487
494
  end
488
495
  ```
496
+ ```
497
+ cache/user/123/2feff8c724e7ce17/nature.jpg
498
+ store/user/456/7f99669fde1e01fc/kitten.jpg
499
+ ...
500
+ ```
489
501
 
490
502
  You might also want to use the `pretty_location` plugin for automatically
491
503
  generating an organized folder structure.
@@ -498,8 +510,8 @@ For default URLs you can use the `default_url` plugin:
498
510
  class ImageUploader < Shrine
499
511
  plugin :default_url
500
512
 
501
- Attacher.default_url do |options|
502
- "/attachments/#{name}/default.jpg"
513
+ Attacher.default_url do |derivative: nil, **|
514
+ "/fallbacks/#{derivative || "original"}.jpg"
503
515
  end
504
516
  end
505
517
  ```
@@ -654,7 +666,7 @@ shows what are Shrine's equivalents.
654
666
 
655
667
  #### `root`, `base_path`, `permissions`, `directory_permissions`
656
668
 
657
- In Shrine these are configured on the FileSystem storage directly.
669
+ In Shrine these are configured on the `FileSystem` storage directly.
658
670
 
659
671
  #### `storage`, `storage_engines`
660
672
 
@@ -212,7 +212,7 @@ Photo.find_each do |photo|
212
212
  next unless attacher.stored?
213
213
 
214
214
  square = attacher.file.download do |original|
215
- ImageProcessor::MiniMagick
215
+ ImageProcessing::MiniMagick
216
216
  .source(original)
217
217
  .resize_to_fill!(150, 150)
218
218
  end
@@ -53,16 +53,17 @@ Photo.find_each do |photo|
53
53
  next unless attacher.stored? # move only attachments uploaded to permanent storage
54
54
 
55
55
  old_attacher = attacher.dup
56
+ current_file = old_attacher.file
56
57
 
57
58
  attacher.set attacher.upload(attacher.file) # reupload file
58
59
  attacher.set_derivatives attacher.upload_derivatives(attacher.derivatives) # reupload derivatives if you have derivatives
59
60
 
60
61
  begin
61
- attacher.atomic_persist # persist changes if attachment has not changed in the meantime
62
- old_attacher.destroy_attached # delete files on old location
63
- rescue Shrine::AttachmentChanged, # attachment has changed during reuploading
64
- ActiveRecord::RecordNotFound # record has been deleted during reuploading
65
- attacher.destroy_attached # delete now orphaned files
62
+ attacher.atomic_persist(current_file) # persist changes if attachment has not changed in the meantime
63
+ old_attacher.destroy_attached # delete files on old location
64
+ rescue Shrine::AttachmentChanged, # attachment has changed during reuploading
65
+ ActiveRecord::RecordNotFound # record has been deleted during reuploading
66
+ attacher.destroy_attached # delete now orphaned files
66
67
  end
67
68
  end
68
69
  ```
data/doc/design.md CHANGED
@@ -2,22 +2,25 @@
2
2
  title: The Design of Shrine
3
3
  ---
4
4
 
5
- *If you want an in-depth walkthrough through the Shrine codebase, see [Notes on study of shrine implementation] article by Jonathan Rochkind.*
5
+ *If you want an in-depth walkthrough through the Shrine codebase, see [Notes on
6
+ study of shrine implementation] article by Jonathan Rochkind.*
6
7
 
7
- There are five main types of objects that you deal with in Shrine:
8
+ There are five main types of classes that you deal with in Shrine:
8
9
 
9
- * Storage
10
- * `Shrine`
11
- * `Shrine::UploadedFile`
12
- * `Shrine::Attacher`
13
- * `Shrine::Attachment`
10
+ | Class | Description |
11
+ | :---- | :---------- |
12
+ | [`Shrine::Storage::*`](#Storage) | Manages files on a particular storage service |
13
+ | [`Shrine`](#Shrine) | Wraps uploads and handles loading plugins |
14
+ | [`Shrine::UploadedFile`](#shrineuploadedfile) | Represents a file uploaded to a storage |
15
+ | [`Shrine::Attacher`](#shrineattacher) | Handles file attachment logic |
16
+ | [`Shrine::Attachment`](#shrineattachment) | Provides convenience model attachment interface |
14
17
 
15
18
  ## Storage
16
19
 
17
20
  On the lowest level we have a storage. A storage class encapsulates file
18
21
  management logic on a particular service. It is what actually performs uploads,
19
22
  generation of URLs, deletions and similar. By convention it is namespaced under
20
- `Shrine::Storage`.
23
+ `Shrine::Storage::*`.
21
24
 
22
25
  ```rb
23
26
  filesystem = Shrine::Storage::FileSystem.new("uploads")
@@ -26,7 +29,7 @@ filesystem.url("foo") #=> "uploads/foo"
26
29
  filesystem.delete("foo")
27
30
  ```
28
31
 
29
- A storage is a PORO which responds to certain methods:
32
+ A storage is a PORO which implements the following interface:
30
33
 
31
34
  ```rb
32
35
  class Shrine
@@ -56,13 +59,14 @@ class Shrine
56
59
  end
57
60
  ```
58
61
 
59
- Storages are typically not used directly, but through `Shrine`.
62
+ Storages are typically not used directly, but through [`Shrine`](#shrine) and
63
+ [`Shrine::UploadedFile`](#shrine-uploadedfile) classes.
60
64
 
61
65
  ## `Shrine`
62
66
 
63
- A `Shrine` object (also called an "uploader") is essentially a wrapper around
64
- the `#upload` storage method. First the storage needs to be registered under a
65
- name:
67
+ The `Shrine` class (also called an "uploader") primarily provides a wrapper
68
+ method around `Storage#upload`. First, the storage needs to be registered under
69
+ a name:
66
70
 
67
71
  ```rb
68
72
  Shrine.storages[:disk] = Shrine::Storage::FileSystem.new("uploads")
@@ -72,7 +76,7 @@ Now we can upload files to the registered storage:
72
76
 
73
77
  ```rb
74
78
  uploaded_file = Shrine.upload(file, :disk)
75
- uploaded_file #=> #<Shrine::UploadedFile>
79
+ uploaded_file #=> #<Shrine::UploadedFile storage=:disk id="6a9fb596cc554efb" ...>
76
80
  ```
77
81
 
78
82
  The argument to `Shrine#upload` must be an IO-like object. The method does the
@@ -84,63 +88,97 @@ following:
84
88
  * closes the file
85
89
  * creates a `Shrine::UploadedFile` from the data
86
90
 
87
- `Shrine` class and subclasses are also used for loading plugins that extend all
88
- core classes. Each `Shrine` subclass has its own subclass of each of the core
89
- classes (`Shrine::UploadedFile`, `Shrine::Attacher`, and `Shrine::Attachment`),
90
- which makes it possible to have different `Shrine` subclasses with differently
91
- customized attachment logic. See [Creating a New Plugin] guide and the [Plugin
92
- system of Sequel and Roda] article for more details on the design of Shrine's
93
- plugin system.
91
+ ### Plugins
92
+
93
+ The `Shrine` class is also used for loading plugins, which provide additional
94
+ functionality by extending core classes.
95
+
96
+ ```rb
97
+ Shrine.plugin :derivatives
98
+
99
+ Shrine::UploadedFile.ancestors #=> [..., Shrine::Plugins::Derivatives::FileMethods, Shrine::UploadedFile::InstanceMethods, ...]
100
+ Shrine::Attacher.ancestors #=> [..., Shrine::Plugins::Derivatives::AttacherMethods, Shrine::Attacher::InstanceMethods, ...]
101
+ Shrine::Attachment.ancestors #=> [..., Shrine::Plugins::Derivatives::AttachmentMethods, Shrine::Attachment::InstanceMethods, ...]
102
+ ```
103
+
104
+ The plugins store their configuration in `Shrine.opts`:
105
+
106
+ ```rb
107
+ Shrine.plugin :derivation_endpoint, secret_key: "foo"
108
+ Shrine.plugin :default_storage, store: :other_store
109
+ Shrine.plugin :activerecord
110
+
111
+ Shrine.opts #=>
112
+ # { derivation_endpoint: { options: { secret_key: "foo" }, derivations: {} },
113
+ # default_storage: { store: :other_store },
114
+ # column: { serializer: Shrine::Plugins::Column::JsonSerializer },
115
+ # model: { cache: true },
116
+ # activerecord: { callbacks: true, validations: true } }
117
+ ```
118
+
119
+ Each `Shrine` subclass has its own copy of the core classes, storages and
120
+ options, which makes it possible to customize attachment logic per uploader.
121
+
122
+ ```rb
123
+ MyUploader = Class.new(Shrine)
124
+ MyUploader::UploadedFile.superclass #=> Shrine::UploadedFile
125
+ MyUploader::Attacher.superclass #=> Shrine::Attacher
126
+ MyUploader::Attachment.superclass #=> Shrine::Attachment
127
+ ```
128
+
129
+ See [Creating a New Plugin] guide and the [Plugin system of Sequel and Roda]
130
+ article for more details on the design of Shrine's plugin system.
94
131
 
95
132
  ## `Shrine::UploadedFile`
96
133
 
97
- `Shrine::UploadedFile` represents a file that was uploaded to a storage, and is
98
- the result of `Shrine#upload`. It is essentially a wrapper around a data hash
99
- containing information about the uploaded file.
134
+ A `Shrine::UploadedFile` object represents a file that was uploaded to a
135
+ storage, containing upload location, storage, and any metadata extracted during
136
+ the upload.
100
137
 
101
138
  ```rb
102
- uploaded_file #=> #<Shrine::UploadedFile>
103
- uploaded_file.data #=>
139
+ uploaded_file #=> #<Shrine::UploadedFile id="949sdjg834.jpg" storage=:store metadata={...}>
140
+
141
+ uploaded_file.id #=> "949sdjg834.jpg"
142
+ uploaded_file.storage_key #=> :store
143
+ uploaded_file.storage #=> #<Shrine::Storage::S3>
144
+ uploaded_file.metadata #=> {...}
145
+ ```
146
+
147
+ It has convenience methods for accessing metadata:
148
+
149
+ ```rb
150
+ uploaded_file.metadata #=>
104
151
  # {
105
- # "storage" => "file_system",
106
- # "id" => "9260ea09d8effd.pdf",
107
- # "metadata" => {
108
- # "filename" => "resume.pdf",
109
- # "mime_type" => "application/pdf",
110
- # "size" => 983294,
111
- # },
152
+ # "filename" => "matrix.mp4",
153
+ # "mime_type" => "video/mp4",
154
+ # "size" => 345993,
112
155
  # }
156
+
157
+ uploaded_file.original_filename #=> "matrix.mp4"
158
+ uploaded_file.extension #=> "mp4"
159
+ uploaded_file.mime_type #=> "video/mp4"
160
+ uploaded_file.size #=> 345993
113
161
  ```
114
162
 
115
- The data hash contains the storage the file was uploaded to, the location, and
116
- some metadata: original filename, MIME type and filesize. The
117
- `Shrine::UploadedFile` object has handy methods which use this data:
163
+ It also has methods that delegate to the storage:
118
164
 
119
165
  ```rb
120
- # metadata methods
121
- uploaded_file.original_filename
122
- uploaded_file.mime_type
123
- uploaded_file.size
124
- # ...
125
-
126
- # storage methods
127
- uploaded_file.url
128
- uploaded_file.exists?
129
- uploaded_file.open
130
- uploaded_file.download
131
- uploaded_file.delete
132
- # ...
166
+ uploaded_file.url #=> "https://my-bucket.s3.amazonaws.com/949sdjg834.jpg"
167
+ uploaded_file.open { |io| ... } # opens the uploaded file stream
168
+ uploaded_file.download { |file| ... } # downloads the uploaded file to disk
169
+ uploaded_file.stream(destination) # streams uploaded content into a writable destination
170
+ uploaded_file.exists? #=> true
171
+ uploaded_file.delete # deletes the uploaded file from the storage
133
172
  ```
134
173
 
135
- A `Shrine::UploadedFile` is itself an IO-like object (representing the
136
- remote file), so it can be passed to `Shrine#upload` as well.
174
+ A `Shrine::UploadedFile` is itself an IO-like object (built on top of
175
+ `Storage#open`), so it can be passed to `Shrine#upload` as well.
137
176
 
138
177
  ## `Shrine::Attacher`
139
178
 
140
179
  We usually want to treat uploaded files as *attachments* to records, saving
141
- their data into a database column. This is the responsibility of
142
- `Shrine::Attacher`. A `Shrine::Attacher` uses `Shrine` uploaders and
143
- `Shrine::UploadedFile` objects internally.
180
+ their data into a database column. This is done by `Shrine::Attacher`, which
181
+ internally uses `Shrine` and `Shrine::UploadedFile` classes.
144
182
 
145
183
  The attaching process requires a temporary and a permanent storage to be
146
184
  registered (by default that's `:cache` and `:store`):
@@ -152,40 +190,50 @@ Shrine.storages = {
152
190
  }
153
191
  ```
154
192
 
155
- A `Shrine::Attacher` is instantiated with a model instance and an attachment
156
- name (an "image" attachment will be saved to `image_data` field):
193
+ A `Shrine::Attacher` can be initialized standalone and handle the common
194
+ attachment flow, which includes dirty tracking (promoting cached file to
195
+ permanent storage, deleting previously attached file), validation, processing,
196
+ serialization etc.
197
+
198
+ ```rb
199
+ attacher = Shrine::Attacher.new
200
+
201
+ # ... user uploads a file ...
202
+
203
+ attacher.assign(io) # uploads to temporary storage
204
+ attacher.file #=> #<Shrine::UploadedFile storage=:cache ...>
205
+
206
+ # ... handle file validations ...
207
+
208
+ attacher.finalize # uploads to permanent storage
209
+ attacher.file #=> #<Shrine::UploadedFile storage=:store ...>
210
+ ```
211
+
212
+ It can also be initialized with a model instance to handle serialization into a
213
+ model attribute:
157
214
 
158
215
  ```rb
159
216
  attacher = Shrine::Attacher.from_model(photo, :image)
160
217
 
161
218
  attacher.assign(file)
162
- attacher.file #=> #<Shrine::UploadedFile storage=:cache ...>
163
- attacher.record.image_data #=> "{\"storage\":\"cache\",\"id\":\"9260ea09d8effd.jpg\",\"metadata\":{...}}"
219
+ photo.image_data #=> "{\"storage\":\"cache\",\"id\":\"9260ea09d8effd.jpg\",\"metadata\":{...}}"
164
220
 
165
221
  attacher.finalize
166
- attacher.file #=> #<Shrine::UploadedFile storage=:store ...>
167
- attacher.record.image_data #=> "{\"storage\":\"store\",\"id\":\"ksdf02lr9sf3la.jpg\",\"metadata\":{...}}"
222
+ photo.image_data #=> "{\"storage\":\"store\",\"id\":\"ksdf02lr9sf3la.jpg\",\"metadata\":{...}}"
168
223
  ```
169
224
 
170
- Above a file is assigned by the attacher, which "caches" (uploads) the file to
171
- the temporary storage. The cached file is then "promoted" (uploaded) to
172
- permanent storage. Behind the scenes a cached `Shrine::UploadedFile` is given
173
- to `Shrine#upload`, which works because `Shrine::UploadedFile` is an IO-like
174
- object. After both caching and promoting the data hash of the uploaded file is
175
- assigned to the record's column as JSON.
176
-
177
- For more details see [Using Attacher].
225
+ For more details, see the [Using Attacher] guide and
226
+ [`entity`][entity]/[`model`][model] plugins.
178
227
 
179
228
  ## `Shrine::Attachment`
180
229
 
181
- `Shrine::Attachment` is the highest level of abstraction. A
182
- `Shrine::Attachment` module exposes the `Shrine::Attacher` object through the
183
- model instance. The `Shrine::Attachment` class is a sublcass of `Module`, which
184
- means that an instance of `Shrine::Attachment` is a module:
230
+ A `Shrine::Attachment` module provides a convenience model interface around the
231
+ `Shrine::Attacher` object. The `Shrine::Attachment` class is a subclass of
232
+ `Module`, which means that an instance of `Shrine::Attachment` is a module:
185
233
 
186
234
  ```rb
187
235
  Shrine::Attachment.new(:image).is_a?(Module) #=> true
188
- Shrine::Attachment.new(:image).instance_methods #=> [:image=, :image, :image_url, :image_attacher]
236
+ Shrine::Attachment.new(:image).instance_methods #=> [:image=, :image, :image_url, :image_attacher, ...]
189
237
 
190
238
  # equivalents
191
239
  Shrine::Attachment.new(:image)
@@ -193,30 +241,31 @@ Shrine::Attachment[:image]
193
241
  Shrine::Attachment(:image)
194
242
  ```
195
243
 
196
- We can include this module to a model:
244
+ We can include this module into a model:
197
245
 
198
246
  ```rb
199
- class Photo
200
- include Shrine::Attachment(:image)
201
- end
247
+ Photo.include Shrine::Attachment(:image)
202
248
  ```
203
249
  ```rb
204
- photo.image = file # shorthand for `photo.image_attacher.assign(file)`
205
- photo.image # shorthand for `photo.image_attacher.get`
206
- photo.image_url # shorthand for `photo.image_attacher.url`
250
+ photo.image = file # shorthand for `photo.image_attacher.assign(file)`
251
+ photo.image # shorthand for `photo.image_attacher.get`
252
+ photo.image_url # shorthand for `photo.image_attacher.url`
207
253
 
208
- photo.image_attacher #=> #<Shrine::Attacher>
254
+ photo.image_attacher #=> #<Shrine::Attacher @cache_key=:cache @store_key=:store ...>
209
255
  ```
210
256
 
211
- When a persistence plugin is loaded, the `Shrine::Attachment` module also
212
- automatically:
257
+ When a persistence plugin is loaded ([`activerecord`][activerecord],
258
+ [`sequel`][sequel]), the `Shrine::Attachment` module also automatically:
213
259
 
214
260
  * syncs Shrine's validation errors with the record
215
261
  * triggers promoting after record is saved
216
- * deletes the uploaded file if attachment was replaced/removed or the record
217
- destroyed
262
+ * deletes the uploaded file if attachment was replaced or the record destroyed
218
263
 
219
264
  [Using Attacher]: https://shrinerb.com/docs/attacher
220
265
  [Notes on study of shrine implementation]: https://bibwild.wordpress.com/2018/09/12/notes-on-study-of-shrine-implementation/
221
266
  [Creating a New Plugin]: https://shrinerb.com/docs/creating-plugins
222
267
  [Plugin system of Sequel and Roda]: https://twin.github.io/the-plugin-system-of-sequel-and-roda/
268
+ [entity]: https://shrinerb.com/docs/plugins/entity
269
+ [model]: https://shrinerb.com/docs/plugins/model
270
+ [activerecord]: https://shrinerb.com/docs/plugins/activerecord
271
+ [sequel]: https://shrinerb.com/docs/plugins/sequel