cached_uploads 0.0.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/lib/cached_uploads.rb +239 -0
- metadata +58 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d59707f56c697db5af61bdd98144ce4a18f08e54
|
4
|
+
data.tar.gz: 2e637d81b72f52c6cc4152f2b02ee0dd9284c3f1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f710fa820e7e70e2107dfa355dbcfb7f929d07c3c9b563f2bad350ef756cc39fbc37fc61a112e84e40ca6877867b8118c473aeb0c933790809ef506770cab0d8
|
7
|
+
data.tar.gz: 09362d5bcb469ba58c70369c2ec65daba22c514be4000ad67f42f1b654c48165f7a445133fbbcea76455508dc117b5ded6e2d3ffc04abb8b254991ff7d55ff89
|
@@ -0,0 +1,239 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'digest/md5'
|
3
|
+
require 'active_support/concern'
|
4
|
+
require 'active_support/core_ext/class/attribute'
|
5
|
+
require 'active_support/core_ext/numeric/time'
|
6
|
+
require 'active_support/core_ext/hash/reverse_merge'
|
7
|
+
|
8
|
+
# This module enables you to upload files and persist them in a cache in the event that
|
9
|
+
# the form must be redisplayed (e.g. due to validation errors). This module also saves
|
10
|
+
# the files to their final location when the record is saved.
|
11
|
+
#
|
12
|
+
# The controller is still responsible for the overall workflow. It should use the normal
|
13
|
+
# ActiveRecord API, with one addition: If the submission is invalid, the controller must
|
14
|
+
# call #write_temporary_files.
|
15
|
+
#
|
16
|
+
# The controller (or cron task, or a worker process) should also call
|
17
|
+
# .clean_temporary_files from time to time. An easy option is to clean up any time an
|
18
|
+
# invalid submission is received.
|
19
|
+
module CachedUploads
|
20
|
+
extend ActiveSupport::Concern
|
21
|
+
|
22
|
+
def delete_permanent_file(file_attr)
|
23
|
+
config = self.class.cached_uploads[file_attr.to_sym]
|
24
|
+
prm_path = send config[:prm_path_method]
|
25
|
+
if File.exists?(prm_path)
|
26
|
+
File.delete prm_path
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def write_permanent_file(file_attr)
|
31
|
+
config = self.class.cached_uploads[file_attr.to_sym]
|
32
|
+
uploaded_file = send file_attr
|
33
|
+
|
34
|
+
if uploaded_file.present?
|
35
|
+
# This *won't* execute if we've set the temporary file MD5 attribute instead of the
|
36
|
+
# file attribute. It will only execute if we're uploading the file for the first time.
|
37
|
+
# (Technically, if both the file and the MD5 are present, this would execute. But that
|
38
|
+
# would be an error state.)
|
39
|
+
|
40
|
+
prm_file_path = send config[:prm_path_method]
|
41
|
+
File.open(prm_file_path, 'wb') do |out_file|
|
42
|
+
uploaded_file.rewind
|
43
|
+
out_file.write uploaded_file.read
|
44
|
+
end
|
45
|
+
elsif send(config[:md5_attr]).present?
|
46
|
+
# This executes if we've set the temporary file MD5 attribute instead of the file
|
47
|
+
# attribute. This is invoked when the user has submitted invalid data at least once.
|
48
|
+
# In which case we've saved the uploaded data to a tempfile on the server. Now the
|
49
|
+
# user is resubmitting with correct data. (We know it's correct because
|
50
|
+
# #write_permanent_file is triggered by an after_save callback.)
|
51
|
+
|
52
|
+
tmp_file_path = send config[:tmp_path_method]
|
53
|
+
prm_file_path = send config[:prm_path_method]
|
54
|
+
FileUtils.cp tmp_file_path, prm_file_path
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Writes the temporary file, basing its name on the MD5 hash of the uploaded file.
|
59
|
+
# Raises if the uploaded file is #blank?.
|
60
|
+
def write_temporary_file(file_attr)
|
61
|
+
config = self.class.cached_uploads[file_attr.to_sym]
|
62
|
+
file = send file_attr
|
63
|
+
|
64
|
+
if file.present?
|
65
|
+
# Read the uploaded file, calc its MD5, and write the MD5 instance variable.
|
66
|
+
file.rewind
|
67
|
+
md5 = Digest::MD5.hexdigest(file.read)
|
68
|
+
send "#{config[:md5_attr]}=", md5
|
69
|
+
|
70
|
+
# Write the temporary file, using its MD5 hash to generate the filename.
|
71
|
+
file.rewind
|
72
|
+
File.open(send(config[:tmp_path_method]), 'wb') do |out_file|
|
73
|
+
out_file.write file.read
|
74
|
+
end
|
75
|
+
else
|
76
|
+
raise "Called #write_temporary_file(:#{file_attr}), but ##{file_attr} was not present."
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Saves any configured temporary files. Controllers should call this method when an
|
81
|
+
# invalid submission is received. Does not save a temporary file if the file itself
|
82
|
+
# had a validation error.
|
83
|
+
def write_temporary_files
|
84
|
+
cached_uploads.each_key do |file_attr|
|
85
|
+
if errors[:file].empty? and send(file_attr).present?
|
86
|
+
write_temporary_file file_attr
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
included do
|
92
|
+
class_attribute :cached_uploads
|
93
|
+
end
|
94
|
+
|
95
|
+
module ClassMethods
|
96
|
+
# Cleans out all temporary files older than the given age.
|
97
|
+
# Typically called by the controller.
|
98
|
+
def clean_temporary_files
|
99
|
+
cached_uploads.each do |file_attr, config|
|
100
|
+
folder = send config[:tmp_folder_method]
|
101
|
+
pattern = File.join folder, '*'
|
102
|
+
|
103
|
+
Dir.glob(pattern).each do |path|
|
104
|
+
if File.basename(path) != '.gitignore' and File.mtime(path) < config[:tmp_file_expiration].ago
|
105
|
+
File.delete path
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Reader and writer methods will be defined for the +file_attr+ name.
|
112
|
+
#
|
113
|
+
# CachedUploads looks for class and instance methods defining the permanent file path,
|
114
|
+
# the temporary file path, and the temporary file folder path. If the file attribute
|
115
|
+
# is for example #screenshot, then the path methods will be, respectively,
|
116
|
+
# #screenshot_path, #tmp_screenshot_path, and .tmp_screenshot folder. The names of
|
117
|
+
# those methods may be overridden--see the "Options" documentation below.
|
118
|
+
#
|
119
|
+
# You may define the path methods like any normal instance method. For convenience
|
120
|
+
# and to keep all related code in one place, you may also pass Procs when you call
|
121
|
+
# .has_cached_uploads, and the methods will be defined for you using the Procs. For
|
122
|
+
# example, instead of explicitly defining a #screenshot_path method, you can do this:
|
123
|
+
#
|
124
|
+
# has_cached_upload(:screenshot, {
|
125
|
+
# folder: ->() { File.join Rails.root, 'uploads/screenshots' }
|
126
|
+
# filename: ->(obj) { "#{obj.id}.png" }
|
127
|
+
# })
|
128
|
+
#
|
129
|
+
# CachedUploads will then automatically define #screenshot_path using the given
|
130
|
+
# +folder+ and +filename+ Procs.
|
131
|
+
#
|
132
|
+
# You may also define an instance attribute storing the uploaded file's extension.
|
133
|
+
# If +file_attr+ is #screenshot, then by default the extension attribute is
|
134
|
+
# #screenshot_ext. If the class responds to the extension attribute, then that
|
135
|
+
# attribute will be set automatically when the file attribute's writer is called.
|
136
|
+
# For example, if the file attribute is #screenshot, then calling #screenshot= will
|
137
|
+
# cause #screenshot_ext to be set.
|
138
|
+
#
|
139
|
+
# Options:
|
140
|
+
#
|
141
|
+
# - +folder+: A Proc that returns an absolute path to the permanent files' folder.
|
142
|
+
#
|
143
|
+
# - +filename+: A Proc that accepts an instance of the class and returns the permanent
|
144
|
+
# filename. Do not include the folder path in the returned value.
|
145
|
+
#
|
146
|
+
# - +tmp_folder+: Like +folder+, but for the temporary files.
|
147
|
+
#
|
148
|
+
# - +tmp_filename+: Like +filename+, but for the temporary files.
|
149
|
+
#
|
150
|
+
# - +prm_path_method+: Name of the instance method that returns the path to the
|
151
|
+
# permanent file. Defaults to +"#{file_attr}_path"+.
|
152
|
+
#
|
153
|
+
# - +tmp_path_method+: Name of the instance method that returns the path to the
|
154
|
+
# temporary file. Defaults to +"tmp_#{file_attr}_path"+.
|
155
|
+
#
|
156
|
+
# - +tmp_folder_method+: Name of the instance method that returns the path to the
|
157
|
+
# temporary files' folder. Defaults to +"tmp_#{file_attr}_folder"+.
|
158
|
+
#
|
159
|
+
# - +tmp_file_expiration+: Optional. Length of time temporary files last before being
|
160
|
+
# cleaned out. Defaults to +48.hours+.
|
161
|
+
#
|
162
|
+
# - +ext_attr+: Name of the instance attribute storing the file's extension. Defaults
|
163
|
+
# to +"#{file_attr}_ext"+.
|
164
|
+
#
|
165
|
+
# - +md5_attr+: Name of the instance attribute storing the file's MD5 hash. Defaults
|
166
|
+
# to +"tmp_#{file_attr}_md5"+.
|
167
|
+
def has_cached_upload(file_attr, options = {})
|
168
|
+
# Set default configs.
|
169
|
+
options.reverse_merge!(
|
170
|
+
prm_path_method: "#{file_attr}_path",
|
171
|
+
tmp_path_method: "tmp_#{file_attr}_path",
|
172
|
+
tmp_folder_method: "tmp_#{file_attr}_folder",
|
173
|
+
tmp_file_expiration: 48.hours,
|
174
|
+
ext_attr: "#{file_attr}_ext",
|
175
|
+
md5_attr: "tmp_#{file_attr}_md5"
|
176
|
+
)
|
177
|
+
|
178
|
+
# Initialize the configs hash.
|
179
|
+
self.cached_uploads ||= {}
|
180
|
+
cached_uploads[file_attr.to_sym] = options
|
181
|
+
|
182
|
+
# Define the reader for the file.
|
183
|
+
attr_reader file_attr
|
184
|
+
|
185
|
+
# Define the writer for the file.
|
186
|
+
class_eval %Q(
|
187
|
+
def #{file_attr}=(f)
|
188
|
+
@#{file_attr} = f
|
189
|
+
if respond_to?('#{options[:ext_attr]}=')
|
190
|
+
self.#{options[:ext_attr]} = File.extname(f.original_filename)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
)
|
194
|
+
|
195
|
+
# Define the accessor for the temporary file MD5 string.
|
196
|
+
attr_accessor options[:md5_attr]
|
197
|
+
|
198
|
+
# Define the path methods, if given.
|
199
|
+
if options[:folder] and options[:filename]
|
200
|
+
define_singleton_method "#{file_attr}_folder" do
|
201
|
+
options[:folder].call self
|
202
|
+
end
|
203
|
+
|
204
|
+
define_method "#{file_attr}_filename" do
|
205
|
+
options[:filename].call(self)
|
206
|
+
end
|
207
|
+
|
208
|
+
define_method "#{file_attr}_path" do
|
209
|
+
File.join self.class.send("#{file_attr}_folder"), send("#{file_attr}_filename")
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# Define the temporary path methods, if given.
|
214
|
+
if options[:tmp_folder] and options[:tmp_filename]
|
215
|
+
define_singleton_method "tmp_#{file_attr}_folder" do
|
216
|
+
options[:tmp_folder].call self
|
217
|
+
end
|
218
|
+
|
219
|
+
define_method "tmp_#{file_attr}_filename" do
|
220
|
+
options[:tmp_filename].call(self)
|
221
|
+
end
|
222
|
+
|
223
|
+
define_method "tmp_#{file_attr}_path" do
|
224
|
+
File.join self.class.send("tmp_#{file_attr}_folder"), send("tmp_#{file_attr}_filename")
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
# Register the save callback.
|
229
|
+
after_save do |obj|
|
230
|
+
obj.write_permanent_file file_attr
|
231
|
+
end
|
232
|
+
|
233
|
+
# Register the delete callback.
|
234
|
+
after_destroy do |obj|
|
235
|
+
obj.delete_permanent_file file_attr
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
metadata
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cached_uploads
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jarrett Colby
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-04-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: turn
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
description: Caches file uploads when validation fails.
|
28
|
+
email: jarrett@madebyhq.com
|
29
|
+
executables: []
|
30
|
+
extensions: []
|
31
|
+
extra_rdoc_files: []
|
32
|
+
files:
|
33
|
+
- lib/cached_uploads.rb
|
34
|
+
homepage: https://github.com/jarrett/cached_uploads
|
35
|
+
licenses:
|
36
|
+
- MIT
|
37
|
+
metadata: {}
|
38
|
+
post_install_message:
|
39
|
+
rdoc_options: []
|
40
|
+
require_paths:
|
41
|
+
- lib
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '0'
|
52
|
+
requirements: []
|
53
|
+
rubyforge_project:
|
54
|
+
rubygems_version: 2.2.2
|
55
|
+
signing_key:
|
56
|
+
specification_version: 4
|
57
|
+
summary: Validation-friendly uploads for Rails
|
58
|
+
test_files: []
|