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.

@@ -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 @id="original.jpg" ...>
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: #<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, ...} ...>,
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
 
@@ -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 @storage_key=:cache ...>
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 @storage_key=:store ...>
166
+ attacher.file #=> #<Shrine::UploadedFile storage=:store ...>
167
167
  attacher.record.image_data #=> "{\"storage\":\"store\",\"id\":\"ksdf02lr9sf3la.jpg\",\"metadata\":{...}}"
168
168
  ```
169
169
 
@@ -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
 
@@ -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
@@ -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 information:
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.data #=> {"id"=>"949sdjg834.jpg","storage"=>"store","metadata"=>{...}}
291
+ uploaded_file #=> #<Shrine::UploadedFile id="949sdjg834.jpg" storage=:store metadata={...}>
292
292
 
293
- uploaded_file.id #=> "949sdjg834.jpg"
294
- uploaded_file.storage #=> #<Shrine::Storage::S3>
295
- uploaded_file.metadata #=> {...}
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 @data={...}>
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 @data={...}>
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
 
@@ -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 @id="f4ba5bdbf366ef0b.jpg" ...>
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 @id="original.jpg" ...>
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 @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, ...} ...>,
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
 
@@ -28,12 +28,12 @@ photo = Photo.new
28
28
 
29
29
  photo.image = file # cache attachment
30
30
 
31
- photo.image #=> #<Shrine::UploadedFile @id="bc2e13.jpg" @storage_key=:cache ...>
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 @id="397eca.jpg" @storage_key=:store ...>
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 @id="bc2e13.jpg" @storage_key=:cache ...>
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 @id="397eca.jpg" @storage_key=:store ...>
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
 
@@ -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 @id="original.jpg" @storage_key=:store ...>
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 @id="small.jpg" @storage_key=:store ...>,
95
- # medium: #<Shrine::UploadedFile @id="medium.jpg" @storage_key=:store ...>,
96
- # large: #<Shrine::UploadedFile @id="large.jpg" @storage_key=:store ...>,
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
 
@@ -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 @id="bc2e13.jpg" @storage_key=:store ...>
133
+ attacher.file #=> #<Shrine::UploadedFile id="bc2e13.jpg" storage=:store ...>
134
134
 
135
135
  attacher.attach(file)
136
- attacher.file #=> #<Shrine::UploadedFile @id="397eca.jpg" @storage_key=:store ...>
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 @id="397eca.jpg" @storage_key=:store ...>
142
+ attacher.file #=> #<Shrine::UploadedFile id="397eca.jpg" storage=:store ...>
143
143
  ```
144
144
 
145
145
  ### Loading entity
@@ -26,12 +26,12 @@ photo = Photo.new
26
26
 
27
27
  photo.image = file
28
28
 
29
- photo.image #=> #<Shrine::UploadedFile @id="bc2e13.jpg" @storage_key=:cache ...>
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 @id="397eca.jpg" @storage_key=:store ...>
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 @id="bc2e13.jpg" @storage_key=:cache ...>
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 @id="397eca.jpg" @storage_key=:store ...>
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
 
@@ -27,12 +27,12 @@ photo = Photo.new
27
27
 
28
28
  photo.image = file # cache attachment
29
29
 
30
- photo.image #=> #<Shrine::UploadedFile @id="bc2e13.jpg" @storage_key=:cache ...>
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 @id="397eca.jpg" @storage_key=:store ...>
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 @id="bc2e13.jpg" @storage_key=:cache ...>
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 @id="397eca.jpg" @storage_key=:store ...>
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
 
@@ -49,10 +49,10 @@ user.avatar_data #=>
49
49
 
50
50
  user.avatar #=>
51
51
  # {
52
- # :original => #<Shrine::UploadedFile @data={"id"=>"0gsdf.jpg", ...}>,
53
- # :large => #<Shrine::UploadedFile @data={"id"=>"lg043.jpg", ...}>,
54
- # :medium => #<Shrine::UploadedFile @data={"id"=>"kd9fk.jpg", ...}>,
55
- # :small => #<Shrine::UploadedFile @data={"id"=>"932fl.jpg", ...}>,
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>
@@ -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 on
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
- * The new **[`derivatives`][derivatives]** plugin has been added for storing
12
- additional processed files alongside the main file.
11
+ ### Derivatives
13
12
 
14
- ```rb
15
- Shrine.plugin :derivatives
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
- - possibility of uploading processed files to different storage
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
- ```rb
101
- class ImageUploader < Shrine
102
- # specify storage for all derivatives
103
- Attacher.derivatives_storage :other_store
104
-
105
- # or specify storage per derivative
106
- Attacher.derivatives_storage { |derivative| :other_store }
107
- end
108
- ```
109
- ```rb
110
- photo = Photo.create(image: file)
111
- photo.image.storage_key #=> :store
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
- photo.image_derivatives!
114
- photo.image_derivatives[:large].storage_key #=> :other_store
115
- ```
38
+ This is a rewrite of the [`versions`][versions] plugin, bringing numerous
39
+ improvements:
116
40
 
117
- * The [`Shrine::Attacher`][attacher] class has been rewritten and can now be
118
- used without models:
41
+ * processed files are separated from the main file
119
42
 
120
43
  ```rb
121
- attacher = Shrine::Attacher.new
122
- attacher.attach(file)
123
- attacher.file #=> #<Shrine::UploadedFile>
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
- The `Attacher#data`, `Attacher#load_data`, and `Attacher.from_data` methods
127
- have been added for dumping and loading the attached file:
65
+ * processing is decoupled from promotion
128
66
 
129
67
  ```rb
130
- # dump attached file into a serializable Hash
131
- data = attacher.data #=> { "id" => "abc123.jpg", "storage" => "store", "metadata" => { ... } }
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
- Several more methods have been added:
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
- # dump attached file data into a JSON string
161
- data = attacher.column_data #=> '{"id":"abc123.jpg","storage":"store","metadata":{...}}'
162
- ```
163
- ```rb
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
- * The new [`entity`][entity] plugin adds support for immutable structs, which
175
- are commonly used with ROM, Hanami and dry-rb.
81
+ Attacher.derivatives_processor :crop do |original, left:, top:, width:, height:|
82
+ vips = ImageProcessing::Vips.source(original)
176
83
 
177
- ```rb
178
- Shrine.plugin :entity
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 = Photo.new(image_data: '{"id":"abc123.jpg","storage":"store","metadata":{...}}')
187
- photo.image #=> #<Shrine::UploadedFile @id="abc123.jpg" @storage_key=:store ...>
188
- ```
89
+ photo.image_derivatives!(:thumbnails)
90
+ photo.image_derivatives #=> { large: ..., medium: ..., small: ... }
91
+ photo.save
189
92
 
190
- * The new [`model`][model] plugin adds support for mutable structs, which is
191
- used by `activerecord` and `sequel` plugins.
93
+ # ... sometime later ...
192
94
 
193
- ```rb
194
- Shrine.plugin :model
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
- * The [`backgrounding`][backgrounding] plugin has been rewritten for more
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.plugin :backgrounding
213
- Shrine::Attacher.promote_block do
214
- PromoteJob.perform_async(self.class.name, record.class.name, record.id, name, file_data)
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
- attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
229
- attacher.atomic_promote
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
- class DestroyJob
237
- include Sidekiq::Worker
238
-
239
- def perform(attacher_class, data)
240
- attacher_class = Object.const_get(attacher_class)
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
- attacher = attacher_class.from_data(data)
243
- attacher.destroy
244
- end
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
- There are several main differences compared to the old implementation:
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
- photo.image_attacher.promote_block do |attacher|
261
- PromoteJob.perform_async(
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
- photo.save # will call our instance-level backgrounding hook
272
- ```
260
+ There are several main differences compared to the old implementation:
273
261
 
274
- * The persistence plugins (`activerecord`, `sequel`) now implement a unified
275
- [persistence] interface:
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
- | Method | Description |
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
- The "atomic" methods use the new [`atomic_helpers`][atomic_helpers] plugin,
284
- and are useful for background jobs. For example, this is how we'd use them to
285
- implement metadata extraction in the background in a concurrency-safe way:
269
+ ```rb
270
+ photo = Photo.new(photo_params)
286
271
 
287
- ```rb
288
- MetadataJob.perform_async(
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
- ```rb
297
- class MetadataJob
298
- include Sidekiq::Worker
299
-
300
- def perform(attacher_class, record_class, record_id, name, file_data)
301
- attacher_class = Object.const_get(attacher_class)
302
- record = Object.const_get(record_class).find(record_id) # if using Active Record
303
-
304
- attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
305
- attacher.refresh_metadata! # extract metadata
306
- attacher.atomic_persist # persist if attachment hasn't changed
307
- rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
308
- # attachment has changed or record has been deleted, nothing to do
309
- end
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 @id="..." @storage_key=:cache ...>
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