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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +87 -33
- data/LICENSE.txt +1 -1
- data/README.md +94 -4
- data/doc/advantages.md +35 -18
- data/doc/attacher.md +16 -17
- data/doc/carrierwave.md +75 -34
- data/doc/changing_derivatives.md +39 -39
- data/doc/design.md +134 -85
- data/doc/external/articles.md +56 -41
- data/doc/external/extensions.md +38 -34
- data/doc/getting_started.md +182 -112
- data/doc/metadata.md +79 -43
- data/doc/multiple_files.md +5 -3
- data/doc/paperclip.md +110 -42
- data/doc/plugins/activerecord.md +5 -5
- data/doc/plugins/add_metadata.md +92 -35
- data/doc/plugins/backgrounding.md +12 -2
- data/doc/plugins/column.md +36 -7
- data/doc/plugins/data_uri.md +2 -2
- data/doc/plugins/default_url.md +6 -3
- data/doc/plugins/derivation_endpoint.md +26 -28
- data/doc/plugins/derivatives.md +205 -169
- data/doc/plugins/determine_mime_type.md +2 -2
- data/doc/plugins/entity.md +3 -3
- data/doc/plugins/form_assign.md +5 -5
- data/doc/plugins/included.md +25 -5
- data/doc/plugins/infer_extension.md +2 -2
- data/doc/plugins/instrumentation.md +1 -1
- data/doc/plugins/metadata_attributes.md +21 -10
- data/doc/plugins/model.md +4 -4
- data/doc/plugins/persistence.md +1 -0
- data/doc/plugins/refresh_metadata.md +5 -4
- data/doc/plugins/remote_url.md +8 -3
- data/doc/plugins/remove_invalid.md +9 -1
- data/doc/plugins/sequel.md +4 -4
- data/doc/plugins/signature.md +11 -2
- data/doc/plugins/store_dimensions.md +2 -2
- data/doc/plugins/type_predicates.md +96 -0
- data/doc/plugins/upload_endpoint.md +7 -11
- data/doc/plugins/upload_options.md +1 -1
- data/doc/plugins/url_options.md +2 -2
- data/doc/plugins/validation.md +14 -4
- data/doc/plugins/validation_helpers.md +3 -3
- data/doc/plugins/versions.md +11 -11
- data/doc/processing.md +289 -125
- data/doc/refile.md +39 -18
- data/doc/release_notes/2.19.0.md +1 -1
- data/doc/release_notes/3.0.0.md +275 -258
- data/doc/release_notes/3.0.1.md +22 -0
- data/doc/release_notes/3.1.0.md +73 -0
- data/doc/release_notes/3.2.0.md +96 -0
- data/doc/release_notes/3.2.1.md +32 -0
- data/doc/release_notes/3.2.2.md +14 -0
- data/doc/securing_uploads.md +3 -3
- data/doc/storage/file_system.md +1 -1
- data/doc/storage/memory.md +19 -0
- data/doc/storage/s3.md +105 -86
- data/doc/testing.md +2 -2
- data/doc/upgrading_to_3.md +115 -33
- data/doc/validation.md +3 -2
- data/lib/shrine.rb +8 -8
- data/lib/shrine/attacher.rb +19 -14
- data/lib/shrine/attachment.rb +5 -5
- data/lib/shrine/plugins.rb +22 -0
- data/lib/shrine/plugins/add_metadata.rb +12 -3
- data/lib/shrine/plugins/default_storage.rb +6 -6
- data/lib/shrine/plugins/default_url.rb +1 -1
- data/lib/shrine/plugins/derivation_endpoint.rb +10 -6
- data/lib/shrine/plugins/derivatives.rb +19 -17
- data/lib/shrine/plugins/determine_mime_type.rb +3 -3
- data/lib/shrine/plugins/entity.rb +6 -6
- data/lib/shrine/plugins/metadata_attributes.rb +1 -1
- data/lib/shrine/plugins/model.rb +3 -3
- data/lib/shrine/plugins/presign_endpoint.rb +2 -2
- data/lib/shrine/plugins/pretty_location.rb +1 -1
- data/lib/shrine/plugins/processing.rb +1 -1
- data/lib/shrine/plugins/refresh_metadata.rb +2 -2
- data/lib/shrine/plugins/remote_url.rb +3 -3
- data/lib/shrine/plugins/remove_invalid.rb +10 -5
- data/lib/shrine/plugins/signature.rb +7 -6
- data/lib/shrine/plugins/store_dimensions.rb +18 -9
- data/lib/shrine/plugins/type_predicates.rb +113 -0
- data/lib/shrine/plugins/upload_endpoint.rb +3 -3
- data/lib/shrine/plugins/upload_options.rb +2 -2
- data/lib/shrine/plugins/url_options.rb +2 -2
- data/lib/shrine/plugins/validation.rb +9 -7
- data/lib/shrine/storage/linter.rb +4 -4
- data/lib/shrine/storage/s3.rb +62 -38
- data/lib/shrine/uploaded_file.rb +5 -1
- data/lib/shrine/version.rb +2 -2
- data/shrine.gemspec +6 -7
- metadata +23 -29
data/doc/refile.md
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
---
|
2
|
-
title:
|
2
|
+
title: Upgrading from Refile
|
3
3
|
---
|
4
4
|
|
5
5
|
This guide is aimed at helping Refile users transition to Shrine, and it consists
|
6
6
|
of three parts:
|
7
7
|
|
8
8
|
1. Explanation of the key differences in design between Refile and Shrine
|
9
|
-
2. Instructions how to migrate
|
9
|
+
2. Instructions how to migrate an existing app that uses Refile to Shrine
|
10
10
|
3. Extensive reference of Refile's interface with Shrine equivalents
|
11
11
|
|
12
12
|
## Overview
|
@@ -110,13 +110,6 @@ into separate columns.
|
|
110
110
|
Shrine provides on-the-fly processing via the
|
111
111
|
[`derivation_endpoint`][derivation_endpoint] plugin:
|
112
112
|
|
113
|
-
```rb
|
114
|
-
# config/routes.rb (Rails)
|
115
|
-
Rails.application.routes.draw do
|
116
|
-
# ...
|
117
|
-
mount ImageUploader.derivation_endpoint => "/derivations/image"
|
118
|
-
end
|
119
|
-
```
|
120
113
|
```rb
|
121
114
|
require "image_processing/mini_magick"
|
122
115
|
|
@@ -132,8 +125,15 @@ class ImageUploader < Shrine
|
|
132
125
|
end
|
133
126
|
end
|
134
127
|
```
|
128
|
+
```rb
|
129
|
+
# config/routes.rb (Rails)
|
130
|
+
Rails.application.routes.draw do
|
131
|
+
# ...
|
132
|
+
mount ImageUploader.derivation_endpoint => "/derivations/image"
|
133
|
+
end
|
134
|
+
```
|
135
135
|
|
136
|
-
Shrine also support processing
|
136
|
+
Shrine also support eager processing using the [`derivatives`][derivatives]
|
137
137
|
plugin.
|
138
138
|
|
139
139
|
### Validation
|
@@ -199,13 +199,18 @@ explains this setup in more detail.
|
|
199
199
|
## Migrating from Refile
|
200
200
|
|
201
201
|
You have an existing app using Refile and you want to transfer it to
|
202
|
-
Shrine. Let's assume we have a `Photo` model with the "image" attachment.
|
203
|
-
|
202
|
+
Shrine. Let's assume we have a `Photo` model with the "image" attachment.
|
203
|
+
|
204
|
+
### 1. Add Shrine column
|
205
|
+
|
206
|
+
First we need to create the `image_data` column for Shrine:
|
204
207
|
|
205
208
|
```rb
|
206
209
|
add_column :photos, :image_data, :text
|
207
210
|
```
|
208
211
|
|
212
|
+
### 2. Dual write
|
213
|
+
|
209
214
|
Afterwards we need to make new uploads write to the `image_data` column. This
|
210
215
|
can be done by including the below module to all models that have Refile
|
211
216
|
attachments:
|
@@ -219,8 +224,7 @@ Shrine.storages = {
|
|
219
224
|
}
|
220
225
|
|
221
226
|
Shrine.plugin :model
|
222
|
-
|
223
|
-
```rb
|
227
|
+
|
224
228
|
module RefileShrineSynchronization
|
225
229
|
def write_shrine_data(name)
|
226
230
|
attacher = Shrine::Attacher.from_model(self, name)
|
@@ -257,8 +261,12 @@ end
|
|
257
261
|
```
|
258
262
|
|
259
263
|
After you deploy this code, the `image_data` column should now be successfully
|
260
|
-
synchronized with new attachments.
|
261
|
-
|
264
|
+
synchronized with new attachments.
|
265
|
+
|
266
|
+
### 3. Data migration
|
267
|
+
|
268
|
+
Next step is to run a script which writes all existing Refile attachments to
|
269
|
+
`image_data`:
|
262
270
|
|
263
271
|
```rb
|
264
272
|
Photo.find_each do |photo|
|
@@ -267,9 +275,22 @@ Photo.find_each do |photo|
|
|
267
275
|
end
|
268
276
|
```
|
269
277
|
|
278
|
+
### 4. Rewrite code
|
279
|
+
|
270
280
|
Now you should be able to rewrite your application so that it uses Shrine
|
271
|
-
instead of Refile
|
272
|
-
the
|
281
|
+
instead of Refile (you can consult the reference in the next section). You can
|
282
|
+
remove the `RefileShrineSynchronization` module as well.
|
283
|
+
|
284
|
+
### 5. Remove Refile columns
|
285
|
+
|
286
|
+
If everything is looking good, we can remove Refile columns:
|
287
|
+
|
288
|
+
```rb
|
289
|
+
remove_column :photos, :image_id
|
290
|
+
remove_column :photos, :image_size
|
291
|
+
remove_column :photos, :image_filename
|
292
|
+
remove_column :photos, :image_content_type
|
293
|
+
```
|
273
294
|
|
274
295
|
## Refile to Shrine direct mapping
|
275
296
|
|
data/doc/release_notes/2.19.0.md
CHANGED
@@ -16,7 +16,7 @@ title: Shrine 2.19.0
|
|
16
16
|
uploaded_file.download
|
17
17
|
uploaded_file.delete
|
18
18
|
```
|
19
|
-
```
|
19
|
+
```
|
20
20
|
Metadata (32ms) – {:storage=>:store, :io=>StringIO, :uploader=>Shrine}
|
21
21
|
Upload (1523ms) – {:storage=>:store, :location=>"ed0e30ddec8b97813f2c1f4cfd1700b4", :io=>StringIO, :upload_options=>{}, :uploader=>Shrine}
|
22
22
|
Exists (755ms) – {:storage=>:store, :location=>"ed0e30ddec8b97813f2c1f4cfd1700b4", :uploader=>Shrine}
|
data/doc/release_notes/3.0.0.md
CHANGED
@@ -3,312 +3,326 @@ title: Shrine 3.0.0
|
|
3
3
|
---
|
4
4
|
|
5
5
|
This guide covers all the changes in the 3.0.0 version of Shrine. If you're
|
6
|
-
currently using Shrine 2.x, see [Upgrading to Shrine 3.x] for instructions
|
7
|
-
how to upgrade.
|
6
|
+
currently using Shrine 2.x, see **[Upgrading to Shrine 3.x]** for instructions
|
7
|
+
on how to upgrade.
|
8
8
|
|
9
9
|
## Major features
|
10
10
|
|
11
|
-
|
12
|
-
additional processed files alongside the main file.
|
11
|
+
### Derivatives
|
13
12
|
|
14
|
-
|
15
|
-
|
16
|
-
```
|
17
|
-
```rb
|
18
|
-
class ImageUploader < Shrine
|
19
|
-
Attacher.derivatives_processor do |original|
|
20
|
-
magick = ImageProcessing::MiniMagick.source(original)
|
21
|
-
|
22
|
-
{
|
23
|
-
large: magick.resize_to_limit!(800, 800),
|
24
|
-
medium: magick.resize_to_limit!(500, 500),
|
25
|
-
small: magick.resize_to_limit!(300, 300),
|
26
|
-
}
|
27
|
-
end
|
28
|
-
end
|
29
|
-
```
|
30
|
-
```rb
|
31
|
-
photo = Photo.new(photo_params)
|
32
|
-
photo.image_derivatives! # creates derivatives
|
33
|
-
photo.save
|
34
|
-
```
|
35
|
-
|
36
|
-
This is a rewrite of the [`versions`][versions] plugin, bringing numerous
|
37
|
-
improvements:
|
38
|
-
|
39
|
-
- processed files are separated from the main file
|
40
|
-
|
41
|
-
```rb
|
42
|
-
photo.image_data #=>
|
43
|
-
# {
|
44
|
-
# "id": "original.jpg",
|
45
|
-
# "storage": "store",
|
46
|
-
# "metadata": { ... },
|
47
|
-
# "derivatives": {
|
48
|
-
# "large": { "id": "large.jpg", "storage": "store", "metadata": { ... } },
|
49
|
-
# "medium": { "id": "medium.jpg", "storage": "store", "metadata": { ... } },
|
50
|
-
# "small": { "id": "small.jpg", "storage": "store", "metadata": { ... } }
|
51
|
-
# }
|
52
|
-
# }
|
53
|
-
|
54
|
-
photo.image #=> #<Shrine::UploadedFile @id="original.jpg" ...>
|
55
|
-
photo.image_derivatives #=>
|
56
|
-
# {
|
57
|
-
# large: #<Shrine::UploadedFile @id="large.jpg" ...>,
|
58
|
-
# medium: #<Shrine::UploadedFile @id="medium.jpg" ...>,
|
59
|
-
# small: #<Shrine::UploadedFile @id="small.jpg" ...>,
|
60
|
-
# }
|
61
|
-
```
|
62
|
-
|
63
|
-
- processing is decoupled from promotion
|
64
|
-
|
65
|
-
```rb
|
66
|
-
photo = Photo.create(image: file) # promote original file to permanent storage
|
67
|
-
photo.image_derivatives! # generate derivatives after promotion
|
68
|
-
photo.save # save derivatives data
|
69
|
-
```
|
70
|
-
|
71
|
-
- ability to add or remove processed files at any point
|
72
|
-
|
73
|
-
```rb
|
74
|
-
class ImageUploader < Shrine
|
75
|
-
Attacher.derivatives_processor :thumbnails do |original|
|
76
|
-
# ...
|
77
|
-
end
|
78
|
-
|
79
|
-
Attacher.derivatives_processor :crop do |original, left:, top:, width:, height:|
|
80
|
-
vips = ImageProcessing::Vips.source(original)
|
81
|
-
|
82
|
-
{ cropped: vips.crop!(left, top, width, height) }
|
83
|
-
end
|
84
|
-
end
|
85
|
-
```
|
86
|
-
```rb
|
87
|
-
photo.image_derivatives!(:thumbnails)
|
88
|
-
photo.image_derivatives #=> { large: ..., medium: ..., small: ... }
|
89
|
-
photo.save
|
90
|
-
|
91
|
-
# ... sometime later ...
|
92
|
-
|
93
|
-
photo.image_derivatives!(:crop, left: 0, top: 0, width: 300, height: 300)
|
94
|
-
photo.image_derivatives #=> { large: ..., medium: ..., small: ..., cropped: ... }
|
95
|
-
photo.save
|
96
|
-
```
|
13
|
+
The new **[`derivatives`][derivatives]** plugin has been added for storing
|
14
|
+
additional processed files alongside the main file.
|
97
15
|
|
98
|
-
|
16
|
+
```rb
|
17
|
+
Shrine.plugin :derivatives
|
18
|
+
```
|
19
|
+
```rb
|
20
|
+
class ImageUploader < Shrine
|
21
|
+
Attacher.derivatives_processor do |original|
|
22
|
+
magick = ImageProcessing::MiniMagick.source(original)
|
99
23
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
24
|
+
{
|
25
|
+
large: magick.resize_to_limit!(800, 800),
|
26
|
+
medium: magick.resize_to_limit!(500, 500),
|
27
|
+
small: magick.resize_to_limit!(300, 300),
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
```
|
32
|
+
```rb
|
33
|
+
photo = Photo.new(photo_params)
|
34
|
+
photo.image_derivatives! # creates derivatives
|
35
|
+
photo.save
|
36
|
+
```
|
112
37
|
|
113
|
-
|
114
|
-
|
115
|
-
```
|
38
|
+
This is a rewrite of the [`versions`][versions] plugin, bringing numerous
|
39
|
+
improvements:
|
116
40
|
|
117
|
-
*
|
118
|
-
used without models:
|
41
|
+
* processed files are separated from the main file
|
119
42
|
|
120
43
|
```rb
|
121
|
-
|
122
|
-
|
123
|
-
|
44
|
+
photo.image_data #=>
|
45
|
+
# {
|
46
|
+
# "id": "original.jpg",
|
47
|
+
# "storage": "store",
|
48
|
+
# "metadata": { ... },
|
49
|
+
# "derivatives": {
|
50
|
+
# "large": { "id": "large.jpg", "storage": "store", "metadata": { ... } },
|
51
|
+
# "medium": { "id": "medium.jpg", "storage": "store", "metadata": { ... } },
|
52
|
+
# "small": { "id": "small.jpg", "storage": "store", "metadata": { ... } }
|
53
|
+
# }
|
54
|
+
# }
|
55
|
+
|
56
|
+
photo.image #=> #<Shrine::UploadedFile id="original.jpg" ...>
|
57
|
+
photo.image_derivatives #=>
|
58
|
+
# {
|
59
|
+
# large: #<Shrine::UploadedFile id="large.jpg" ...>,
|
60
|
+
# medium: #<Shrine::UploadedFile id="medium.jpg" ...>,
|
61
|
+
# small: #<Shrine::UploadedFile id="small.jpg" ...>,
|
62
|
+
# }
|
124
63
|
```
|
125
64
|
|
126
|
-
|
127
|
-
have been added for dumping and loading the attached file:
|
65
|
+
* processing is decoupled from promotion
|
128
66
|
|
129
67
|
```rb
|
130
|
-
#
|
131
|
-
|
132
|
-
|
133
|
-
```rb
|
134
|
-
# initialize attacher from attached file data...
|
135
|
-
attacher = Shrine::Attacher.from_data(data)
|
136
|
-
attacher.file #=> #<Shrine::UploadedFile @id="abc123.jpg" @storage_key=:store @metadata={...}>
|
137
|
-
|
138
|
-
# ...or load attached file into an existing attacher
|
139
|
-
attacher = Shrine::Attacher.new
|
140
|
-
attacher.load_data(data)
|
141
|
-
attacher.file #=> #<Shrine::UploadedFile>
|
68
|
+
photo = Photo.create(image: file) # promote original file to permanent storage
|
69
|
+
photo.image_derivatives! # generate derivatives after promotion
|
70
|
+
photo.save # save derivatives data
|
142
71
|
```
|
143
72
|
|
144
|
-
|
73
|
+
- ability to add or remove processed files at any point
|
145
74
|
|
146
|
-
- `Attacher#attach` – attaches the file directly to permanent storage
|
147
|
-
- `Attacher#attach_cached` – extracted from `Attacher#assign`
|
148
|
-
- `Attacher#upload` – calls `Shrine#upload`, passing `:record` and `:name` context
|
149
|
-
- `Attacher#file` – alias for `Attacher#get`
|
150
|
-
- `Attacher#cache_key` – returns temporary storage key (`:cache` by default)
|
151
|
-
- `Attacher#store_key` – returns permanent storage key (`:store` by default)
|
152
|
-
|
153
|
-
* The new [`column`][column] plugin adds the ability to serialize attached file
|
154
|
-
data, in format suitable for writing into a database column.
|
155
|
-
|
156
|
-
```rb
|
157
|
-
Shrine.plugin :column
|
158
|
-
```
|
159
75
|
```rb
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
# initialize attacher from attached file data...
|
165
|
-
attacher = Shrine::Attacher.from_column(data)
|
166
|
-
attacher.file #=> #<Shrine::UploadedFile @id="abc123.jpg" @storage_key=:store @metadata={...}>
|
167
|
-
|
168
|
-
# ...or load attached file into an existing attacher
|
169
|
-
attacher = Shrine::Attacher.new
|
170
|
-
attacher.load_column(data)
|
171
|
-
attacher.file #=> #<Shrine::UploadedFile>
|
172
|
-
```
|
76
|
+
class ImageUploader < Shrine
|
77
|
+
Attacher.derivatives_processor :thumbnails do |original|
|
78
|
+
# ...
|
79
|
+
end
|
173
80
|
|
174
|
-
|
175
|
-
|
81
|
+
Attacher.derivatives_processor :crop do |original, left:, top:, width:, height:|
|
82
|
+
vips = ImageProcessing::Vips.source(original)
|
176
83
|
|
177
|
-
|
178
|
-
|
179
|
-
```
|
180
|
-
```rb
|
181
|
-
class Photo < Hanami::Entity
|
182
|
-
include Shrine::Attachment(:image)
|
84
|
+
{ cropped: vips.crop!(left, top, width, height) }
|
85
|
+
end
|
183
86
|
end
|
184
87
|
```
|
185
88
|
```rb
|
186
|
-
photo
|
187
|
-
photo.
|
188
|
-
|
89
|
+
photo.image_derivatives!(:thumbnails)
|
90
|
+
photo.image_derivatives #=> { large: ..., medium: ..., small: ... }
|
91
|
+
photo.save
|
189
92
|
|
190
|
-
|
191
|
-
used by `activerecord` and `sequel` plugins.
|
93
|
+
# ... sometime later ...
|
192
94
|
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
```rb
|
197
|
-
class Photo < Struct.new(:image_data)
|
198
|
-
include Shrine::Attachment(:image)
|
199
|
-
end
|
200
|
-
```
|
201
|
-
```rb
|
202
|
-
photo = Photo.new
|
203
|
-
photo.image = file
|
204
|
-
photo.image #=> #<Shrine::UploadedFile @id="abc123.jpg" @storage_key=:cache ...>
|
205
|
-
photo.image_data #=> #=> '{"id":"abc123.jpg", "storage":"cache", "metadata":{...}}'
|
95
|
+
photo.image_derivatives!(:crop, left: 0, top: 0, width: 300, height: 300)
|
96
|
+
photo.image_derivatives #=> { large: ..., medium: ..., small: ..., cropped: ... }
|
97
|
+
photo.save
|
206
98
|
```
|
207
99
|
|
208
|
-
*
|
209
|
-
flexibility and simplicity. The new usage is much more explicit:
|
100
|
+
* possibility of uploading processed files to different storage
|
210
101
|
|
211
102
|
```rb
|
212
|
-
Shrine
|
213
|
-
|
214
|
-
|
215
|
-
end
|
216
|
-
Shrine::Attacher.destroy_block do
|
217
|
-
DestroyJob.perform_async(self.class.name, data)
|
218
|
-
end
|
219
|
-
```
|
220
|
-
```rb
|
221
|
-
class PromoteJob
|
222
|
-
include Sidekiq::Worker
|
223
|
-
|
224
|
-
def perform(attacher_class, record_class, record.id, name, file_data)
|
225
|
-
attacher_class = Object.const_get(attacher_class)
|
226
|
-
record = Object.const_get(record_class).find(record_id) # if using Active Record
|
103
|
+
class ImageUploader < Shrine
|
104
|
+
# specify storage for all derivatives
|
105
|
+
Attacher.derivatives_storage :other_store
|
227
106
|
|
228
|
-
|
229
|
-
|
230
|
-
rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
|
231
|
-
# attachment has changed or the record has been deleted, nothing to do
|
232
|
-
end
|
107
|
+
# or specify storage per derivative
|
108
|
+
Attacher.derivatives_storage { |derivative| :other_store }
|
233
109
|
end
|
234
110
|
```
|
235
111
|
```rb
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
112
|
+
photo = Photo.create(image: file)
|
113
|
+
photo.image.storage_key #=> :store
|
114
|
+
|
115
|
+
photo.image_derivatives!
|
116
|
+
photo.image_derivatives[:large].storage_key #=> :other_store
|
117
|
+
```
|
118
|
+
|
119
|
+
### Attacher redesign
|
120
|
+
|
121
|
+
The [`Shrine::Attacher`][attacher] class has been rewritten and can now be
|
122
|
+
used without models:
|
123
|
+
|
124
|
+
```rb
|
125
|
+
attacher = Shrine::Attacher.new
|
126
|
+
attacher.attach(file)
|
127
|
+
attacher.file #=> #<Shrine::UploadedFile>
|
128
|
+
```
|
129
|
+
|
130
|
+
The `Attacher#data`, `Attacher#load_data`, and `Attacher.from_data` methods
|
131
|
+
have been added for dumping and loading the attached file:
|
132
|
+
|
133
|
+
```rb
|
134
|
+
# dump attached file into a serializable Hash
|
135
|
+
data = attacher.data #=> { "id" => "abc123.jpg", "storage" => "store", "metadata" => { ... } }
|
136
|
+
```
|
137
|
+
```rb
|
138
|
+
# initialize attacher from attached file data...
|
139
|
+
attacher = Shrine::Attacher.from_data(data)
|
140
|
+
attacher.file #=> #<Shrine::UploadedFile id="abc123.jpg" storage=:store metadata={...}>
|
141
|
+
|
142
|
+
# ...or load attached file into an existing attacher
|
143
|
+
attacher = Shrine::Attacher.new
|
144
|
+
attacher.load_data(data)
|
145
|
+
attacher.file #=> #<Shrine::UploadedFile>
|
146
|
+
```
|
147
|
+
|
148
|
+
Several more methods have been added:
|
149
|
+
|
150
|
+
- `Attacher#attach` – attaches the file directly to permanent storage
|
151
|
+
- `Attacher#attach_cached` – extracted from `Attacher#assign`
|
152
|
+
- `Attacher#upload` – calls `Shrine#upload`, passing `:record` and `:name` context
|
153
|
+
- `Attacher#file` – alias for `Attacher#get`
|
154
|
+
- `Attacher#cache_key` – returns temporary storage key (`:cache` by default)
|
155
|
+
- `Attacher#store_key` – returns permanent storage key (`:store` by default)
|
156
|
+
|
157
|
+
#### Column
|
158
|
+
|
159
|
+
The new [`column`][column] plugin adds the ability to serialize attached file
|
160
|
+
data, in format suitable for writing into a database column.
|
161
|
+
|
162
|
+
```rb
|
163
|
+
Shrine.plugin :column
|
164
|
+
```
|
165
|
+
```rb
|
166
|
+
# dump attached file data into a JSON string
|
167
|
+
data = attacher.column_data #=> '{"id":"abc123.jpg","storage":"store","metadata":{...}}'
|
168
|
+
```
|
169
|
+
```rb
|
170
|
+
# initialize attacher from attached file data...
|
171
|
+
attacher = Shrine::Attacher.from_column(data)
|
172
|
+
attacher.file #=> #<Shrine::UploadedFile id="abc123.jpg" storage=:store metadata={...}>
|
173
|
+
|
174
|
+
# ...or load attached file into an existing attacher
|
175
|
+
attacher = Shrine::Attacher.new
|
176
|
+
attacher.load_column(data)
|
177
|
+
attacher.file #=> #<Shrine::UploadedFile>
|
178
|
+
```
|
179
|
+
|
180
|
+
#### Entity
|
181
|
+
|
182
|
+
The new [`entity`][entity] plugin adds support for immutable structs, which
|
183
|
+
are commonly used with ROM, Hanami and dry-rb.
|
184
|
+
|
185
|
+
```rb
|
186
|
+
Shrine.plugin :entity
|
187
|
+
```
|
188
|
+
```rb
|
189
|
+
class Photo < Hanami::Entity
|
190
|
+
include Shrine::Attachment(:image)
|
191
|
+
end
|
192
|
+
```
|
193
|
+
```rb
|
194
|
+
photo = Photo.new(image_data: '{"id":"abc123.jpg","storage":"store","metadata":{...}}')
|
195
|
+
photo.image #=> #<Shrine::UploadedFile id="abc123.jpg" storage=:store ...>
|
196
|
+
```
|
197
|
+
|
198
|
+
#### Model
|
199
|
+
|
200
|
+
The new [`model`][model] plugin adds support for mutable structs, which is
|
201
|
+
used by `activerecord` and `sequel` plugins.
|
202
|
+
|
203
|
+
```rb
|
204
|
+
Shrine.plugin :model
|
205
|
+
```
|
206
|
+
```rb
|
207
|
+
class Photo < Struct.new(:image_data)
|
208
|
+
include Shrine::Attachment(:image)
|
209
|
+
end
|
210
|
+
```
|
211
|
+
```rb
|
212
|
+
photo = Photo.new
|
213
|
+
photo.image = file
|
214
|
+
photo.image #=> #<Shrine::UploadedFile id="abc123.jpg" storage=:cache ...>
|
215
|
+
photo.image_data #=> #=> '{"id":"abc123.jpg", "storage":"cache", "metadata":{...}}'
|
216
|
+
```
|
217
|
+
|
218
|
+
### Backgrounding rewrite
|
241
219
|
|
242
|
-
|
243
|
-
|
244
|
-
|
220
|
+
* The [`backgrounding`][backgrounding] plugin has been rewritten for more
|
221
|
+
flexibility and simplicity. The new usage is much more explicit:
|
222
|
+
|
223
|
+
```rb
|
224
|
+
Shrine.plugin :backgrounding
|
225
|
+
Shrine::Attacher.promote_block do
|
226
|
+
PromoteJob.perform_async(self.class.name, record.class.name, record.id, name, file_data)
|
227
|
+
end
|
228
|
+
Shrine::Attacher.destroy_block do
|
229
|
+
DestroyJob.perform_async(self.class.name, data)
|
230
|
+
end
|
231
|
+
```
|
232
|
+
```rb
|
233
|
+
class PromoteJob
|
234
|
+
include Sidekiq::Worker
|
235
|
+
|
236
|
+
def perform(attacher_class, record_class, record.id, name, file_data)
|
237
|
+
attacher_class = Object.const_get(attacher_class)
|
238
|
+
record = Object.const_get(record_class).find(record_id) # if using Active Record
|
239
|
+
|
240
|
+
attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
|
241
|
+
attacher.atomic_promote
|
242
|
+
rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
|
243
|
+
# attachment has changed or the record has been deleted, nothing to do
|
245
244
|
end
|
246
|
-
|
245
|
+
end
|
246
|
+
```
|
247
|
+
```rb
|
248
|
+
class DestroyJob
|
249
|
+
include Sidekiq::Worker
|
247
250
|
|
248
|
-
|
249
|
-
|
250
|
-
- we are in charge of passing the record to the background job
|
251
|
-
- we can access the attacher before promotion
|
252
|
-
- we can react to errors that caused promotion to abort
|
253
|
-
|
254
|
-
We can now also register backgrounding hooks on an attacher instance, allowing
|
255
|
-
us to pass additional parameters to the background job:
|
256
|
-
|
257
|
-
```rb
|
258
|
-
photo = Photo.new(photo_params)
|
251
|
+
def perform(attacher_class, data)
|
252
|
+
attacher_class = Object.const_get(attacher_class)
|
259
253
|
|
260
|
-
|
261
|
-
|
262
|
-
attacher.class.name,
|
263
|
-
attacher.record.class.name,
|
264
|
-
attacher.record.id,
|
265
|
-
attacher.name,
|
266
|
-
attacher.file_data,
|
267
|
-
current_user.id, # <== parameters from the controller
|
268
|
-
)
|
254
|
+
attacher = attacher_class.from_data(data)
|
255
|
+
attacher.destroy
|
269
256
|
end
|
257
|
+
end
|
258
|
+
```
|
270
259
|
|
271
|
-
|
272
|
-
```
|
260
|
+
There are several main differences compared to the old implementation:
|
273
261
|
|
274
|
-
|
275
|
-
|
262
|
+
- we are in charge of passing the record to the background job
|
263
|
+
- we can access the attacher before promotion
|
264
|
+
- we can react to errors that caused promotion to abort
|
276
265
|
|
277
|
-
|
278
|
-
|
279
|
-
| `Attacher#persist` | persists attachment data |
|
280
|
-
| `Attacher#atomic_persist` | persists attachment data if attachment hasn’t changed |
|
281
|
-
| `Attacher#atomic_promote` | promotes cached file and atomically persists changes |
|
266
|
+
We can now also register backgrounding hooks on an attacher instance, allowing
|
267
|
+
us to pass additional parameters to the background job:
|
282
268
|
|
283
|
-
|
284
|
-
|
285
|
-
implement metadata extraction in the background in a concurrency-safe way:
|
269
|
+
```rb
|
270
|
+
photo = Photo.new(photo_params)
|
286
271
|
|
287
|
-
|
288
|
-
|
272
|
+
photo.image_attacher.promote_block do |attacher|
|
273
|
+
PromoteJob.perform_async(
|
289
274
|
attacher.class.name,
|
290
275
|
attacher.record.class.name,
|
291
276
|
attacher.record.id,
|
292
277
|
attacher.name,
|
293
278
|
attacher.file_data,
|
279
|
+
current_user.id, # <== parameters from the controller
|
294
280
|
)
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
281
|
+
end
|
282
|
+
|
283
|
+
photo.save # will call our instance-level backgrounding hook
|
284
|
+
```
|
285
|
+
|
286
|
+
### Persistence interface
|
287
|
+
|
288
|
+
The persistence plugins (`activerecord`, `sequel`) now implement a unified
|
289
|
+
[persistence] interface:
|
290
|
+
|
291
|
+
| Method | Description |
|
292
|
+
| :----------------- | :---------- |
|
293
|
+
| `Attacher#persist` | persists attachment data |
|
294
|
+
| `Attacher#atomic_persist` | persists attachment data if attachment hasn’t changed |
|
295
|
+
| `Attacher#atomic_promote` | promotes cached file and atomically persists changes |
|
296
|
+
|
297
|
+
The "atomic" methods use the new [`atomic_helpers`][atomic_helpers] plugin,
|
298
|
+
and are useful for background jobs. For example, this is how we'd use them to
|
299
|
+
implement metadata extraction in the background in a concurrency-safe way:
|
300
|
+
|
301
|
+
```rb
|
302
|
+
MetadataJob.perform_async(
|
303
|
+
attacher.class.name,
|
304
|
+
attacher.record.class.name,
|
305
|
+
attacher.record.id,
|
306
|
+
attacher.name,
|
307
|
+
attacher.file_data,
|
308
|
+
)
|
309
|
+
```
|
310
|
+
```rb
|
311
|
+
class MetadataJob
|
312
|
+
include Sidekiq::Worker
|
313
|
+
|
314
|
+
def perform(attacher_class, record_class, record_id, name, file_data)
|
315
|
+
attacher_class = Object.const_get(attacher_class)
|
316
|
+
record = Object.const_get(record_class).find(record_id) # if using Active Record
|
317
|
+
|
318
|
+
attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
|
319
|
+
attacher.refresh_metadata! # extract metadata
|
320
|
+
attacher.atomic_persist # persist if attachment hasn't changed
|
321
|
+
rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
|
322
|
+
# attachment has changed or record has been deleted, nothing to do
|
310
323
|
end
|
311
|
-
|
324
|
+
end
|
325
|
+
```
|
312
326
|
|
313
327
|
## Other new plugins
|
314
328
|
|
@@ -352,8 +366,8 @@ how to upgrade.
|
|
352
366
|
```
|
353
367
|
```rb
|
354
368
|
attacher = photo.image_attacher
|
355
|
-
attacher.form_assign("image" => file, "title" => "...", "description" => "...")
|
356
|
-
attacher.file #=> #<Shrine::UploadedFile
|
369
|
+
attacher.form_assign({ "image" => file, "title" => "...", "description" => "..." })
|
370
|
+
attacher.file #=> #<Shrine::UploadedFile id="..." storage=:cache ...>
|
357
371
|
```
|
358
372
|
|
359
373
|
## Other features
|
@@ -686,6 +700,9 @@ how to upgrade.
|
|
686
700
|
* The `Attacher#replace` method has been renamed to
|
687
701
|
`Attacher#destroy_previous`.
|
688
702
|
|
703
|
+
* The `Attacher#assign` method now raises an exception when non-cached uploaded
|
704
|
+
file is assigned.
|
705
|
+
|
689
706
|
* The `Attacher#attached?` method now returns whether a file is attached,
|
690
707
|
regardless of whether it was changed or not.
|
691
708
|
|