refile 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +27 -0
- data/.rspec +1 -0
- data/.travis.yml +8 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +476 -0
- data/Rakefile +11 -0
- data/app/assets/javascripts/refile.js +50 -0
- data/app/helpers/attachment_helper.rb +52 -0
- data/config.ru +8 -0
- data/config/locales/en.yml +5 -0
- data/config/routes.rb +3 -0
- data/lib/refile.rb +72 -0
- data/lib/refile/app.rb +97 -0
- data/lib/refile/attachment.rb +89 -0
- data/lib/refile/attachment/active_record.rb +24 -0
- data/lib/refile/backend/file_system.rb +70 -0
- data/lib/refile/backend/s3.rb +129 -0
- data/lib/refile/file.rb +65 -0
- data/lib/refile/image_processing.rb +73 -0
- data/lib/refile/rails.rb +36 -0
- data/lib/refile/random_hasher.rb +5 -0
- data/lib/refile/version.rb +3 -0
- data/refile.gemspec +34 -0
- data/spec/refile/app_spec.rb +151 -0
- data/spec/refile/attachment_spec.rb +141 -0
- data/spec/refile/backend/file_system_spec.rb +30 -0
- data/spec/refile/backend/s3_spec.rb +11 -0
- data/spec/refile/backend_examples.rb +215 -0
- data/spec/refile/features/direct_upload_spec.rb +29 -0
- data/spec/refile/features/normal_upload_spec.rb +36 -0
- data/spec/refile/features/presigned_upload_spec.rb +29 -0
- data/spec/refile/fixtures/hello.txt +1 -0
- data/spec/refile/fixtures/large.txt +44 -0
- data/spec/refile/spec_helper.rb +58 -0
- data/spec/refile/test_app.rb +46 -0
- data/spec/refile/test_app/app/assets/javascripts/application.js +40 -0
- data/spec/refile/test_app/app/controllers/application_controller.rb +2 -0
- data/spec/refile/test_app/app/controllers/direct_posts_controller.rb +15 -0
- data/spec/refile/test_app/app/controllers/home_controller.rb +4 -0
- data/spec/refile/test_app/app/controllers/normal_posts_controller.rb +19 -0
- data/spec/refile/test_app/app/controllers/presigned_posts_controller.rb +30 -0
- data/spec/refile/test_app/app/models/post.rb +5 -0
- data/spec/refile/test_app/app/views/direct_posts/new.html.erb +16 -0
- data/spec/refile/test_app/app/views/home/index.html.erb +1 -0
- data/spec/refile/test_app/app/views/layouts/application.html.erb +14 -0
- data/spec/refile/test_app/app/views/normal_posts/new.html.erb +20 -0
- data/spec/refile/test_app/app/views/normal_posts/show.html.erb +9 -0
- data/spec/refile/test_app/app/views/presigned_posts/new.html.erb +16 -0
- data/spec/refile/test_app/config/database.yml +7 -0
- data/spec/refile/test_app/config/routes.rb +17 -0
- data/spec/refile/test_app/public/favicon.ico +0 -0
- data/spec/refile_spec.rb +35 -0
- metadata +294 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c82ae33f74a7a012f5d04acd5c72ad9675b4e3b7
|
4
|
+
data.tar.gz: 984abf68f34e0725fb18164c0923eeaa2b992b86
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7393be87fdddfc7fd09b46bfab369dc9364ef30b1f61ebd03de568bc826101206de81474ee6f03215672bd7794d1ce017deadc403b062cba545132d3177aa565
|
7
|
+
data.tar.gz: 53ff261be44222e0fe7afac11e30372ed488884695bcca01569f2a1e58e3f30100765cda72349f4cac54b24a6f8fba660ae6bbf7d2699b739cbb253f69194176
|
data/.gitignore
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
23
|
+
|
24
|
+
s3.yml
|
25
|
+
spec/refile/test_app/log
|
26
|
+
spec/refile/test_app/tmp
|
27
|
+
spec/refile/test_app/db/*.sqlite
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
-r refile/spec_helper
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Jonas Nicklas
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,476 @@
|
|
1
|
+
# Refile
|
2
|
+
|
3
|
+
[](https://travis-ci.org/elabs/refile)
|
4
|
+
|
5
|
+
Refile is a modern file upload library for Ruby applications. It is simple, yet
|
6
|
+
powerful. Refile is an attempt by CarrierWave's original author to fix the
|
7
|
+
design mistakes and overengineering in CarrierWave.
|
8
|
+
|
9
|
+
Features:
|
10
|
+
|
11
|
+
- Configurable backends, file system, S3, etc...
|
12
|
+
- Convenient integration with ORMs
|
13
|
+
- On the fly manipulation of images and other files
|
14
|
+
- Streaming IO for fast and memory friendly uploads
|
15
|
+
- Works across form redisplays, i.e. when validations fail, even on S3
|
16
|
+
- Effortless direct uploads, even to S3
|
17
|
+
|
18
|
+
## Quick start, Rails
|
19
|
+
|
20
|
+
Add the gem:
|
21
|
+
|
22
|
+
``` ruby
|
23
|
+
gem "mini_magick"
|
24
|
+
gem "refile", require: ["refile/rails", "refile/image_processing"]
|
25
|
+
```
|
26
|
+
|
27
|
+
Use the `attachment` method to use Refile in a model:
|
28
|
+
|
29
|
+
``` ruby
|
30
|
+
class User < ActiveRecord::Base
|
31
|
+
attachment :profile_image
|
32
|
+
end
|
33
|
+
```
|
34
|
+
|
35
|
+
Generate a migration:
|
36
|
+
|
37
|
+
``` sh
|
38
|
+
rails generate migration add_profile_image_to_users profile_image_id:string
|
39
|
+
rake db:migrate
|
40
|
+
```
|
41
|
+
|
42
|
+
Add an attachment field to your form:
|
43
|
+
|
44
|
+
``` erb
|
45
|
+
<%= form_for @user do |form| %>
|
46
|
+
<%= form.attachment_field :profile_image %>
|
47
|
+
<% end %>
|
48
|
+
```
|
49
|
+
|
50
|
+
Set up strong parameters:
|
51
|
+
|
52
|
+
``` ruby
|
53
|
+
def user_params
|
54
|
+
params.require(:user).permit(:profile_image, :profile_image_cache_id)
|
55
|
+
end
|
56
|
+
```
|
57
|
+
|
58
|
+
And start uploading! Finally show the file in your view:
|
59
|
+
|
60
|
+
``` erb
|
61
|
+
<%= image_tag attachment_url(@user, :profile_image, :fill, 300, 300) %>
|
62
|
+
```
|
63
|
+
|
64
|
+
## How it works
|
65
|
+
|
66
|
+
Refile consists of several parts:
|
67
|
+
|
68
|
+
1. Backends: cache and persist files
|
69
|
+
2. Model attachments: map files to model columns
|
70
|
+
3. A Rack application: streams files and accepts uploads
|
71
|
+
4. Rails helpers: conveniently generate markup in your views
|
72
|
+
4. A JavaScript library: facilitates direct uploads
|
73
|
+
|
74
|
+
Let's look at each of these in more detail!
|
75
|
+
|
76
|
+
## 1. Backend
|
77
|
+
|
78
|
+
Files are uploaded to a backend. The backend assigns an ID to this file, which
|
79
|
+
will be unique for this file within the backend.
|
80
|
+
|
81
|
+
Let's look at a simple example of using the backend:
|
82
|
+
|
83
|
+
``` ruby
|
84
|
+
backend = Refile::Backend::FileSystem.new("tmp")
|
85
|
+
|
86
|
+
file = backend.upload(StringIO.new("hello"))
|
87
|
+
file.id # => "b205bc..."
|
88
|
+
file.read # => "hello"
|
89
|
+
|
90
|
+
backend.get(file.id).read # => "hello"
|
91
|
+
```
|
92
|
+
|
93
|
+
As you may notice, backends are "flat". Files do not have directories, nor do
|
94
|
+
they have names or permissions, they are only identified by their ID.
|
95
|
+
|
96
|
+
Refile has a global registry of backends, accessed through `Refile.backends`.
|
97
|
+
|
98
|
+
There are two "special" backends, which are only really special in that they
|
99
|
+
are the default backends for attachments. They are `cache` and `store`.
|
100
|
+
|
101
|
+
The cache is intended to be transient. Files are added here before they are
|
102
|
+
meant to be permanently stored. Usually files are then moved to the store for
|
103
|
+
permanent storage, but this isn't always the case.
|
104
|
+
|
105
|
+
Suppose for example that a user uploads a file in a form and receives a
|
106
|
+
validation error. In that case the file has been temporarily stored in the
|
107
|
+
cache. The user might decide to fix the error and resubmit, at which point the
|
108
|
+
file will be promoted to the store. On the other hand, the user might simply
|
109
|
+
give up and leave, now the file is left in the cache for later cleanup.
|
110
|
+
|
111
|
+
Refile has convenient accessors for setting the `cache` and `store`, so for
|
112
|
+
example you can switch to the S3 backend like this:
|
113
|
+
|
114
|
+
``` ruby
|
115
|
+
# config/initializers/refile.rb
|
116
|
+
require "refile/backend/s3"
|
117
|
+
|
118
|
+
aws = {
|
119
|
+
access_key_id: "xyz",
|
120
|
+
secret_access_key: "abc",
|
121
|
+
bucket: "my-bucket",
|
122
|
+
}
|
123
|
+
Refile.cache = Refile::Backend::S3.new(prefix: "cache", **aws)
|
124
|
+
Refile.store = Refile::Backend::S3.new(prefix: "store", **aws)
|
125
|
+
```
|
126
|
+
|
127
|
+
Try this in the quick start example above and your files are now uploaded to
|
128
|
+
S3.
|
129
|
+
|
130
|
+
Backends also provide the option of restricting the size of files they accept.
|
131
|
+
For example:
|
132
|
+
|
133
|
+
``` ruby
|
134
|
+
Refile.cache = Refile::Backend::S3.new(max_size: 10.megabytes, ...)
|
135
|
+
```
|
136
|
+
|
137
|
+
### Uploadable
|
138
|
+
|
139
|
+
The `upload` method on backends can be called with a variety of objects. It
|
140
|
+
requires that the object passed to it behaves similarly to Ruby IO objects, in
|
141
|
+
particular it must implement the methods `size`, `read(length = nil, buffer =
|
142
|
+
nil)`, `eof?` and `close`. All of `File`, `Tempfile`,
|
143
|
+
`ActionDispath::UploadedFile` and `StringIO` implement this interface, however
|
144
|
+
`String` does not. If you want to upload a file from a `String` you must wrap
|
145
|
+
it in a `StringIO` first.
|
146
|
+
|
147
|
+
## 2. Attachments
|
148
|
+
|
149
|
+
You've already seen the `attachment` method:
|
150
|
+
|
151
|
+
``` ruby
|
152
|
+
class User < ActiveRecord::Base
|
153
|
+
attachment :profile_image
|
154
|
+
end
|
155
|
+
```
|
156
|
+
|
157
|
+
Calling `attachment` generates a getter and setter with the given name. When
|
158
|
+
you assign a file to the setter, it is uploaded to the cache:
|
159
|
+
|
160
|
+
``` ruby
|
161
|
+
User.new
|
162
|
+
|
163
|
+
# with a ActionDispatch::UploadedFile
|
164
|
+
user.profile_image = params[:file]
|
165
|
+
|
166
|
+
# with a regular File object
|
167
|
+
File.open("/some/path", "rb") do |file|
|
168
|
+
user.profile_image = file
|
169
|
+
end
|
170
|
+
|
171
|
+
# or a StringIO
|
172
|
+
user.profile_image = StringIO.new("hello world")
|
173
|
+
|
174
|
+
user.profile_image.id # => "fec421..."
|
175
|
+
user.profile_image.read # => "hello world"
|
176
|
+
```
|
177
|
+
|
178
|
+
When you call `save` on the record, the uploaded file is transferred from the
|
179
|
+
cache to the store. Where possible, Refile does this move efficiently. For example
|
180
|
+
if both `cache` and `store` are on the same S3 account, instead of downloading
|
181
|
+
the file and uploading it again, Refile will simply issue a copy command to S3.
|
182
|
+
|
183
|
+
### Other ORMs
|
184
|
+
|
185
|
+
Refile is built to integrate with ORMs other than ActiveRecord, but this being
|
186
|
+
a very young gem, such integrations do not yet exist. Take a look at the [ActiveRecord
|
187
|
+
integration](lib/refile/attachment/active_record.rb), building your own should
|
188
|
+
not be too difficult.
|
189
|
+
|
190
|
+
### Pure Ruby classes
|
191
|
+
|
192
|
+
You can also use attachments in pure Ruby classes like this:
|
193
|
+
|
194
|
+
``` ruby
|
195
|
+
class User
|
196
|
+
extend Refile::Attachment
|
197
|
+
|
198
|
+
attr_accessor :profile_image_id
|
199
|
+
|
200
|
+
attachment :profile_image
|
201
|
+
end
|
202
|
+
```
|
203
|
+
|
204
|
+
## 3. Rack Application
|
205
|
+
|
206
|
+
Refile includes a Rack application (an endpoint, not a middleware). This application
|
207
|
+
streams files from backends and can even accept file uploads and upload them to
|
208
|
+
backends.
|
209
|
+
|
210
|
+
**Important:** Unlike other file upload solutions, Refile always streams your files thorugh your
|
211
|
+
application. It cannot generate URLs to your files. This means that you should
|
212
|
+
**always** put a CDN or other HTTP cache in front of your application. Serving
|
213
|
+
files through your app takes a lot of resources and you want it to happen rarely.
|
214
|
+
|
215
|
+
Setting this up is actually quite simple, you can use the same CDN you would use
|
216
|
+
for your application's static assets. [This blog post](http://www.happybearsoftware.com/use-cloudfront-and-the-rails-asset-pipeline-to-speed-up-your-app.html)
|
217
|
+
explains how to set this up (bonus: faster static assets!). Once you've set this
|
218
|
+
up, simply configure Refile to use your CDN:
|
219
|
+
|
220
|
+
``` ruby
|
221
|
+
Refile.host = "//your-dist-url.cloudfront.net"
|
222
|
+
```
|
223
|
+
|
224
|
+
Using a [procol-relative URL](http://www.paulirish.com/2010/the-protocol-relative-url/) for `Refile.host` is recommended.
|
225
|
+
|
226
|
+
### Mounting
|
227
|
+
|
228
|
+
If you are using Rails and have required [refile/rails.rb](lib/refile/rails.rb),
|
229
|
+
then the Rack application is mounted for you at `/attachments`. You should be able
|
230
|
+
to see this when you run `rake routes`.
|
231
|
+
|
232
|
+
You could also run the application on its own, it doesn't need to be mounted to
|
233
|
+
work.
|
234
|
+
|
235
|
+
### Retrieving files
|
236
|
+
|
237
|
+
Files can be retrieved from the application by calling:
|
238
|
+
|
239
|
+
```
|
240
|
+
GET /attachments/:backend_name/:id/:filename
|
241
|
+
```
|
242
|
+
|
243
|
+
The `:filename` serves no other purpose than generating a nice name when the user
|
244
|
+
downloads the file, it does not in any way affect the downloaded file. For caching
|
245
|
+
purposes you should always use the same filename for the same file. The Rails helpers
|
246
|
+
default this to the name of the column.
|
247
|
+
|
248
|
+
### Processing
|
249
|
+
|
250
|
+
Refile provides on the fly processing of files. You can trigger it by calling
|
251
|
+
a URL like this:
|
252
|
+
|
253
|
+
```
|
254
|
+
GET /attachments/:backend_name/:processor_name/*args/:id/:filename
|
255
|
+
```
|
256
|
+
|
257
|
+
Suppose we have uploaded a file:
|
258
|
+
|
259
|
+
``` ruby
|
260
|
+
Refile.cache.upload(StringIO.new("hello")).id # => "a4e8ce"
|
261
|
+
```
|
262
|
+
|
263
|
+
And we've defined a processor like this:
|
264
|
+
|
265
|
+
``` ruby
|
266
|
+
Refile.processor :reverse do |file|
|
267
|
+
StringIO.new(file.read.reverse)
|
268
|
+
end
|
269
|
+
```
|
270
|
+
|
271
|
+
Then you could do the following.
|
272
|
+
|
273
|
+
``` sh
|
274
|
+
curl http://127.0.0.1:3000/attachments/cache/reverse/a4e8ce/some_file.txt
|
275
|
+
elloh
|
276
|
+
```
|
277
|
+
|
278
|
+
Refile calls `call` on the processor and passes in the retrieved file, as well
|
279
|
+
as all additional arguments sent through the URL. See the
|
280
|
+
[built in image processors](lib/refile/image_processing.rb) for a more advanced
|
281
|
+
example.
|
282
|
+
|
283
|
+
## 4. Rails helpers
|
284
|
+
|
285
|
+
Refile provides the `attachment_field` form helper which generates a file field
|
286
|
+
as well as a hidden field, suffixed with `cache_id`. This field keeps track of
|
287
|
+
the file in case it is not yet permanently stored, for example if validations
|
288
|
+
fail. It is also used for direct and presigned uploads. For this reason it is
|
289
|
+
highly recommended to use `attachment_field` instead of `file_field`.
|
290
|
+
|
291
|
+
``` erb
|
292
|
+
<%= form_for @user do |form| %>
|
293
|
+
<%= form.attachment_field :profile_image %>
|
294
|
+
<% end %>
|
295
|
+
```
|
296
|
+
|
297
|
+
Will generate something like:
|
298
|
+
|
299
|
+
``` html
|
300
|
+
<form action="/users" enctype="multipart/form-data" method="post">
|
301
|
+
<input name="user[profile_image_cache_id]" type="hidden">
|
302
|
+
<input name="user[profile_image]" type="file">
|
303
|
+
</form>
|
304
|
+
```
|
305
|
+
|
306
|
+
The `attachment_url` helper can then be used for generating URLs for the uploaded
|
307
|
+
files:
|
308
|
+
|
309
|
+
``` erb
|
310
|
+
<%= image_tag attachment_url(@user, :profile_image) %>
|
311
|
+
```
|
312
|
+
|
313
|
+
Any additional arguments to it are included in the URL as processor arguments:
|
314
|
+
|
315
|
+
``` erb
|
316
|
+
<%= image_tag attachment_url(@user, :profile_image, :fill, 300, 300) %>
|
317
|
+
```
|
318
|
+
|
319
|
+
## 5. JavaScript library
|
320
|
+
|
321
|
+
Refile's JavaScript library is small but powerful.
|
322
|
+
|
323
|
+
Uploading files is slow, so anything we can do to speed up the process is going
|
324
|
+
to lead to happier users. One way to cheat is to start uploading files directly
|
325
|
+
after the user has chosen a file, instead of waiting until they hit the submit
|
326
|
+
button. This provides a significantly better user experience. Implementing this
|
327
|
+
is usually tricky, but thankfully Refile makes it very easy.
|
328
|
+
|
329
|
+
First, load the JavaScript file. If you're using the asset pipeline, you can
|
330
|
+
simply include it like this:
|
331
|
+
|
332
|
+
``` javascript
|
333
|
+
//= require refile
|
334
|
+
```
|
335
|
+
|
336
|
+
Otherwise you can grab a copy [here](https://raw.githubusercontent.com/elabs/refile/master/app/assets/javascripts/refile.js).
|
337
|
+
|
338
|
+
Now mark the field for direct upload:
|
339
|
+
|
340
|
+
``` erb
|
341
|
+
<%= form.attachment_field :profile_image, direct: true %>
|
342
|
+
```
|
343
|
+
|
344
|
+
There is no step 3 ;)
|
345
|
+
|
346
|
+
The file is now uploaded to the `cache` immediately after the user chooses a file.
|
347
|
+
If you try this in the browser, you'll notice that an AJAX request is fired as
|
348
|
+
soon as you choose a file. Then when you submit to the server, the file is no
|
349
|
+
longer submitted, only its id.
|
350
|
+
|
351
|
+
If you want to improve the experience of this, the JavaScript library fires
|
352
|
+
a couple of custom DOM events. These events bubble, so you can also listen for
|
353
|
+
them on the form for example:
|
354
|
+
|
355
|
+
``` javascript
|
356
|
+
form.addEventListener("upload:start", function() {
|
357
|
+
// ...
|
358
|
+
});
|
359
|
+
|
360
|
+
form.addEventListener("upload:success", function() {
|
361
|
+
// ...
|
362
|
+
});
|
363
|
+
|
364
|
+
input.addEventListener("upload:progress", function() {
|
365
|
+
// ...
|
366
|
+
});
|
367
|
+
```
|
368
|
+
|
369
|
+
You can also listen for them with jQuery, even with event delegation:
|
370
|
+
|
371
|
+
``` javascript
|
372
|
+
$(document).on("upload:start", "form", function(e) {
|
373
|
+
// ...
|
374
|
+
});
|
375
|
+
```
|
376
|
+
|
377
|
+
This way you could for example disable the submit button until all files have
|
378
|
+
uploaded:
|
379
|
+
|
380
|
+
``` javascript
|
381
|
+
$(document).on("upload:start", "form", function(e) {
|
382
|
+
$(this).find("input[type=submit]").attr("disabled", true)
|
383
|
+
});
|
384
|
+
|
385
|
+
$(document).on("upload:complete", "form", function(e) {
|
386
|
+
if(!$(this).find("input.uploading").length) {
|
387
|
+
$(this).find("input[type=submit]").removeAttr("disabled")
|
388
|
+
}
|
389
|
+
});
|
390
|
+
```
|
391
|
+
|
392
|
+
### Presigned uploads
|
393
|
+
|
394
|
+
Amazon S3 supports uploads directly from the browser to S3 buckets. With this
|
395
|
+
feature you can bypass your application entirely; uploads never hit your application
|
396
|
+
at all. Unfortunately the default configuration of S3 buckets does not allow
|
397
|
+
cross site AJAX requests from posting to buckets. Fixing this is easy though.
|
398
|
+
|
399
|
+
- Open the AWS S3 console and locate your bucket
|
400
|
+
- Right click on it and choose "Properties"
|
401
|
+
- Open the "Permission" section
|
402
|
+
- Click "Add CORS Configuration"
|
403
|
+
|
404
|
+
The default configuration only allows "GET", you'll want to allow "POST" as well.
|
405
|
+
It could look something like this:
|
406
|
+
|
407
|
+
``` xml
|
408
|
+
<CORSConfiguration>
|
409
|
+
<CORSRule>
|
410
|
+
<AllowedOrigin>*</AllowedOrigin>
|
411
|
+
<AllowedMethod>GET</AllowedMethod>
|
412
|
+
<AllowedMethod>POST</AllowedMethod>
|
413
|
+
<MaxAgeSeconds>3000</MaxAgeSeconds>
|
414
|
+
<AllowedHeader>Authorization</AllowedHeader>
|
415
|
+
</CORSRule>
|
416
|
+
</CORSConfiguration>
|
417
|
+
```
|
418
|
+
|
419
|
+
If you're paranoid you can restrict the allowed origin to only your domain, but
|
420
|
+
since your bucket is only writable with authentication anyway, this shouldn't
|
421
|
+
be necessary.
|
422
|
+
|
423
|
+
Note that you do not need to, and in fact you shouldn't, make your bucket world
|
424
|
+
writable.
|
425
|
+
|
426
|
+
Once you've put in the new configuration, click "Save".
|
427
|
+
|
428
|
+
Now you can enable presigned uploads:
|
429
|
+
|
430
|
+
``` erb
|
431
|
+
<%= form.attachment_field :profile_image, presigned: true %>
|
432
|
+
```
|
433
|
+
|
434
|
+
You can also enable both direct and presigned uploads, and it'll fall back to
|
435
|
+
direct uploads if presigned uploads aren't available. This is useful if you're
|
436
|
+
using the FileSystem backend in development or test mode and the S3 backend in
|
437
|
+
production mode.
|
438
|
+
|
439
|
+
``` erb
|
440
|
+
<%= form.attachment_field :profile_image, direct: true, presigned: true %>
|
441
|
+
```
|
442
|
+
|
443
|
+
### Browser compatibility
|
444
|
+
|
445
|
+
Refile's JavaScript library requires HTML5 features which are unavailable on
|
446
|
+
IE9 and earlier versions. All other major browsers are supported. Note though
|
447
|
+
that it has not yet been extensively tested.
|
448
|
+
|
449
|
+
## Cache expiry
|
450
|
+
|
451
|
+
Files will accumulate in your cache, and you'll probably want to remove them
|
452
|
+
after some time.
|
453
|
+
|
454
|
+
The FileSystem backend does not currently provide any method of doing this. PRs
|
455
|
+
welcome ;)
|
456
|
+
|
457
|
+
On S3 this can be conveniently handled through lifecycle rules. Exactly how
|
458
|
+
depends a bit on your setup. If you are using the suggested setup of having
|
459
|
+
one bucket with `cache` and `store` being directories in that bucket (or prefixes
|
460
|
+
in S3 parlance), then follow the following steps, otherwise adapt them to your
|
461
|
+
needs:
|
462
|
+
|
463
|
+
- Open the AWS S3 console and locate your bucket
|
464
|
+
- Right click on it and choose "Properties"
|
465
|
+
- Open the "Lifecycle" section
|
466
|
+
- Click "Add rule"
|
467
|
+
- Choose "Apply the rule to: A prefix"
|
468
|
+
- Enter "cache/" as the prefix (trailing slash!)
|
469
|
+
- Click "Configure rule"
|
470
|
+
- For "Action on Objects" you'll probably want to choose "Permanently Delete Only"
|
471
|
+
- Choose whatever number of days you're comfortable with, I chose "1"
|
472
|
+
- Click "Review" and finally "Create and activate Rule"
|
473
|
+
|
474
|
+
## License
|
475
|
+
|
476
|
+
[MIT](LICENSE.txt)
|