shrine 3.1.0 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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