Module: EchoUploads::Model::ClassMethods

Defined in:
lib/echo_uploads/model.rb

Instance Method Summary collapse

Instance Method Details

#echo_upload(attr, options = {}) ⇒ Object

Options:

  • key: A Proc that takes an ActionDispatch::UploadedFile and returns a key uniquely identifying the file. If this option is not specified, the key is computed as the SHA-512 hash of the file contents. A digest of the file’s contents should always be at least a part of the key.

  • expires: Length of time temporary files will be persisted. Defaults to 1.day.

  • storage: A class that persists uploaded files to disk, to the cloud, or to wherever else you want. Defaults to Rails.configuration.echo_uploads.storage, which in turn is EchoUploads::FilesystemStore by default.

  • map: A Proc that accepts an ActionDispatch::Htttp::UploadedFile and an instance of EchoUploads::Mapper. It should transform the file data (e.g. scaling an image). It should then write the transformed data to one of more temporary files. To get the temporary file path(s), call #write on the Mapper. See readme.md for an example. The :map option can also accept a symbol naming an an instance method that works the same way as the previously described Proc.

  • multiple: You use the :map option to write multiple versions of the file. E.g. multiple thumbnail sizes. If you do so, you must pass multiple: true. This will make the association with EchoUploads::File a has_many instead of a has_one. The first file you write in the map function becomes the default. E.g.: Your model is called Widget, and the upload file attribute is called photo. You pass :map with a method that writes three files. If you call Widget#photo_path, it will return the path to the first of the three files.

  • write_tmp_file: Normally, on a failed attempt to save the record, Echo Uploads writes a temp file. That way, the user can fix the validation errors without re-uploading the file. This option determines when the temp file is written. The default is :after_rollback, meaning the temp file is written on a failed attempt to save the record. Set to false to turn off temp file saving. You can then save temp files manually by calling Set to :after_validation and the temp file will be written on validation failure. (Warning: Although ActiveRecord implicitly validates before saving, it does so during a transaction. So setting this option to :after_validation will prevent temp files being written during calls to #save and similar methods.)



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/echo_uploads/model.rb', line 107

def echo_upload(attr, options = {})
  options = {
    expires: 1.day,
    storage: Rails.configuration.echo_uploads.storage,
    key: ::EchoUploads::File.default_key_proc,
    write_tmp_file: :after_rollback
  }.merge(options)
  
  # Init the config object. We can't use [] syntax to set the hash key because
  # class_attribute expects you to call the setter method every time the
  # attribute value changes. (Merely calling [] would just mutate the referenced
  # object, and wouldn't invoke the setter.)
  self.echo_uploads_config ||= {}
  self.echo_uploads_config = echo_uploads_config.merge attr => {}
  
  # Define reader method for the file attribute.
  if Rails::VERSION::MAJOR >= 5 and Rails::VERSION::MINOR >= 1
    attribute attr
  else
    attr_reader attr
    define_method("#{attr}=") do |file|
      instance_variable_set "@#{attr}", file
      if send(attr).present?
        # Mark as dirty.
        attribute_will_change! attr
      end
    end
  end
          
  # Define the accessor methods for the mapped version(s) of the file. Returns
  # an array.
  define_method("mapped_#{attr}") do
    unless instance_variable_get("@mapped_#{attr}")
      file = send attr
      mapper = ::EchoUploads::Mapper.new file
      if options[:map].is_a? Proc
        options[:map].call file, mapper
      else
        send(options[:map], file, mapper)
      end
      # Write an array of ActionDispatch::Http::UploadedFile objects to the instance
      # variable.
      instance_variable_set("@mapped_#{attr}", mapper.outputs)
    end
    instance_variable_get("@mapped_#{attr}")
  end
  
  # Define the original filename method.
  define_method("#{attr}_original_filename") do
    (attr, options, &:original_filename)
  end
  
  # Define the path method. This method will raise if the given storage
  # class doesn't support the #path method.
  define_method("#{attr}_path") do
    (attr, options) do |meta|
      meta.path
    end
  end
  
  # Define the MIME type method.
  define_method("#{attr}_mime") do
    (attr, options, &:mime_type)
  end
  alias_method "#{attr}_mime_type", "#{attr}_mime"
  
  # Define the key method
  define_method("#{attr}_key") do
    (attr, options, &:key)
  end
  
  # Define the storage method.
  define_method("#{attr}_storage") do
    (attr, options, &:storage)
  end
  
  # Define the url method.
  define_method("#{attr}_url") do |options = {}|
    (attr, options) do |meta|
      if meta.storage.respond_to?(:url)
        meta.storage.url meta.key, options
      else
        raise(
          NoMethodError,
          "The Echo Uploads file store you've selected, " +
          "#{meta.storage.class.to_s}, does not support the #url method."
        )
      end
    end
  end
  
  # Define the has_x? method. Returns true if a permanent or temporary file has been
  # persisted, or if a file (which may not be valid) has been uploaded this request
  # cycle.
  define_method("has_#{attr}?") do
    # Does this record have a permanent file?
    send("has_prm_#{attr}?") or
    
    # Did the submitted form "remember" a previously saved metadata record?
    send("has_tmp_#{attr}?") or
    
    # Has a new file been uploaded in this request cycle?
    send(attr).present?
  end
  
  # Define the has_prm_x? method. Returns true if the permanent metadata record
  # exists and has its owner set to this object.
  define_method("has_prm_#{attr}?") do
    send("#{attr}_metadata").present? and send("#{attr}_metadata").persisted?
  end
  
  # Define the has_tmp_x? method. Returns true if the record "remembers"
  # a temporary metadata record. (Typically because validation errors caused
  # the form to be redisplayed.)
  define_method("has_tmp_#{attr}?") do
    send("#{attr}_tmp_metadata").present?
  end
  
  # Define the read_x method. Delegates to the #read method of the store (e.g.
  # FilesystemStore).
  define_method("read_#{attr}") do
    (attr, options, &:read)
  end
  
  define_method("write_#{attr}") do |&block|
    echo_uploads_write_prm_file(attr, options, &block)
  end
  
  define_method("#{attr}_size") do
    (attr, options, &:size)
  end
  
  define_method("maybe_write_tmp_#{attr}") do
    echo_uploads_maybe_write_tmp_file(attr, options)
  end
  
  define_method("destroy_#{attr}") do
    (attr, options, &:destroy)
  end
  
  # Define the association with the metadata model.
  if options[:multiple]
    has_many("#{attr}_metadatas".to_sym,
      ->() { where(owner_attr: attr) },
      as: :owner, dependent: :destroy, class_name: '::EchoUploads::File'
    )
    
    alias_method attr.to_s.pluralize, "#{attr}_metadatas"
    
    define_method("#{attr}_metadata") do
      send("#{attr}_metadatas").first
    end
    
    define_method("#{attr}_metadata=") do |val|
      send("#{attr}_metadatas") << val
    end
  else
    has_one("#{attr}_metadata".to_sym,
      ->() { where(owner_attr: attr) },
      as: :owner, dependent: :destroy, class_name: '::EchoUploads::File'
    )
  end
  
  # Define the temp attribute for the metadata model.
  attr_accessor "#{attr}_tmp_metadata"
  
  echo_uploads_configure_tmp_file_writing attr, options
  
  echo_uploads_configure_prm_file_writing attr, options
end