echo_uploads 0.0.2 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b33b3e17496be452917ef4e82609a8fb39348ad4
4
- data.tar.gz: 24efa6d8430db50e74d7079cf927ea2216a52917
3
+ metadata.gz: 9302fa84aaf621556620c40904c1c8ead227b53d
4
+ data.tar.gz: 8ffbf38511536c4fee0a341c19f8a30424d3f54f
5
5
  SHA512:
6
- metadata.gz: d29afadb8644e6681922d70c5ebf9799cccff6cf1cd438e5596694a1304474e30189b4dd666b81a544e9890b58b9354e6a1bec1d4669a324fc8709e6c56d14f7
7
- data.tar.gz: 10f158f2e2d911ab0f54490b7d8f216b720d8316692601c68dc1e5acfc017f5b89c2cbbd1a1716838cf962a4e9e096e38aa3e292526e35358c3561dc2b89eb29
6
+ metadata.gz: b53ba87f3dd560c8b163e2a8aba350f520eb982675a7c8629977816e473a80ea1b1d321dabc1716dade423ea20abaae833265f03b72f90be8966bc5a484ad2cc
7
+ data.tar.gz: 6110cf77bc31bc2906bb89c24fd493792ce84926ec44dc966f989cee08019a541f821ba6f937d12de731a12e2c580335a7d5af6e33728986603225e62b662b56
@@ -9,11 +9,22 @@ module EchoUploads
9
9
 
10
10
  before_destroy :delete_file_conditionally
11
11
 
12
- def compute_mime!
13
- type = MIME::Types.type_for(original_filename).first
12
+ attr_accessor :file
13
+
14
+ def compute_mime!(options)
15
+ if file and file.is_a?(::EchoUploads::MappedFile)
16
+ name = file.mapped_filename
17
+ else
18
+ name = original_filename
19
+ end
20
+ type = MIME::Types.type_for(name).first
14
21
  self.mime_type = type ? type.content_type : 'application/octet-stream'
15
22
  end
16
23
 
24
+ def compute_key!(file, options)
25
+ self.key = options[:key].call file
26
+ end
27
+
17
28
  # Returns a proc that takes as its only argument an ActionDispatch::UploadedFile
18
29
  # and returns a key string.
19
30
  def self.default_key_proc
@@ -39,30 +50,47 @@ module EchoUploads
39
50
  original_basename + original_extension
40
51
  end
41
52
 
42
- # Pass in an attribute name, an ActionDispatch::UploadedFile, and an options hash.
43
- def persist!(attr, file, mapped_file, options)
44
- # If .echo_uploads was called with the :map option, we need to use the mapped file.
45
- file_to_write = mapped_file || file
53
+ def path
54
+ storage.path key
55
+ end
56
+
57
+ # Pass in an attribute name, an ActionDispatch::Http::UploadedFile, and an options hash.
58
+ # Must set #file attribute first.
59
+ def persist!(attr, options)
60
+ unless(
61
+ file.is_a?(ActionDispatch::Http::UploadedFile) or
62
+ file.is_a?(Rack::Test::UploadedFile)
63
+ )
64
+ raise(
65
+ "Expected #file to be a ActionDispatch::Http::UploadedFile "+
66
+ "or Rack::Test::UploadedFile, but was #{file.inspect}"
67
+ )
68
+ end
46
69
 
47
70
  # Configure and save the metadata object.
48
- self.key = options[:key].call file_to_write
71
+ compute_key! file, options
49
72
  self.owner_attr = attr
50
73
  self.original_extension = ::File.extname(file.original_filename)
51
74
  self.original_basename = ::File.basename(file.original_filename, original_extension)
52
- compute_mime!
53
- self.storage_type = options[:storage].name
75
+ compute_mime! options
76
+ if options[:storage].is_a? String
77
+ self.storage_type = options[:storage]
78
+ else
79
+ self.storage_type = options[:storage].name
80
+ end
54
81
  save!
55
82
 
56
83
  # Write the file to the filestore.
57
-
58
- if file_to_write.is_a?(ActionDispatch::Http::UploadedFile)
59
- storage.write key, file_to_write.tempfile
84
+ if file.is_a?(ActionDispatch::Http::UploadedFile)
85
+ storage.write key, file.tempfile
60
86
  else
61
- storage.write key, file_to_write
87
+ storage.write key, file
62
88
  end
63
- if file_to_write == mapped_file
64
- mapped_file.close
65
- #::File.delete mapped_file.path
89
+
90
+ # If we mapped the files, they were temporarily written to tmp/echo_uploads.
91
+ # Delete them.
92
+ if file.is_a?(::EchoUploads::MappedFile)
93
+ ::File.delete file.path
66
94
  end
67
95
 
68
96
  # Prune any expired temporary files. (Unless automatic pruning was turned off in
@@ -1,5 +1,26 @@
1
1
  module EchoUploads
2
2
  class FilesystemStore < ::EchoUploads::AbstractStore
3
+ def delete(key)
4
+ _path = path(key)
5
+ ::File.delete(_path) if ::File.exists?(_path)
6
+ end
7
+
8
+ def exists?(key)
9
+ ::File.exists? path(key)
10
+ end
11
+
12
+ def open(key)
13
+ ::File.open(path(key), 'rb', &block)
14
+ end
15
+
16
+ def path(key)
17
+ ::File.join folder, key
18
+ end
19
+
20
+ def read(key)
21
+ File.read path(key)
22
+ end
23
+
3
24
  def write(key, file)
4
25
  _path = path key
5
26
  unless ::File.exists?(_path)
@@ -14,23 +35,6 @@ module EchoUploads
14
35
  end
15
36
  end
16
37
 
17
- def read(key)
18
- File.read path(key)
19
- end
20
-
21
- def delete(key)
22
- _path = path(key)
23
- ::File.delete(_path) if ::File.exists?(_path)
24
- end
25
-
26
- def open(key)
27
- ::File.open(path(key), 'rb', &block)
28
- end
29
-
30
- def path(key)
31
- ::File.join folder, key
32
- end
33
-
34
38
  private
35
39
 
36
40
  # Can be customized in your per-environment config like this:
@@ -0,0 +1,5 @@
1
+ module EchoUploads
2
+ class MappedFile < ActionDispatch::Http::UploadedFile
3
+ attr_accessor :mapped_filename
4
+ end
5
+ end
@@ -0,0 +1,35 @@
1
+ require 'fileutils'
2
+
3
+ module EchoUploads
4
+ class Mapper
5
+ def initialize(file)
6
+ unless(
7
+ file.is_a?(ActionDispatch::Http::UploadedFile) or
8
+ file.is_a?(Rack::Test::UploadedFile)
9
+ )
10
+ raise(
11
+ "Expected file to be a ActionDispatch::Http::UploadedFile "+
12
+ "or Rack::Test::UploadedFile, but was #{file.inspect}"
13
+ )
14
+ end
15
+
16
+ @uploaded_file = file
17
+ @outputs = []
18
+ end
19
+
20
+ attr_reader :outputs
21
+
22
+ def write(ext)
23
+ folder = ::File.join Rails.root, 'tmp/echo_uploads'
24
+ FileUtils.mkdir_p folder
25
+ path = ::File.join(folder, SecureRandom.hex(15) + ext)
26
+ yield path
27
+ file = ::File.open path, 'rb'
28
+ mapped_file = ::EchoUploads::MappedFile.new(
29
+ tempfile: file, filename: @uploaded_file.original_filename
30
+ )
31
+ mapped_file.mapped_filename = ::File.basename path
32
+ outputs << mapped_file
33
+ end
34
+ end
35
+ end
@@ -19,9 +19,11 @@ module EchoUploads
19
19
 
20
20
  def echo_uploads_data
21
21
  Base64.encode64(JSON.dump(self.class.echo_uploads_config.inject({}) do |hash, (attr, cfg)|
22
- meta = send("#{attr}_tmp_metadata")
23
- if meta
24
- hash[attr] = {'id' => meta.id, 'key' => meta.key}
22
+ metas = send("#{attr}_tmp_metadata")
23
+ if metas
24
+ hash[attr] = metas.map do |meta|
25
+ {'id' => meta.id, 'key' => meta.key}
26
+ end
25
27
  end
26
28
  hash
27
29
  end)).strip
@@ -30,17 +32,35 @@ module EchoUploads
30
32
  # Pass in a hash that's been encoded as JSON and then Base64.
31
33
  def echo_uploads_data=(data)
32
34
  parsed = JSON.parse Base64.decode64(data)
35
+ # parsed will look like:
36
+ # { 'attr1' => [ {'id' => 1, 'key' => 'abc...'} ] }
37
+ unless parsed.is_a? Hash
38
+ raise ArgumentError, "Invalid JSON structure in: #{parsed.inspect}"
39
+ end
33
40
  parsed.each do |attr, attr_data|
34
- # Must verify that the metadata record is temporary. If not, an attacker could
35
- # pass the ID of a permanent record and change its owner.
36
- if meta = ::EchoUploads::File.where(id: attr_data['id'], key: attr_data['key'], temporary: true).first
37
- send("#{attr}_tmp_metadata=", meta)
41
+ # If the :map option was passed, there may be multiple variants of the uploaded
42
+ # file. Even if not, attr_data is still a one-element array.
43
+ unless attr_data.is_a? Array
44
+ raise ArgumentError, "Invalid JSON structure in: #{parsed.inspect}"
45
+ end
46
+ attr_data.each do |variant_data|
47
+ unless variant_data.is_a? Hash
48
+ raise ArgumentError, "Invalid JSON structure in: #{parsed.inspect}"
49
+ end
50
+ if meta = ::EchoUploads::File.where(
51
+ id: variant_data['id'], key: variant_data['key'], temporary: true
52
+ ).first
53
+ if send("#{attr}_tmp_metadata").nil?
54
+ send "#{attr}_tmp_metadata=", []
55
+ end
56
+ send("#{attr}_tmp_metadata") << meta
57
+ end
38
58
  end
39
59
  end
40
60
  end
41
61
 
42
62
  # Helper method used internally Echo Uploads.
43
- def map_metadata(attr)
63
+ def echo_uploads_map_metadata(attr, options)
44
64
  meta = send("#{attr}_metadata")
45
65
  meta ? yield(meta) : nil
46
66
  end
@@ -54,16 +74,26 @@ module EchoUploads
54
74
  # - +expires+: Length of time temporary files will be persisted. Defaults to
55
75
  # +1.day+.
56
76
  # - +storage+: A class that persists uploaded files to disk, to the cloud, or to
57
- # wherever else you want. Defaults to +EchoUploads::FilesystemStore+.
58
- # - +map+: A Proc that accepts an ActionDispatch::Htttp::UploadedFile and a path to
59
- # a temporary file. It should transform the file data (e.g. scaling an image). It
60
- # should then write the transformed data to the temporary file path. Can also
61
- # accept a symbol naming an an instance method that works the same way as the
62
- # previously described Proc.
77
+ # wherever else you want. Defaults to +Rails.configuration.echo_uploads.storage+,
78
+ # which in turn is +EchoUploads::FilesystemStore+ by default.
79
+ # - +map+: A Proc that accepts an ActionDispatch::Htttp::UploadedFile and an
80
+ # instance of +EchoUploads::Mapper+. It should transform the file data (e.g.
81
+ # scaling an image). It should then write the transformed data to one of more
82
+ # temporary files. To get the temporary file path(s), call +#write+ on the
83
+ # +Mapper+. See readme.md for an example. The +:map+ option can also accept a
84
+ # symbol naming an an instance method that works the same way as the previously
85
+ # described Proc.
86
+ # - +multiple+: You use the +:map+ option to write multiple versions of the file.
87
+ # E.g. multiple thumbnail sizes. If you do so, you must pass +multiple: true+.
88
+ # This will make the association with +EchoUploads::File+ a +has_many+ instead of
89
+ # a +has_one+. The first file you write in the map function becomes the default.
90
+ # E.g.: Your model is called +Widget+, and the upload file attribute is called
91
+ # +photo+. You pass +:map+ with a method that writes three files. If you call
92
+ # +Widget#photo_path+, it will return the path to the first of the three files.
63
93
  def echo_upload(attr, options = {})
64
94
  options = {
65
95
  expires: 1.day,
66
- storage: ::EchoUploads::FilesystemStore,
96
+ storage: Rails.configuration.echo_uploads.storage,
67
97
  key: ::EchoUploads::File.default_key_proc
68
98
  }.merge(options)
69
99
 
@@ -80,43 +110,45 @@ module EchoUploads
80
110
  # Define the writer method for the file attribute.
81
111
  define_method("#{attr}=") do |file|
82
112
  if options[:map]
83
- mapped_file_path = ::File.join Rails.root, 'tmp', SecureRandom.hex(15)
113
+ mapper = ::EchoUploads::Mapper.new file
84
114
  if options[:map].is_a? Proc
85
- options[:map].call file, mapped_file_path
115
+ options[:map].call file, mapper
86
116
  else
87
- send(options[:map], file, mapped_file_path)
117
+ send(options[:map], file, mapper)
88
118
  end
89
- mapped_file = ::File.open mapped_file_path, 'rb'
90
- send "mapped_#{attr}=", mapped_file
119
+ # Write an array of ActionDispatch::Http::UploadedFile objects to the instance
120
+ # variable.
121
+ send "mapped_#{attr}=", mapper.outputs
91
122
  end
92
123
  instance_variable_set "@#{attr}", file
93
124
  end
94
125
 
95
- # Define the accessor methods for the mapped version of the file.
126
+ # Define the accessor methods for the mapped version(s) of the file. Returns
127
+ # an array.
96
128
  attr_accessor "mapped_#{attr}"
97
129
 
130
+ # Define the original filename method.
131
+ define_method("#{attr}_original_filename") do
132
+ echo_uploads_map_metadata(attr, options, &:original_filename)
133
+ end
134
+
98
135
  # Define the path method. This method will raise if the given storage
99
136
  # class doesn't support the #path method.
100
137
  define_method("#{attr}_path") do
101
- map_metadata(attr) do |meta|
102
- meta.storage.path meta.key
138
+ echo_uploads_map_metadata(attr, options) do |meta|
139
+ meta.path
103
140
  end
104
141
  end
105
142
 
106
143
  # Define the MIME type method.
107
144
  define_method("#{attr}_mime") do
108
- map_metadata(attr, &:mime_type)
145
+ echo_uploads_map_metadata(attr, options, &:mime_type)
109
146
  end
110
147
  alias_method "#{attr}_mime_type", "#{attr}_mime"
111
148
 
112
- # Define the original filename method.
113
- define_method("#{attr}_original_filename") do
114
- map_metadata(attr, &:original_filename)
115
- end
116
-
117
149
  # Define the key method
118
150
  define_method("#{attr}_key") do
119
- map_metadata(attr, &:key)
151
+ echo_uploads_map_metadata(attr, options, &:key)
120
152
  end
121
153
 
122
154
  # Define the has_x? method. Returns true if a permanent or temporary file has been
@@ -147,10 +179,27 @@ module EchoUploads
147
179
  end
148
180
 
149
181
  # Define the association with the metadata model.
150
- has_one("#{attr}_metadata".to_sym,
151
- ->() { where(owner_attr: attr) },
152
- as: :owner, dependent: :destroy, class_name: '::EchoUploads::File'
153
- )
182
+ if options[:multiple]
183
+ has_many("#{attr}_metadatas".to_sym,
184
+ ->() { where(owner_attr: attr) },
185
+ as: :owner, dependent: :destroy, class_name: '::EchoUploads::File'
186
+ )
187
+
188
+ alias_method attr.to_s.pluralize, "#{attr}_metadatas"
189
+
190
+ define_method("#{attr}_metadata") do
191
+ send("#{attr}_metadatas").first
192
+ end
193
+
194
+ define_method("#{attr}_metadata=") do |val|
195
+ send("#{attr}_metadatas") << val
196
+ end
197
+ else
198
+ has_one("#{attr}_metadata".to_sym,
199
+ ->() { where(owner_attr: attr) },
200
+ as: :owner, dependent: :destroy, class_name: '::EchoUploads::File'
201
+ )
202
+ end
154
203
 
155
204
  # Define the temp attribute for the metadata model.
156
205
  attr_accessor "#{attr}_tmp_metadata"
@@ -10,34 +10,80 @@ module EchoUploads
10
10
  after_save do |model|
11
11
  if (file = send(attr)).present?
12
12
  # A file is being uploaded during this request cycle.
13
- if meta = send("#{attr}_metadata")
14
- # A previous permanent file exists. This is a new version being uploaded.
15
- # Delete the old version from the disk if no other metadata record
16
- # references it.
17
- meta.delete_file_conditionally
13
+
14
+ if options[:multiple]
15
+ metas = send("#{attr}_metadatas")
18
16
  else
19
- # No previous permanent file exists.
20
- meta = ::EchoUploads::File.new(owner: model, temporary: false)
21
- send("#{attr}_metadata=", meta)
17
+ metas = [send("#{attr}_metadata")].compact
22
18
  end
23
- meta.persist! attr, file, send("mapped_#{attr}"), options
24
- elsif meta = send("#{attr}_tmp_metadata") and meta.temporary
19
+
20
+ # metas is now an array of ::EchoUploads::File instances. The array may
21
+ # be empty.
22
+
23
+ if metas.any?
24
+ # Previous permanent file(s) exist. This is a new version being uploaded.
25
+ # Delete the old version(s).
26
+ metas.each(&:destroy)
27
+ end
28
+
29
+ # No previous permanent files exists. The metas array is currently empty or
30
+ # else contains deleted records. We need to rebuild that array by constructing
31
+ # (but not yet saving) new EchoUploads::File objects.
32
+
33
+ if options[:multiple]
34
+ mapped_files = send("mapped_#{attr}") ||
35
+ raise('echo_uploads called with :multiple, but :map option was missing')
36
+ metas = mapped_files.map do |mapped_file|
37
+ ::EchoUploads::File.new(
38
+ owner: model, temporary: false, file: mapped_file
39
+ )
40
+ end
41
+ send("#{attr}_metadatas=", metas)
42
+ else
43
+ metas = [::EchoUploads::File.new(
44
+ owner: model, temporary: false, file: send(attr)
45
+ )]
46
+ send("#{attr}_metadata=", metas.first)
47
+ end
48
+
49
+ # metas is still an array of the EchoUploads::File instances. If the array was
50
+ # initially empty (meaning no previous permanent file existed), then it has
51
+ # since been populated.
52
+
53
+ metas.each do |meta|
54
+ meta.persist! attr, options
55
+ end
56
+ elsif metas = send("#{attr}_tmp_metadata")
25
57
  # A file has not been uploaded during this request cycle. However, the
26
58
  # submitted form "remembered" a temporary metadata record that was previously
27
- # saved. We mark it as permanent and set its owner.
28
- #
29
- # But first, we must delete any existing metadata record. (It's possible we
59
+ # saved.
60
+
61
+ # Delete any existing metadata record. (It's possible we
30
62
  # were trying to replace an old version of the file, and there were validation
31
63
  # errors on the first attempt.)
32
- if old = model.send("#{attr}_metadata")
64
+
65
+ if options[:multiple]
66
+ model.send("#{attr}_metadatas").each(&:destroy)
67
+ elsif old = model.send("#{attr}_metadata")
33
68
  old.destroy
34
69
  end
35
70
 
36
- meta.owner = model
37
- send("#{attr}_metadata=", meta)
38
- meta.temporary = false
39
- meta.expires_at = nil
40
- meta.save!
71
+ # We need not call persist! here, because the file is already persisted. (Nor
72
+ # could we call it, because persist! requires an
73
+ # ActionDispatch::HTTP::UploadedFile.) Mark the metadata record as permanent
74
+ # and set its owner.
75
+ metas.each do |meta|
76
+ meta.owner = model
77
+ meta.temporary = false
78
+ meta.expires_at = nil
79
+ meta.save!
80
+ end
81
+
82
+ if options[:multiple]
83
+ send("#{attr}_metadatas=", metas)
84
+ else
85
+ send("#{attr}_metadata=", metas.first)
86
+ end
41
87
  end
42
88
  end
43
89
  end
@@ -2,6 +2,9 @@ require 'ostruct'
2
2
 
3
3
  module EchoUploads
4
4
  class Railtie < Rails::Railtie
5
- config.echo_uploads = OpenStruct.new
5
+ config.echo_uploads = OpenStruct.new(
6
+ storage: 'EchoUploads::FilesystemStore',
7
+ s3: OpenStruct.new(bucket: nil, folder: nil)
8
+ )
6
9
  end
7
10
  end
@@ -0,0 +1,51 @@
1
+ # Uses the official Amazon Web Services SDK gem:
2
+ # gem install aws-sdk
3
+ module EchoUploads
4
+ class S3Store < ::EchoUploads::AbstractStore
5
+ def delete(key)
6
+ bucket.objects[key].delete
7
+ end
8
+
9
+ def exists?(key)
10
+ bucket.objects[key].exists?
11
+ end
12
+
13
+ def read(key)
14
+ data = ''
15
+ bucket.objects[key].read { |chunk| data << chunk }
16
+ data
17
+ end
18
+
19
+ def url(key, options = {})
20
+ options = {method: :read}.merge(options)
21
+ bucket.objects[key].url_for options.delete(:method), options
22
+ end
23
+
24
+ def write(key, file)
25
+ bucket.objects[key].write file
26
+ end
27
+
28
+ private
29
+
30
+ def bucket
31
+ if Rails.configuration.echo_uploads.aws
32
+ s3 = AWS::S3.new Rails.configuration.echo_uploads.aws
33
+ else
34
+ s3 = AWS::S3.new
35
+ end
36
+ bucket_name = Rails.configuration.echo_uploads.s3.bucket || raise(
37
+ 'You must define config.echo_uploads.s3.bucket in your application config.'
38
+ )
39
+ if s3.buckets[bucket_name].nil?
40
+ s3.buckets.create bucket_name
41
+ end
42
+ s3.buckets[bucket_name]
43
+ end
44
+
45
+ def folder
46
+ Rails.configuration.echo_uploads.s3.folder || raise(
47
+ 'You must define config.echo_uploads.s3.folder in your application config.'
48
+ )
49
+ end
50
+ end
51
+ end
@@ -28,11 +28,32 @@ module EchoUploads
28
28
  # That's fine. We'll now have a permanent and a temporary one. The temporary
29
29
  # one will replace the permanent one if and when the user resubmits with
30
30
  # valid data.
31
- meta = ::EchoUploads::File.new(
32
- owner: nil, temporary: true, expires_at: options[:expires].from_now
33
- )
34
- meta.persist! attr, file, send("mapped_#{attr}"), options
35
- send("#{attr}_tmp_metadata=", meta)
31
+
32
+ # Construct an array of EchoUploads::File instances. The array might have only
33
+ # one element.
34
+ if options[:multiple]
35
+ mapped_files = send("mapped_#{attr}") ||
36
+ raise('echo_uploads called with :multiple, but :map option was missing')
37
+ metas = mapped_files.map do |mapped_file|
38
+ ::EchoUploads::File.new(
39
+ owner: nil, temporary: true, expires_at: options[:expires].from_now,
40
+ file: mapped_file
41
+ )
42
+ end
43
+ else
44
+ metas = [::EchoUploads::File.new(
45
+ owner: nil, temporary: true, expires_at: options[:expires].from_now,
46
+ file: send(attr)
47
+ )]
48
+ end
49
+
50
+ # Persist each file. (There might only be one, though.)
51
+ metas.each do |meta|
52
+ meta.persist! attr, options
53
+ end
54
+
55
+ # Set the attr_tmp_metadata attribute so the form can remember our records.
56
+ send("#{attr}_tmp_metadata=", metas)
36
57
  end
37
58
  end
38
59
 
data/lib/echo_uploads.rb CHANGED
@@ -6,5 +6,8 @@ require 'echo_uploads/perm_file_saving'
6
6
  require 'echo_uploads/temp_file_saving'
7
7
  require 'echo_uploads/model'
8
8
  require 'echo_uploads/file'
9
+ require 'echo_uploads/mapper'
10
+ require 'echo_uploads/mapped_file'
9
11
  require 'echo_uploads/abstract_store'
10
- require 'echo_uploads/filesystem_store'
12
+ require 'echo_uploads/filesystem_store'
13
+ require 'echo_uploads/s3_store'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: echo_uploads
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jarrett Colby
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-07-08 00:00:00.000000000 Z
11
+ date: 2014-07-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mime-types
@@ -38,7 +38,9 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
- description: ''
41
+ description: Gracefully handles invalid form submissions, so users don't have to resubmit
42
+ the file. Supports transforming the file before saving, e.g. scaling an image. Compatible
43
+ with any storage mechanism, including the local filesystem and the cloud.
42
44
  email: jarrett@madebyhq.com
43
45
  executables: []
44
46
  extensions: []
@@ -48,9 +50,12 @@ files:
48
50
  - lib/echo_uploads/abstract_store.rb
49
51
  - lib/echo_uploads/file.rb
50
52
  - lib/echo_uploads/filesystem_store.rb
53
+ - lib/echo_uploads/mapped_file.rb
54
+ - lib/echo_uploads/mapper.rb
51
55
  - lib/echo_uploads/model.rb
52
56
  - lib/echo_uploads/perm_file_saving.rb
53
57
  - lib/echo_uploads/railtie.rb
58
+ - lib/echo_uploads/s3_store.rb
54
59
  - lib/echo_uploads/temp_file_saving.rb
55
60
  - lib/echo_uploads/validation.rb
56
61
  homepage: https://github.com/jarrett/echo_uploads