refile 0.5.5 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/refile.rb +252 -27
- data/lib/refile/app.rb +55 -14
- data/lib/refile/attacher.rb +39 -40
- data/lib/refile/attachment.rb +28 -13
- data/lib/refile/attachment/active_record.rb +90 -1
- data/lib/refile/attachment_definition.rb +47 -0
- data/lib/refile/backend/s3.rb +1 -147
- data/lib/refile/backend_macros.rb +13 -5
- data/lib/refile/custom_logger.rb +3 -1
- data/lib/refile/file.rb +9 -0
- data/lib/refile/image_processing.rb +1 -143
- data/lib/refile/rails.rb +30 -0
- data/lib/refile/rails/attachment_helper.rb +27 -16
- data/lib/refile/signature.rb +5 -0
- data/lib/refile/simple_form.rb +17 -0
- data/lib/refile/version.rb +1 -1
- data/spec/refile/active_record_helper.rb +11 -0
- data/spec/refile/app_spec.rb +197 -20
- data/spec/refile/attachment/active_record_spec.rb +298 -1
- data/spec/refile/attachment_helper_spec.rb +39 -0
- data/spec/refile/attachment_spec.rb +53 -5
- data/spec/refile/backend_examples.rb +13 -2
- data/spec/refile/backend_macros_spec.rb +27 -6
- data/spec/refile/custom_logger_spec.rb +2 -3
- data/spec/refile/features/direct_upload_spec.rb +18 -0
- data/spec/refile/features/multiple_upload_spec.rb +122 -0
- data/spec/refile/features/normal_upload_spec.rb +5 -3
- data/spec/refile/features/presigned_upload_spec.rb +4 -0
- data/spec/refile/features/simple_form_spec.rb +8 -0
- data/spec/refile/fixtures/monkey.txt +1 -0
- data/spec/refile/fixtures/world.txt +1 -0
- data/spec/refile/spec_helper.rb +21 -11
- data/spec/refile_spec.rb +253 -24
- metadata +12 -303
- data/.gitignore +0 -27
- data/.rspec +0 -2
- data/.rubocop.yml +0 -68
- data/.travis.yml +0 -21
- data/.yardopts +0 -1
- data/CONTRIBUTING.md +0 -33
- data/Gemfile +0 -3
- data/History.md +0 -96
- data/LICENSE.txt +0 -22
- data/README.md +0 -651
- data/Rakefile +0 -19
- data/app/assets/javascripts/refile.js +0 -63
- data/config.ru +0 -8
- data/config/locales/en.yml +0 -8
- data/config/routes.rb +0 -5
- data/refile.gemspec +0 -42
- data/spec/refile/backend/s3_spec.rb +0 -11
- data/spec/refile/test_app.rb +0 -65
- data/spec/refile/test_app/app/assets/javascripts/application.js +0 -42
- data/spec/refile/test_app/app/controllers/application_controller.rb +0 -2
- data/spec/refile/test_app/app/controllers/direct_posts_controller.rb +0 -15
- data/spec/refile/test_app/app/controllers/home_controller.rb +0 -4
- data/spec/refile/test_app/app/controllers/normal_posts_controller.rb +0 -48
- data/spec/refile/test_app/app/controllers/presigned_posts_controller.rb +0 -31
- data/spec/refile/test_app/app/models/post.rb +0 -5
- data/spec/refile/test_app/app/views/direct_posts/new.html.erb +0 -20
- data/spec/refile/test_app/app/views/home/index.html.erb +0 -1
- data/spec/refile/test_app/app/views/layouts/application.html.erb +0 -14
- data/spec/refile/test_app/app/views/normal_posts/_form.html.erb +0 -28
- data/spec/refile/test_app/app/views/normal_posts/edit.html.erb +0 -1
- data/spec/refile/test_app/app/views/normal_posts/index.html +0 -5
- data/spec/refile/test_app/app/views/normal_posts/new.html.erb +0 -1
- data/spec/refile/test_app/app/views/normal_posts/show.html.erb +0 -19
- data/spec/refile/test_app/app/views/presigned_posts/new.html.erb +0 -16
- data/spec/refile/test_app/config/database.yml +0 -7
- data/spec/refile/test_app/config/routes.rb +0 -17
- data/spec/refile/test_app/public/favicon.ico +0 -0
data/.travis.yml
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
language: ruby
|
2
|
-
|
3
|
-
rvm:
|
4
|
-
- 2.1
|
5
|
-
- 2.2
|
6
|
-
- ruby-head
|
7
|
-
|
8
|
-
gemfile:
|
9
|
-
- Gemfile
|
10
|
-
|
11
|
-
cache: bundler
|
12
|
-
|
13
|
-
sudo: false
|
14
|
-
|
15
|
-
before_script:
|
16
|
-
- export DISPLAY=:99.0
|
17
|
-
- sh -e /etc/init.d/xvfb start
|
18
|
-
|
19
|
-
matrix:
|
20
|
-
allow_failures:
|
21
|
-
- rvm: ruby-head
|
data/.yardopts
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
--hide-api private --hide-void-return --markup markdown
|
data/CONTRIBUTING.md
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
## Security issues
|
2
|
-
|
3
|
-
If you have found a security related issue, please do not file an issue on
|
4
|
-
GitHub or send a PR addressing the issue. Contact
|
5
|
-
[Jonas](mailto:jonas.nicklas@gmail.com) directly. You will be given public
|
6
|
-
credit for your disclosure.
|
7
|
-
|
8
|
-
## Reporting issues
|
9
|
-
|
10
|
-
Please try to answer the following questions in your bug report:
|
11
|
-
|
12
|
-
- What did you do?
|
13
|
-
- What did you expect to happen?
|
14
|
-
- What happened instead?
|
15
|
-
|
16
|
-
Make sure to include as much relevant information as possible. Ruby version,
|
17
|
-
Refile version, OS and any stack traces you have are very valuable.
|
18
|
-
|
19
|
-
## Pull Requests
|
20
|
-
|
21
|
-
- **Add tests!** Your patch won't be accepted if it doesn't have tests.
|
22
|
-
|
23
|
-
- **Document any change in behaviour**. Make sure the README and any other
|
24
|
-
relevant documentation are kept up-to-date.
|
25
|
-
|
26
|
-
- **Create topic branches**. Please don't ask us to pull from your master branch.
|
27
|
-
|
28
|
-
- **One pull request per feature**. If you want to do more than one thing, send
|
29
|
-
multiple pull requests.
|
30
|
-
|
31
|
-
- **Send coherent history**. Make sure each individual commit in your pull
|
32
|
-
request is meaningful. If you had to make multiple intermediate commits while
|
33
|
-
developing, please squash them before sending them to us.
|
data/Gemfile
DELETED
data/History.md
DELETED
@@ -1,96 +0,0 @@
|
|
1
|
-
# 0.5.5
|
2
|
-
|
3
|
-
Release date: 2015-05-19
|
4
|
-
|
5
|
-
- [FIXED] Upgrade rest-client version due to security concerns.
|
6
|
-
|
7
|
-
# 0.5.3
|
8
|
-
|
9
|
-
Release date: 2015-01-18
|
10
|
-
|
11
|
-
- [FIXED] More stringent checks for ID validity.
|
12
|
-
- [CHANGED] `Refile.attachment_url` not uses `Refile.mount_point` as the prefix by default.
|
13
|
-
|
14
|
-
# 0.5.2
|
15
|
-
|
16
|
-
Release date: 2015-01-13
|
17
|
-
|
18
|
-
- [ADDED] Can generate URLs without using the Rails helper via `Refile.attachment_url`
|
19
|
-
- [FIXED] Regression in `attachment_image_tag`, was not using `Refile.host`.
|
20
|
-
- [FIXED] Record without file can be updated when content type and filename are not persisted
|
21
|
-
- [FIXED] Remove `id` attribute from hidden field, so it doesn't get confused with the file field
|
22
|
-
|
23
|
-
# 0.5.1
|
24
|
-
|
25
|
-
Release date: 2015-01-11
|
26
|
-
|
27
|
-
- [FIXED] Set content type from extension properly
|
28
|
-
- [FIXED] Support animated GIFs when changing format
|
29
|
-
|
30
|
-
# 0.5.0
|
31
|
-
|
32
|
-
Release date: 2015-01-09
|
33
|
-
|
34
|
-
- [ADDED] Can add custom types for easier content type validations
|
35
|
-
- [ADDED] Can persist filename, size and content type
|
36
|
-
- [CHANGED] The `cache_id` field is no longer necessary and no longer need to be permitted in the controller
|
37
|
-
- [CHANGED] Improved logging
|
38
|
-
|
39
|
-
# 0.4.2
|
40
|
-
|
41
|
-
Release date: 2014-12-27
|
42
|
-
|
43
|
-
- [FIXED] Regression in S3 backend
|
44
|
-
|
45
|
-
# 0.4.1
|
46
|
-
|
47
|
-
Release date: 2014-12-26
|
48
|
-
|
49
|
-
- [CHANGED] Improved IO performance
|
50
|
-
- [FIXED] Work around a bug in Ruby 2.2
|
51
|
-
|
52
|
-
# 0.4.0
|
53
|
-
|
54
|
-
Release date: 2014-12-26
|
55
|
-
|
56
|
-
- [ADDED] Pass through additional args to S3
|
57
|
-
- [ADDED] Rack app sets far future expiry headers
|
58
|
-
- [ADDED] Sinatra app supports CORS preflight requests
|
59
|
-
- [ADDED] Helpers can take `host` option
|
60
|
-
- [ADDED] File type validations
|
61
|
-
- [ADDED] attachment_field accept attribute is set from file type restrictions
|
62
|
-
- [CHANGED] Dynamically generated methods in attachments are included via Module
|
63
|
-
- [CHANGED] Rack app replaced with Sinatra app
|
64
|
-
- [FIXED] Various content type fixes in Sinatra app
|
65
|
-
- [FIXED] Don't set id of record if it is frozen
|
66
|
-
|
67
|
-
# 0.3.0
|
68
|
-
|
69
|
-
Release date: 2014-12-14
|
70
|
-
|
71
|
-
- [ADDED] Can upload files via URL
|
72
|
-
|
73
|
-
# 0.2.5
|
74
|
-
|
75
|
-
Release date: 2014-12-12
|
76
|
-
|
77
|
-
- [ADDED] CarrierWave style `remove_` attribute
|
78
|
-
- [ADDED] Files are deleted after model is destroyed
|
79
|
-
- [FIXED] Spec files can be required by external gems
|
80
|
-
- [FIXED] Refile should work inside other Rails engines
|
81
|
-
|
82
|
-
# 0.2.4
|
83
|
-
|
84
|
-
Release date: 2014-12-08
|
85
|
-
|
86
|
-
- [ADDED] Supports format option with image processing
|
87
|
-
|
88
|
-
# 0.2.3
|
89
|
-
|
90
|
-
Release date: 2014-12-08
|
91
|
-
|
92
|
-
- [ADDED] Support for passing format to processors
|
93
|
-
- [FIXED] Support IE10
|
94
|
-
- [FIXED] Gracefully degrade on IE9, IE8 and IE7
|
95
|
-
- [FIXED] Success event is fired at the appropriate time
|
96
|
-
- [FIXED] Works with apps which don't define root_url
|
data/LICENSE.txt
DELETED
@@ -1,22 +0,0 @@
|
|
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
DELETED
@@ -1,651 +0,0 @@
|
|
1
|
-
# Refile
|
2
|
-
|
3
|
-
[![Gem Version](https://badge.fury.io/rb/refile.svg)](http://badge.fury.io/rb/refile)
|
4
|
-
[![Build Status](https://travis-ci.org/elabs/refile.svg?branch=master)](https://travis-ci.org/elabs/refile)
|
5
|
-
[![Code Climate](https://codeclimate.com/github/elabs/refile/badges/gpa.svg)](https://codeclimate.com/github/elabs/refile)
|
6
|
-
[![Inline docs](http://inch-ci.org/github/elabs/refile.svg?branch=master)](http://inch-ci.org/github/elabs/refile)
|
7
|
-
|
8
|
-
Refile is a modern file upload library for Ruby applications. It is simple, yet
|
9
|
-
powerful.
|
10
|
-
|
11
|
-
Links:
|
12
|
-
|
13
|
-
- [API documentation](http://www.rubydoc.info/gems/refile)
|
14
|
-
- [Source Code](https://github.com/elabs/refile)
|
15
|
-
|
16
|
-
Features:
|
17
|
-
|
18
|
-
- Configurable backends, file system, S3, etc...
|
19
|
-
- Convenient integration with ORMs
|
20
|
-
- On the fly manipulation of images and other files
|
21
|
-
- Streaming IO for fast and memory friendly uploads
|
22
|
-
- Works across form redisplays, i.e. when validations fail, even on S3
|
23
|
-
- Effortless direct uploads, even to S3
|
24
|
-
|
25
|
-
## Quick start, Rails
|
26
|
-
|
27
|
-
Add the gem:
|
28
|
-
|
29
|
-
``` ruby
|
30
|
-
gem "mini_magick"
|
31
|
-
gem "refile", require: ["refile/rails", "refile/image_processing"]
|
32
|
-
```
|
33
|
-
|
34
|
-
We're requiring both Refile's Rails integration and image processing via the
|
35
|
-
[MiniMagick](https://github.com/minimagick/minimagick) gem, which requires
|
36
|
-
[ImageMagick](http://imagemagick.org/) to be installed. To install it simply
|
37
|
-
run:
|
38
|
-
|
39
|
-
``` sh
|
40
|
-
brew install imagemagick # OS X
|
41
|
-
sudo apt-get install imagemagick # Ubuntu
|
42
|
-
```
|
43
|
-
|
44
|
-
Use the `attachment` method to use Refile in a model:
|
45
|
-
|
46
|
-
``` ruby
|
47
|
-
class User < ActiveRecord::Base
|
48
|
-
attachment :profile_image
|
49
|
-
end
|
50
|
-
```
|
51
|
-
|
52
|
-
Generate a migration:
|
53
|
-
|
54
|
-
``` sh
|
55
|
-
rails generate migration add_profile_image_to_users profile_image_id:string
|
56
|
-
rake db:migrate
|
57
|
-
```
|
58
|
-
|
59
|
-
Add an attachment field to your form:
|
60
|
-
|
61
|
-
``` erb
|
62
|
-
<%= form_for @user do |form| %>
|
63
|
-
<%= form.attachment_field :profile_image %>
|
64
|
-
<% end %>
|
65
|
-
```
|
66
|
-
|
67
|
-
Set up strong parameters:
|
68
|
-
|
69
|
-
``` ruby
|
70
|
-
def user_params
|
71
|
-
params.require(:user).permit(:profile_image)
|
72
|
-
end
|
73
|
-
```
|
74
|
-
|
75
|
-
And start uploading! Finally show the file in your view:
|
76
|
-
|
77
|
-
``` erb
|
78
|
-
<%= image_tag attachment_url(@user, :profile_image, :fill, 300, 300) %>
|
79
|
-
```
|
80
|
-
|
81
|
-
## How it works
|
82
|
-
|
83
|
-
Refile consists of several parts:
|
84
|
-
|
85
|
-
1. Backends: cache and persist files
|
86
|
-
2. Model attachments: map files to model columns
|
87
|
-
3. A Rack application: streams files and accepts uploads
|
88
|
-
4. Rails helpers: conveniently generate markup in your views
|
89
|
-
5. A JavaScript library: facilitates direct uploads
|
90
|
-
|
91
|
-
Let's look at each of these in more detail!
|
92
|
-
|
93
|
-
## 1. Backend
|
94
|
-
|
95
|
-
Files are uploaded to a backend. The backend assigns an ID to this file, which
|
96
|
-
will be unique for this file within the backend.
|
97
|
-
|
98
|
-
Let's look at a simple example of using the backend:
|
99
|
-
|
100
|
-
``` ruby
|
101
|
-
backend = Refile::Backend::FileSystem.new("tmp")
|
102
|
-
|
103
|
-
file = backend.upload(StringIO.new("hello"))
|
104
|
-
file.id # => "b205bc..."
|
105
|
-
file.read # => "hello"
|
106
|
-
|
107
|
-
backend.get(file.id).read # => "hello"
|
108
|
-
```
|
109
|
-
|
110
|
-
As you may notice, backends are "flat". Files do not have directories, nor do
|
111
|
-
they have names or permissions, they are only identified by their ID.
|
112
|
-
|
113
|
-
Refile has a global registry of backends, accessed through `Refile.backends`.
|
114
|
-
|
115
|
-
There are two "special" backends, which are only really special in that they
|
116
|
-
are the default backends for attachments. They are `cache` and `store`.
|
117
|
-
|
118
|
-
The cache is intended to be transient. Files are added here before they are
|
119
|
-
meant to be permanently stored. Usually files are then moved to the store for
|
120
|
-
permanent storage, but this isn't always the case.
|
121
|
-
|
122
|
-
Suppose for example that a user uploads a file in a form and receives a
|
123
|
-
validation error. In that case the file has been temporarily stored in the
|
124
|
-
cache. The user might decide to fix the error and resubmit, at which point the
|
125
|
-
file will be promoted to the store. On the other hand, the user might simply
|
126
|
-
give up and leave, now the file is left in the cache for later cleanup.
|
127
|
-
|
128
|
-
Refile has convenient accessors for setting the `cache` and `store`, so for
|
129
|
-
example you can switch to the S3 backend like this:
|
130
|
-
|
131
|
-
``` ruby
|
132
|
-
# config/initializers/refile.rb
|
133
|
-
require "refile/backend/s3"
|
134
|
-
|
135
|
-
aws = {
|
136
|
-
access_key_id: "xyz",
|
137
|
-
secret_access_key: "abc",
|
138
|
-
bucket: "my-bucket",
|
139
|
-
}
|
140
|
-
Refile.cache = Refile::Backend::S3.new(prefix: "cache", **aws)
|
141
|
-
Refile.store = Refile::Backend::S3.new(prefix: "store", **aws)
|
142
|
-
```
|
143
|
-
|
144
|
-
And add to your Gemfile:
|
145
|
-
```ruby
|
146
|
-
gem "aws-sdk"
|
147
|
-
```
|
148
|
-
|
149
|
-
Try this in the quick start example above and your files are now uploaded to
|
150
|
-
S3.
|
151
|
-
|
152
|
-
Backends also provide the option of restricting the size of files they accept.
|
153
|
-
For example:
|
154
|
-
|
155
|
-
``` ruby
|
156
|
-
Refile.cache = Refile::Backend::S3.new(max_size: 10.megabytes, ...)
|
157
|
-
```
|
158
|
-
|
159
|
-
The Refile gem ships with [S3](lib/refile/backend/s3.rb) and
|
160
|
-
[FileSystem](lib/refile/backend/file_system.rb) backends. Additional backends
|
161
|
-
are provided by other gems.
|
162
|
-
|
163
|
-
- [Fog](https://github.com/elabs/refile-fog) provides support for a ton of
|
164
|
-
different cloud storage providers, including Google Storage and Rackspace
|
165
|
-
CloudFiles.
|
166
|
-
- [Postgresql](https://github.com/krists/refile-postgres)
|
167
|
-
- [In Memory](https://github.com/jnicklas/refile-memory)
|
168
|
-
|
169
|
-
### Uploadable
|
170
|
-
|
171
|
-
The `upload` method on backends can be called with a variety of objects. It
|
172
|
-
requires that the object passed to it behaves similarly to Ruby IO objects, in
|
173
|
-
particular it must implement the methods `size`, `read(length = nil, buffer =
|
174
|
-
nil)`, `eof?` and `close`. All of `File`, `Tempfile`,
|
175
|
-
`ActionDispath::UploadedFile` and `StringIO` implement this interface, however
|
176
|
-
`String` does not. If you want to upload a file from a `String` you must wrap
|
177
|
-
it in a `StringIO` first.
|
178
|
-
|
179
|
-
## 2. Attachments
|
180
|
-
|
181
|
-
You've already seen the `attachment` method:
|
182
|
-
|
183
|
-
``` ruby
|
184
|
-
class User < ActiveRecord::Base
|
185
|
-
attachment :profile_image
|
186
|
-
end
|
187
|
-
```
|
188
|
-
|
189
|
-
Calling `attachment` generates a getter and setter with the given name. When
|
190
|
-
you assign a file to the setter, it is uploaded to the cache:
|
191
|
-
|
192
|
-
``` ruby
|
193
|
-
User.new
|
194
|
-
|
195
|
-
# with a ActionDispatch::UploadedFile
|
196
|
-
user.profile_image = params[:file]
|
197
|
-
|
198
|
-
# with a regular File object
|
199
|
-
File.open("/some/path", "rb") do |file|
|
200
|
-
user.profile_image = file
|
201
|
-
end
|
202
|
-
|
203
|
-
# or a StringIO
|
204
|
-
user.profile_image = StringIO.new("hello world")
|
205
|
-
|
206
|
-
user.profile_image.id # => "fec421..."
|
207
|
-
user.profile_image.read # => "hello world"
|
208
|
-
```
|
209
|
-
|
210
|
-
When you call `save` on the record, the uploaded file is transferred from the
|
211
|
-
cache to the store. Where possible, Refile does this move efficiently. For example
|
212
|
-
if both `cache` and `store` are on the same S3 account, instead of downloading
|
213
|
-
the file and uploading it again, Refile will simply issue a copy command to S3.
|
214
|
-
|
215
|
-
### Other ORMs
|
216
|
-
|
217
|
-
Refile is built to integrate with ORMs other than ActiveRecord, but this being
|
218
|
-
a very young gem, such integrations do not yet exist. Take a look at the [ActiveRecord
|
219
|
-
integration](lib/refile/attachment/active_record.rb), building your own should
|
220
|
-
not be too difficult.
|
221
|
-
|
222
|
-
### Pure Ruby classes
|
223
|
-
|
224
|
-
You can also use attachments in pure Ruby classes like this:
|
225
|
-
|
226
|
-
``` ruby
|
227
|
-
class User
|
228
|
-
extend Refile::Attachment
|
229
|
-
|
230
|
-
attr_accessor :profile_image_id
|
231
|
-
|
232
|
-
attachment :profile_image
|
233
|
-
end
|
234
|
-
```
|
235
|
-
|
236
|
-
## 3. Rack Application
|
237
|
-
|
238
|
-
Refile includes a Rack application (an endpoint, not a middleware), written in
|
239
|
-
Sinatra. This application streams files from backends and can even accept file
|
240
|
-
uploads and upload them to backends.
|
241
|
-
|
242
|
-
**Important:** Unlike other file upload solutions, Refile always streams your files through your
|
243
|
-
application. It cannot generate URLs to your files. This means that you should
|
244
|
-
**always** put a CDN or other HTTP cache in front of your application. Serving
|
245
|
-
files through your app takes a lot of resources and you want it to happen rarely.
|
246
|
-
|
247
|
-
Setting this up is actually quite simple, you can use the same CDN you would use
|
248
|
-
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)
|
249
|
-
explains how to set this up (bonus: faster static assets!). Once you've set this
|
250
|
-
up, simply configure Refile to use your CDN:
|
251
|
-
|
252
|
-
``` ruby
|
253
|
-
Refile.host = "//your-dist-url.cloudfront.net"
|
254
|
-
```
|
255
|
-
|
256
|
-
Using a [protocol-relative URL](http://www.paulirish.com/2010/the-protocol-relative-url/) for `Refile.host` is recommended.
|
257
|
-
|
258
|
-
### Mounting
|
259
|
-
|
260
|
-
If you are using Rails and have required [refile/rails.rb](lib/refile/rails.rb),
|
261
|
-
then the Rack application is mounted for you at `/attachments`. You should be able
|
262
|
-
to see this when you run `rake routes`.
|
263
|
-
|
264
|
-
You could also run the application on its own, it doesn't need to be mounted to
|
265
|
-
work.
|
266
|
-
|
267
|
-
### Retrieving files
|
268
|
-
|
269
|
-
Files can be retrieved from the application by calling:
|
270
|
-
|
271
|
-
```
|
272
|
-
GET /attachments/:backend_name/:id/:filename
|
273
|
-
```
|
274
|
-
|
275
|
-
The `:filename` serves no other purpose than generating a nice name when the user
|
276
|
-
downloads the file, it does not in any way affect the downloaded file. For caching
|
277
|
-
purposes you should always use the same filename for the same file. The Rails helpers
|
278
|
-
default this to the name of the column.
|
279
|
-
|
280
|
-
### Processing
|
281
|
-
|
282
|
-
Refile provides on the fly processing of files. You can trigger it by calling
|
283
|
-
a URL like this:
|
284
|
-
|
285
|
-
```
|
286
|
-
GET /attachments/:backend_name/:processor_name/*args/:id/:filename
|
287
|
-
```
|
288
|
-
|
289
|
-
Suppose we have uploaded a file:
|
290
|
-
|
291
|
-
``` ruby
|
292
|
-
Refile.cache.upload(StringIO.new("hello")).id # => "a4e8ce"
|
293
|
-
```
|
294
|
-
|
295
|
-
And we've defined a processor like this:
|
296
|
-
|
297
|
-
``` ruby
|
298
|
-
Refile.processor :reverse do |file|
|
299
|
-
StringIO.new(file.read.reverse)
|
300
|
-
end
|
301
|
-
```
|
302
|
-
|
303
|
-
Then you could do the following.
|
304
|
-
|
305
|
-
``` sh
|
306
|
-
curl http://127.0.0.1:3000/attachments/cache/reverse/a4e8ce/some_file.txt
|
307
|
-
elloh
|
308
|
-
```
|
309
|
-
|
310
|
-
Refile calls `call` on the processor and passes in the retrieved file, as well
|
311
|
-
as all additional arguments sent through the URL. See the
|
312
|
-
[built in image processors](lib/refile/image_processing.rb) for a more advanced
|
313
|
-
example.
|
314
|
-
|
315
|
-
## 4. Rails helpers
|
316
|
-
|
317
|
-
Refile provides the `attachment_field` form helper which generates a file field
|
318
|
-
as well as a hidden field. This field keeps track of the file in case it is not
|
319
|
-
yet permanently stored, for example if validations fail. It is also used for
|
320
|
-
direct and presigned uploads. For this reason it is highly recommended to use
|
321
|
-
`attachment_field` instead of `file_field`.
|
322
|
-
|
323
|
-
``` erb
|
324
|
-
<%= form_for @user do |form| %>
|
325
|
-
<%= form.attachment_field :profile_image %>
|
326
|
-
<% end %>
|
327
|
-
```
|
328
|
-
|
329
|
-
Will generate something like:
|
330
|
-
|
331
|
-
``` html
|
332
|
-
<form action="/users" enctype="multipart/form-data" method="post">
|
333
|
-
<input name="user[profile_image]" type="hidden">
|
334
|
-
<input name="user[profile_image]" type="file">
|
335
|
-
</form>
|
336
|
-
```
|
337
|
-
|
338
|
-
The `attachment_url` helper can then be used for generating URLs for the uploaded
|
339
|
-
files:
|
340
|
-
|
341
|
-
``` erb
|
342
|
-
<%= link_to "Image", attachment_url(@user, :profile_image) %>
|
343
|
-
```
|
344
|
-
|
345
|
-
Any additional arguments to it are included in the URL as processor arguments:
|
346
|
-
|
347
|
-
``` erb
|
348
|
-
<%= link_to "Image", attachment_url(@user, :profile_image, :fill, 300, 300) %>
|
349
|
-
```
|
350
|
-
|
351
|
-
There's also a helper for generating image tags:
|
352
|
-
|
353
|
-
``` erb
|
354
|
-
<%= attachment_image_tag(@user, :profile_image, :fill, 300, 300) %>
|
355
|
-
```
|
356
|
-
|
357
|
-
With this helper you can specify an image which is used as a fallback in case
|
358
|
-
no file has been uploaded:
|
359
|
-
|
360
|
-
``` erb
|
361
|
-
<%= attachment_image_tag(@user, :profile_image, :fill, 300, 300, fallback: "default.png") %>
|
362
|
-
```
|
363
|
-
|
364
|
-
## 5. JavaScript library
|
365
|
-
|
366
|
-
Refile's JavaScript library is small but powerful.
|
367
|
-
|
368
|
-
Uploading files is slow, so anything we can do to speed up the process is going
|
369
|
-
to lead to happier users. One way to cheat is to start uploading files directly
|
370
|
-
after the user has chosen a file, instead of waiting until they hit the submit
|
371
|
-
button. This provides a significantly better user experience. Implementing this
|
372
|
-
is usually tricky, but thankfully Refile makes it very easy.
|
373
|
-
|
374
|
-
First, load the JavaScript file. If you're using the asset pipeline, you can
|
375
|
-
simply include it like this:
|
376
|
-
|
377
|
-
``` javascript
|
378
|
-
//= require refile
|
379
|
-
```
|
380
|
-
|
381
|
-
Otherwise you can grab a copy [here](https://raw.githubusercontent.com/elabs/refile/master/app/assets/javascripts/refile.js).
|
382
|
-
Be sure to always update your copy of this file when you upgrade to the latest
|
383
|
-
Refile version.
|
384
|
-
|
385
|
-
Now mark the field for direct upload:
|
386
|
-
|
387
|
-
``` erb
|
388
|
-
<%= form.attachment_field :profile_image, direct: true %>
|
389
|
-
```
|
390
|
-
|
391
|
-
There is no step 3 ;)
|
392
|
-
|
393
|
-
The file is now uploaded to the `cache` immediately after the user chooses a file.
|
394
|
-
If you try this in the browser, you'll notice that an AJAX request is fired as
|
395
|
-
soon as you choose a file. Then when you submit to the server, the file is no
|
396
|
-
longer submitted, only its id.
|
397
|
-
|
398
|
-
If you want to improve the experience of this, the JavaScript library fires
|
399
|
-
a couple of custom DOM events. These events bubble, so you can also listen for
|
400
|
-
them on the form for example:
|
401
|
-
|
402
|
-
``` javascript
|
403
|
-
form.addEventListener("upload:start", function() {
|
404
|
-
// ...
|
405
|
-
});
|
406
|
-
|
407
|
-
form.addEventListener("upload:success", function() {
|
408
|
-
// ...
|
409
|
-
});
|
410
|
-
|
411
|
-
input.addEventListener("upload:progress", function() {
|
412
|
-
// ...
|
413
|
-
});
|
414
|
-
```
|
415
|
-
|
416
|
-
You can also listen for them with jQuery, even with event delegation:
|
417
|
-
|
418
|
-
``` javascript
|
419
|
-
$(document).on("upload:start", "form", function(e) {
|
420
|
-
// ...
|
421
|
-
});
|
422
|
-
```
|
423
|
-
|
424
|
-
This way you could for example disable the submit button until all files have
|
425
|
-
uploaded:
|
426
|
-
|
427
|
-
``` javascript
|
428
|
-
$(document).on("upload:start", "form", function(e) {
|
429
|
-
$(this).find("input[type=submit]").attr("disabled", true)
|
430
|
-
});
|
431
|
-
|
432
|
-
$(document).on("upload:complete", "form", function(e) {
|
433
|
-
if(!$(this).find("input.uploading").length) {
|
434
|
-
$(this).find("input[type=submit]").removeAttr("disabled")
|
435
|
-
}
|
436
|
-
});
|
437
|
-
```
|
438
|
-
|
439
|
-
### Presigned uploads
|
440
|
-
|
441
|
-
Amazon S3 supports uploads directly from the browser to S3 buckets. With this
|
442
|
-
feature you can bypass your application entirely; uploads never hit your application
|
443
|
-
at all. Unfortunately the default configuration of S3 buckets does not allow
|
444
|
-
cross site AJAX requests from posting to buckets. Fixing this is easy though.
|
445
|
-
|
446
|
-
- Open the AWS S3 console and locate your bucket
|
447
|
-
- Right click on it and choose "Properties"
|
448
|
-
- Open the "Permission" section
|
449
|
-
- Click "Add CORS Configuration"
|
450
|
-
|
451
|
-
The default configuration only allows "GET", you'll want to allow "POST" as
|
452
|
-
well. You'll also want to permit the "Content-Type" and "Origin" headers.
|
453
|
-
|
454
|
-
It could look something like this:
|
455
|
-
|
456
|
-
``` xml
|
457
|
-
<CORSConfiguration>
|
458
|
-
<CORSRule>
|
459
|
-
<AllowedOrigin>*</AllowedOrigin>
|
460
|
-
<AllowedMethod>GET</AllowedMethod>
|
461
|
-
<AllowedMethod>POST</AllowedMethod>
|
462
|
-
<MaxAgeSeconds>3000</MaxAgeSeconds>
|
463
|
-
<AllowedHeader>Authorization</AllowedHeader>
|
464
|
-
<AllowedHeader>Content-Type</AllowedHeader>
|
465
|
-
<AllowedHeader>Origin</AllowedHeader>
|
466
|
-
</CORSRule>
|
467
|
-
</CORSConfiguration>
|
468
|
-
```
|
469
|
-
|
470
|
-
If you're paranoid you can restrict the allowed origin to only your domain, but
|
471
|
-
since your bucket is only writable with authentication anyway, this shouldn't
|
472
|
-
be necessary.
|
473
|
-
|
474
|
-
Note that you do not need to, and in fact you shouldn't, make your bucket world
|
475
|
-
writable.
|
476
|
-
|
477
|
-
Once you've put in the new configuration, click "Save".
|
478
|
-
|
479
|
-
Now you can enable presigned uploads:
|
480
|
-
|
481
|
-
``` erb
|
482
|
-
<%= form.attachment_field :profile_image, presigned: true %>
|
483
|
-
```
|
484
|
-
|
485
|
-
You can also enable both direct and presigned uploads, and it'll fall back to
|
486
|
-
direct uploads if presigned uploads aren't available. This is useful if you're
|
487
|
-
using the FileSystem backend in development or test mode and the S3 backend in
|
488
|
-
production mode.
|
489
|
-
|
490
|
-
``` erb
|
491
|
-
<%= form.attachment_field :profile_image, direct: true, presigned: true %>
|
492
|
-
```
|
493
|
-
|
494
|
-
### Browser compatibility
|
495
|
-
|
496
|
-
Refile's JavaScript library requires HTML5 features which are unavailable on
|
497
|
-
IE9 and earlier versions. All other major browsers are supported.
|
498
|
-
|
499
|
-
## Additional metadata
|
500
|
-
|
501
|
-
In the quick start example above, we chose to only store the file id, but often
|
502
|
-
it is useful to store the file's filename, size and content type as well.
|
503
|
-
Refile makes it easy to extract this data and store it alongside the id. All you
|
504
|
-
need to do is add columns for these:
|
505
|
-
|
506
|
-
``` ruby
|
507
|
-
class StoreMetadata < ActiveRecord::Migration
|
508
|
-
def change
|
509
|
-
add_column :users, :profile_image_filename, :string
|
510
|
-
add_column :users, :profile_image_size, :integer
|
511
|
-
add_column :users, :profile_image_content_type, :string
|
512
|
-
end
|
513
|
-
end
|
514
|
-
```
|
515
|
-
|
516
|
-
These columns will now be filled automatically.
|
517
|
-
|
518
|
-
## File type validations
|
519
|
-
|
520
|
-
Refile can check that attached files have a given content type or extension.
|
521
|
-
This allows you to warn users if they try to upload an invalid file.
|
522
|
-
|
523
|
-
**Important:** You should regard this as a convenience feature for your users,
|
524
|
-
not a security feature. Both file extension and content type can easily be
|
525
|
-
spoofed.
|
526
|
-
|
527
|
-
In order to limit attachments to an extension or content type, you can provide
|
528
|
-
them like this:
|
529
|
-
|
530
|
-
``` ruby
|
531
|
-
attachment :cv, extension: "pdf"
|
532
|
-
attachment :profile_image, content_type: "image/jpeg"
|
533
|
-
```
|
534
|
-
|
535
|
-
You can also provide a list of content types or extensions:
|
536
|
-
|
537
|
-
``` ruby
|
538
|
-
attachment :cv, extension: ["pdf", "doc"]
|
539
|
-
attachment :profile_image, content_type: ["image/jpeg", "image/png", "image/gif"]
|
540
|
-
```
|
541
|
-
|
542
|
-
Since the combination of JPEG, PNG and GIF is so common, you can also specify
|
543
|
-
this more succinctly like this:
|
544
|
-
|
545
|
-
``` ruby
|
546
|
-
attachment :profile_image, type: :image
|
547
|
-
```
|
548
|
-
|
549
|
-
When a user uploads a file with an invalid extension or content type and
|
550
|
-
submits the form, they'll be presented with a validation error.
|
551
|
-
|
552
|
-
If you use a particular content type or set of content types frequently
|
553
|
-
you can define your own types like this:
|
554
|
-
|
555
|
-
``` ruby
|
556
|
-
Refile.types[:document] = Refile::Type.new(:document,
|
557
|
-
content_type: %w[text/plain application/pdf]
|
558
|
-
)
|
559
|
-
```
|
560
|
-
|
561
|
-
Now you can use them like this:
|
562
|
-
|
563
|
-
``` ruby
|
564
|
-
attachment :profile_image, type: :document
|
565
|
-
```
|
566
|
-
|
567
|
-
## Removing attached files
|
568
|
-
|
569
|
-
File input fields unfortunately do not have the option of removing an already
|
570
|
-
uploaded file. This is problematic when editing a model which has a file attached
|
571
|
-
and the user wants to remove this file. To work around this, Refile automatically
|
572
|
-
adds an attribute to your model when you use the `attachment` method, which is
|
573
|
-
designed to be used with a checkbox in a form.
|
574
|
-
|
575
|
-
``` erb
|
576
|
-
<%= form_for @user do |form| %>
|
577
|
-
<%= form.label :profile_image %>
|
578
|
-
<%= form.attachment_field :profile_image %>
|
579
|
-
|
580
|
-
<%= form.check_box :remove_profile_image %>
|
581
|
-
<%= form.label :remove_profile_image %>
|
582
|
-
<% end %>
|
583
|
-
```
|
584
|
-
|
585
|
-
Don't forget to permit this attribute in your controller:
|
586
|
-
|
587
|
-
``` ruby
|
588
|
-
def user_params
|
589
|
-
params.require(:user).permit(:profile_image, :remove_profile_image)
|
590
|
-
end
|
591
|
-
```
|
592
|
-
|
593
|
-
Now when you check this checkbox and submit the form, the previously attached
|
594
|
-
file will be removed.
|
595
|
-
|
596
|
-
## Fetching remote files by URL
|
597
|
-
|
598
|
-
You might want to give you users the option of uploading a file by its URL.
|
599
|
-
This could be either just via a textfield or through some other interface.
|
600
|
-
Refile makes it easy to fetch this file and upload it. Just add a field like
|
601
|
-
this:
|
602
|
-
|
603
|
-
``` erb
|
604
|
-
<%= form_for @user do |form| %>
|
605
|
-
<%= form.label :profile_image, "Attach image" %>
|
606
|
-
<%= form.attachment_field :profile_image %>
|
607
|
-
|
608
|
-
<%= form.label :remote_profile_image_url, "Or specify URL" %>
|
609
|
-
<%= form.text_field :remote_profile_image_url %>
|
610
|
-
<% end %>
|
611
|
-
```
|
612
|
-
|
613
|
-
Then permit this field in your controller:
|
614
|
-
|
615
|
-
``` ruby
|
616
|
-
def user_params
|
617
|
-
params.require(:user).permit(:profile_image, :remote_profile_image_url)
|
618
|
-
end
|
619
|
-
```
|
620
|
-
|
621
|
-
Refile will now fetch the file from the given URL, following redirects if
|
622
|
-
needed.
|
623
|
-
|
624
|
-
## Cache expiry
|
625
|
-
|
626
|
-
Files will accumulate in your cache, and you'll probably want to remove them
|
627
|
-
after some time.
|
628
|
-
|
629
|
-
The FileSystem backend does not currently provide any method of doing this. PRs
|
630
|
-
welcome ;)
|
631
|
-
|
632
|
-
On S3 this can be conveniently handled through lifecycle rules. Exactly how
|
633
|
-
depends a bit on your setup. If you are using the suggested setup of having
|
634
|
-
one bucket with `cache` and `store` being directories in that bucket (or prefixes
|
635
|
-
in S3 parlance), then follow the following steps, otherwise adapt them to your
|
636
|
-
needs:
|
637
|
-
|
638
|
-
- Open the AWS S3 console and locate your bucket
|
639
|
-
- Right click on it and choose "Properties"
|
640
|
-
- Open the "Lifecycle" section
|
641
|
-
- Click "Add rule"
|
642
|
-
- Choose "Apply the rule to: A prefix"
|
643
|
-
- Enter "cache/" as the prefix (trailing slash!)
|
644
|
-
- Click "Configure rule"
|
645
|
-
- For "Action on Objects" you'll probably want to choose "Permanently Delete Only"
|
646
|
-
- Choose whatever number of days you're comfortable with, I chose "1"
|
647
|
-
- Click "Review" and finally "Create and activate Rule"
|
648
|
-
|
649
|
-
## License
|
650
|
-
|
651
|
-
[MIT](LICENSE.txt)
|