shrine 3.0.0 → 3.2.2

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.

Potentially problematic release.


This version of shrine might be problematic. Click here for more details.

Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +87 -33
  3. data/LICENSE.txt +1 -1
  4. data/README.md +94 -4
  5. data/doc/advantages.md +35 -18
  6. data/doc/attacher.md +16 -17
  7. data/doc/carrierwave.md +75 -34
  8. data/doc/changing_derivatives.md +39 -39
  9. data/doc/design.md +134 -85
  10. data/doc/external/articles.md +56 -41
  11. data/doc/external/extensions.md +38 -34
  12. data/doc/getting_started.md +182 -112
  13. data/doc/metadata.md +79 -43
  14. data/doc/multiple_files.md +5 -3
  15. data/doc/paperclip.md +110 -42
  16. data/doc/plugins/activerecord.md +5 -5
  17. data/doc/plugins/add_metadata.md +92 -35
  18. data/doc/plugins/backgrounding.md +12 -2
  19. data/doc/plugins/column.md +36 -7
  20. data/doc/plugins/data_uri.md +2 -2
  21. data/doc/plugins/default_url.md +6 -3
  22. data/doc/plugins/derivation_endpoint.md +26 -28
  23. data/doc/plugins/derivatives.md +205 -169
  24. data/doc/plugins/determine_mime_type.md +2 -2
  25. data/doc/plugins/entity.md +3 -3
  26. data/doc/plugins/form_assign.md +5 -5
  27. data/doc/plugins/included.md +25 -5
  28. data/doc/plugins/infer_extension.md +2 -2
  29. data/doc/plugins/instrumentation.md +1 -1
  30. data/doc/plugins/metadata_attributes.md +21 -10
  31. data/doc/plugins/model.md +4 -4
  32. data/doc/plugins/persistence.md +1 -0
  33. data/doc/plugins/refresh_metadata.md +5 -4
  34. data/doc/plugins/remote_url.md +8 -3
  35. data/doc/plugins/remove_invalid.md +9 -1
  36. data/doc/plugins/sequel.md +4 -4
  37. data/doc/plugins/signature.md +11 -2
  38. data/doc/plugins/store_dimensions.md +2 -2
  39. data/doc/plugins/type_predicates.md +96 -0
  40. data/doc/plugins/upload_endpoint.md +7 -11
  41. data/doc/plugins/upload_options.md +1 -1
  42. data/doc/plugins/url_options.md +2 -2
  43. data/doc/plugins/validation.md +14 -4
  44. data/doc/plugins/validation_helpers.md +3 -3
  45. data/doc/plugins/versions.md +11 -11
  46. data/doc/processing.md +289 -125
  47. data/doc/refile.md +39 -18
  48. data/doc/release_notes/2.19.0.md +1 -1
  49. data/doc/release_notes/3.0.0.md +275 -258
  50. data/doc/release_notes/3.0.1.md +22 -0
  51. data/doc/release_notes/3.1.0.md +73 -0
  52. data/doc/release_notes/3.2.0.md +96 -0
  53. data/doc/release_notes/3.2.1.md +32 -0
  54. data/doc/release_notes/3.2.2.md +14 -0
  55. data/doc/securing_uploads.md +3 -3
  56. data/doc/storage/file_system.md +1 -1
  57. data/doc/storage/memory.md +19 -0
  58. data/doc/storage/s3.md +105 -86
  59. data/doc/testing.md +2 -2
  60. data/doc/upgrading_to_3.md +115 -33
  61. data/doc/validation.md +3 -2
  62. data/lib/shrine.rb +8 -8
  63. data/lib/shrine/attacher.rb +19 -14
  64. data/lib/shrine/attachment.rb +5 -5
  65. data/lib/shrine/plugins.rb +22 -0
  66. data/lib/shrine/plugins/add_metadata.rb +12 -3
  67. data/lib/shrine/plugins/default_storage.rb +6 -6
  68. data/lib/shrine/plugins/default_url.rb +1 -1
  69. data/lib/shrine/plugins/derivation_endpoint.rb +10 -6
  70. data/lib/shrine/plugins/derivatives.rb +19 -17
  71. data/lib/shrine/plugins/determine_mime_type.rb +3 -3
  72. data/lib/shrine/plugins/entity.rb +6 -6
  73. data/lib/shrine/plugins/metadata_attributes.rb +1 -1
  74. data/lib/shrine/plugins/model.rb +3 -3
  75. data/lib/shrine/plugins/presign_endpoint.rb +2 -2
  76. data/lib/shrine/plugins/pretty_location.rb +1 -1
  77. data/lib/shrine/plugins/processing.rb +1 -1
  78. data/lib/shrine/plugins/refresh_metadata.rb +2 -2
  79. data/lib/shrine/plugins/remote_url.rb +3 -3
  80. data/lib/shrine/plugins/remove_invalid.rb +10 -5
  81. data/lib/shrine/plugins/signature.rb +7 -6
  82. data/lib/shrine/plugins/store_dimensions.rb +18 -9
  83. data/lib/shrine/plugins/type_predicates.rb +113 -0
  84. data/lib/shrine/plugins/upload_endpoint.rb +3 -3
  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/s3.rb +62 -38
  90. data/lib/shrine/uploaded_file.rb +5 -1
  91. data/lib/shrine/version.rb +2 -2
  92. data/shrine.gemspec +6 -7
  93. metadata +23 -29
@@ -42,7 +42,7 @@ gem "image_processing", "~> 1.8"
42
42
  require "image_processing/mini_magick"
43
43
 
44
44
  class ImageUploader < Shrine
45
- Attacher.derivatives_processor do |original|
45
+ Attacher.derivatives do |original|
46
46
  magick = ImageProcessing::MiniMagick.source(original)
47
47
 
48
48
  # generate the thumbnails you want here
@@ -93,16 +93,16 @@ now you want to reprocess them for existing attachments.*
93
93
  Let's assume we've made the following change and have deployed it to production:
94
94
 
95
95
  ```diff
96
- Attacher.derivatives_processor do |original|
97
- magick = ImageProcessing::MiniMagick.source(original)
96
+ Attacher.derivatives do |original|
97
+ magick = ImageProcessing::MiniMagick.source(original)
98
98
  + .saver(quality: 85)
99
99
 
100
- {
101
- small: magick.resize_to_limit!(300, 300),
102
- medium: magick.resize_to_limit!(500, 500),
103
- large: magick.resize_to_limit!(800, 800),
104
- }
105
- end
100
+ {
101
+ small: magick.resize_to_limit!(300, 300),
102
+ medium: magick.resize_to_limit!(500, 500),
103
+ large: magick.resize_to_limit!(800, 800),
104
+ }
105
+ end
106
106
  ```
107
107
 
108
108
  We can now run the following script to reprocess derivatives for all existing
@@ -139,16 +139,16 @@ you want to reprocess them for existing attachments.*
139
139
  Let's assume we've made a following change and have deployed it to production:
140
140
 
141
141
  ```diff
142
- Attacher.derivatives_processor do |original|
143
- magick = ImageProcessing::MiniMagick.source(original)
144
-
145
- {
146
- small: magick.resize_to_limit!(300, 300),
147
- - medium: magick.resize_to_limit!(500, 500),
148
- + medium: magick.resize_to_limit!(600, 600),
149
- large: magick.resize_to_limit!(800, 800),
150
- }
151
- end
142
+ Attacher.derivatives do |original|
143
+ magick = ImageProcessing::MiniMagick.source(original)
144
+
145
+ {
146
+ small: magick.resize_to_limit!(300, 300),
147
+ - medium: magick.resize_to_limit!(500, 500),
148
+ + medium: magick.resize_to_limit!(600, 600),
149
+ large: magick.resize_to_limit!(800, 800),
150
+ }
151
+ end
152
152
  ```
153
153
 
154
154
  We can now run the following script to reprocess the derivative for all
@@ -189,16 +189,16 @@ you want to add it to existing attachments.*
189
189
  Let's assume we've made a following change and have deployed it to production:
190
190
 
191
191
  ```diff
192
- Attacher.derivatives_processor do |original|
193
- magick = ImageProcessing::MiniMagick.source(original)
194
-
195
- {
196
- + square: magick.resize_to_fill!(150, 150),
197
- small: magick.resize_to_limit!(300, 300),
198
- medium: magick.resize_to_limit!(600, 600),
199
- large: magick.resize_to_limit!(800, 800),
200
- }
201
- end
192
+ Attacher.derivatives do |original|
193
+ magick = ImageProcessing::MiniMagick.source(original)
194
+
195
+ {
196
+ + square: magick.resize_to_fill!(150, 150),
197
+ small: magick.resize_to_limit!(300, 300),
198
+ medium: magick.resize_to_limit!(600, 600),
199
+ large: magick.resize_to_limit!(800, 800),
200
+ }
201
+ end
202
202
  ```
203
203
 
204
204
  We can now run following script to add the new derivative for all existing
@@ -239,16 +239,16 @@ existing attachments.*
239
239
  Let's assume we've made the following change and have deployed it to production:
240
240
 
241
241
  ```diff
242
- Attacher.derivatives_processor do |original|
243
- magick = ImageProcessing::MiniMagick.source(original)
244
-
245
- {
246
- - square: magick.resize_to_fill!(150, 150),
247
- small: magick.resize_to_limit!(300, 300),
248
- medium: magick.resize_to_limit!(600, 600),
249
- large: magick.resize_to_limit!(800, 800),
250
- }
251
- end
242
+ Attacher.derivatives do |original|
243
+ magick = ImageProcessing::MiniMagick.source(original)
244
+
245
+ {
246
+ - square: magick.resize_to_fill!(150, 150),
247
+ small: magick.resize_to_limit!(300, 300),
248
+ medium: magick.resize_to_limit!(600, 600),
249
+ large: magick.resize_to_limit!(800, 800),
250
+ }
251
+ end
252
252
  ```
253
253
 
254
254
  We can now run following script to remove the unused derivative for all
@@ -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::*` | Manages files on a particular storage service |
13
+ | `Shrine` | Wraps uploads and handles loading plugins |
14
+ | `Shrine::UploadedFile` | Represents a file uploaded to a storage |
15
+ | `Shrine::Attacher` | Handles file attachment logic |
16
+ | `Shrine::Attachment` | 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_key=: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_key=: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
@@ -4,47 +4,62 @@ title: Articles
4
4
 
5
5
  ## Official articles
6
6
 
7
- * [Introducing Shrine](http://twin.github.io/introducing-shrine)
8
- * [Asynchronous File Uploads](http://twin.github.io/file-uploads-asynchronous-world)
9
- * [Shrine 2.0 Released](https://twin.github.io/shrine-2-0-released/)
10
- * [Shrine meets Transloadit](https://twin.github.io/shrine-meets-transloadit/)
11
- * [Resumable File Uploads in Ruby](https://twin.github.io/resumable-file-uploads-in-ruby/)
12
- * [Adding Direct S3 Uploads to a Roda App with Shrine](https://github.com/shrinerb/shrine/wiki/Adding-Direct-S3-Uploads)
13
- * [Adding Resumable Uploads to a Roda App with Shrine](https://github.com/shrinerb/shrine/wiki/Adding-Resumable-Uploads)
14
- * [Better File Uploads with Shrine: Motivation](https://twin.github.io/better-file-uploads-with-shrine-motivation/)
15
- * [Better File Uploads with Shrine: Uploader](https://twin.github.io/better-file-uploads-with-shrine-uploader/)
16
- * [Better File Uploads with Shrine: Attachment](https://twin.github.io/better-file-uploads-with-shrine-attachment/)
17
- * [Better File Uploads with Shrine: Processing](https://twin.github.io/better-file-uploads-with-shrine-processing/)
18
- * [Better File Uploads with Shrine: Metadata](https://twin.github.io/better-file-uploads-with-shrine-metadata/)
19
- * [Better File Uploads with Shrine: Direct Uploads](https://twin.github.io/better-file-uploads-with-shrine-direct-uploads)
7
+ | Article | Published |
8
+ | :------- | --------: |
9
+ | [Better File Uploads with Shrine: Eager Processing](https://twin.github.io/better-file-uploads-with-shrine-eager-processing) | 12&nbsp;Dec&nbsp;2019 |
10
+ | [Shrine 3.0 Released](https://twin.github.io/shrine-3-0-released/) | 14&nbsp;Oct&nbsp;2019 |
11
+ | [Upcoming Features in Shrine 3.0](https://twin.github.io/upcoming-features-in-shrine-3-0/) | 29&nbsp;Aug&nbsp;2019 |
12
+ | [Better File Uploads with Shrine: Direct Uploads](https://twin.github.io/better-file-uploads-with-shrine-direct-uploads/) | 08&nbsp;Jan&nbsp;2018 |
13
+ | [Better File Uploads with Shrine: Metadata](https://twin.github.io/better-file-uploads-with-shrine-metadata/) | 07&nbsp;Nov&nbsp;2016 |
14
+ | [Better File Uploads with Shrine: Processing](https://twin.github.io/better-file-uploads-with-shrine-processing/) | 31&nbsp;Oct&nbsp;2016 |
15
+ | [Better File Uploads with Shrine: Attachment](https://twin.github.io/better-file-uploads-with-shrine-attachment/) | 17&nbsp;Sep&nbsp;2016 |
16
+ | [Better File Uploads with Shrine: Uploader](https://twin.github.io/better-file-uploads-with-shrine-uploader/) | 16&nbsp;Sep&nbsp;2016 |
17
+ | [Better File Uploads with Shrine: Motivation](https://twin.github.io/better-file-uploads-with-shrine-motivation/) | 11&nbsp;Sep&nbsp;2016 |
18
+ | [Resumable File Uploads in Ruby](https://twin.github.io/resumable-file-uploads-in-ruby/) | 04&nbsp;Sep&nbsp;2016 |
19
+ | [Shrine meets Transloadit](https://twin.github.io/shrine-meets-transloadit/) | 11&nbsp;Jul&nbsp;2016 |
20
+ | [Shrine 2.0 Released](https://twin.github.io/shrine-2-0-released/) | 20&nbsp;May&nbsp;2016 |
21
+ | [Asynchronous File Uploads](http://twin.github.io/file-uploads-asynchronous-world) | 18&nbsp;Jan&nbsp;2016 |
22
+ | [Introducing Shrine](http://twin.github.io/introducing-shrine) | 04&nbsp;Oct&nbsp;2015 |
20
23
 
21
24
  ## Other Articles
22
25
 
23
- * [Uploading files with Shrine in Hanami](http://katafrakt.me/2016/02/04/shrine-hanami-uploads/)
24
- * [Rails File Uploading You Can Believe in with Shrine](http://www.sitepoint.com/rails-file-uploading-you-can-believe-in-with-shrine/)
25
- * [Uploading Files With Rails and Shrine](https://code.tutsplus.com/tutorials/uploading-files-with-rails-and-shrine--cms-27596)
26
- * [Upload images using Shrine.rb and Dropzone.js](https://codyeatworld.com/2017/04/18/rails-uploading-images-confidently-with-shrine-rb/)
27
- * [Background file uploads to S3 using Shrine](http://elixirator.com/blog/2017/background-file-uploads-to-s3-using-shrine.html)
28
- * [Rails + Shrine + DropzoneJS](https://stephencodes.com/rails-5-shrine-dropzonejs/)
29
- * [Uploading Files with Rails and ActionCable](https://scotch.io/tutorials/uploading-files-with-rails-and-actioncable)
30
- * [Creating Streaming Radio With Rails and Icecast](https://scotch.io/tutorials/creating-online-streaming-radio-with-rails-and-icecast)
31
- * [Multiple Photo Upload using Shrine](https://github.com/pyksoft/multi-photo-upload#multiple-photo-upload-using-shrine)
32
- * [Store Your Files on S3: Setup & Configuration](https://www.ironin.it/blog/store-your-files-on-s3-using-the-ruby-shrine-gem-part-1.html)
33
- * [Store Your Files on S3: Direct Uploads](https://www.ironin.it/blog/store-your-files-on-s3-using-the-ruby-shrine-gem-part-2.html)
34
- * [Store Your Files on S3: Uploading from a URL](https://www.ironin.it/blog/store-your-files-on-s3-using-the-ruby-shrine-gem-part-3.html)
35
- * [How to use Trix and Shrine for WYSIWYG Editing with Drag-and-Drop Image Uploading](http://headway.io/blog/how-to-use-trix-and-shrine-for-wysiwyg-editing-with-drag-and-drop-image-uploading/)
36
- * [Happy users uploading files with Rails 5, Shrine, and Vue.js](https://itnext.io/happy-users-uploading-files-with-rails-5-shrine-and-vue-js-bbcc470a327f)
37
- * [Notes on study of shrine implementation](https://bibwild.wordpress.com/2018/09/12/notes-on-study-of-shrine-implementation/)
38
- * [GraphQL file upload with Shrine](https://blog.stanko.io/graphql-file-upload-with-shrine-45fa26463c68)
39
-
40
- ## Videos
41
-
42
- * [wroc_love.rb Handling file uploads for a modern developer](https://www.youtube.com/watch?v=fP2JGjTZU2s)
43
- * [File Uploads in Rails with Shrine (GoRails)](https://gorails.com/episodes/file-uploading-with-shrine?autoplay=1)
44
- * [Direct File Uploads to S3: Part 1 (GoRails)](https://gorails.com/episodes/direct-file-uploads-to-s3-part-1?autoplay=1)
45
- * [Direct File Uploads to S3: Part 2 (GoRails)](https://gorails.com/episodes/direct-file-uploads-to-s3-part-2?autoplay=1)
46
- * [Direct File Uploads to S3: Part 3 (GoRails)](https://gorails.com/episodes/direct-file-uploads-to-s3-part-3?autoplay=1)
47
- * [Backgrounding and Video Transcoding (GoRails)](https://gorails.com/episodes/shrine-background-and-video-transcoding?autoplay=1)
48
- * [Multiple File Uploads with Shrine (GoRails)](https://gorails.com/episodes/multiple-file-uploads-with-shrine?autoplay=1)
49
- * [Trix WYSIWYG Editor And File Uploads (GoRails)](https://gorails.com/episodes/trix-editor?autoplay=1)
50
- * [Uploading Files to DigitalOcean Spaces (GoRails)](https://gorails.com/episodes/digital-ocean-spaces-with-rails?autoplay=1)
26
+ | Article | Published |
27
+ | :------ | --------: |
28
+ | [Microsoft Azure data storage library](https://syndicode.com/2019/12/17/microsoft-azure-data-storage-library-rewrite-the-existing-library-to-get-things-done/) | 17&nbsp;Dec&nbsp;2019 |
29
+ | [How to use REST clients / native apps to make Shrine client uploads and S3 work for you (no Javascript, just backend)](https://dev.to/rob117/how-to-use-rest-clients--native-apps-to-make-shrine-client-uploads-and-s3-work-for-you-no-javascript-just-backend-322h) | 16&nbsp;Feb&nbsp;2019 |
30
+ | [GraphQL file upload with Shrine](https://blog.stanko.io/graphql-file-upload-with-shrine-45fa26463c68) | 02&nbsp;Jan&nbsp;2019 |
31
+ | [Notes on study of shrine implementation](https://bibwild.wordpress.com/2018/09/12/notes-on-study-of-shrine-implementation/) | 12&nbsp;Sep&nbsp;2018 |
32
+ | [Happy users uploading files with Rails 5, Shrine, and Vue.js](https://itnext.io/happy-users-uploading-files-with-rails-5-shrine-and-vue-js-bbcc470a327f) | 04&nbsp;Sep&nbsp;2018 |
33
+ | [Rails 5 + Shrine + DropzoneJS](https://stephencodes.com/rails-5-shrine-dropzonejs/) | 20&nbsp;Oct&nbsp;2018 |
34
+ | [How to use Trix and Shrine for WYSIWYG Editing with Drag-and-Drop Image Uploading](http://headway.io/blog/how-to-use-trix-and-shrine-for-wysiwyg-editing-with-drag-and-drop-image-uploading/) | 26&nbsp;Jan&nbsp;2018 |
35
+ | [Store Your Files on S3: Uploading from a URL](https://www.ironin.it/blog/store-your-files-on-s3-using-the-ruby-shrine-gem-part-3.html) | 28&nbsp;Dec&nbsp;2017 |
36
+ | [Store Your Files on S3: Direct Uploads](https://www.ironin.it/blog/store-your-files-on-s3-using-the-ruby-shrine-gem-part-2.html) | 15&nbsp;Dec&nbsp;2017 |
37
+ | [Store Your Files on S3: Setup & Configuration](https://www.ironin.it/blog/store-your-files-on-s3-using-the-ruby-shrine-gem-part-1.html) | 27&nbsp;Nov&nbsp;2017 |
38
+ | [Creating Streaming Radio With Rails and Icecast](https://scotch.io/tutorials/creating-online-streaming-radio-with-rails-and-icecast) | 06&nbsp;Nov&nbsp;2017 |
39
+ | [Multiple Photo Upload using Shrine](https://github.com/pyksoft/multi-photo-upload#multiple-photo-upload-using-shrine) | 02&nbsp;Nov&nbsp;2017 |
40
+ | [Uploading Files with Rails and ActionCable](https://scotch.io/tutorials/uploading-files-with-rails-and-actioncable) | 19&nbsp;Oct&nbsp;2017 |
41
+ | [Background file uploads to S3 using Shrine](https://elixirator.com/blog/background-file-uploads-to-s3-using-shrine/) | 21&nbsp;Jul&nbsp;2017 |
42
+ | [Upload images using Shrine.rb and Dropzone.js](https://codyeatworld.com/2017/04/18/rails-uploading-images-confidently-with-shrine-rb/) | 18&nbsp;Apr&nbsp;2017 |
43
+ | [Uploading Files With Rails and Shrine](https://code.tutsplus.com/tutorials/uploading-files-with-rails-and-shrine--cms-27596) | 26&nbsp;Dec&nbsp;2016 |
44
+ | [Rails File Uploading You Can Believe in with Shrine](http://www.sitepoint.com/rails-file-uploading-you-can-believe-in-with-shrine/) | 11&nbsp;Apr&nbsp;2016 |
45
+ | [Uploading files with Shrine in Hanami](http://katafrakt.me/2016/02/04/shrine-hanami-uploads/) | 04&nbsp;Feb&nbsp;2016 |
46
+
47
+ ## Screencasts
48
+
49
+ | Screencast | Source | Published |
50
+ | :---- | :------ | --------: |
51
+ | [Uploading Files to DigitalOcean Spaces](https://gorails.com/episodes/digital-ocean-spaces-with-rails?autoplay=1) | GoRails | 31&nbsp;Oct&nbsp;2017 |
52
+ | [Trix WYSIWYG Editor And File Uploads](https://gorails.com/episodes/trix-editor?autoplay=1) | GoRails | 03&nbsp;Oct&nbsp;2017 |
53
+ | [Multiple File Uploads with Shrine](https://gorails.com/episodes/multiple-file-uploads-with-shrine?autoplay=1) | GoRails | 14&nbsp;Dec&nbsp;2016 |
54
+ | [Backgrounding and Video Transcoding](https://gorails.com/episodes/shrine-background-and-video-transcoding?autoplay=1) | GoRails | 03&nbsp;Nov&nbsp;2016 |
55
+ | [Direct File Uploads to S3: Part 3](https://gorails.com/episodes/direct-file-uploads-to-s3-part-3?autoplay=1) | GoRails | 05&nbsp;Oct&nbsp;2016 |
56
+ | [Direct File Uploads to S3: Part 2](https://gorails.com/episodes/direct-file-uploads-to-s3-part-2?autoplay=1) | GoRails | 05&nbsp;Oct&nbsp;2016 |
57
+ | [Direct File Uploads to S3: Part 1](https://gorails.com/episodes/direct-file-uploads-to-s3-part-1?autoplay=1) | GoRails | 05&nbsp;Oct&nbsp;2016 |
58
+ | [File Uploads in Rails with Shrine](https://gorails.com/episodes/file-uploading-with-shrine?autoplay=1) | GoRails | 23&nbsp;Sep&nbsp;2016 |
59
+
60
+ ## Talks
61
+
62
+ | Talk | Source | Date |
63
+ | :---- | :------ | --------: |
64
+ | [Handling file uploads for a modern developer](https://www.youtube.com/watch?v=fP2JGjTZU2s) | wroc_love.rb | 24&nbsp;Mar&nbsp;2019 |
65
+ | [Shrine – Handle File Uploads like it's 2017](https://www.youtube.com/watch?v=plD-RkKEay0) | Melbourne Ruby | 27&nbsp;Feb&nbsp;2017 |