refile 0.5.5 → 0.6.0

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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/lib/refile.rb +252 -27
  3. data/lib/refile/app.rb +55 -14
  4. data/lib/refile/attacher.rb +39 -40
  5. data/lib/refile/attachment.rb +28 -13
  6. data/lib/refile/attachment/active_record.rb +90 -1
  7. data/lib/refile/attachment_definition.rb +47 -0
  8. data/lib/refile/backend/s3.rb +1 -147
  9. data/lib/refile/backend_macros.rb +13 -5
  10. data/lib/refile/custom_logger.rb +3 -1
  11. data/lib/refile/file.rb +9 -0
  12. data/lib/refile/image_processing.rb +1 -143
  13. data/lib/refile/rails.rb +30 -0
  14. data/lib/refile/rails/attachment_helper.rb +27 -16
  15. data/lib/refile/signature.rb +5 -0
  16. data/lib/refile/simple_form.rb +17 -0
  17. data/lib/refile/version.rb +1 -1
  18. data/spec/refile/active_record_helper.rb +11 -0
  19. data/spec/refile/app_spec.rb +197 -20
  20. data/spec/refile/attachment/active_record_spec.rb +298 -1
  21. data/spec/refile/attachment_helper_spec.rb +39 -0
  22. data/spec/refile/attachment_spec.rb +53 -5
  23. data/spec/refile/backend_examples.rb +13 -2
  24. data/spec/refile/backend_macros_spec.rb +27 -6
  25. data/spec/refile/custom_logger_spec.rb +2 -3
  26. data/spec/refile/features/direct_upload_spec.rb +18 -0
  27. data/spec/refile/features/multiple_upload_spec.rb +122 -0
  28. data/spec/refile/features/normal_upload_spec.rb +5 -3
  29. data/spec/refile/features/presigned_upload_spec.rb +4 -0
  30. data/spec/refile/features/simple_form_spec.rb +8 -0
  31. data/spec/refile/fixtures/monkey.txt +1 -0
  32. data/spec/refile/fixtures/world.txt +1 -0
  33. data/spec/refile/spec_helper.rb +21 -11
  34. data/spec/refile_spec.rb +253 -24
  35. metadata +12 -303
  36. data/.gitignore +0 -27
  37. data/.rspec +0 -2
  38. data/.rubocop.yml +0 -68
  39. data/.travis.yml +0 -21
  40. data/.yardopts +0 -1
  41. data/CONTRIBUTING.md +0 -33
  42. data/Gemfile +0 -3
  43. data/History.md +0 -96
  44. data/LICENSE.txt +0 -22
  45. data/README.md +0 -651
  46. data/Rakefile +0 -19
  47. data/app/assets/javascripts/refile.js +0 -63
  48. data/config.ru +0 -8
  49. data/config/locales/en.yml +0 -8
  50. data/config/routes.rb +0 -5
  51. data/refile.gemspec +0 -42
  52. data/spec/refile/backend/s3_spec.rb +0 -11
  53. data/spec/refile/test_app.rb +0 -65
  54. data/spec/refile/test_app/app/assets/javascripts/application.js +0 -42
  55. data/spec/refile/test_app/app/controllers/application_controller.rb +0 -2
  56. data/spec/refile/test_app/app/controllers/direct_posts_controller.rb +0 -15
  57. data/spec/refile/test_app/app/controllers/home_controller.rb +0 -4
  58. data/spec/refile/test_app/app/controllers/normal_posts_controller.rb +0 -48
  59. data/spec/refile/test_app/app/controllers/presigned_posts_controller.rb +0 -31
  60. data/spec/refile/test_app/app/models/post.rb +0 -5
  61. data/spec/refile/test_app/app/views/direct_posts/new.html.erb +0 -20
  62. data/spec/refile/test_app/app/views/home/index.html.erb +0 -1
  63. data/spec/refile/test_app/app/views/layouts/application.html.erb +0 -14
  64. data/spec/refile/test_app/app/views/normal_posts/_form.html.erb +0 -28
  65. data/spec/refile/test_app/app/views/normal_posts/edit.html.erb +0 -1
  66. data/spec/refile/test_app/app/views/normal_posts/index.html +0 -5
  67. data/spec/refile/test_app/app/views/normal_posts/new.html.erb +0 -1
  68. data/spec/refile/test_app/app/views/normal_posts/show.html.erb +0 -19
  69. data/spec/refile/test_app/app/views/presigned_posts/new.html.erb +0 -16
  70. data/spec/refile/test_app/config/database.yml +0 -7
  71. data/spec/refile/test_app/config/routes.rb +0 -17
  72. data/spec/refile/test_app/public/favicon.ico +0 -0
@@ -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
@@ -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
@@ -1,3 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- gemspec
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
@@ -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)