shrine 3.0.0 → 3.0.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of shrine might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +41 -33
- data/README.md +85 -0
- data/doc/advantages.md +3 -3
- data/doc/attacher.md +14 -15
- data/doc/carrierwave.md +4 -4
- data/doc/design.md +2 -2
- data/doc/external/articles.md +1 -0
- data/doc/external/extensions.md +1 -0
- data/doc/getting_started.md +8 -7
- data/doc/paperclip.md +5 -5
- data/doc/plugins/activerecord.md +4 -4
- data/doc/plugins/derivatives.md +4 -4
- data/doc/plugins/entity.md +3 -3
- data/doc/plugins/model.md +4 -4
- data/doc/plugins/sequel.md +4 -4
- data/doc/plugins/versions.md +4 -4
- data/doc/release_notes/3.0.0.md +274 -257
- data/doc/release_notes/3.0.1.md +18 -0
- data/doc/upgrading_to_3.md +19 -1
- data/lib/shrine/attacher.rb +1 -1
- data/lib/shrine/plugins/derivatives.rb +3 -3
- data/lib/shrine/plugins/metadata_attributes.rb +1 -1
- data/lib/shrine/uploaded_file.rb +5 -0
- data/lib/shrine/version.rb +1 -1
- metadata +3 -2
data/doc/carrierwave.md
CHANGED
@@ -179,15 +179,15 @@ uploaded processed files into the database (including any extracted metadata),
|
|
179
179
|
which then becomes the source of truth on which versions have been generated.
|
180
180
|
|
181
181
|
```rb
|
182
|
-
photo.image #=> #<Shrine::UploadedFile
|
182
|
+
photo.image #=> #<Shrine::UploadedFile id="original.jpg" ...>
|
183
183
|
photo.image_derivatives #=> {}
|
184
184
|
|
185
185
|
photo.image_derivatives! # triggers processing
|
186
186
|
photo.image_derivatives #=>
|
187
187
|
# {
|
188
|
-
# large:
|
189
|
-
# medium: #<Shrine::UploadedFile
|
190
|
-
# small:
|
188
|
+
# large: #<Shrine::UploadedFile id="large.jpg" metadata={"size"=>873232, ...} ...>,
|
189
|
+
# medium: #<Shrine::UploadedFile id="medium.jpg" metadata={"size"=>94823, ...} ...>,
|
190
|
+
# small: #<Shrine::UploadedFile id="small.jpg" metadata={"size"=>37322, ...} ...>,
|
191
191
|
# }
|
192
192
|
```
|
193
193
|
|
data/doc/design.md
CHANGED
@@ -159,11 +159,11 @@ name (an "image" attachment will be saved to `image_data` field):
|
|
159
159
|
attacher = Shrine::Attacher.from_model(photo, :image)
|
160
160
|
|
161
161
|
attacher.assign(file)
|
162
|
-
attacher.file #=> #<Shrine::UploadedFile
|
162
|
+
attacher.file #=> #<Shrine::UploadedFile storage=:cache ...>
|
163
163
|
attacher.record.image_data #=> "{\"storage\":\"cache\",\"id\":\"9260ea09d8effd.jpg\",\"metadata\":{...}}"
|
164
164
|
|
165
165
|
attacher.finalize
|
166
|
-
attacher.file #=> #<Shrine::UploadedFile
|
166
|
+
attacher.file #=> #<Shrine::UploadedFile storage=:store ...>
|
167
167
|
attacher.record.image_data #=> "{\"storage\":\"store\",\"id\":\"ksdf02lr9sf3la.jpg\",\"metadata\":{...}}"
|
168
168
|
```
|
169
169
|
|
data/doc/external/articles.md
CHANGED
@@ -17,6 +17,7 @@ title: Articles
|
|
17
17
|
* [Better File Uploads with Shrine: Processing](https://twin.github.io/better-file-uploads-with-shrine-processing/)
|
18
18
|
* [Better File Uploads with Shrine: Metadata](https://twin.github.io/better-file-uploads-with-shrine-metadata/)
|
19
19
|
* [Better File Uploads with Shrine: Direct Uploads](https://twin.github.io/better-file-uploads-with-shrine-direct-uploads)
|
20
|
+
* [Shrine 3.0 Released](https://twin.github.io/shrine-3-0-released/)
|
20
21
|
|
21
22
|
## Other Articles
|
22
23
|
|
data/doc/external/extensions.md
CHANGED
@@ -36,6 +36,7 @@ title: Extensions
|
|
36
36
|
* [shrine-mongoid](https://github.com/shrinerb/shrine-mongoid)
|
37
37
|
* [shrine-rails](https://github.com/abepetrillo/shrine-rails)
|
38
38
|
* [shrine-reform](https://github.com/shrinerb/shrine-reform)
|
39
|
+
* [shrine-rom](https://github.com/shrinerb/shrine-rom)
|
39
40
|
* [shrine-tus](https://github.com/shrinerb/shrine-tus)
|
40
41
|
|
41
42
|
## Libraries
|
data/doc/getting_started.md
CHANGED
@@ -278,7 +278,7 @@ uploader.upload Shrine::UploadedFile.new(...) # upload from Shrine
|
|
278
278
|
|
279
279
|
The `Shrine::UploadedFile` object represents the file that was uploaded to a
|
280
280
|
storage, and it's what's returned from `Shrine#upload` or when retrieving a
|
281
|
-
record [attachment]. It contains the following
|
281
|
+
record [attachment]. It contains the following data:
|
282
282
|
|
283
283
|
| Key | Description |
|
284
284
|
| :------- | :---------- |
|
@@ -288,11 +288,12 @@ record [attachment]. It contains the following information:
|
|
288
288
|
|
289
289
|
```rb
|
290
290
|
uploaded_file = uploader.upload(file)
|
291
|
-
uploaded_file
|
291
|
+
uploaded_file #=> #<Shrine::UploadedFile id="949sdjg834.jpg" storage=:store metadata={...}>
|
292
292
|
|
293
|
-
uploaded_file.id
|
294
|
-
uploaded_file.
|
295
|
-
uploaded_file.
|
293
|
+
uploaded_file.id # => "949sdjg834.jpg"
|
294
|
+
uploaded_file.storage_key # => :store
|
295
|
+
uploaded_file.storage # => #<Shrine::Storage::S3>
|
296
|
+
uploaded_file.metadata # => {...}
|
296
297
|
```
|
297
298
|
|
298
299
|
It comes with many convenient methods that delegate to the storage:
|
@@ -364,13 +365,13 @@ photo.image #=> nil
|
|
364
365
|
|
365
366
|
# the assigned file is cached to temporary storage and written to `image_data` column
|
366
367
|
photo.image = File.open("waterfall.jpg")
|
367
|
-
photo.image #=> #<Shrine::UploadedFile
|
368
|
+
photo.image #=> #<Shrine::UploadedFile ...>
|
368
369
|
photo.image_url #=> "/uploads/cache/0sdfllasfi842.jpg"
|
369
370
|
photo.image_data #=> '{"id":"0sdfllasfi842.jpg","storage":"cache","metadata":{...}}'
|
370
371
|
|
371
372
|
# the cached file is promoted to permanent storage and saved to `image_data` column
|
372
373
|
photo.save
|
373
|
-
photo.image #=> #<Shrine::UploadedFile
|
374
|
+
photo.image #=> #<Shrine::UploadedFile ...>
|
374
375
|
photo.image_url #=> "/uploads/store/l02kladf8jlda.jpg"
|
375
376
|
photo.image_data #=> '{"id":"l02kladf8jlda.jpg","storage":"store","metadata":{...}}'
|
376
377
|
|
data/doc/paperclip.md
CHANGED
@@ -141,7 +141,7 @@ Record][activerecord], [Sequel][sequel], [ROM][rom], [Hanami][hanami] and
|
|
141
141
|
```rb
|
142
142
|
attacher = ImageUploader::Attacher.new
|
143
143
|
attacher.attach File.open("nature.jpg")
|
144
|
-
attacher.file #=> #<Shrine::UploadedFile
|
144
|
+
attacher.file #=> #<Shrine::UploadedFile id="f4ba5bdbf366ef0b.jpg" ...>
|
145
145
|
attacher.url #=> "https://my-bucket.s3.amazonaws.com/f4ba5bdbf366ef0b.jpg"
|
146
146
|
attacher.data #=> { "id" => "f4ba5bdbf366ef0b.jpg", "storage" => "store", "metadata" => { ... } }
|
147
147
|
```
|
@@ -206,15 +206,15 @@ uploaded processed files into the database (including any extracted metadata),
|
|
206
206
|
which then becomes the source of truth on which versions have been generated.
|
207
207
|
|
208
208
|
```rb
|
209
|
-
photo.image #=> #<Shrine::UploadedFile
|
209
|
+
photo.image #=> #<Shrine::UploadedFile id="original.jpg" ...>
|
210
210
|
photo.image_derivatives #=> {}
|
211
211
|
|
212
212
|
photo.image_derivatives! # triggers processing
|
213
213
|
photo.image_derivatives #=>
|
214
214
|
# {
|
215
|
-
# large: #<Shrine::UploadedFile
|
216
|
-
# medium: #<Shrine::UploadedFile
|
217
|
-
# small: #<Shrine::UploadedFile
|
215
|
+
# large: #<Shrine::UploadedFile id="large.jpg" metadata={"size"=>873232, ...} ...>,
|
216
|
+
# medium: #<Shrine::UploadedFile id="medium.jpg" metadata={"size"=>94823, ...} ...>,
|
217
|
+
# small: #<Shrine::UploadedFile id="small.jpg" metadata={"size"=>37322, ...} ...>,
|
218
218
|
# }
|
219
219
|
```
|
220
220
|
|
data/doc/plugins/activerecord.md
CHANGED
@@ -28,12 +28,12 @@ photo = Photo.new
|
|
28
28
|
|
29
29
|
photo.image = file # cache attachment
|
30
30
|
|
31
|
-
photo.image #=> #<Shrine::UploadedFile
|
31
|
+
photo.image #=> #<Shrine::UploadedFile id="bc2e13.jpg" storage=:cache ...>
|
32
32
|
photo.image_data #=> '{"id":"bc2e13.jpg","storage":"cache","metadata":{...}}'
|
33
33
|
|
34
34
|
photo.save # persist, promote attachment, then persist again
|
35
35
|
|
36
|
-
photo.image #=> #<Shrine::UploadedFile
|
36
|
+
photo.image #=> #<Shrine::UploadedFile id="397eca.jpg" storage=:store ...>
|
37
37
|
photo.image_data #=> '{"id":"397eca.jpg","storage":"store","metadata":{...}}'
|
38
38
|
|
39
39
|
photo.destroy # delete attachment
|
@@ -192,14 +192,14 @@ attacher = ImageUploader::Attacher.from_model(photo, :image)
|
|
192
192
|
|
193
193
|
attacher.assign(file) # cache
|
194
194
|
|
195
|
-
attacher.file #=> #<Shrine::UploadedFile
|
195
|
+
attacher.file #=> #<Shrine::UploadedFile id="bc2e13.jpg" storage=:cache ...>
|
196
196
|
photo.image_data #=> '{"id":"bc2e13.jpg","storage":"cache","metadata":{...}}'
|
197
197
|
|
198
198
|
photo.save # persist
|
199
199
|
attacher.finalize # promote
|
200
200
|
photo.save # persist
|
201
201
|
|
202
|
-
attacher.file #=> #<Shrine::UploadedFile
|
202
|
+
attacher.file #=> #<Shrine::UploadedFile id="397eca.jpg" storage=:store ...>
|
203
203
|
photo.image_data #=> '{"id":"397eca.jpg","storage":"store","metadata":{...}}'
|
204
204
|
```
|
205
205
|
|
data/doc/plugins/derivatives.md
CHANGED
@@ -87,13 +87,13 @@ The `#<name>_derivatives!` model method delegates to
|
|
87
87
|
`Shrine::Attacher` directly:
|
88
88
|
|
89
89
|
```rb
|
90
|
-
attacher.file #=> #<Shrine::UploadedFile
|
90
|
+
attacher.file #=> #<Shrine::UploadedFile id="original.jpg" storage=:store ...>
|
91
91
|
attacher.create_derivatives # calls registered processor and uploads results
|
92
92
|
attacher.derivatives #=>
|
93
93
|
# {
|
94
|
-
# small: #<Shrine::UploadedFile
|
95
|
-
# medium: #<Shrine::UploadedFile
|
96
|
-
# large: #<Shrine::UploadedFile
|
94
|
+
# small: #<Shrine::UploadedFile id="small.jpg" storage=:store ...>,
|
95
|
+
# medium: #<Shrine::UploadedFile id="medium.jpg" storage=:store ...>,
|
96
|
+
# large: #<Shrine::UploadedFile id="large.jpg" storage=:store ...>,
|
97
97
|
# }
|
98
98
|
```
|
99
99
|
|
data/doc/plugins/entity.md
CHANGED
@@ -130,16 +130,16 @@ end
|
|
130
130
|
photo = Photo.new(image_data: '{"id":"...","storage":"...","metadata":{...}}')
|
131
131
|
attacher = ImageUploader::Attacher.from_entity(photo, :image)
|
132
132
|
|
133
|
-
attacher.file #=> #<Shrine::UploadedFile
|
133
|
+
attacher.file #=> #<Shrine::UploadedFile id="bc2e13.jpg" storage=:store ...>
|
134
134
|
|
135
135
|
attacher.attach(file)
|
136
|
-
attacher.file #=> #<Shrine::UploadedFile
|
136
|
+
attacher.file #=> #<Shrine::UploadedFile id="397eca.jpg" storage=:store ...>
|
137
137
|
attacher.column_values #=> { image_data: '{"id":"397eca.jpg","storage":"store","metadata":{...}}' }
|
138
138
|
|
139
139
|
photo = Photo.new(attacher.column_values)
|
140
140
|
attacher = ImageUploader::Attacher.from_entity(photo, :image)
|
141
141
|
|
142
|
-
attacher.file #=> #<Shrine::UploadedFile
|
142
|
+
attacher.file #=> #<Shrine::UploadedFile id="397eca.jpg" storage=:store ...>
|
143
143
|
```
|
144
144
|
|
145
145
|
### Loading entity
|
data/doc/plugins/model.md
CHANGED
@@ -26,12 +26,12 @@ photo = Photo.new
|
|
26
26
|
|
27
27
|
photo.image = file
|
28
28
|
|
29
|
-
photo.image #=> #<Shrine::UploadedFile
|
29
|
+
photo.image #=> #<Shrine::UploadedFile id="bc2e13.jpg" storage=:cache ...>
|
30
30
|
photo.image_data #=> '{"id":"bc2e13.jpg","storage":"cache","metadata":{...}}'
|
31
31
|
|
32
32
|
photo.image_attacher.finalize
|
33
33
|
|
34
|
-
photo.image #=> #<Shrine::UploadedFile
|
34
|
+
photo.image #=> #<Shrine::UploadedFile id="397eca.jpg" storage=:store ...>
|
35
35
|
photo.image_data #=> '{"id":"397eca.jpg","storage":"store","metadata":{...}}'
|
36
36
|
```
|
37
37
|
|
@@ -127,12 +127,12 @@ attacher = ImageUploader::Attacher.from_model(photo, :image)
|
|
127
127
|
|
128
128
|
attacher.assign(file) # cache
|
129
129
|
|
130
|
-
attacher.file #=> #<Shrine::UploadedFile
|
130
|
+
attacher.file #=> #<Shrine::UploadedFile id="bc2e13.jpg" storage=:cache ...>
|
131
131
|
photo.image_data #=> '{"id":"bc2e13.jpg","storage":"cache","metadata":{...}}'
|
132
132
|
|
133
133
|
attacher.finalize # promote
|
134
134
|
|
135
|
-
attacher.file #=> #<Shrine::UploadedFile
|
135
|
+
attacher.file #=> #<Shrine::UploadedFile id="397eca.jpg" storage=:store ...>
|
136
136
|
photo.image_data #=> '{"id":"397eca.jpg","storage":"store","metadata":{...}}'
|
137
137
|
```
|
138
138
|
|
data/doc/plugins/sequel.md
CHANGED
@@ -27,12 +27,12 @@ photo = Photo.new
|
|
27
27
|
|
28
28
|
photo.image = file # cache attachment
|
29
29
|
|
30
|
-
photo.image #=> #<Shrine::UploadedFile
|
30
|
+
photo.image #=> #<Shrine::UploadedFile id="bc2e13.jpg" storage=:cache ...>
|
31
31
|
photo.image_data #=> '{"id":"bc2e13.jpg","storage":"cache","metadata":{...}}'
|
32
32
|
|
33
33
|
photo.save # persist, promote attachment, then persist again
|
34
34
|
|
35
|
-
photo.image #=> #<Shrine::UploadedFile
|
35
|
+
photo.image #=> #<Shrine::UploadedFile id="397eca.jpg" storage=:store ...>
|
36
36
|
photo.image_data #=> '{"id":"397eca.jpg","storage":"store","metadata":{...}}'
|
37
37
|
|
38
38
|
photo.destroy # delete attachment
|
@@ -161,14 +161,14 @@ attacher = ImageUploader::Attacher.from_model(photo, :image)
|
|
161
161
|
|
162
162
|
attacher.assign(file) # cache
|
163
163
|
|
164
|
-
attacher.file #=> #<Shrine::UploadedFile
|
164
|
+
attacher.file #=> #<Shrine::UploadedFile id="bc2e13.jpg" storage=:cache ...>
|
165
165
|
photo.image_data #=> '{"id":"bc2e13.jpg","storage":"cache","metadata":{...}}'
|
166
166
|
|
167
167
|
photo.save # persist
|
168
168
|
attacher.finalize # promote
|
169
169
|
photo.save # persist
|
170
170
|
|
171
|
-
attacher.file #=> #<Shrine::UploadedFile
|
171
|
+
attacher.file #=> #<Shrine::UploadedFile id="397eca.jpg" storage=:store ...>
|
172
172
|
photo.image_data #=> '{"id":"397eca.jpg","storage":"store","metadata":{...}}'
|
173
173
|
```
|
174
174
|
|
data/doc/plugins/versions.md
CHANGED
@@ -49,10 +49,10 @@ user.avatar_data #=>
|
|
49
49
|
|
50
50
|
user.avatar #=>
|
51
51
|
# {
|
52
|
-
# :original => #<Shrine::UploadedFile
|
53
|
-
# :large => #<Shrine::UploadedFile
|
54
|
-
# :medium => #<Shrine::UploadedFile
|
55
|
-
# :small => #<Shrine::UploadedFile
|
52
|
+
# :original => #<Shrine::UploadedFile id=0gsdf.jpg" ...}>,
|
53
|
+
# :large => #<Shrine::UploadedFile id=lg043.jpg" ...}>,
|
54
|
+
# :medium => #<Shrine::UploadedFile id=kd9fk.jpg" ...}>,
|
55
|
+
# :small => #<Shrine::UploadedFile id=932fl.jpg" ...}>,
|
56
56
|
# }
|
57
57
|
|
58
58
|
user.avatar[:medium] #=> #<Shrine::UploadedFile>
|
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
|
|
@@ -353,7 +367,7 @@ how to upgrade.
|
|
353
367
|
```rb
|
354
368
|
attacher = photo.image_attacher
|
355
369
|
attacher.form_assign("image" => file, "title" => "...", "description" => "...")
|
356
|
-
attacher.file #=> #<Shrine::UploadedFile
|
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
|
|