echo_uploads 0.0.16 → 0.0.17

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: 3d46f6288ea37a24e3903287feb0158b686ba5fd
4
- data.tar.gz: e545e0653e756f4d276b411dcf19334263fb6130
3
+ metadata.gz: 785ecfba2a86dcdf461ba6240cde71d5acc3cc34
4
+ data.tar.gz: 53b675e496cd46bdfddf3f27185e249fdcb647fa
5
5
  SHA512:
6
- metadata.gz: c5ca94083493932ec6eb68c39e7958c3e28457a8cf07eeb58c15b0addd1793e619cba306a34aaad5c797e43d01efa6c931179c6e8f2f272a4e3ebb6c379369d9
7
- data.tar.gz: ff9aabaea5385a75630421189f18152619af6e83d1b600d780b17503f46cda3619970544180765042240dd800f172743f838e3e835bc836bb36dd0c16ef85faa
6
+ metadata.gz: 7c3038650d90266e2c6cd4c68d2b4c805c806cab9a2c803e981020ab9d3163f2ff589541e093b4e98de7ab9ebd7ace2bffb66c47bff89d56a25e3d54ac05ecff
7
+ data.tar.gz: dac64b6e5495fb691a9ba9e12488edfdade91cbb1cd42ee12c468a0b3a5cc97f2b2376810bdbcfa7ac16fc12f97313817024c8c242e281db9578ba722a1ee898
@@ -0,0 +1,13 @@
1
+ module EchoUploads
2
+ module Callbacks
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ unless included_modules.include? ::EchoUploads::Model
7
+ include ::EchoUploads::Model
8
+ end
9
+
10
+ define_model_callbacks :failed_save, only: [:after]
11
+ end
12
+ end
13
+ end
@@ -21,10 +21,6 @@ module EchoUploads
21
21
  self.mime_type = type ? type.content_type : 'application/octet-stream'
22
22
  end
23
23
 
24
- def compute_key!(file, options)
25
- self.key = options[:key].call file
26
- end
27
-
28
24
  # Returns a proc that takes as its only argument an ActionDispatch::UploadedFile
29
25
  # and returns a key string.
30
26
  def self.default_key_proc
@@ -68,7 +64,7 @@ module EchoUploads
68
64
  end
69
65
 
70
66
  # Configure and save the metadata object.
71
- compute_key! file, options
67
+ self.key = options[:key].call file # Normall, this is .default_key_proc.
72
68
  self.owner_attr = attr
73
69
  self.original_extension = ::File.extname(file.original_filename)
74
70
  self.original_basename = ::File.basename(file.original_filename, original_extension)
@@ -92,7 +88,7 @@ module EchoUploads
92
88
 
93
89
  # If we mapped the files, they were temporarily written to tmp/echo_uploads.
94
90
  # Delete them.
95
- if file.is_a?(::EchoUploads::MappedFile)
91
+ if file.is_a?(::EchoUploads::MappedFile) and ::File.exists?(file.path)
96
92
  ::File.delete file.path
97
93
  end
98
94
 
@@ -5,16 +5,14 @@ require 'securerandom'
5
5
 
6
6
  module EchoUploads
7
7
  module Model
8
- def self.included(base)
9
- base.class_eval do
10
- class_attribute :echo_uploads_config
11
-
12
- include ::EchoUploads::Validation
13
- include ::EchoUploads::PermFileSaving
14
- include ::EchoUploads::TempFileSaving
15
-
16
- extend ClassMethods
17
- end
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ class_attribute :echo_uploads_config
12
+
13
+ include ::EchoUploads::Validation
14
+ include ::EchoUploads::PrmFileWriting
15
+ include ::EchoUploads::TmpFileWriting
18
16
  end
19
17
 
20
18
  def echo_uploads_data
@@ -67,15 +65,19 @@ module EchoUploads
67
65
 
68
66
  module ClassMethods
69
67
  # Options:
68
+ #
70
69
  # - +key+: A Proc that takes an ActionDispatch::UploadedFile and returns a key
71
70
  # uniquely identifying the file. If this option is not specified, the key is
72
71
  # computed as the SHA-512 hash of the file contents. A digest of the file's
73
72
  # contents should always be at least a part of the key.
73
+ #
74
74
  # - +expires+: Length of time temporary files will be persisted. Defaults to
75
75
  # +1.day+.
76
+ #
76
77
  # - +storage+: A class that persists uploaded files to disk, to the cloud, or to
77
78
  # wherever else you want. Defaults to +Rails.configuration.echo_uploads.storage+,
78
79
  # which in turn is +EchoUploads::FilesystemStore+ by default.
80
+ #
79
81
  # - +map+: A Proc that accepts an ActionDispatch::Htttp::UploadedFile and an
80
82
  # instance of +EchoUploads::Mapper+. It should transform the file data (e.g.
81
83
  # scaling an image). It should then write the transformed data to one of more
@@ -83,6 +85,7 @@ module EchoUploads
83
85
  # +Mapper+. See readme.md for an example. The +:map+ option can also accept a
84
86
  # symbol naming an an instance method that works the same way as the previously
85
87
  # described Proc.
88
+ #
86
89
  # - +multiple+: You use the +:map+ option to write multiple versions of the file.
87
90
  # E.g. multiple thumbnail sizes. If you do so, you must pass +multiple: true+.
88
91
  # This will make the association with +EchoUploads::File+ a +has_many+ instead of
@@ -90,11 +93,23 @@ module EchoUploads
90
93
  # E.g.: Your model is called +Widget+, and the upload file attribute is called
91
94
  # +photo+. You pass +:map+ with a method that writes three files. If you call
92
95
  # +Widget#photo_path+, it will return the path to the first of the three files.
96
+ #
97
+ # - +write_tmp_file+: Normally, on a failed attempt to save the record, Echo Uploads
98
+ # writes a temp file. That way, the user can fix the validation errors without
99
+ # re-uploading the file. This option determines when the temp file is written. The
100
+ # default is +:after_rollback+, meaning the temp file is written on a failed
101
+ # attempt to save the record. Set to +false+ to turn off temp file saving. You can
102
+ # then save temp files manually by calling Set to +:after_validation+ and the temp
103
+ # file will be written on validation failure. (Warning: Although ActiveRecord
104
+ # implicitly validates before saving, it does so during a transaction. So setting
105
+ # this option to +:after_validation+ will prevent temp files being written during
106
+ # calls to +#save+ and similar methods.)
93
107
  def echo_upload(attr, options = {})
94
108
  options = {
95
109
  expires: 1.day,
96
110
  storage: Rails.configuration.echo_uploads.storage,
97
- key: ::EchoUploads::File.default_key_proc
111
+ key: ::EchoUploads::File.default_key_proc,
112
+ write_tmp_file: :after_rollback
98
113
  }.merge(options)
99
114
 
100
115
  # Init the config object. We can't use [] syntax to set the hash key because
@@ -208,6 +223,10 @@ module EchoUploads
208
223
  echo_uploads_map_metadata(attr, options, &:size)
209
224
  end
210
225
 
226
+ define_method("maybe_write_tmp_#{attr}") do
227
+ echo_uploads_maybe_write_tmp_file(attr, options)
228
+ end
229
+
211
230
  # Define the association with the metadata model.
212
231
  if options[:multiple]
213
232
  has_many("#{attr}_metadatas".to_sym,
@@ -234,9 +253,9 @@ module EchoUploads
234
253
  # Define the temp attribute for the metadata model.
235
254
  attr_accessor "#{attr}_tmp_metadata"
236
255
 
237
- configure_temp_file_saving attr, options
256
+ echo_uploads_configure_tmp_file_writing attr, options
238
257
 
239
- configure_perm_file_saving attr, options
258
+ echo_uploads_configure_prm_file_writing attr, options
240
259
  end
241
260
  end
242
261
  end
@@ -1,21 +1,19 @@
1
1
  module EchoUploads
2
- module PermFileSaving
3
- def self.included(base)
4
- base.class_eval { extend ClassMethods }
5
- end
2
+ module PrmFileWriting
3
+ extend ActiveSupport::Concern
6
4
 
7
5
  module ClassMethods
8
- def configure_perm_file_saving(attr, options)
6
+ def echo_uploads_configure_prm_file_writing(attr, options)
9
7
  # Save the file and the metadata after this model saves.
10
8
  after_save do |model|
11
- @echo_uploads_perm_files_saved ||= {}
12
- if (file = send(attr)).present? and @echo_uploads_perm_files_saved[attr.to_sym] != file
9
+ @echo_uploads_prm_files_saved ||= {}
10
+ if (file = send(attr)).present? and @echo_uploads_prm_files_saved[attr.to_sym] != file
13
11
  # A file is being uploaded during this request cycle. Further, we have not
14
12
  # already done the permanent file saving during this request cycle. (It's
15
13
  # not uncommon for a model to be saved twice in one request. If we ran this
16
14
  # code twice, we'd have duplicate effort at best and exceptions at worst.)
17
15
 
18
- @echo_uploads_perm_files_saved[attr.to_sym] = file
16
+ @echo_uploads_prm_files_saved[attr.to_sym] = file
19
17
 
20
18
  if options[:multiple]
21
19
  metas = send("#{attr}_metadatas")
@@ -0,0 +1,139 @@
1
+ module EchoUploads
2
+ # This comment is current as of Rails 4.2.5.
3
+ #
4
+ # This module writes temporary EchoUploads::Files on failed attempts to save the main
5
+ # ActiveRecord model. Because ActiveRecord wraps save attempts in transactions, we can't
6
+ # use after_save callbacks. If we tried, the EchoUploads::Files would be lost upon
7
+ # rollback. Instead, we have to wrap #save, #create, and #update methods so that the
8
+ # EchoUploads::Files are saved outside the transactions.
9
+ #
10
+ # Here is the ancestor chain for ActiveRecord::Base, including this module,
11
+ # highest-priority first:
12
+ #
13
+ # EchoUploads::TmpFileWriting -> ActiveRecord::Transactions -> ActiveRecord::Persistence
14
+ #
15
+ # Here is the control flow for each of the main persistence methods. (T) denotes that
16
+ # the method wraps all subsequent calls in a transaction.
17
+ #
18
+ # #save
19
+ # EchoUploads::TmpFileWriting#save -> ActiveRecord::Transactions#save (T) ->
20
+ # ActiveRecord::Persistence#save
21
+ #
22
+ # #create
23
+ # ActiveRecord::Persistence#create -> EchoUploads::TmpFileWriting#save ->
24
+ # ActiveRecord::Transactions#save (T) -> ActiveRecord::Persistence#save
25
+ #
26
+ # #update
27
+ # EchoUploads::TmpFileWriting#update -> ActiveRecord::Persistence#update (T) ->
28
+ # EchoUploads::TmpFileWriting#save -> ActiveRecord::Transactions#save (T) ->
29
+ # ActiveRecord::Persistence#save
30
+ #
31
+ # Per the above, #save and #create are easy enough: We just wrap #save. But #update is
32
+ # problematic because it starts its own transaction and then delegates to #save. Because
33
+ # of that outer transaction, we can't rely on the #save wrapper. Instead, we have to
34
+ # wrap #update. To prevent writing the temp file twice (once in #update and again in
35
+ # #save), #update sets @echo_uploads_persistence_wrapped. This tells the #save
36
+ # wrapper not to write the temp file.
37
+
38
+ module TmpFileWriting
39
+ extend ActiveSupport::Concern
40
+
41
+ included do
42
+ extend ClassMethods
43
+ class_attribute :echo_uploads_save_wrapper
44
+ self.echo_uploads_save_wrapper = []
45
+ end
46
+
47
+ # On a failed attempt to save (typically due to validation errors), save the file
48
+ # and metadata. Metadata record will be given the temporary flag.
49
+ def echo_uploads_maybe_write_tmp_file(attr, options)
50
+ if (file = send(attr)).present? and errors[attr].empty?
51
+ # A file has been uploaded. Validation failed, but the file itself was valid.
52
+ # Thus, we must persist a temporary file.
53
+ #
54
+ # It's possible at this point that the record already has a permanent file.
55
+ # That's fine. We'll now have a permanent and a temporary one. The temporary
56
+ # one will replace the permanent one if and when the user resubmits with
57
+ # valid data.
58
+
59
+ # Construct an array of EchoUploads::File instances. The array might have only
60
+ # one element.
61
+ if options[:multiple]
62
+ mapped_files = send("mapped_#{attr}") ||
63
+ raise('echo_uploads called with :multiple, but :map option was missing')
64
+ metas = mapped_files.map do |mapped_file|
65
+ ::EchoUploads::File.new(
66
+ owner: nil, temporary: true, expires_at: options[:expires].from_now,
67
+ file: mapped_file
68
+ )
69
+ end
70
+ else
71
+ metas = [::EchoUploads::File.new(
72
+ owner: nil, temporary: true, expires_at: options[:expires].from_now,
73
+ file: send(attr)
74
+ )]
75
+ end
76
+
77
+ # Persist each file. (There might only be one, though.)
78
+ metas.each do |meta|
79
+ meta.persist! attr, options
80
+ end
81
+
82
+ # Set the attr_tmp_metadata attribute so the form can remember our records.
83
+ send("#{attr}_tmp_metadata=", metas)
84
+ end
85
+ end
86
+
87
+ def echo_uploads_persistence_wrapper
88
+ success = yield
89
+ unless success
90
+ self.class.echo_uploads_save_wrapper.each do |attr, options|
91
+ echo_uploads_maybe_write_tmp_file(attr, options)
92
+ end
93
+ if self.class.included_modules.include? ::EchoUploads::Callbacks
94
+ run_callbacks :failed_save
95
+ end
96
+ end
97
+ success
98
+ end
99
+
100
+
101
+ def save(*)
102
+ if @echo_uploads_persistence_wrapped
103
+ super
104
+ else
105
+ echo_uploads_persistence_wrapper { super }
106
+ end
107
+ end
108
+
109
+
110
+ def update(*)
111
+ echo_uploads_persistence_wrapper do
112
+ begin
113
+ @echo_uploads_persistence_wrapped = true
114
+ super
115
+ ensure
116
+ @echo_uploads_persistence_wrapped = nil
117
+ end
118
+ end
119
+ end
120
+
121
+ alias_method :update_attributes, :update
122
+
123
+ module ClassMethods
124
+ def echo_uploads_configure_tmp_file_writing(attr, options)
125
+ if options[:write_tmp_file] == :after_rollback
126
+ # Because ActiveRecord rolls back the transaction on validation failure, we
127
+ # can't just use a convenient after_validation callback. Nor can we use an
128
+ # after_rollback hook, because that causes all kinds of bizarre side-effects,
129
+ # especially in the test environment.
130
+ self.echo_uploads_save_wrapper += [[attr, options]]
131
+ elsif options[:write_tmp_file] == :after_validation
132
+ after_validation do
133
+ echo_uploads_maybe_write_tmp_file(attr, options) { errors.empty? }
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
@@ -1,3 +1,3 @@
1
1
  module EchoUploads
2
- VERSION = '0.0.16'
2
+ VERSION = '0.0.17'
3
3
  end
data/lib/echo_uploads.rb CHANGED
@@ -3,8 +3,9 @@ module EchoUploads; end
3
3
  require 'echo_uploads/version'
4
4
  require 'echo_uploads/railtie'
5
5
  require 'echo_uploads/validation'
6
- require 'echo_uploads/perm_file_saving'
7
- require 'echo_uploads/temp_file_saving'
6
+ require 'echo_uploads/prm_file_writing'
7
+ require 'echo_uploads/tmp_file_writing'
8
+ require 'echo_uploads/callbacks'
8
9
  require 'echo_uploads/model'
9
10
  require 'echo_uploads/file'
10
11
  require 'echo_uploads/mapper'
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.16
4
+ version: 0.0.17
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jarrett Colby
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-09-18 00:00:00.000000000 Z
11
+ date: 2015-12-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mime-types
@@ -48,15 +48,16 @@ extra_rdoc_files: []
48
48
  files:
49
49
  - lib/echo_uploads.rb
50
50
  - lib/echo_uploads/abstract_store.rb
51
+ - lib/echo_uploads/callbacks.rb
51
52
  - lib/echo_uploads/file.rb
52
53
  - lib/echo_uploads/filesystem_store.rb
53
54
  - lib/echo_uploads/mapped_file.rb
54
55
  - lib/echo_uploads/mapper.rb
55
56
  - lib/echo_uploads/model.rb
56
- - lib/echo_uploads/perm_file_saving.rb
57
+ - lib/echo_uploads/prm_file_writing.rb
57
58
  - lib/echo_uploads/railtie.rb
58
59
  - lib/echo_uploads/s3_store.rb
59
- - lib/echo_uploads/temp_file_saving.rb
60
+ - lib/echo_uploads/tmp_file_writing.rb
60
61
  - lib/echo_uploads/validation.rb
61
62
  - lib/echo_uploads/version.rb
62
63
  homepage: https://github.com/jarrett/echo_uploads
@@ -1,100 +0,0 @@
1
- module EchoUploads
2
- module TempFileSaving
3
- def self.included(base)
4
- base.class_eval { extend ClassMethods }
5
- end
6
-
7
- # On a failed attempt to save (typically due to validation errors), save the file
8
- # and metadata. Metadata record will be given the temporary flag.
9
- #
10
- # To deal with the various persistence methods (#save, #create,
11
- # #update_attributes), and the fact that ActiveRecord rolls back the transaction
12
- # on validation failure, we can't just use a convenient after_validation callback.
13
- # Instead, we have to do some trickery with .alias_method_chain.
14
- def maybe_save_temp_file(attr, options)
15
- success = yield
16
-
17
- # Because of the tangled way ActiveRecord's persistence methods delegate to each
18
- # other, maybe_save_temp_file sometimes gets called twice. That's unavoidable. To
19
- # workaround that issue, we check whether we're calling #save from within #update.
20
- @echo_uploads_saving ||= {}
21
- @echo_uploads_updating ||= {}
22
- unless @echo_uploads_saving[attr] and @echo_uploads_updating[attr]
23
- if (file = send(attr)).present? and !success and errors[attr].empty?
24
- # A file has been uploaded. Validation failed, but the file itself was valid.
25
- # Thus, we must persist a temporary file.
26
- #
27
- # It's possible at this point that the record already has a permanent file.
28
- # That's fine. We'll now have a permanent and a temporary one. The temporary
29
- # one will replace the permanent one if and when the user resubmits with
30
- # valid data.
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)
57
- end
58
- end
59
-
60
- success
61
- end
62
-
63
- module ClassMethods
64
- # Wraps ActiveRecord's persistence methods. We can't use a callback for this. See
65
- # the comment above for an explanation of why.
66
- def configure_temp_file_saving(attr, options)
67
- # Wrap the #save method. This also suffices for #create.
68
- define_method("save_with_#{attr}_temp_file") do |*args|
69
- @echo_uploads_saving ||= {}
70
- @echo_uploads_saving[attr] = true
71
- begin
72
- success = maybe_save_temp_file(attr, options) do
73
- send "save_without_#{attr}_temp_file", *args
74
- end
75
- success
76
- ensure
77
- @echo_uploads_saving.delete attr
78
- end
79
- end
80
- alias_method_chain :save, "#{attr}_temp_file".to_sym
81
-
82
- # Wrap the #update and #update_attributes methods.
83
- define_method("update_with_#{attr}_temp_file") do |*args|
84
- @echo_uploads_updating ||= {}
85
- @echo_uploads_updating[attr] = true
86
- begin
87
- success = maybe_save_temp_file(attr, options) do
88
- send "update_without_#{attr}_temp_file", *args
89
- end
90
- success
91
- ensure
92
- @echo_uploads_updating.delete attr
93
- end
94
- end
95
- alias_method_chain :update, "#{attr}_temp_file".to_sym
96
- alias_method :update_attributes, "update_with_#{attr}_temp_file".to_sym
97
- end
98
- end
99
- end
100
- end