file_upload 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (145) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +34 -0
  4. data/app/assets/javascripts/file_upload/application.js +13 -0
  5. data/app/assets/javascripts/file_upload.js.erb +148 -0
  6. data/app/assets/stylesheets/file_upload/application.css +13 -0
  7. data/app/assets/stylesheets/file_upload.css +3 -0
  8. data/app/controllers/file_upload/application_controller.rb +4 -0
  9. data/app/controllers/file_upload/redis_files_controller.rb +54 -0
  10. data/app/helpers/file_upload/application_helper.rb +4 -0
  11. data/app/helpers/file_upload/tag_helper.rb +25 -0
  12. data/app/models/file_upload/builder_mixin.rb +13 -0
  13. data/app/models/file_upload/redis_file.rb +159 -0
  14. data/app/models/file_upload/redis_file_readable.rb +21 -0
  15. data/app/models/file_upload/temp_file.rb +17 -0
  16. data/app/views/file_upload/redis_files/index.html.erb +1 -0
  17. data/app/views/file_upload/redis_files/new.html.erb +9 -0
  18. data/app/views/file_upload/redis_files/show.html.erb +15 -0
  19. data/app/views/file_upload/tags/_multiple.html.erb +44 -0
  20. data/app/views/file_upload/tags/_single.html.erb +46 -0
  21. data/app/views/layouts/file_upload/application.html.erb +14 -0
  22. data/config/routes.rb +6 -0
  23. data/lib/file_upload/engine.rb +40 -0
  24. data/lib/file_upload/version.rb +3 -0
  25. data/lib/file_upload.rb +7 -0
  26. data/lib/tasks/file_upload_tasks.rake +4 -0
  27. data/test/dummy/README.rdoc +28 -0
  28. data/test/dummy/Rakefile +6 -0
  29. data/test/dummy/app/assets/javascripts/application.js +15 -0
  30. data/test/dummy/app/assets/javascripts/jquery-1.10.2.min.js +6 -0
  31. data/test/dummy/app/assets/javascripts/jquery-ui.js +15003 -0
  32. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  33. data/test/dummy/app/controllers/application_controller.rb +5 -0
  34. data/test/dummy/app/controllers/db_files_controller.rb +8 -0
  35. data/test/dummy/app/controllers/emails_controller.rb +43 -0
  36. data/test/dummy/app/controllers/users_controller.rb +42 -0
  37. data/test/dummy/app/helpers/application_helper.rb +2 -0
  38. data/test/dummy/app/models/db_file.rb +13 -0
  39. data/test/dummy/app/models/email.rb +10 -0
  40. data/test/dummy/app/models/user.rb +9 -0
  41. data/test/dummy/app/views/db_files/_db_file.html.erb +1 -0
  42. data/test/dummy/app/views/emails/_email.html.erb +3 -0
  43. data/test/dummy/app/views/emails/_form.html.erb +24 -0
  44. data/test/dummy/app/views/emails/edit.html.erb +3 -0
  45. data/test/dummy/app/views/emails/index.html.erb +5 -0
  46. data/test/dummy/app/views/emails/new.html.erb +3 -0
  47. data/test/dummy/app/views/emails/show.html.erb +14 -0
  48. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  49. data/test/dummy/app/views/users/_form.html.erb +20 -0
  50. data/test/dummy/app/views/users/_user.html.erb +1 -0
  51. data/test/dummy/app/views/users/edit.html.erb +3 -0
  52. data/test/dummy/app/views/users/index.html.erb +5 -0
  53. data/test/dummy/app/views/users/new.html.erb +3 -0
  54. data/test/dummy/app/views/users/show.html.erb +8 -0
  55. data/test/dummy/bin/bundle +3 -0
  56. data/test/dummy/bin/rails +4 -0
  57. data/test/dummy/bin/rake +4 -0
  58. data/test/dummy/config/application.rb +28 -0
  59. data/test/dummy/config/boot.rb +5 -0
  60. data/test/dummy/config/database.yml +31 -0
  61. data/test/dummy/config/environment.rb +5 -0
  62. data/test/dummy/config/environments/development.rb +29 -0
  63. data/test/dummy/config/environments/production.rb +80 -0
  64. data/test/dummy/config/environments/test.rb +36 -0
  65. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  66. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  67. data/test/dummy/config/initializers/inflections.rb +16 -0
  68. data/test/dummy/config/initializers/mime_types.rb +5 -0
  69. data/test/dummy/config/initializers/secret_token.rb +12 -0
  70. data/test/dummy/config/initializers/session_store.rb +3 -0
  71. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  72. data/test/dummy/config/locales/en.yml +23 -0
  73. data/test/dummy/config/routes.rb +7 -0
  74. data/test/dummy/config.ru +4 -0
  75. data/test/dummy/db/development.sqlite3 +0 -0
  76. data/test/dummy/db/migrate/20130820025256_add_users.rb +21 -0
  77. data/test/dummy/db/schema.rb +34 -0
  78. data/test/dummy/log/development.log +15541 -0
  79. data/test/dummy/log/test.log +26004 -0
  80. data/test/dummy/public/404.html +58 -0
  81. data/test/dummy/public/422.html +58 -0
  82. data/test/dummy/public/500.html +57 -0
  83. data/test/dummy/public/favicon.ico +0 -0
  84. data/test/dummy/tmp/cache/assets/development/sprockets/1027254ad9f9317017cee3f9f844a862 +0 -0
  85. data/test/dummy/tmp/cache/assets/development/sprockets/13fe41fee1fe35b49d145bcc06610705 +0 -0
  86. data/test/dummy/tmp/cache/assets/development/sprockets/21e0ff88cf13ca60a86a9b629cdca847 +0 -0
  87. data/test/dummy/tmp/cache/assets/development/sprockets/24cf35cebd10e0755a5585f9e486aa35 +0 -0
  88. data/test/dummy/tmp/cache/assets/development/sprockets/27413a86db5db5977d5dd1d87c724907 +0 -0
  89. data/test/dummy/tmp/cache/assets/development/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
  90. data/test/dummy/tmp/cache/assets/development/sprockets/2f5c6d9c2c3107f94ad59710d3b31f75 +0 -0
  91. data/test/dummy/tmp/cache/assets/development/sprockets/3131541e8089635bc8595e2da83d1d81 +0 -0
  92. data/test/dummy/tmp/cache/assets/development/sprockets/32f1d616324cf52e9876bb83b9b0841a +0 -0
  93. data/test/dummy/tmp/cache/assets/development/sprockets/33474263b3841cd98ef63cde45dc85b2 +0 -0
  94. data/test/dummy/tmp/cache/assets/development/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
  95. data/test/dummy/tmp/cache/assets/development/sprockets/3fa0d13a4820d4aa56f86ed4af4b88fc +0 -0
  96. data/test/dummy/tmp/cache/assets/development/sprockets/4e07f6f4475a0dca9066eec990db088a +0 -0
  97. data/test/dummy/tmp/cache/assets/development/sprockets/5512d0b5788ce193fd26bfadccd21304 +0 -0
  98. data/test/dummy/tmp/cache/assets/development/sprockets/5d5ab91fa7d1eee7502e703747d4e933 +0 -0
  99. data/test/dummy/tmp/cache/assets/development/sprockets/6b7e97595c37161f619adfd2b7724ea2 +0 -0
  100. data/test/dummy/tmp/cache/assets/development/sprockets/87fa3cc4e2e49762d146f3d1709b3fb0 +0 -0
  101. data/test/dummy/tmp/cache/assets/development/sprockets/8806d06f37e08b22f9546e50ab98d671 +0 -0
  102. data/test/dummy/tmp/cache/assets/development/sprockets/8cdad172c92eaf76e1b71c84f58a192e +0 -0
  103. data/test/dummy/tmp/cache/assets/development/sprockets/91e53bdf30a2b8e78bf99ca794addf70 +0 -0
  104. data/test/dummy/tmp/cache/assets/development/sprockets/96e2b2964d1d31d997a1af35ad0afed2 +0 -0
  105. data/test/dummy/tmp/cache/assets/development/sprockets/ab5c07d7e81fb5d5cf7460715f22bef6 +0 -0
  106. data/test/dummy/tmp/cache/assets/development/sprockets/b94837629e2fb2ef40ea66348764a9a2 +0 -0
  107. data/test/dummy/tmp/cache/assets/development/sprockets/c360bf7b66d29ebdf71b5814574b5fed +0 -0
  108. data/test/dummy/tmp/cache/assets/development/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
  109. data/test/dummy/tmp/cache/assets/development/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
  110. data/test/dummy/tmp/cache/assets/development/sprockets/e046b0166566aeeb2da51f2bde543364 +0 -0
  111. data/test/dummy/tmp/cache/assets/development/sprockets/ec2d15616d5d09eb1693b761c72948ce +0 -0
  112. data/test/dummy/tmp/cache/assets/development/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
  113. data/test/dummy/tmp/cache/assets/development/sprockets/fc0327db3eb7c687c31f2acf94871f86 +0 -0
  114. data/test/dummy/tmp/cache/assets/test/sprockets/1027254ad9f9317017cee3f9f844a862 +0 -0
  115. data/test/dummy/tmp/cache/assets/test/sprockets/13fe41fee1fe35b49d145bcc06610705 +0 -0
  116. data/test/dummy/tmp/cache/assets/test/sprockets/21e0ff88cf13ca60a86a9b629cdca847 +0 -0
  117. data/test/dummy/tmp/cache/assets/test/sprockets/24cf35cebd10e0755a5585f9e486aa35 +0 -0
  118. data/test/dummy/tmp/cache/assets/test/sprockets/27413a86db5db5977d5dd1d87c724907 +0 -0
  119. data/test/dummy/tmp/cache/assets/test/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
  120. data/test/dummy/tmp/cache/assets/test/sprockets/32f1d616324cf52e9876bb83b9b0841a +0 -0
  121. data/test/dummy/tmp/cache/assets/test/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
  122. data/test/dummy/tmp/cache/assets/test/sprockets/3fa0d13a4820d4aa56f86ed4af4b88fc +0 -0
  123. data/test/dummy/tmp/cache/assets/test/sprockets/5d5ab91fa7d1eee7502e703747d4e933 +0 -0
  124. data/test/dummy/tmp/cache/assets/test/sprockets/6b7e97595c37161f619adfd2b7724ea2 +0 -0
  125. data/test/dummy/tmp/cache/assets/test/sprockets/87fa3cc4e2e49762d146f3d1709b3fb0 +0 -0
  126. data/test/dummy/tmp/cache/assets/test/sprockets/8806d06f37e08b22f9546e50ab98d671 +0 -0
  127. data/test/dummy/tmp/cache/assets/test/sprockets/ab5c07d7e81fb5d5cf7460715f22bef6 +0 -0
  128. data/test/dummy/tmp/cache/assets/test/sprockets/b94837629e2fb2ef40ea66348764a9a2 +0 -0
  129. data/test/dummy/tmp/cache/assets/test/sprockets/c360bf7b66d29ebdf71b5814574b5fed +0 -0
  130. data/test/dummy/tmp/cache/assets/test/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
  131. data/test/dummy/tmp/cache/assets/test/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
  132. data/test/dummy/tmp/cache/assets/test/sprockets/e046b0166566aeeb2da51f2bde543364 +0 -0
  133. data/test/dummy/tmp/cache/assets/test/sprockets/ec2d15616d5d09eb1693b761c72948ce +0 -0
  134. data/test/dummy/tmp/cache/assets/test/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
  135. data/test/dummy/tmp/cache/assets/test/sprockets/fc0327db3eb7c687c31f2acf94871f86 +0 -0
  136. data/test/fixtures/avatar.jpeg +0 -0
  137. data/test/fixtures/test_upload.txt +1 -0
  138. data/test/integration/multiple_upload_test.rb +131 -0
  139. data/test/integration/single_upload_test.rb +157 -0
  140. data/test/test_helper.rb +60 -0
  141. data/test/unit/redis_file_readable_test.rb +30 -0
  142. data/test/unit/redis_file_test.rb +30 -0
  143. data/vendor/assets/javascripts/jquery.fileupload.js +1113 -0
  144. data/vendor/assets/javascripts/load-image.js +232 -0
  145. metadata +401 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 411251a54632d2f6b1e5947507780ec4e303e438
4
+ data.tar.gz: 6cba04bb20df8f7125541cb1727ca43970c4a8df
5
+ SHA512:
6
+ metadata.gz: fa9a5651d4becd0b90d1c583fd883c9bb8e3d79221f0915157499c02a83be1266d21176c1d1b87e6676c98fe6922b171e13ed456fdf3365c19aea643ad6c2491
7
+ data.tar.gz: 1634bafd7a0c24478d7481ebfc873304a05283c91e79baad50fe0dddf77e02073fc6a9f07063d6ec07147a32f7c92397d0a2c60a123bbb810fa8cc60be142a74
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2013 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,34 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'FileUpload'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+
21
+
22
+ Bundler::GemHelper.install_tasks
23
+
24
+ require 'rake/testtask'
25
+
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << 'lib'
28
+ t.libs << 'test'
29
+ t.pattern = 'test/**/*_test.rb'
30
+ t.verbose = false
31
+ end
32
+
33
+
34
+ task default: :test
@@ -0,0 +1,13 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file.
9
+ //
10
+ // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require_tree .
@@ -0,0 +1,148 @@
1
+ //= require load-image
2
+ //= require jquery.fileupload
3
+
4
+ $(function() {
5
+ // prevent default browser drapdrop behavior
6
+ $(document).bind('drop dragover', function (e) {
7
+ e.preventDefault();
8
+ });
9
+
10
+ function setProgress(item, percent) {
11
+ item.attr('aria-valuenow', percent)
12
+ .find(".bar").css("width", percent + "%");
13
+ }
14
+
15
+ function setupFileField(inp) {
16
+ var input = $(inp),
17
+ form = input.parents("form"),
18
+ attributeName = input.attr("data-attr-name"),
19
+ modelName = input.attr("data-model-name"),
20
+ container = form.find("." + attributeName),
21
+ list = container.find("ul"),
22
+ multiple = input.attr("multiple"),
23
+ preview = form.find(input.attr("data-preview") || ".preview"),
24
+ add = form.find(".add"),
25
+ inputPrefix = modelName + "[" + attributeName + "_attributes]",
26
+ inputName,
27
+ autosubmit = input.attr("data-autosubmit"),
28
+ url = input.attr("data-file-upload");
29
+
30
+ if(multiple) {
31
+ inputName = inputPrefix + "[REPLACE_ID][key]";
32
+ } else {
33
+ inputName = inputPrefix + "[key]";
34
+ }
35
+
36
+ input.fileupload({
37
+ url: url,
38
+ dataType: 'json',
39
+ maxFileSize: 5000000,
40
+ formData: function(form) { return []; },
41
+ type: 'POST',
42
+ // this is called after the files have been uploaded
43
+ done: function(e, data) {
44
+ // replace the input with a new one
45
+ replaceInput(this);
46
+
47
+ if(preview.length > 0) {
48
+ window.loadImage(data.files[0], function(img) {
49
+ preview.html(img);
50
+ });
51
+ }
52
+ // add field with file id
53
+ $.each(data.result.files, function(i, file) {
54
+ // load preview
55
+ if(multiple) {
56
+ // add checkbox field
57
+ var item = data.files[i].item;
58
+ item.find(".progress").replaceWith(
59
+ $('<input type="checkbox" checked="checked"/>')
60
+ .attr('name', inputName.replace("REPLACE_ID", Math.floor(Math.random() * 1000000)))
61
+ .val(file.id)
62
+ );
63
+ } else {
64
+ // add key hidden field
65
+ container.find(".inputs .progress").replaceWith('<input type="hidden" name="' + inputName + '" value="' + file.id + '"/><input type="checkbox" checked="checked" name="' + inputPrefix + "[_destroy]" + '" value="0"/>');
66
+ }
67
+ });
68
+
69
+ if(autosubmit) {
70
+ form.submit();
71
+ }
72
+ },
73
+ fail: function(e, data){
74
+ replaceInput(this);
75
+
76
+ if (data.jqXHR.status === 413) {
77
+ message = 'The size limit is 10MB.';
78
+ } else {
79
+ message = 'Upload failed, please try again.';
80
+ }
81
+ container.find('.error').html(message);
82
+
83
+ // data.files is 1-length array with the file of the ajax event
84
+ $.each(data.files, function(i, file) {
85
+ var item = data.files[i].item;
86
+ item.remove();
87
+ });
88
+ },
89
+ // this is called after selecting a file
90
+ add: function(e, data) {
91
+ // clear errors
92
+ container.find('.error').html('');
93
+
94
+ $.each(data.files, function(i, file) {
95
+ if(multiple) {
96
+ file.item = $("<li/>").append('<label class="checkbox"></label>');
97
+ file.item.find('label').text(file.name + " (" + (Math.round(file.size/10.24)/100) + "kB)" )
98
+ .prepend($("<div class='progress'><div class='bar'></div></div>"));
99
+ file.item.appendTo(list);
100
+ } else {
101
+ var label = $("<label class='checkbox'></label").text(file.name + " (" + (Math.round(file.size/10.24)/100) + "kB)").prepend("<div class='progress'><div class='bar'></div></div>");
102
+ container.find(".inputs").html(label);
103
+ }
104
+ });
105
+
106
+ // do the ajax file upload
107
+ data.submit();
108
+ }
109
+ });
110
+
111
+ // file type limiting option
112
+ if(input.attr("data-accept")) {
113
+ input.fileupload("option", "acceptFileTypes", new RegExp(input.attr("data-accept"), "i"));
114
+ }
115
+
116
+ if(!add.data("input")) {
117
+ add.click(function(evt) {
118
+ $(this).data("input").click();
119
+ evt.preventDefault();
120
+ return false;
121
+ });
122
+ }
123
+ add.data("input", input);
124
+
125
+ input.trigger("upload:setupcomplete");
126
+ }
127
+
128
+ function replaceInput(inp) {
129
+ var that = $(inp),
130
+ clone = that.clone();
131
+ that.replaceWith(clone);
132
+ clone.trigger("upload:enable");
133
+ }
134
+
135
+ function handleSubmit(evt) {
136
+ evt.preventDefault();
137
+ return false;
138
+ }
139
+
140
+ function setupEach(evt) {
141
+ $(this).each(function(i, el) {
142
+ setupFileField(el);
143
+ });
144
+ }
145
+
146
+ $('body').on("upload:enable", ":file[data-file-upload]", setupEach);
147
+ $(":file[data-file-upload]").trigger("upload:enable");
148
+ });
@@ -0,0 +1,13 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the top of the
9
+ * compiled file, but it's generally better to create a new file per style scope.
10
+ *
11
+ *= require_self
12
+ *= require_tree .
13
+ */
@@ -0,0 +1,3 @@
1
+ .file_upload .hide {
2
+ display: none;
3
+ }
@@ -0,0 +1,4 @@
1
+ module FileUpload
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,54 @@
1
+ class FileUpload::RedisFilesController < ApplicationController
2
+
3
+ def index
4
+ end
5
+
6
+ def new
7
+ @redis_file = FileUpload::RedisFile.new
8
+ end
9
+
10
+ def create
11
+ @redis_file = FileUpload::RedisFile.new(params[:redis_file])
12
+ if @redis_file.save
13
+ redirect_to @redis_file
14
+ else
15
+ render :new
16
+ end
17
+ end
18
+
19
+ def show
20
+ @redis_file = FileUpload::RedisFile.find(params[:id])
21
+ end
22
+
23
+ def preview
24
+ @redis_file = FileUpload::RedisFile.find(params[:id])
25
+ send_data @redis_file.data, type: @redis_file.type, disposition: "inline"
26
+ end
27
+
28
+ def destroy
29
+ @redis_file = FileUpload::RedisFile.find(params[:id])
30
+ @redis_file.destroy
31
+ end
32
+
33
+ def bulk
34
+ unless params[:redis_files]
35
+ render status: 400, json: { files: [{error: 'No files uploaded.'}] }
36
+ return
37
+ end
38
+
39
+ files = Array(params.require(:redis_files)).map do |file|
40
+ f = FileUpload::RedisFile.new({
41
+ file_io: file[:data]
42
+ })
43
+ f.save
44
+ f
45
+ end
46
+ respond_to do |format|
47
+ format.js {
48
+ render json: {
49
+ files: files.map{|file| file.to_json}
50
+ }
51
+ }
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,4 @@
1
+ module FileUpload
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,25 @@
1
+ module FileUpload
2
+ module TagHelper
3
+ def multiple_file_upload_field(model_name, attribute_name, value, options = {}, &block)
4
+ render(layout: "file_upload/tags/multiple", locals: {
5
+ model_name: model_name,
6
+ attribute_name: attribute_name,
7
+ value: value,
8
+ options: options
9
+ }, &block)
10
+ end
11
+
12
+ def single_file_upload_field(model_name, attribute_name, value, options = {}, &block)
13
+ render(layout: "file_upload/tags/single", locals: {
14
+ model_name: model_name,
15
+ attribute_name: attribute_name,
16
+ value: value,
17
+ options: options
18
+ }, &block)
19
+ end
20
+
21
+ def file_size(file)
22
+ number_with_precision(file.size / 1024.0, precision: 2)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,13 @@
1
+ module FileUpload
2
+ module BuilderMixin
3
+
4
+ def multiple_file_upload(field, options = {}, &block)
5
+ @template.multiple_file_upload_field(@object_name, field, @object.send(field).to_a, options, &block)
6
+ end
7
+
8
+ def single_file_upload(field, options = {}, &block)
9
+ @template.single_file_upload_field(@object_name, field, @object.send(field), options, &block)
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,159 @@
1
+ module FileUpload
2
+ class RedisFile
3
+ extend ActiveModel::Naming
4
+
5
+ # 1 hour
6
+ FILE_EXPIRY = 60 * 60
7
+
8
+ attr_accessor :file_io
9
+ attr_writer :key, :name, :id
10
+
11
+ def initialize(attrs = {})
12
+ attrs.each do |k, v|
13
+ send("#{k}=", v) if respond_to?("#{k}=")
14
+ end
15
+ end
16
+
17
+ class << self
18
+
19
+ def find_by_id(uuid)
20
+ key = base_key(uuid)
21
+ name, type, size = redis.mget(name_key(key), type_key(key), size_key(key))
22
+ if name && type && size
23
+ new({
24
+ id: uuid,
25
+ key: key,
26
+ name: name,
27
+ type: type,
28
+ size: size
29
+ })
30
+ else
31
+ nil
32
+ end
33
+ end
34
+
35
+ def find(uuid)
36
+ find_by_id(uuid) || raise(ActiveRecord::RecordNotFound)
37
+ end
38
+
39
+ def name_key(base_key)
40
+ "#{base_key}:name"
41
+ end
42
+
43
+ def data_key(base_key)
44
+ "#{base_key}:data"
45
+ end
46
+
47
+ def type_key(base_key)
48
+ "#{base_key}:type"
49
+ end
50
+
51
+ def size_key(base_key)
52
+ "#{base_key}:size"
53
+ end
54
+
55
+ def base_key(uuid)
56
+ "redis_file:#{uuid}"
57
+ end
58
+ end
59
+
60
+ def key
61
+ @key ||= self.class.base_key(id)
62
+ end
63
+
64
+ def id
65
+ @id ||= SecureRandom.random_number(1000000000)
66
+ end
67
+
68
+ def data_key
69
+ self.class.data_key(key)
70
+ end
71
+
72
+ def name_key
73
+ self.class.name_key(key)
74
+ end
75
+
76
+ def type_key
77
+ self.class.type_key(key)
78
+ end
79
+
80
+ def size_key
81
+ self.class.size_key(key)
82
+ end
83
+
84
+ def name
85
+ @name ||= begin
86
+ if file_io
87
+ file_io.original_filename
88
+ else
89
+ nil
90
+ end
91
+ end
92
+ end
93
+
94
+ def data
95
+ @data ||= begin
96
+ if file_io
97
+ file_io.read
98
+ elsif key
99
+ redis.get(data_key)
100
+ end
101
+ end
102
+ end
103
+
104
+ def data=(data)
105
+ @data = data
106
+ end
107
+
108
+ def size
109
+ @size ||= data.size
110
+ end
111
+
112
+ def type
113
+ @type ||= FileMagic.new(FileMagic::MAGIC_MIME_TYPE).buffer(data)
114
+ end
115
+
116
+ def save
117
+ redis.setex(data_key, FILE_EXPIRY, data)
118
+ redis.setex(name_key, FILE_EXPIRY, name)
119
+ redis.setex(type_key, FILE_EXPIRY, type)
120
+ redis.setex(size_key, FILE_EXPIRY, size)
121
+ end
122
+
123
+ def destroy
124
+ redis.del(data_key, name_key, type_key, size_key)
125
+ end
126
+
127
+ def to_json
128
+ {
129
+ size: size,
130
+ name: name,
131
+ type: type,
132
+ id: id,
133
+ key: key
134
+ }
135
+ end
136
+
137
+ def to_key
138
+ [key]
139
+ end
140
+
141
+ def to_param
142
+ id.to_s
143
+ end
144
+
145
+ def persisted?
146
+ redis.exists(data_key)
147
+ end
148
+
149
+ protected
150
+
151
+ def redis
152
+ self.class.redis
153
+ end
154
+
155
+ def self.redis
156
+ @redis ||= FileUpload::Engine.config.redis
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,21 @@
1
+ module FileUpload
2
+ module RedisFileReadable
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ attr_reader :key
7
+ attr_accessor :copy_from
8
+ end
9
+
10
+ def key=(key)
11
+ @key = key
12
+ if redis_file = RedisFile.find(key)
13
+ self.data = redis_file.data
14
+ self.name = redis_file.name
15
+ self.content_type = redis_file.type
16
+ self.size = redis_file.size
17
+ end
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,17 @@
1
+ module FileUpload
2
+ class TempFile
3
+ attr_accessor :name, :content_type, :data, :size
4
+
5
+ include FileUpload::RedisFileReadable
6
+
7
+ def initialize(attributes = {})
8
+ attributes.each do |k, v|
9
+ self.send("#{k}=", v) if respond_to?("#{k}=")
10
+ end
11
+ end
12
+
13
+ def persisted?
14
+ false
15
+ end
16
+ end
17
+ end
@@ -0,0 +1 @@
1
+ <h1>Redis Files</h1>
@@ -0,0 +1,9 @@
1
+ <h1>New File</h1>
2
+
3
+ <%= form_for(@redis_file, multipart: true) do |form| %>
4
+
5
+ <%= form.file_field :file_io %>
6
+
7
+ <%= form.submit class: "btn btn-primary" %>
8
+
9
+ <% end %>
@@ -0,0 +1,15 @@
1
+ <h1>Redis File</h1>
2
+
3
+ <dl>
4
+ <dt>Key:</dt>
5
+ <dd><%= @redis_file.key %></dd>
6
+
7
+ <dt>Name:</dt>
8
+ <dd><%= @redis_file.name %></dd>
9
+
10
+ <dt>Size:</dt>
11
+ <dd><%= @redis_file.size %></dd>
12
+
13
+ <dt>Type:</dt>
14
+ <dd><%= @redis_file.type %></dd>
15
+ </dl>
@@ -0,0 +1,44 @@
1
+ <%
2
+ # model_name
3
+ # attribute_name
4
+ # value
5
+ # options
6
+ preview = options.delete(:preview) || false
7
+ hide_files = options.delete(:hide_files) || false
8
+ prefix = "#{model_name}[#{attribute_name}_attributes]"
9
+ file_attrs = {
10
+ id: "#{model_name}_#{attribute_name}_file",
11
+ data: {
12
+ :"model-name" => model_name,
13
+ :"attr-name" => attribute_name,
14
+ :"file-upload" => file_upload.bulk_redis_files_path
15
+ },
16
+ :multiple => true
17
+ }.tap do |a|
18
+ a[:"data-autosubmit"] = true if options.delete(:autosubmit)
19
+ end
20
+ -%>
21
+ <div class="file_upload <%= attribute_name %>">
22
+ <ul class="unstyled<%= ' hide' if hide_files %>">
23
+ <% Array(value).each do |file| -%>
24
+ <li><label>
25
+ <% if file.persisted? %>
26
+ <%= hidden_field_tag("#{prefix}[#{file.id}][id]", file.id) %>
27
+ <%= hidden_field_tag("#{prefix}[#{file.id}][_destroy]", 1) %>
28
+ <%= check_box_tag("#{prefix}[#{file.id}][_destroy]", 0, checked: true) %>
29
+ <% elsif file.key %>
30
+ <%= check_box_tag("#{prefix}[#{SecureRandom.random_number(1000000000)}][key]", file.key, checked: true) %>
31
+ <% elsif file.copy_from %>
32
+ <%= check_box_tag("#{prefix}[#{SecureRandom.random_number(1000000000)}][copy_from]", file.copy_from, checked: true) %>
33
+ <% end %>
34
+ <%= file.name %> (<%= file_size file %>)kB
35
+ </label></li>
36
+ <% end -%>
37
+ </ul>
38
+ <% if preview %><p class="preview"><%= image_tag(value.url) if value && value.persisted? %></p><% end %>
39
+ <div class="errors"></div>
40
+ <div class="hide">
41
+ <%= file_field_tag "redis_files[][data]", file_attrs %>
42
+ </div>
43
+ <%= yield %>
44
+ </div>
@@ -0,0 +1,46 @@
1
+ <%
2
+ # model_name
3
+ # attribute_name
4
+ # value
5
+ # options
6
+ preview = options.delete(:preview) || false
7
+ hide_files = options.delete(:hide_files) || false
8
+ prefix = "#{model_name}[#{attribute_name}_attributes]"
9
+ file_attrs = {
10
+ id: "#{model_name}_#{attribute_name}_file",
11
+ data: {
12
+ :"model-name" => model_name,
13
+ :"attr-name" => attribute_name,
14
+ :"file-upload" => file_upload.bulk_redis_files_path
15
+ }
16
+ }.tap do |a|
17
+ a[:"data-autosubmit"] = true if options.delete(:autosubmit)
18
+ end
19
+ -%>
20
+ <div class="file_upload <%= attribute_name %>">
21
+ <% if value && value.persisted? %>
22
+ <%= hidden_field_tag("#{prefix}[id]", value.id) %>
23
+ <% end %>
24
+ <%= hidden_field_tag("#{prefix}[_destroy]", 1) %>
25
+ <div class="inputs <%= 'hide' if hide_files %>">
26
+ <% if value %>
27
+ <label>
28
+ <% if value.persisted? %>
29
+ <%= check_box_tag("#{prefix}[_destroy]", 0, checked: true) %>
30
+ <% elsif value.key %>
31
+ <%= hidden_field_tag("#{prefix}[key]", value.key) %>
32
+ <%= check_box_tag("#{prefix}[_destroy]", 0, checked: true) %>
33
+ <% elsif value.copy_from %>
34
+ <%= check_box_tag("#{prefix}][copy_from]", file.copy_from, checked: true) %>
35
+ <% end %>
36
+ <%= value.name %> (<%= file_size value %>)kB
37
+ </label>
38
+ <% end %>
39
+ </div>
40
+ <% if preview %><p class="preview"><%= image_tag(value.url) if value && value.url %></p><% end %>
41
+ <div class="errors"></div>
42
+ <div class="hide">
43
+ <%= file_field_tag "redis_files[][data]", file_attrs %>
44
+ </div>
45
+ <%= yield %>
46
+ </div>
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>FileUpload</title>
5
+ <%= stylesheet_link_tag "file_upload/application", media: "all" %>
6
+ <%= javascript_include_tag "file_upload/application" %>
7
+ <%= csrf_meta_tags %>
8
+ </head>
9
+ <body>
10
+
11
+ <%= yield %>
12
+
13
+ </body>
14
+ </html>