lib/carrierwave/mount.rb
module CarrierWave
##
# If a Class is extended with this module, it gains the mount_uploader
# method, which is used for mapping attributes to uploaders and allowing
# easy assignment.
#
# You can use mount_uploader with pretty much any class, however it is
# intended to be used with some kind of persistent storage, like an ORM.
# If you want to persist the uploaded files in a particular Class, it
# needs to implement a `read_uploader` and a `write_uploader` method.
#
module Mount
##
# === Returns
#
# [Hash{Symbol => CarrierWave}] what uploaders are mounted on which columns
#
def uploaders
@uploaders ||= superclass.respond_to?(:uploaders) ? superclass.uploaders.dup : {}
end
def uploader_options
@uploader_options ||= superclass.respond_to?(:uploader_options) ? superclass.uploader_options.dup : {}
end
##
# Return a particular option for a particular uploader
#
# === Parameters
#
# [column (Symbol)] The column the uploader is mounted at
# [option (Symbol)] The option, e.g. validate_integrity
#
# === Returns
#
# [Object] The option value
#
def uploader_option(column, option)
if uploader_options[column].has_key?(option)
uploader_options[column][option]
else
uploaders[column].send(option)
end
end
##
# Mounts the given uploader on the given column. This means that assigning
# and reading from the column will upload and retrieve files. Supposing
# that a User class has an uploader mounted on image, you can assign and
# retrieve files like this:
#
# @user.image # => <Uploader>
# @user.image.store!(some_file_object)
#
# @user.image.url # => '/some_url.png'
#
# It is also possible (but not recommended) to omit the uploader, which
# will create an anonymous uploader class.
#
# Passing a block makes it possible to customize the uploader. This can be
# convenient for brevity, but if there is any significant logic in the
# uploader, you should do the right thing and have it in its own file.
#
# === Added instance methods
#
# Supposing a class has used +mount_uploader+ to mount an uploader on a column
# named +image+, in that case the following methods will be added to the class:
#
# [image] Returns an instance of the uploader only if anything has been uploaded
# [image=] Caches the given file
#
# [image_url] Returns the url to the uploaded file
#
# [image_cache] Returns a string that identifies the cache location of the file
# [image_cache=] Retrieves the file from the cache based on the given cache name
#
# [remote_image_url] Returns previously cached remote url
# [remote_image_url=] Retrieve the file from the remote url
#
# [remove_image] An attribute reader that can be used with a checkbox to mark a file for removal
# [remove_image=] An attribute writer that can be used with a checkbox to mark a file for removal
# [remove_image?] Whether the file should be removed when store_image! is called.
#
# [store_image!] Stores a file that has been assigned with +image=+
# [remove_image!] Removes the uploaded file from the filesystem.
#
# [image_integrity_error] Returns an error object if the last file to be assigned caused an integrity error
# [image_processing_error] Returns an error object if the last file to be assigned caused a processing error
# [image_download_error] Returns an error object if the last file to be remotely assigned caused a download error
#
# [image_identifier] Reads out the identifier of the file
#
# === Parameters
#
# [column (Symbol)] the attribute to mount this uploader on
# [uploader (CarrierWave::Uploader)] the uploader class to mount
# [options (Hash{Symbol => Object})] a set of options
# [&block (Proc)] customize anonymous uploaders
#
# === Options
#
# [:mount_on => Symbol] if the name of the column to be serialized to differs you can override it using this option
# [:ignore_integrity_errors => Boolean] if set to true, integrity errors will result in caching failing silently
# [:ignore_processing_errors => Boolean] if set to true, processing errors will result in caching failing silently
#
# === Examples
#
# Mounting uploaders on different columns.
#
# class Song
# mount_uploader :lyrics, LyricsUploader
# mount_uploader :alternative_lyrics, LyricsUploader
# mount_uploader :file, SongUploader
# end
#
# This will add an anonymous uploader with only the default settings:
#
# class Data
# mount_uploader :csv
# end
#
# this will add an anonymous uploader overriding the store_dir:
#
# class Product
# mount_uploader :blueprint do
# def store_dir
# 'blueprints'
# end
# end
# end
#
def mount_uploader(column, uploader=nil, options={}, &block)
mount_base(column, uploader, options.merge(multiple: false), &block)
mod = Module.new
include mod
mod.class_eval <<-RUBY, __FILE__, __LINE__+1
def #{column}
_mounter(:#{column}).uploaders[0] ||= _mounter(:#{column}).blank_uploader
end
def #{column}=(new_file)
_mounter(:#{column}).cache([new_file])
end
def #{column}_url(*args)
#{column}.url(*args)
end
def #{column}_cache
_mounter(:#{column}).cache_names[0]
end
def #{column}_cache=(cache_name)
_mounter(:#{column}).cache_names = [cache_name]
end
def remote_#{column}_url
[_mounter(:#{column}).remote_urls].flatten[0]
end
def remote_#{column}_url=(url)
_mounter(:#{column}).remote_urls = url
end
def remote_#{column}_request_header=(header)
_mounter(:#{column}).remote_request_headers = [header]
end
def #{column}_identifier
_mounter(:#{column}).read_identifiers[0]
end
def #{column}_integrity_error
#{column}_integrity_errors.last
end
def #{column}_processing_error
#{column}_processing_errors.last
end
def #{column}_download_error
#{column}_download_errors.last
end
RUBY
end
##
# Mounts the given uploader on the given array column. This means that
# assigning and reading from the array column will upload and retrieve
# multiple files. Supposing that a User class has an uploader mounted on
# images, and that images can store an array, for example it could be a
# PostgreSQL JSON column. You can assign and retrieve files like this:
#
# @user.images # => []
# @user.images = [some_file_object]
# @user.images # => [<Uploader>]
#
# @user.images[0].url # => '/some_url.png'
#
# It is also possible (but not recommended) to omit the uploader, which
# will create an anonymous uploader class.
#
# Passing a block makes it possible to customize the uploader. This can be
# convenient for brevity, but if there is any significant logic in the
# uploader, you should do the right thing and have it in its own file.
#
# === Added instance methods
#
# Supposing a class has used +mount_uploaders+ to mount an uploader on a column
# named +images+, in that case the following methods will be added to the class:
#
# [images] Returns an array of uploaders for each uploaded file
# [images=] Caches the given files
#
# [images_urls] Returns the urls to the uploaded files
#
# [images_cache] Returns a string that identifies the cache location of the files
# [images_cache=] Retrieves the files from the cache based on the given cache name
#
# [remote_image_urls] Returns previously cached remote urls
# [remote_image_urls=] Retrieve files from the given remote urls
#
# [remove_images] An attribute reader that can be used with a checkbox to mark the files for removal
# [remove_images=] An attribute writer that can be used with a checkbox to mark the files for removal
# [remove_images?] Whether the files should be removed when store_image! is called.
#
# [store_images!] Stores all files that have been assigned with +images=+
# [remove_images!] Removes the uploaded file from the filesystem.
#
# [image_integrity_errors] Returns error objects of files which failed to pass integrity check
# [image_processing_errors] Returns error objects of files which failed to be processed
# [image_download_errors] Returns error objects of files which failed to be downloaded
#
# [image_identifiers] Reads out the identifiers of the files
#
# === Parameters
#
# [column (Symbol)] the attribute to mount this uploader on
# [uploader (CarrierWave::Uploader)] the uploader class to mount
# [options (Hash{Symbol => Object})] a set of options
# [&block (Proc)] customize anonymous uploaders
#
# === Options
#
# [:mount_on => Symbol] if the name of the column to be serialized to differs you can override it using this option
# [:ignore_integrity_errors => Boolean] if set to true, integrity errors will result in caching failing silently
# [:ignore_processing_errors => Boolean] if set to true, processing errors will result in caching failing silently
#
# === Examples
#
# Mounting uploaders on different columns.
#
# class Song
# mount_uploaders :lyrics, LyricsUploader
# mount_uploaders :alternative_lyrics, LyricsUploader
# mount_uploaders :files, SongUploader
# end
#
# This will add an anonymous uploader with only the default settings:
#
# class Data
# mount_uploaders :csv_files
# end
#
# this will add an anonymous uploader overriding the store_dir:
#
# class Product
# mount_uploaders :blueprints do
# def store_dir
# 'blueprints'
# end
# end
# end
#
def mount_uploaders(column, uploader=nil, options={}, &block)
mount_base(column, uploader, options.merge(multiple: true), &block)
mod = Module.new
include mod
mod.class_eval <<-RUBY, __FILE__, __LINE__+1
def #{column}
_mounter(:#{column}).uploaders
end
def #{column}=(new_files)
_mounter(:#{column}).cache(new_files)
end
def #{column}_urls(*args)
_mounter(:#{column}).urls(*args)
end
def #{column}_cache
names = _mounter(:#{column}).cache_names
names.to_json if names.present?
end
def #{column}_cache=(cache_name)
_mounter(:#{column}).cache_names = JSON.parse(cache_name) if cache_name.present?
end
def remote_#{column}_urls
_mounter(:#{column}).remote_urls
end
def remote_#{column}_urls=(urls)
_mounter(:#{column}).remote_urls = urls
end
def remote_#{column}_request_headers=(headers)
_mounter(:#{column}).remote_request_headers = headers
end
def #{column}_identifiers
_mounter(:#{column}).read_identifiers
end
RUBY
end
private
def mount_base(column, uploader=nil, options={}, &block)
include CarrierWave::Mount::Extension
uploader = build_uploader(uploader, column, &block)
uploaders[column.to_sym] = uploader
uploader_options[column.to_sym] = options
# Make sure to write over accessors directly defined on the class.
# Simply super to the included module below.
class_eval <<-RUBY, __FILE__, __LINE__+1
def #{column}; super; end
def #{column}=(new_file); super; end
RUBY
mod = Module.new
include mod
mod.class_eval <<-RUBY, __FILE__, __LINE__+1
def #{column}?
_mounter(:#{column}).present?
end
def remove_#{column}
_mounter(:#{column}).remove
end
def remove_#{column}!
_mounter(:#{column}).remove!
self.remove_#{column} = true
write_#{column}_identifier
end
def remove_#{column}=(value)
_mounter(:#{column}).remove = value
end
def remove_#{column}?
_mounter(:#{column}).remove?
end
def store_#{column}!
_mounter(:#{column}).store!
end
def #{column}_integrity_errors
_mounter(:#{column}).integrity_errors
end
def #{column}_processing_errors
_mounter(:#{column}).processing_errors
end
def #{column}_download_errors
_mounter(:#{column}).download_errors
end
def write_#{column}_identifier
_mounter(:#{column}).write_identifier
end
def mark_remove_#{column}_false
_mounter(:#{column}).remove = false
end
def reset_previous_changes_for_#{column}
_mounter(:#{column}).reset_changes!
end
def remove_previously_stored_#{column}
_mounter(:#{column}).remove_previous
end
def remove_rolled_back_#{column}
_mounter(:#{column}).remove_added
end
RUBY
end
def build_uploader(uploader, column, &block)
uploader ||= CarrierWave::Uploader::Base
return uploader unless block_given?
uploader = Class.new(uploader)
const_set("CarrierWave#{column.to_s.camelize}Uploader", uploader)
uploader.class_eval(&block)
uploader
end
module Extension
##
# overwrite this to read from a serialized attribute
#
def read_uploader(column); end
##
# overwrite this to write to a serialized attribute
#
def write_uploader(column, identifier); end
private
def initialize_dup(other)
@_mounters = @_mounters.dup
super
end
def _mounter(column)
# We cannot memoize in frozen objects :(
return Mounter.build(self, column) if frozen?
@_mounters ||= {}
@_mounters[column] ||= Mounter.build(self, column)
end
end # Extension
end # Mount
end # CarrierWave