shrine 2.17.1 → 2.18.0

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.

@@ -61,7 +61,7 @@ The plugin also extends the `Attacher#url` to accept versions:
61
61
 
62
62
  ```rb
63
63
  user.avatar_url(:large)
64
- user.avatar_url(:small, download: true) # with URL options
64
+ user.avatar_url(:small, public: true) # with URL options
65
65
  ```
66
66
 
67
67
  `Shrine.uploaded_file` will also instantiate a hash of `Shrine::UploadedFile`
data/doc/refile.md CHANGED
@@ -14,18 +14,15 @@ named "storages", it uses the same IO abstraction for uploading and representing
14
14
  uploaded files, similar attachment logic, and direct uploads are also supported.
15
15
 
16
16
  While in Refile you work with storages directly, Shrine uses *uploaders* which
17
- act as wrappers around storages:
17
+ wrap storage uploads:
18
18
 
19
19
  ```rb
20
20
  storage = Shrine.storages[:store]
21
- storage #=> #<Shrine::Storage::S3 ...>
21
+ storage #=> #<Shrine::Storage::S3>
22
22
 
23
- uploader = Shrine.new(:store)
24
- uploader #=> #<Shrine @storage_key=:store @storage=#<Shrine::Storage::S3>>
25
- uploader.storage #=> #<Shrine::Storage::S3 ...>
26
-
27
- uploaded_file = uploader.upload(image)
28
- uploaded_file #=> #<Shrine::UploadedFile>
23
+ uploaded_file = Shrine.upload(image, :store)
24
+ uploaded_file #=> #<Shrine::UploadedFile ...>
25
+ uploaded_file.storage #=> #<Shrine::Storage::S3>
29
26
  ```
30
27
 
31
28
  This way Shrine can perform tasks like generating location, extracting
@@ -352,7 +349,7 @@ In Shrine equivalents are (private) methods `Shrine#extract_filename` and
352
349
  #### `.app_url`
353
350
 
354
351
  You should use your framework to generate the URL to your mounted direct
355
- enpdoint.
352
+ endpoint.
356
353
 
357
354
  #### `.attachment_url`, `.file_url`
358
355
 
@@ -4,7 +4,7 @@
4
4
  endpoint instance, which override any plugin options.
5
5
 
6
6
  ```rb
7
- Shrine.download_enpdoint(disposition: "attachment")
7
+ Shrine.download_endpoint(disposition: "attachment")
8
8
  ```
9
9
 
10
10
  * The `Shrine::Attacher#assign_remote_url` method in the `remote_url` plugin
@@ -119,7 +119,7 @@
119
119
  anymore, it's now a PORO whose instance responds to `#call`. This shouldn't
120
120
  affect your code unless you were calling Roda methods on that class.
121
121
 
122
- * The plugin options of `upload_enpdoint`, `presign_endpoint`, and
122
+ * The plugin options of `upload_endpoint`, `presign_endpoint`, and
123
123
  `download_endpoint` are now internally stored in a different place in
124
124
  `Shrine.opts`. This shouldn't affect your code unless you were accessing
125
125
  these options directly.
@@ -0,0 +1,155 @@
1
+ ## New features
2
+
3
+ * Added `Shrine.upload_response` to `upload_endpoint` plugin for handling
4
+ uploads inside a custom controller. This allows authenticating uploads on the
5
+ controller level:
6
+
7
+ ```rb
8
+ # config/routes.rb (Rails)
9
+ Rails.application.routes.draw do
10
+ # ...
11
+ post "/images/upload" => "uploads#image"
12
+ end
13
+ ```
14
+ ```rb
15
+ # app/controllers/uploads_controller.rb (Rails)
16
+ class UploadsController < ApplicationController
17
+ def image
18
+ authenticate_user!
19
+
20
+ set_rack_response ImageUploader.upload_response(:cache, env)
21
+ end
22
+
23
+ private
24
+
25
+ def set_rack_response((status, headers, body))
26
+ self.status = status
27
+ self.headers.merge!(headers)
28
+ self.response_body = body
29
+ end
30
+ end
31
+ ```
32
+
33
+ * Added `Shrine.presign_response` to `presign_endpoint` plugin for handling
34
+ uploads inside a custom controller. This allows authenticating uploads on the
35
+ controller level:
36
+
37
+ ```rb
38
+ # config/routes.rb (Rails)
39
+ Rails.application.routes.draw do
40
+ # ...
41
+ post "/images/presign", to: "presigns#image"
42
+ end
43
+ ```
44
+ ```rb
45
+ # app/controllers/presigns_controller.rb (Rails)
46
+ class PresignsController < ApplicationController
47
+ def image
48
+ authenticate_user!
49
+
50
+ set_rack_response ImageUploader.presign_response(:cache, env)
51
+ end
52
+
53
+ private
54
+
55
+ def set_rack_response((status, headers, body))
56
+ self.status = status
57
+ self.headers.merge!(headers)
58
+ self.response_body = body
59
+ end
60
+ end
61
+ ```
62
+
63
+ * The `:url` option has been added to the `upload_endpoint` plugin for
64
+ returning the uploaded file URL in the response.
65
+
66
+ ```rb
67
+ plugin :upload_endpoint, url: true
68
+ # or
69
+ plugin :upload_endpoint, url: { public: true }
70
+ # or
71
+ plugin :upload_endpoint, url: -> (uploaded_file, request) {
72
+ uploaded_file.url(**options)
73
+ }
74
+ ```
75
+ ```rb
76
+ {
77
+ "data": { "id": "...", "storage": "...", "metadata": {...} },
78
+ "url": "https://example.com/path/to/file"
79
+ }
80
+ ```
81
+
82
+ This will additionally be recognized by Uppy, so e.g. the Dashboard plugin
83
+ will display preview link to the file.
84
+
85
+ ```js
86
+ uppy.on('upload-success', (file, response) => {
87
+ response.uploadURL // => "https://example.com/path/to/file"
88
+ })
89
+ ```
90
+
91
+ ## Other improvements
92
+
93
+ * The `upload_endpoint` now accepts the `files[]` array that Uppy's XHR Upload
94
+ plugin sends by default. This means the `fieldName` parameter can now be
95
+ omitted.
96
+
97
+ ```js
98
+ // BEFORE
99
+ uppy.use(Uppy.XHRUpload, {
100
+ endpoint: '/upload',
101
+ fieldName: 'file',
102
+ })
103
+
104
+ // AFTER
105
+ uppy.use(Uppy.XHRUpload, {
106
+ endpoint: '/upload',
107
+ })
108
+ ```
109
+
110
+ * The `Shrine.upload` convenience method has been added, which is a bit shorter
111
+ when you don't need the uploader instance.
112
+
113
+ ```rb
114
+ Shrine.upload(io, :storage)
115
+
116
+ # expands to
117
+
118
+ uploader = Shrine.new(:storage)
119
+ uploader.upload(io)
120
+ ```
121
+
122
+ * The `Shrine.Attachment(...)` shorthand for `Shrine::Attachment.new(...)` has
123
+ been added.
124
+
125
+ ```rb
126
+ class Photo
127
+ include Shrine::Attachment(:image) # expands to Shrine::Attachment.new(:image)
128
+ end
129
+ ```
130
+
131
+ * The `parsed_json` and `rack_file` plugins now correctly retain the second
132
+ argument in the `Attacher#assign` method signature.
133
+
134
+ ## Backwards compatibility
135
+
136
+ * The `aws-sdk-s3` version lower than `1.14.0` has been deprecated for
137
+ `Shrine::Storage::S3` and will be removed in Shrine 3.
138
+
139
+ * The `:download` option in `Shrine::Storage::S3#url` has been deprecated and
140
+ will be removed in Shrine 3. The `:response_content_disposition` option
141
+ should be used instead.
142
+
143
+ ```rb
144
+ # This is deprecated:
145
+ uploaded_file.url(download: true)
146
+
147
+ # Use this:
148
+ uploaded_file.url(response_content_disposition: "attachment")
149
+ ```
150
+
151
+ * `Shrine::Storage::S3#upload` doesn't backfill the `size` metadata value for
152
+ input IOs with unknown size (e.g. pipes, sockets). This behaviour was not
153
+ documented and added unnecessary complexity. Moreover, this functionality
154
+ should be storage agnostic, so if someone requests it we can add it back in
155
+ form of a plugin.
@@ -25,12 +25,9 @@ uploaded_file.eof? # => false
25
25
  uploaded_file.close # closes the underlying IO object (this should be called when you're done)
26
26
  ```
27
27
 
28
- In reality these methods are simply delegated on the IO object returned by the
29
- `Storage#open` method of the underlying Shrine storage. For
30
- `Shrine::Storage::FileSystem` this IO object will be a `File` object, while for
31
- `Shrine::Storage::S3` (and most other remote storages) it will be a
32
- [`Down::ChunkedIO`] object. `Storage#open` is implicitly called when any of
33
- these IO methods are called for the first time.
28
+ These methods are simply delegated on the IO object returned by the
29
+ `Storage#open` method of the underlying Shrine storage. `Storage#open` is
30
+ implicitly called when any of these IO methods are called for the first time.
34
31
 
35
32
  ```rb
36
33
  uploaded_file.read(10) # calls `Storage#open` and assigns result to an instance variable
@@ -45,6 +42,48 @@ You can retrieve the underlying IO object returned by `Storage#open` with
45
42
  uploaded_file.to_io # the underlying IO object returned by `Storage#open`
46
43
  ```
47
44
 
45
+ ## `Storage#open`
46
+
47
+ The underlying IO object that `Shrine::UploadedFile` will use depends on the
48
+ storage. The `FileSystem` storage will return a `File` object, while `S3` and
49
+ most other remote storages will return [`Down::ChunkedIO`] that downloads file
50
+ content on-demand.
51
+
52
+ ```rb
53
+ Shrine.storages = {
54
+ file_system: Shrine::Storage::FileSystem.new(...),
55
+ s3: Shrine::Storage::S3.new(...),
56
+ }
57
+
58
+ local_file = Shrine.upload(file, :file_system)
59
+ local_file.to_io #=> #<File:/path/to/file>
60
+
61
+ remote_file = Shrine.upload(file, :s3)
62
+ remote_file.to_io #=> #<Down::ChunkedIO> (opens HTTP connection)
63
+ remote_file.read(1*1024*1024) # downloads first 1MB
64
+ remote_file.read(1*1024*1024) # downloads next 1MB
65
+ remote_file.close # closes HTTP connection
66
+ ```
67
+
68
+ The `Down::ChunkedIO` object will cache downloaded content to disk in order to
69
+ be rewindable, which is used in a places such as metadata extraction.
70
+
71
+ ```rb
72
+ remote_file.read(1*1024*1024) # downloads and caches first 1MB
73
+ remote_file.rewind
74
+ remote_file.read(1*1024*1024) # reads first 1MB from the cache
75
+ remote_file.read(1*1024*1024) # downloads and caches next 1MB
76
+ ```
77
+
78
+ If you want to turn off caching to disk, most storages allow you to pass
79
+ `:rewindable` to `Storage#open`:
80
+
81
+ ```rb
82
+ remote_file.open(rewindable: false)
83
+ remote_file.read(1*1024*1024) # downloads first 1MB (no caching to disk)
84
+ remote_file.rewind #~> IOError: this Down::ChunkedIO is not rewindable
85
+ ```
86
+
48
87
  ## Opening
49
88
 
50
89
  The `Shrine::UploadedFile#open` method can be used to open the uploaded file
data/doc/storage/s3.md CHANGED
@@ -4,7 +4,7 @@ The S3 storage handles uploads to Amazon S3 service, using the [aws-sdk-s3]
4
4
  gem:
5
5
 
6
6
  ```rb
7
- gem "aws-sdk-s3", "~> 1.2"
7
+ gem "aws-sdk-s3", "~> 1.14"
8
8
  ```
9
9
 
10
10
  It can be initialized by providing the bucket name and credentials:
@@ -101,18 +101,16 @@ are generated using the storage directly.
101
101
 
102
102
  ## URL options
103
103
 
104
- This storage supports various URL options that will be forwarded from uploaded
105
- file.
104
+ Other than [`:host`](#url-host) and [`:public`](#public-uploads) URL options,
105
+ all additional options are forwarded to [`Aws::S3::Object#presigned_url`].
106
106
 
107
107
  ```rb
108
- s3.url(public: true) # public URL without signed parameters
109
- s3.url(download: true) # forced download URL
110
- ```
111
-
112
- All other options are forwarded to the aws-sdk-s3 gem:
113
-
114
- ```rb
115
- s3.url(expires_in: 15, response_content_disposition: "...")
108
+ s3.url(
109
+ expires_in: 15,
110
+ response_content_disposition: ContentDisposition.attachment("my-filename"),
111
+ response_content_type: "foo/bar",
112
+ # ...
113
+ )
116
114
  ```
117
115
 
118
116
  ## URL Host
@@ -258,7 +256,7 @@ To use Amazon S3's [Transfer Acceleration] feature, set
258
256
  `:use_accelerate_endpoint` to `true` when initializing the storage:
259
257
 
260
258
  ```rb
261
- Shrine::Storage::S3.new(use_accelerate_enpdoint: true, **other_options)
259
+ Shrine::Storage::S3.new(use_accelerate_endpoint: true, **other_options)
262
260
  ```
263
261
 
264
262
  ## Clearing cache
@@ -285,6 +283,7 @@ can scale exponentially by using more prefixes.
285
283
  [uploading]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#put-instance_method
286
284
  [copying]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#copy_from-instance_method
287
285
  [presigning]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#presigned_post-instance_method
286
+ [`Aws::S3::Object#presigned_url`]: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#presigned_url-instance_method
288
287
  [aws-sdk-s3]: https://github.com/aws/aws-sdk-ruby/tree/master/gems/aws-sdk-s3
289
288
  [Transfer Acceleration]: http://docs.aws.amazon.com/AmazonS3/latest/dev/transfer-acceleration.html
290
289
  [object lifecycle]: http://docs.aws.amazon.com/AmazonS3/latest/UG/lifecycle-configuration-bucket-no-versioning.html
data/lib/shrine.rb CHANGED
@@ -91,10 +91,9 @@ class Shrine
91
91
  end
92
92
 
93
93
  # Retrieves the storage under the given identifier (can be a Symbol or
94
- # a String), and raises Shrine::Error if the storage is missing.
94
+ # a String), raising Shrine::Error if the storage is missing.
95
95
  def find_storage(name)
96
- storages.each { |key, value| return value if key.to_s == name.to_s }
97
- raise Error, "storage #{name.inspect} isn't registered on #{self}"
96
+ storages[name.to_sym] or fail Error, "storage #{name.inspect} isn't registered on #{self}"
98
97
  end
99
98
 
100
99
  # Generates an instance of Shrine::Attachment to be included in the
@@ -103,10 +102,18 @@ class Shrine
103
102
  # class Photo
104
103
  # include Shrine.attachment(:image) # creates a Shrine::Attachment object
105
104
  # end
106
- def attachment(name, *args)
105
+ def Attachment(name, *args)
107
106
  self::Attachment.new(name, *args)
108
107
  end
109
- alias [] attachment
108
+ alias attachment Attachment
109
+ alias [] Attachment
110
+
111
+ # Uploads the file to the specified storage. It delegates to `Shrine#upload`.
112
+ #
113
+ # Shrine.upload(io, :store) #=> #<Shrine::UploadedFile>
114
+ def upload(io, storage, context = {})
115
+ new(storage).upload(io, context)
116
+ end
110
117
 
111
118
  # Instantiates a Shrine::UploadedFile from a hash, and optionally
112
119
  # yields the returned object.
@@ -161,6 +168,8 @@ class Shrine
161
168
  attr_reader :storage
162
169
 
163
170
  # Accepts a storage symbol registered in `Shrine.storages`.
171
+ #
172
+ # Shrine.new(:store)
164
173
  def initialize(storage_key)
165
174
  @storage = self.class.find_storage(storage_key)
166
175
  @storage_key = storage_key.to_sym
@@ -176,6 +185,11 @@ class Shrine
176
185
  # optional context hash (used internally by Shrine::Attacher). It calls
177
186
  # user-defined #process, and afterwards it calls #store. The `io` is
178
187
  # closed after upload.
188
+ #
189
+ # uploader.upload(io)
190
+ # uploader.upload(io, metadata: { "foo" => "bar" }) # add metadata
191
+ # uploader.upload(io, location: "path/to/file") # specify location
192
+ # uploader.upload(io, upload_options: { acl: "public-read" }) # add upload options
179
193
  def upload(io, context = {})
180
194
  io = processed(io, context) || io
181
195
  store(io, context)
@@ -52,7 +52,7 @@ class Shrine
52
52
  def initialize(record, name, cache: :cache, store: :store)
53
53
  @cache = shrine_class.new(cache)
54
54
  @store = shrine_class.new(store)
55
- @context = {record: record, name: name}
55
+ @context = { record: record, name: name }
56
56
  @errors = []
57
57
  end
58
58
 
@@ -63,11 +63,9 @@ class Shrine
63
63
  end
64
64
 
65
65
  module AttachmentMethods
66
- def initialize(*)
66
+ def initialize(name, **options)
67
67
  super
68
68
 
69
- name = attachment_name
70
-
71
69
  define_method :"#{name}_data_uri=" do |uri|
72
70
  send(:"#{name}_attacher").data_uri = uri
73
71
  end
@@ -31,6 +31,27 @@ class Shrine
31
31
  **options,
32
32
  )
33
33
  end
34
+
35
+ # Calls the presign endpoint passing the request information, and
36
+ # returns the Rack response triple.
37
+ #
38
+ # It performs the same mounting logic that Rack and other web
39
+ # frameworks use, and is meant for cases where statically mounting the
40
+ # endpoint in the router isn't enough.
41
+ def presign_response(storage_key, env, **options)
42
+ script_name = env["SCRIPT_NAME"]
43
+ path_info = env["PATH_INFO"]
44
+
45
+ begin
46
+ env["SCRIPT_NAME"] += path_info
47
+ env["PATH_INFO"] = ""
48
+
49
+ presign_endpoint(storage_key, **options).call(env)
50
+ ensure
51
+ env["SCRIPT_NAME"] = script_name
52
+ env["PATH_INFO"] = path_info
53
+ end
54
+ end
34
55
  end
35
56
  end
36
57
 
@@ -17,11 +17,9 @@ class Shrine
17
17
  end
18
18
 
19
19
  module AttachmentMethods
20
- def initialize(*)
20
+ def initialize(name, **options)
21
21
  super
22
22
 
23
- name = attachment_name
24
-
25
23
  define_method :"#{name}_remote_url=" do |url|
26
24
  send(:"#{name}_attacher").remote_url = url
27
25
  end