echo_uploads 0.0.16 → 0.0.17

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