s3ff 0.9.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.
- checksums.yaml +7 -0
- data/README.md +107 -0
- data/app/assets/javascripts/s3ff.js +76 -0
- data/lib/s3ff/engine.rb +4 -0
- data/lib/s3ff/railtie.rb +6 -0
- data/lib/s3ff/version.rb +3 -0
- data/lib/s3ff/view_helper.rb +46 -0
- data/lib/s3ff.rb +32 -0
- metadata +107 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: aa68843504870dde78c2146cbd742ec26a9c5826
|
4
|
+
data.tar.gz: a90a1ceb009cc4488aef06886bdac71806667a62
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: dca77539e239ef3ca2c666861f75a9adac27945d461615f6bb7132bffe77aa8faf5a0ceb7c60e565edb127b9a41fced54580e97d65dc13231194a6ee6741070b
|
7
|
+
data.tar.gz: 60e7ed6509d3a9ef284c754df7380cf7b4b3f6955435790bff7c78b5fbd1cb2558dfc551d3823cef0507208cc163005aaf55af3d7e2dbc32cdfb1330563f8e1e
|
data/README.md
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
# S3FF
|
2
|
+
|
3
|
+
Using `s3_file_field` with `paperclip`
|
4
|
+
|
5
|
+
## Install
|
6
|
+
|
7
|
+
### 1. Add javascript
|
8
|
+
|
9
|
+
in your application.js
|
10
|
+
|
11
|
+
```
|
12
|
+
//= require s3ff
|
13
|
+
```
|
14
|
+
|
15
|
+
### 2. Change `file_field` input to use `s3_file_field`
|
16
|
+
|
17
|
+
```
|
18
|
+
= form_for :user do |f|
|
19
|
+
= f.s3_file_field :avatar
|
20
|
+
```
|
21
|
+
|
22
|
+
or if you're using `simple_form`
|
23
|
+
|
24
|
+
```
|
25
|
+
= simple_form_for :user do |f|
|
26
|
+
= f.input :avatar do
|
27
|
+
= f.s3_file_field :avatar, :class => "form-control"
|
28
|
+
```
|
29
|
+
|
30
|
+
### 3. Add footer
|
31
|
+
|
32
|
+
```
|
33
|
+
= include_s3ff_templates
|
34
|
+
```
|
35
|
+
|
36
|
+
NOTE: Feel free to modify & render the templates manually, but keep the `s3ff_` prefixed CSS classes for our javascript to work properly.
|
37
|
+
|
38
|
+
## What will happen
|
39
|
+
|
40
|
+
To illustate, if you have a file field like this
|
41
|
+
|
42
|
+
```
|
43
|
+
<input type="file" name="user[avatar]">
|
44
|
+
```
|
45
|
+
|
46
|
+
When `s3ff` kicks in, it would upgrade the field to a `s3_file_field`. When your user chooses a file, it will be uploaded, with a progress indicator, directly into your S3 bucket (see `s3_file_field` gem for configuration). Your `form` will be disabled during the upload and re-enabled once upload completes. After this process, 4 new hidden form fields will be attached to your form:
|
47
|
+
|
48
|
+
```
|
49
|
+
<input type="file" name="user[avatar_direct_url]" value="https://....">
|
50
|
+
<input type="file" name="user[avatar_file_name]" value="face.png">
|
51
|
+
<input type="file" name="user[avatar_file_size]" value="162534">
|
52
|
+
<input type="file" name="user[avatar_content_type]" value="image/png">
|
53
|
+
```
|
54
|
+
|
55
|
+
## Code changes to your app
|
56
|
+
|
57
|
+
`s3ff` designed to minimize moving parts and code changes to your Rails app - all it does is give you 4 form fields in return for every direct s3 file upload that happened in your user's browser.
|
58
|
+
|
59
|
+
How you deal with these form fields are entirely up to you. Here's a simple way:
|
60
|
+
|
61
|
+
#### 1. Edit strong parameters
|
62
|
+
|
63
|
+
If your controller was specifying
|
64
|
+
|
65
|
+
```
|
66
|
+
params.require(:user).permit(:avatar)
|
67
|
+
```
|
68
|
+
|
69
|
+
It would need to be changed to accept the new form fields
|
70
|
+
|
71
|
+
```
|
72
|
+
params.require(:user).permit(:avatar,
|
73
|
+
:avatar_direct_url,
|
74
|
+
:avatar_file_name,
|
75
|
+
:avatar_file_size,
|
76
|
+
:avatar_content_type
|
77
|
+
)
|
78
|
+
```
|
79
|
+
|
80
|
+
#### 2. Upgrade model to also accept direct url
|
81
|
+
|
82
|
+
If your model was originally
|
83
|
+
|
84
|
+
```
|
85
|
+
class User < ActiveRecord::Base
|
86
|
+
has_attached_file :avatar
|
87
|
+
end
|
88
|
+
```
|
89
|
+
|
90
|
+
Download the file from S3 when given `avatar_direct_url`. This leave all your existing Paperclip code and logic unchanged.
|
91
|
+
|
92
|
+
```
|
93
|
+
class User < ActiveRecord::Base
|
94
|
+
has_attached_file :avatar
|
95
|
+
|
96
|
+
attr_accessor :avatar_direct_url
|
97
|
+
def avatar_direct_url=(value)
|
98
|
+
self.avatar = open(value) if value.present?
|
99
|
+
end
|
100
|
+
end
|
101
|
+
```
|
102
|
+
|
103
|
+
|
104
|
+
|
105
|
+
### License
|
106
|
+
|
107
|
+
This repository is MIT-licensed.
|
@@ -0,0 +1,76 @@
|
|
1
|
+
//= require s3_file_field
|
2
|
+
//= require jsrender.min
|
3
|
+
|
4
|
+
$(function() {
|
5
|
+
|
6
|
+
// adapted from http://www.html5rocks.com/en/tutorials/cors/
|
7
|
+
function iCanHasCORSRequest() {
|
8
|
+
var xhr = new XMLHttpRequest();
|
9
|
+
return (("withCredentials" in xhr) || (typeof XDomainRequest != "undefined"));
|
10
|
+
}
|
11
|
+
if (! iCanHasCORSRequest()) return;
|
12
|
+
|
13
|
+
var ready = function() {
|
14
|
+
var s3ff_init = function () {
|
15
|
+
if ($(this).hasClass('s3ff_enabled')) return;
|
16
|
+
|
17
|
+
var that = $(this).addClass('s3ff_enabled');
|
18
|
+
var multi = that.attr('multiple');
|
19
|
+
var label = that.wrap($.templates($('#s3ff_label').html()).render()).parents('.s3ff_label').attr('for', that.attr('id'));
|
20
|
+
var wrap = that.wrap('<div></div>').parent();
|
21
|
+
var section = function(file, selector) {
|
22
|
+
var upload = label.find("#upload-" + file.unique_id);
|
23
|
+
return (selector ? upload.find(selector) : upload);
|
24
|
+
};
|
25
|
+
|
26
|
+
var s3ff_handlers = {
|
27
|
+
change: function(e, data) {
|
28
|
+
if (! multi) label.children('.s3ff_section').remove();
|
29
|
+
},
|
30
|
+
always: function(e, data) {
|
31
|
+
wrap.parents("form").find("[type='submit']").each(function() {
|
32
|
+
this.uploading = this.uploading || 0;
|
33
|
+
$(this).attr({'disabled': (--this.uploading > 0)});
|
34
|
+
});
|
35
|
+
},
|
36
|
+
add: function(e, data) {
|
37
|
+
wrap.parents("form").find("[type='submit']").each(function() {
|
38
|
+
this.uploading = this.uploading || 0;
|
39
|
+
$(this).attr({'disabled': (++this.uploading > 0)});
|
40
|
+
});
|
41
|
+
data.submit();
|
42
|
+
},
|
43
|
+
send: function(e, data) {
|
44
|
+
label.append($.templates($('#s3ff_upload').html()).render(data.files[0]));
|
45
|
+
},
|
46
|
+
done: function(e, data) {
|
47
|
+
var upload = section(data.files[0]);
|
48
|
+
var field_prefix = that.attr('name').replace(/\]$/, '');
|
49
|
+
upload.append($.templates($('#s3ff_done').html()).render(data, { field_prefix: field_prefix }));
|
50
|
+
upload.find('.s3ff_progress').hide();
|
51
|
+
},
|
52
|
+
fail: function(e, data) {
|
53
|
+
section(data.files[0], '.s3ff_progress').html($.templates($('#s3ff_fail').html()).render(data));
|
54
|
+
},
|
55
|
+
progress: function(e, data) {
|
56
|
+
var pct = (parseInt(data.loaded / data.total * 100, 10)) + "%";
|
57
|
+
section(data.files[0], '.s3ff_progress .s3ff_bar').css({width: pct}).text(pct);
|
58
|
+
}
|
59
|
+
};
|
60
|
+
|
61
|
+
var data = that.data('s3ff');
|
62
|
+
if (data) {
|
63
|
+
s3ff_handlers.send.apply(this, [null, data]);
|
64
|
+
s3ff_handlers.done.apply(this, [null, data]);
|
65
|
+
}
|
66
|
+
|
67
|
+
that.S3FileField(s3ff_handlers);
|
68
|
+
};
|
69
|
+
|
70
|
+
var selector = "[data-acl][data-aws-access-key-id]:not(.s3ff_enabled)";
|
71
|
+
$(selector).each(s3ff_init);
|
72
|
+
$(document).on("mouseenter mousedown touchstart", selector, s3ff_init);
|
73
|
+
};
|
74
|
+
|
75
|
+
$(document).on('page:change', ready);
|
76
|
+
});
|
data/lib/s3ff/engine.rb
ADDED
data/lib/s3ff/railtie.rb
ADDED
data/lib/s3ff/version.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
module S3FF
|
2
|
+
module ViewHelper
|
3
|
+
def include_s3ff_templates(map = { _direct_url: 'result.url', _file_name: 'result.filename', _file_size: 'result.filesize', _content_type: 'result.filetype' })
|
4
|
+
<<-EOM
|
5
|
+
<div style="display:none;">
|
6
|
+
<script id="s3ff_label" type="text/x-tmpl">
|
7
|
+
<label class="s3ff_label" style="display:block;"></label>
|
8
|
+
</script>
|
9
|
+
<script id="s3ff_upload" type="text/x-tmpl" data-jsv-tmpl="_0">
|
10
|
+
<div class="s3ff_section" id="upload-{{:unique_id}}" style="margin-top:1em;">
|
11
|
+
<div class="progress s3ff_progress">
|
12
|
+
<div class="progress-bar progress-bar-striped active s3ff_bar" style="width: 0%"></div>
|
13
|
+
</div>
|
14
|
+
</div>
|
15
|
+
</script>
|
16
|
+
<script id="s3ff_fail" type="text/x-tmpl">
|
17
|
+
<div class="progress-bar progress-bar-danger" style="width: 80%">
|
18
|
+
<span class="fa fa-exclamation-triangle"></span>
|
19
|
+
{{if failReason}}
|
20
|
+
{{:failReason}}
|
21
|
+
{{else}}
|
22
|
+
Upload failed!
|
23
|
+
{{/if}}
|
24
|
+
</div>
|
25
|
+
</script>
|
26
|
+
<script id="s3ff_done" type="text/x-tmpl">
|
27
|
+
<button class="close" data-dismiss="alert" style="margin-right:4px;" type="button">
|
28
|
+
<span aria-hidden="true">×</span><span class="sr-only">Close</span>
|
29
|
+
</button>
|
30
|
+
<span class="form-control">
|
31
|
+
<span class="fa fa-file-o"></span>
|
32
|
+
{{:result.filename}}
|
33
|
+
{{if result.filesize}}
|
34
|
+
({{:result.filesize}} bytes)
|
35
|
+
{{/if}}
|
36
|
+
</span>
|
37
|
+
<div style="display:none;">
|
38
|
+
#{map.collect {|k,v| "<input name=\"{{>~field_prefix}}#{k}]\" type=\"hidden\" value=\"{{:#{v}}}\" />" }.join}
|
39
|
+
</div>
|
40
|
+
</script>
|
41
|
+
</div>
|
42
|
+
EOM
|
43
|
+
.html_safe
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/s3ff.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 's3_file_field'
|
2
|
+
require 's3ff/view_helper'
|
3
|
+
require 's3ff/railtie'
|
4
|
+
require 's3ff/engine'
|
5
|
+
|
6
|
+
ActionView::Base.send(:include, S3FF::ViewHelper)
|
7
|
+
|
8
|
+
S3FileField::FormBuilder.class_eval do
|
9
|
+
def s3_file_field_with_s3ff(method, options = {})
|
10
|
+
changes = @object.try(:changes) || {} # { attr => [old_value, new_value], ... }
|
11
|
+
if new_direct_url = changes["#{method}_direct_url"].try(:last)
|
12
|
+
# if *_direct_url_changed? it means we're re-rendering
|
13
|
+
# :. we should prepopulate the s3ff fields to avoid re-uploading
|
14
|
+
options[:data] ||= {}
|
15
|
+
options[:data].merge!({
|
16
|
+
s3ff: {
|
17
|
+
files: [{
|
18
|
+
unique_id: "#{@object_name.parameterize}#{SecureRandom.hex}",
|
19
|
+
}],
|
20
|
+
result: {
|
21
|
+
filename: changes["#{method}_file_name"].try(:last) || File.basename(new_direct_url),
|
22
|
+
filesize: changes["#{method}_file_size"].try(:last),
|
23
|
+
filetype: changes["#{method}_content_type"].try(:last),
|
24
|
+
url: new_direct_url,
|
25
|
+
}
|
26
|
+
}
|
27
|
+
})
|
28
|
+
end
|
29
|
+
s3_file_field_without_s3ff(method, options)
|
30
|
+
end
|
31
|
+
alias_method_chain :s3_file_field, :s3ff
|
32
|
+
end
|
metadata
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: s3ff
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Chew Choon Keat
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-10-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: s3_file_field
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.3.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.3.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.3'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: Direct S3 upload using CORS with s3_file_field + paperclip
|
70
|
+
email:
|
71
|
+
- choonkeat@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- README.md
|
77
|
+
- app/assets/javascripts/s3ff.js
|
78
|
+
- lib/s3ff.rb
|
79
|
+
- lib/s3ff/engine.rb
|
80
|
+
- lib/s3ff/railtie.rb
|
81
|
+
- lib/s3ff/version.rb
|
82
|
+
- lib/s3ff/view_helper.rb
|
83
|
+
homepage: https://github.com/choonkeat/s3ff
|
84
|
+
licenses:
|
85
|
+
- MIT
|
86
|
+
metadata: {}
|
87
|
+
post_install_message:
|
88
|
+
rdoc_options: []
|
89
|
+
require_paths:
|
90
|
+
- lib
|
91
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
requirements: []
|
102
|
+
rubyforge_project:
|
103
|
+
rubygems_version: 2.2.2
|
104
|
+
signing_key:
|
105
|
+
specification_version: 4
|
106
|
+
summary: Direct S3 upload using CORS with s3_file_field + paperclip
|
107
|
+
test_files: []
|