Module: EchoUploads::TmpFileWriting

Extended by:
ActiveSupport::Concern
Defined in:
lib/echo_uploads/tmp_file_writing.rb

Overview

This comment is current as of Rails 4.2.5.

This module writes temporary EchoUploads::Files on failed attempts to save the main ActiveRecord model. Because ActiveRecord wraps save attempts in transactions, we can’t use after_save callbacks. If we tried, the EchoUploads::Files would be lost upon rollback. Instead, we have to wrap the #save, #create, and #update methods so that the EchoUploads::Files are saved outside the transactions.

Here is the ancestor chain for ActiveRecord::Base, including this module, highest-priority first:

EchoUploads::TmpFileWriting -> ActiveRecord::Transactions -> ActiveRecord::Persistence

Here is the control flow for each of the main persistence methods. (T) denotes that the method wraps all subsequent calls in a transaction.

#save

EchoUploads::TmpFileWriting#save -> ActiveRecord::Transactions#save (T) ->
ActiveRecord::Persistence#save

#create

ActiveRecord::Persistence#create -> EchoUploads::TmpFileWriting#save ->
ActiveRecord::Transactions#save (T) -> ActiveRecord::Persistence#save

#update

EchoUploads::TmpFileWriting#update -> ActiveRecord::Persistence#update (T) -> 
EchoUploads::TmpFileWriting#save -> ActiveRecord::Transactions#save (T) ->
ActiveRecord::Persistence#save

Per the above, #save and #create are easy enough: We just wrap #save. But #update is problematic because it starts its own transaction and then delegates to #save. Because of that outer transaction, we can’t rely on the #save wrapper. Instead, we have to wrap #update. To prevent writing the temp file twice (once in #update and again in #save), #update sets @echo_uploads_persistence_wrapped. This tells the #save wrapper not to write the temp file.

Defined Under Namespace

Modules: ClassMethods

Instance Method Summary collapse

Instance Method Details

#echo_uploads_maybe_write_tmp_file(attr, options) ⇒ Object

On a failed attempt to save (typically due to validation errors), save the file and metadata. Metadata record will be given the temporary flag.



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/echo_uploads/tmp_file_writing.rb', line 49

def echo_uploads_maybe_write_tmp_file(attr, options)
  if (file = send(attr)).present? and errors[attr].empty?
    # A file has been uploaded. Validation failed, but the file itself was valid.
    # Thus, we must persist a temporary file.
    # 
    # It's possible at this point that the record already has a permanent file.
    # That's fine. We'll now have a permanent and a temporary one. The temporary
    # one will replace the permanent one if and when the user resubmits with
    # valid data.
    
    # Construct an array of EchoUploads::File instances. The array might have only
    # one element.
    if options[:multiple]
      mapped_files = send("mapped_#{attr}") ||
        raise('echo_uploads called with :multiple, but :map option was missing')
      metas = mapped_files.map do |mapped_file|
        ::EchoUploads::File.new(
          owner: nil, temporary: true, expires_at: options[:expires].from_now,
          file: mapped_file
        )
      end
    else
      metas = [::EchoUploads::File.new(
        owner: nil, temporary: true, expires_at: options[:expires].from_now,
        file: send(attr)
      )]
    end
    
    # Persist each file. (There might only be one, though.)
    metas.each do |meta|
      meta.persist! attr, options
    end
    
    # Set the attr_tmp_metadata attribute so the form can remember our records.
    send("#{attr}_tmp_metadata=", metas)
  end
end

#echo_uploads_persistence_wrapperObject



87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/echo_uploads/tmp_file_writing.rb', line 87

def echo_uploads_persistence_wrapper
  success = yield
  unless success
    self.class.echo_uploads_save_wrapper.each do |attr, options|
      echo_uploads_maybe_write_tmp_file(attr, options)
    end
    if self.class.included_modules.include? ::EchoUploads::Callbacks
      run_callbacks :failed_save
    end
  end
  success
end

#saveObject



101
102
103
104
105
106
107
# File 'lib/echo_uploads/tmp_file_writing.rb', line 101

def save(*)
  if @echo_uploads_persistence_wrapped
    super
  else
    echo_uploads_persistence_wrapper { super }
  end
end

#updateObject Also known as: update_attributes



110
111
112
113
114
115
116
117
118
119
# File 'lib/echo_uploads/tmp_file_writing.rb', line 110

def update(*)
  echo_uploads_persistence_wrapper do
    begin
      @echo_uploads_persistence_wrapped = true
      super
    ensure
      @echo_uploads_persistence_wrapped = nil
    end
  end
end