echo_uploads 0.0.2 → 0.0.4

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 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