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 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
+ });
@@ -0,0 +1,4 @@
1
+ module S3FF
2
+ class Engine < ::Rails::Engine
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module S3FF
2
+ class Railtie < Rails::Railtie
3
+ initializer 'railtie.configure_rails_initialization' do |_app|
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,3 @@
1
+ module S3FF
2
+ VERSION = '0.9.0'
3
+ end
@@ -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">&times;</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: []