shrine 3.0.0.beta2 → 3.0.0.beta3
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 +45 -1
- data/README.md +100 -106
- data/doc/advantages.md +90 -88
- data/doc/attacher.md +322 -152
- data/doc/carrierwave.md +105 -113
- data/doc/changing_derivatives.md +308 -0
- data/doc/changing_location.md +92 -21
- data/doc/changing_storage.md +107 -0
- data/doc/creating_plugins.md +1 -1
- data/doc/design.md +8 -9
- data/doc/direct_s3.md +3 -2
- data/doc/metadata.md +97 -78
- data/doc/multiple_files.md +3 -3
- data/doc/paperclip.md +89 -88
- data/doc/plugins/activerecord.md +3 -12
- data/doc/plugins/backgrounding.md +126 -100
- data/doc/plugins/derivation_endpoint.md +4 -5
- data/doc/plugins/derivatives.md +63 -32
- data/doc/plugins/download_endpoint.md +54 -1
- data/doc/plugins/entity.md +1 -0
- data/doc/plugins/form_assign.md +53 -0
- data/doc/plugins/mirroring.md +37 -16
- data/doc/plugins/multi_cache.md +22 -0
- data/doc/plugins/presign_endpoint.md +1 -1
- data/doc/plugins/remote_url.md +19 -4
- data/doc/plugins/validation.md +83 -0
- data/doc/processing.md +149 -133
- data/doc/refile.md +68 -63
- data/doc/release_notes/3.0.0.md +835 -0
- data/doc/securing_uploads.md +56 -36
- data/doc/storage/s3.md +2 -2
- data/doc/testing.md +104 -120
- data/doc/upgrading_to_3.md +538 -0
- data/doc/validation.md +48 -87
- data/lib/shrine.rb +7 -4
- data/lib/shrine/attacher.rb +16 -6
- data/lib/shrine/plugins/activerecord.rb +33 -14
- data/lib/shrine/plugins/atomic_helpers.rb +1 -1
- data/lib/shrine/plugins/backgrounding.rb +23 -89
- data/lib/shrine/plugins/data_uri.rb +13 -2
- data/lib/shrine/plugins/derivation_endpoint.rb +7 -11
- data/lib/shrine/plugins/derivatives.rb +44 -20
- data/lib/shrine/plugins/download_endpoint.rb +26 -0
- data/lib/shrine/plugins/form_assign.rb +6 -3
- data/lib/shrine/plugins/keep_files.rb +2 -2
- data/lib/shrine/plugins/mirroring.rb +62 -22
- data/lib/shrine/plugins/model.rb +2 -2
- data/lib/shrine/plugins/multi_cache.rb +27 -0
- data/lib/shrine/plugins/remote_url.rb +25 -10
- data/lib/shrine/plugins/remove_invalid.rb +1 -1
- data/lib/shrine/plugins/sequel.rb +39 -20
- data/lib/shrine/plugins/validation.rb +3 -0
- data/lib/shrine/storage/s3.rb +16 -1
- data/lib/shrine/uploaded_file.rb +1 -0
- data/lib/shrine/version.rb +1 -1
- data/shrine.gemspec +1 -1
- metadata +12 -7
- data/doc/migrating_storage.md +0 -76
- data/doc/regenerating_versions.md +0 -143
- data/lib/shrine/plugins/attacher_options.rb +0 -55
data/doc/plugins/remote_url.md
CHANGED
@@ -48,11 +48,26 @@ gem "http"
|
|
48
48
|
```rb
|
49
49
|
require "down/http"
|
50
50
|
|
51
|
-
|
52
|
-
|
53
|
-
|
51
|
+
plugin :remote_url, downloader: -> (url, **options) {
|
52
|
+
Down::Http.download(url, **options) do |client|
|
53
|
+
client.follow(max_hops: 2).timeout(connect: 2, read: 2)
|
54
|
+
end
|
55
|
+
}
|
56
|
+
```
|
57
|
+
|
58
|
+
Any `Down::NotFound` and `Down::TooLarge` exceptions will be rescued and
|
59
|
+
converted into validation errors. If you want to convert any other exceptions
|
60
|
+
into validation errors, you can raise them as
|
61
|
+
`Shrine::Plugins::RemoteUrl::DownloadError`:
|
54
62
|
|
55
|
-
|
63
|
+
```rb
|
64
|
+
plugin :remote_url, downloader: -> (url, **options) {
|
65
|
+
begin
|
66
|
+
RestClient.get(url)
|
67
|
+
rescue RestClient::ExceptionWithResponse => error
|
68
|
+
raise Shrine::Plugins::RemoteUrl::DownloadError, "remote file not found"
|
69
|
+
end
|
70
|
+
}
|
56
71
|
```
|
57
72
|
|
58
73
|
## Uploader options
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# Validation
|
2
|
+
|
3
|
+
The [`validation`][validation] plugin provides a framework for validating
|
4
|
+
attached files. For some useful validators, see the
|
5
|
+
[`validation_helpers`][validation_helpers] plugin.
|
6
|
+
|
7
|
+
```rb
|
8
|
+
plugin :validation
|
9
|
+
```
|
10
|
+
|
11
|
+
The `Attacher.validate` method is used to register a validation block, which
|
12
|
+
is called on attachment:
|
13
|
+
|
14
|
+
```rb
|
15
|
+
class VideoUploader < Shrine
|
16
|
+
Attacher.validate do
|
17
|
+
if file.duration > 5*60*60
|
18
|
+
errors << "duration must not be longer than 5 hours"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
```
|
23
|
+
```rb
|
24
|
+
attacher.assign(file)
|
25
|
+
attacher.errors #=> ["duration must not be longer than 5 hours"]
|
26
|
+
```
|
27
|
+
|
28
|
+
The validation block is executed in context of a `Shrine::Attacher` instance:
|
29
|
+
|
30
|
+
```rb
|
31
|
+
class VideoUploader < Shrine
|
32
|
+
Attacher.validate do
|
33
|
+
self #=> #<VideoUploader::Attacher>
|
34
|
+
|
35
|
+
record #=> #<Movie>
|
36
|
+
name #=> :video
|
37
|
+
file #=> #<Shrine::UploadedFile>
|
38
|
+
end
|
39
|
+
end
|
40
|
+
```
|
41
|
+
|
42
|
+
## Inheritance
|
43
|
+
|
44
|
+
If you're subclassing an uploader that has validations defined, you can call
|
45
|
+
those validations via `super()`:
|
46
|
+
|
47
|
+
```rb
|
48
|
+
class ApplicationUploader < Shrine
|
49
|
+
Attacher.validate { validate_max_size 5.megabytes }
|
50
|
+
end
|
51
|
+
```
|
52
|
+
```rb
|
53
|
+
class ImageUploader < ApplicationUploader
|
54
|
+
Attacher.validate do
|
55
|
+
super() # empty parentheses are required
|
56
|
+
validate_mime_type %w[image/jpeg image/png image/webp]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
```
|
60
|
+
|
61
|
+
## Validation options
|
62
|
+
|
63
|
+
You can pass options to the validator via the `:validate` option:
|
64
|
+
|
65
|
+
```rb
|
66
|
+
attacher.assign(file, validate: { foo: "bar" })
|
67
|
+
```
|
68
|
+
```rb
|
69
|
+
class MyUploader < Shrine
|
70
|
+
Attacher.validate do |**options|
|
71
|
+
options #=> { foo: "bar" }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
```
|
75
|
+
|
76
|
+
You can also skip validation by passing `validate: false`:
|
77
|
+
|
78
|
+
```rb
|
79
|
+
attacher.assign(file, validate: false) # skips validation
|
80
|
+
```
|
81
|
+
|
82
|
+
[validation]: /lib/shrine/plugins/validation.rb
|
83
|
+
[validation_helpers]: /doc/plugins/validation_helpers.md#readme
|
data/doc/processing.md
CHANGED
@@ -1,19 +1,9 @@
|
|
1
1
|
# File Processing
|
2
2
|
|
3
|
-
Shrine allows you to process files
|
4
|
-
|
5
|
-
attached to a record
|
6
|
-
|
7
|
-
requested.
|
8
|
-
|
9
|
-
With both ways you need to define some kind of processing block, which accepts
|
10
|
-
a source file and is expected to return the processed result file.
|
11
|
-
|
12
|
-
```rb
|
13
|
-
some_process_block do |source_file|
|
14
|
-
# process source file and return the result
|
15
|
-
end
|
16
|
-
```
|
3
|
+
Shrine allows you to process attached files up front or on-the-fly. For
|
4
|
+
example, if your app is accepting image uploads, you can generate a predefined
|
5
|
+
set of of thumbnails when the image is attached to a record, or you can have
|
6
|
+
thumbnails generated dynamically as they're needed.
|
17
7
|
|
18
8
|
How you're going to implement processing is entirely up to you. For images it's
|
19
9
|
recommended to use the **[ImageProcessing]** gem, which provides wrappers for
|
@@ -24,12 +14,10 @@ Here is an example of generating a thumbnail with ImageProcessing:
|
|
24
14
|
```sh
|
25
15
|
$ brew install imagemagick
|
26
16
|
```
|
27
|
-
|
28
17
|
```rb
|
29
18
|
# Gemfile
|
30
|
-
gem "image_processing", "~> 1.
|
19
|
+
gem "image_processing", "~> 1.8"
|
31
20
|
```
|
32
|
-
|
33
21
|
```rb
|
34
22
|
require "image_processing/mini_magick"
|
35
23
|
|
@@ -40,107 +28,147 @@ thumbnail = ImageProcessing::MiniMagick
|
|
40
28
|
thumbnail #=> #<Tempfile:...> (a 600x400 thumbnail of the source image)
|
41
29
|
```
|
42
30
|
|
43
|
-
## Processing
|
44
|
-
|
45
|
-
Shrine allows you to process files before they're uploaded to a storage. It's
|
46
|
-
generally best to process cached files when they're being promoted to permanent
|
47
|
-
storage, because (a) at that point the file has already been successfully
|
48
|
-
[validated][validation], (b) the parent record has been saved and the database
|
49
|
-
transaction has been committed, and (c) this can be delayed into a [background
|
50
|
-
job][backgrounding].
|
31
|
+
## Processing up front
|
51
32
|
|
52
|
-
|
53
|
-
|
54
|
-
|
33
|
+
Let's say we're handling images, and want to generate a predefined set of
|
34
|
+
thumbnails with various dimensions. We can use the `derivatives` plugin to
|
35
|
+
upload and save the processed files:
|
55
36
|
|
56
37
|
```rb
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
io #=> #<Shrine::UploadedFile ...>
|
62
|
-
context #=> {:record=>#<Photo...>,:name=>:image,...}
|
38
|
+
Shrine.plugin :derivatives
|
39
|
+
```
|
40
|
+
```rb
|
41
|
+
require "image_processing/mini_magick"
|
63
42
|
|
64
|
-
|
43
|
+
class ImageUploader < Shrine
|
44
|
+
Attacher.derivatives_processor do |original|
|
45
|
+
magick = ImageProcessing::MiniMagick.source(original)
|
46
|
+
|
47
|
+
{
|
48
|
+
large: magick.resize_to_limit!(800, 800),
|
49
|
+
medium: magick.resize_to_limit!(500, 500),
|
50
|
+
small: magick.resize_to_limit!(300, 300),
|
51
|
+
}
|
65
52
|
end
|
66
53
|
end
|
67
54
|
```
|
55
|
+
```rb
|
56
|
+
photo = Photo.new
|
57
|
+
photo.image_derivatives! # calls derivatives processor
|
58
|
+
photo.save
|
59
|
+
```
|
68
60
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
result should be file(s) that will be uploaded to permanent storage.
|
73
|
-
|
74
|
-
### Versions
|
75
|
-
|
76
|
-
Let's say we're handling images, and want to generate thumbnails of various
|
77
|
-
dimensions. In this case we can use the ImageProcessing gem to generate the
|
78
|
-
thumbnails, and return a hash of processed files at the end of the block. We'll
|
79
|
-
need to load the `versions` plugin which extends Shrine with the ability to
|
80
|
-
handle collections of files inside the same attachment.
|
61
|
+
After the processed files are uploaded, their data is saved into the
|
62
|
+
`<attachment>_data` column. You can then retrieve the derivatives as
|
63
|
+
[`Shrine::UploadedFile`][uploaded file] objects:
|
81
64
|
|
82
65
|
```rb
|
83
|
-
|
66
|
+
photo.image(:large) #=> #<Shrine::UploadedFile ...>
|
67
|
+
photo.image(:large).url #=> "/uploads/store/lg043.jpg"
|
68
|
+
photo.image(:large).size #=> 5825949
|
69
|
+
photo.image(:large).mime_type #=> "image/jpeg"
|
70
|
+
```
|
84
71
|
|
85
|
-
|
86
|
-
plugin :processing # allows hooking into promoting
|
87
|
-
plugin :versions # enable Shrine to handle a hash of files
|
88
|
-
plugin :delete_raw # delete processed files after uploading
|
72
|
+
### Backgrounding
|
89
73
|
|
90
|
-
|
91
|
-
|
74
|
+
Since file processing can be time consuming, it's recommended to move it into a
|
75
|
+
background job.
|
92
76
|
|
93
|
-
|
94
|
-
io.download do |original|
|
95
|
-
pipeline = ImageProcessing::MiniMagick.source(original)
|
77
|
+
#### With promotion
|
96
78
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
end
|
79
|
+
The simplest way is to use the [`backgrounding`][backgrounding] plugin to move
|
80
|
+
promotion into a background job, and then create derivatives as part of
|
81
|
+
promotion:
|
101
82
|
|
102
|
-
|
83
|
+
```rb
|
84
|
+
Shrine.plugin :backgrounding
|
85
|
+
Shrine::Attacher.promote_block { PromoteJob.perform_later(self.class, record, name, file_data) }
|
86
|
+
```
|
87
|
+
```rb
|
88
|
+
class PromoteJob < ActiveJob::Base
|
89
|
+
def perform(attacher_class, record, name, file_data)
|
90
|
+
attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
|
91
|
+
attacher.create_derivatives # calls derivatives processor
|
92
|
+
attacher.atomic_promote
|
93
|
+
rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
|
94
|
+
# attachment has changed or the record has been deleted, nothing to do
|
103
95
|
end
|
104
96
|
end
|
105
97
|
```
|
106
98
|
|
107
|
-
|
108
|
-
ever need to reprocess it.**
|
109
|
-
|
110
|
-
### Conditional processing
|
111
|
-
|
112
|
-
The process block yields the attached file uploaded to temporary storage, so we
|
113
|
-
have information like file extension and MIME type available. Together with
|
114
|
-
ImageProcessing's chainable API, it's easy to do conditional proccessing.
|
99
|
+
#### Separate from promotion
|
115
100
|
|
116
|
-
|
117
|
-
|
118
|
-
this might look like:
|
101
|
+
Derivatives don't need to be created as part of the attachment flow, you can
|
102
|
+
create them at any point after promotion:
|
119
103
|
|
120
104
|
```rb
|
121
|
-
|
122
|
-
|
105
|
+
DerivativesJob.perform_later(
|
106
|
+
attacher.class,
|
107
|
+
attacher.record,
|
108
|
+
attacher.name,
|
109
|
+
attacher.file_data,
|
110
|
+
)
|
111
|
+
```
|
112
|
+
```rb
|
113
|
+
class DerivativesJob < ActiveJob::Base
|
114
|
+
def perform(attacher_class, record, name, file_data)
|
115
|
+
attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
|
116
|
+
attacher.create_derivatives # calls derivatives processor
|
117
|
+
attacher.atomic_persist
|
118
|
+
rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
|
119
|
+
attacher&.destroy_attached # delete now orphaned derivatives
|
120
|
+
end
|
121
|
+
end
|
122
|
+
```
|
123
123
|
|
124
|
-
|
125
|
-
pipeline = ImageProcessing::Vips.source(original)
|
124
|
+
#### Concurrent processing
|
126
125
|
|
127
|
-
|
128
|
-
unless io.mime_type == "image/png"
|
129
|
-
pipeline = pipeline
|
130
|
-
.convert("jpeg")
|
131
|
-
.saver(interlace: true)
|
132
|
-
end
|
126
|
+
You can also generate derivatives concurrently:
|
133
127
|
|
134
|
-
|
135
|
-
|
136
|
-
|
128
|
+
```rb
|
129
|
+
class ImageUploader < Shrine
|
130
|
+
THUMBNAILS = {
|
131
|
+
large: [800, 800],
|
132
|
+
medium: [500, 500],
|
133
|
+
small: [300, 300],
|
134
|
+
}
|
135
|
+
|
136
|
+
Attacher.derivatives_processor do |original, name:|
|
137
|
+
thumbnail = ImageProcessing::MiniMagick
|
138
|
+
.source(original)
|
139
|
+
.resize_to_limit!(*THUMBNAILS.fetch(name))
|
140
|
+
|
141
|
+
{ name => thumbnail }
|
142
|
+
end
|
143
|
+
end
|
144
|
+
```
|
145
|
+
```rb
|
146
|
+
ImageUploader::THUMBNAILS.each_key do |derivative_name|
|
147
|
+
DerivativeJob.perform_later(
|
148
|
+
attacher.class,
|
149
|
+
attacher.record,
|
150
|
+
attacher.name,
|
151
|
+
attacher.file_data,
|
152
|
+
derivative_name,
|
153
|
+
)
|
154
|
+
end
|
155
|
+
```
|
156
|
+
```rb
|
157
|
+
class DerivativeJob < ActiveJob::Base
|
158
|
+
def perform(attacher_class, record, name, file_data, derivative_name)
|
159
|
+
attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
|
160
|
+
attacher.create_derivatives(name: derivative_name)
|
161
|
+
attacher.atomic_persist do |reloaded_attacher|
|
162
|
+
# make sure we don't override derivatives created in other jobs
|
163
|
+
attacher.merge_derivatives(reloaded_attacher.derivatives)
|
164
|
+
end
|
165
|
+
rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
|
166
|
+
attacher.derivatives[derivative_name].delete # delete now orphaned derivative
|
137
167
|
end
|
138
|
-
|
139
|
-
versions
|
140
168
|
end
|
141
169
|
```
|
142
170
|
|
143
|
-
### Processing other
|
171
|
+
### Processing other filetypes
|
144
172
|
|
145
173
|
So far we've only been talking about processing images. However, there is
|
146
174
|
nothing image-specific in Shrine's processing API, you can just as well process
|
@@ -151,32 +179,23 @@ generic gem.
|
|
151
179
|
To demonstrate, here is an example of transcoding videos using
|
152
180
|
[streamio-ffmpeg]:
|
153
181
|
|
182
|
+
```rb
|
183
|
+
# Gemfile
|
184
|
+
gem "streamio-ffmpeg"
|
185
|
+
```
|
154
186
|
```rb
|
155
187
|
require "streamio-ffmpeg"
|
156
|
-
require "tempfile"
|
157
188
|
|
158
189
|
class VideoUploader < Shrine
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
process(:store) do |io, context|
|
164
|
-
versions = { original: io }
|
190
|
+
Attacher.derivatives_processor do |original|
|
191
|
+
transcoded = Tempfile.new ["transcoded", ".mp4"]
|
192
|
+
screenshot = Tempfile.new ["screenshot", ".jpg"]
|
165
193
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
movie = FFMPEG::Movie.new(original.path)
|
171
|
-
movie.transcode(transcoded.path)
|
172
|
-
movie.screenshot(screenshot.path)
|
173
|
-
|
174
|
-
[transcoded, screenshot].each(&:open) # refresh file descriptors
|
175
|
-
|
176
|
-
versions.merge!(transcoded: transcoded, screenshot: screenshot)
|
177
|
-
end
|
194
|
+
movie = FFMPEG::Movie.new(original.path)
|
195
|
+
movie.transcode(transcoded.path)
|
196
|
+
movie.screenshot(screenshot.path)
|
178
197
|
|
179
|
-
|
198
|
+
{ transcoded: transcoded, screenshot: screenshot }
|
180
199
|
end
|
181
200
|
end
|
182
201
|
```
|
@@ -185,13 +204,13 @@ end
|
|
185
204
|
|
186
205
|
Generating image thumbnails on upload can be a pain to maintain, because
|
187
206
|
whenever you need to add a new version or change an existing one, you need to
|
188
|
-
retroactively apply it to all existing uploads (see the [
|
207
|
+
retroactively apply it to all existing uploads (see the [Managing Derivatives]
|
189
208
|
guide for more details).
|
190
209
|
|
191
210
|
As an alternative, it's very common to instead generate thumbnails dynamically
|
192
211
|
as they're requested, and then cache them for future requests. This strategy is
|
193
|
-
known as "on-the-fly processing", and it's suitable for
|
194
|
-
or document previews.
|
212
|
+
known as "on-the-fly processing", and it's suitable for short-running
|
213
|
+
processing such as creating image thumbnails or document previews.
|
195
214
|
|
196
215
|
Shrine provides on-the-fly processing functionality via the
|
197
216
|
[`derivation_endpoint`][derivation_endpoint] plugin. The basic setup is the
|
@@ -257,7 +276,7 @@ $ brew install vips
|
|
257
276
|
|
258
277
|
```rb
|
259
278
|
# Gemfile
|
260
|
-
gem "image_processing", "~> 1.
|
279
|
+
gem "image_processing", "~> 1.8"
|
261
280
|
```
|
262
281
|
|
263
282
|
```rb
|
@@ -271,30 +290,27 @@ thumbnail = ImageProcessing::Vips
|
|
271
290
|
thumbnail #=> #<Tempfile:...> (a 600x400 thumbnail of the source image)
|
272
291
|
```
|
273
292
|
|
274
|
-
###
|
293
|
+
### Parallelize uploading
|
275
294
|
|
276
|
-
If you're generating
|
277
|
-
[
|
295
|
+
If you're generating derivatives, you can parallelize the uploads using the
|
296
|
+
[concurrent-ruby] gem:
|
278
297
|
|
279
298
|
```rb
|
280
299
|
# Gemfile
|
281
|
-
gem "
|
282
|
-
gem "image_optim"
|
283
|
-
gem "image_optim_pack" # precompiled binaries
|
300
|
+
gem "concurrent-ruby"
|
284
301
|
```
|
285
|
-
|
286
302
|
```rb
|
287
|
-
require "
|
303
|
+
require "concurrent"
|
288
304
|
|
289
|
-
|
290
|
-
.source(image)
|
291
|
-
.resize_to_limit!(600, 400)
|
305
|
+
derivatives = attacher.process_derivatives
|
292
306
|
|
293
|
-
|
294
|
-
|
307
|
+
tasks = derivatives.map do |name, file|
|
308
|
+
Concurrent::Promises.future(name, file) do |name, file|
|
309
|
+
attacher.add_derivative(name, file)
|
310
|
+
end
|
311
|
+
end
|
295
312
|
|
296
|
-
|
297
|
-
thumbnail
|
313
|
+
Concurrent::Promises.zip(*tasks).wait!
|
298
314
|
```
|
299
315
|
|
300
316
|
### External processing
|
@@ -319,7 +335,7 @@ class ImageUploader < Shrine
|
|
319
335
|
prefix: "derivations/image",
|
320
336
|
download: false
|
321
337
|
|
322
|
-
derivation :thumbnail do |
|
338
|
+
derivation :thumbnail do |width, height|
|
323
339
|
# generate thumbnails using ImageOptim.com
|
324
340
|
down = Down::Http.new(method: :post)
|
325
341
|
down.download("https://im2.io/<USERNAME>/#{width}x#{height}/#{source.url}")
|
@@ -359,7 +375,7 @@ Now when we upload our images to Cloudinary, we can generate URLs with various
|
|
359
375
|
processing parameters:
|
360
376
|
|
361
377
|
```rb
|
362
|
-
photo.
|
378
|
+
photo.image_url(width: 100, height: 100, crop: :fit)
|
363
379
|
#=> "http://res.cloudinary.com/myapp/image/upload/w_100,h_100,c_fit/nature.jpg"
|
364
380
|
```
|
365
381
|
|
@@ -369,15 +385,15 @@ photo.image.url(width: 100, height: 100, crop: :fit)
|
|
369
385
|
[GraphicsMagick]: http://www.graphicsmagick.org
|
370
386
|
[libvips]: http://libvips.github.io/libvips/
|
371
387
|
[Why is libvips quick]: https://github.com/libvips/libvips/wiki/Why-is-libvips-quick
|
372
|
-
[image_optim]: https://github.com/toy/image_optim
|
373
388
|
[ImageOptim.com]: https://imageoptim.com/api
|
374
389
|
[streamio-ffmpeg]: https://github.com/streamio/streamio-ffmpeg
|
375
|
-
[
|
390
|
+
[Managing Derivatives]: /doc/changing_derivatives.md#readme
|
376
391
|
[Cloudinary]: https://cloudinary.com
|
377
392
|
[shrine-cloudinary]: https://github.com/shrinerb/shrine-cloudinary
|
378
393
|
[backgrounding]: /doc/plugins/backgrounding.md#readme
|
379
|
-
[validation]: /doc/validation.md#readme
|
380
394
|
[ruby-vips]: https://github.com/libvips/ruby-vips
|
381
395
|
[MiniMagick]: https://github.com/minimagick/minimagick
|
382
396
|
[derivation_endpoint]: /doc/plugins/derivation_endpoint.md#readme
|
383
397
|
[derivation_endpoint performance]: /doc/plugins/derivation_endpoint.md#performance
|
398
|
+
[derivatives]: /doc/plugins/derivatives.md#readme
|
399
|
+
[concurrent-ruby]: https://github.com/ruby-concurrency/concurrent-ruby
|