class Paperclip::Attachment

the file upon assignment.
when the model saves, deletes when the model is destroyed, and processes
The Attachment class manages the files for a given attachment. It saves

def self.default_options

def self.default_options
  @default_options ||= {
    :convert_options       => {},
    :default_style         => :original,
    :default_url           => "/:attachment/:style/missing.png",
    :escape_url            => true,
    :restricted_characters => /[&$+,\/:;=?@<>\[\]\{\}\|\\\^~%# ]/,
    :filename_cleaner      => nil,
    :hash_data             => ":class/:attachment/:id/:style/:updated_at",
    :hash_digest           => "SHA1",
    :interpolator          => Paperclip::Interpolations,
    :only_process          => [],
    :path                  => ":rails_root/public:url",
    :preserve_files        => false,
    :processors            => [:thumbnail],
    :source_file_options   => {},
    :storage               => :filesystem,
    :styles                => {},
    :url                   => "/system/:class/:attachment/:id_partition/:style/:filename",
    :url_generator         => Paperclip::UrlGenerator,
    :use_default_time_zone => true,
    :use_timestamp         => true,
    :whiny                 => Paperclip.options[:whiny] || Paperclip.options[:whiny_thumbnails],
    :validate_media_type   => true,
    :adapter_options       => { hash_digest: Digest::MD5 },
    :check_validity_before_processing => true
  }
end

def able_to_store_created_at?

Check if attachment database table has a created_at field
def able_to_store_created_at?
  @instance.respond_to?("#{name}_created_at".to_sym)
end

def active_validator_classes

def active_validator_classes
  @instance.class.validators.map(&:class)
end

def after_flush_writes

called by storage after the writes are flushed and before @queued_for_write is cleared
def after_flush_writes
  unlink_files(@queued_for_write.values)
end

def as_json(options = nil)

def as_json(options = nil)
  to_s((options && options[:style]) || default_style)
end

def assign(uploaded_file)

new_user.avatar = old_user.avatar
attachment:
addition to form uploads, you can also assign another Paperclip
previous file for deletion, to be flushed away on #save of its host. In
errors, assigns attributes, and processes the file. It also queues up the
What gets called when you call instance.attachment = File. It clears
def assign(uploaded_file)
  @file = Paperclip.io_adapters.for(uploaded_file,
                                    @options[:adapter_options])
  ensure_required_accessors!
  ensure_required_validations!
  if @file.assignment?
    clear(*only_process)
    if @file.nil?
      nil
    else
      assign_attributes
      post_process_file
      reset_file_if_original_reprocessed
    end
  else
    nil
  end
end

def assign_attributes

def assign_attributes
  @queued_for_write[:original] = @file
  assign_file_information
  assign_fingerprint { @file.fingerprint }
  assign_timestamps
end

def assign_file_information

def assign_file_information
  instance_write(:file_name, cleanup_filename(@file.original_filename))
  instance_write(:content_type, @file.content_type.to_s.strip)
  instance_write(:file_size, @file.size)
end

def assign_fingerprint

def assign_fingerprint
  if instance_respond_to?(:fingerprint)
    instance_write(:fingerprint, yield)
  end
end

def assign_timestamps

def assign_timestamps
  if has_enabled_but_unset_created_at?
    instance_write(:created_at, Time.now)
  end
  instance_write(:updated_at, Time.now)
end

def blank?

def blank?
  not present?
end

def cleanup_filename(filename)

def cleanup_filename(filename)
  filename_cleaner.call(filename)
end

def clear(*styles_to_clear)

use #destroy.
nil to the attachment. Does NOT save. If you wish to clear AND save,
Clears out the attachment. Has the same effect as previously assigning
def clear(*styles_to_clear)
  if styles_to_clear.any?
    queue_some_for_delete(*styles_to_clear)
  else
    queue_all_for_delete
    @queued_for_write  = {}
    @errors            = {}
  end
end

def content_type

in the _content_type attribute of the model.
Returns the content_type of the file as originally assigned, and lives
def content_type
  instance_read(:content_type)
end

def created_at

lives in the _created_at attribute of the model.
Returns the creation time of the file as originally assigned, and
def created_at
  if able_to_store_created_at?
    time = instance_read(:created_at)
    time && time.to_f.to_i
  end
end

def default_options

def default_options
  {
    :timestamp => @options[:use_timestamp],
    :escape => @options[:escape_url]
  }
end

def default_style

def default_style
  @options[:default_style]
end

def destroy

wipe out the existing attachment but not save, use #clear.
nil to the attachment *and saving*. This is permanent. If you wish to
Destroys the attachment. Has the same effect as previously assigning
def destroy
  clear
  save
end

def dirty!

def dirty!
  @dirty = true
end

def dirty?

Returns true if there are changes that need to be saved.
def dirty?
  @dirty
end

def ensure_required_accessors! #:nodoc:

:nodoc:
def ensure_required_accessors! #:nodoc:
  %w(file_name).each do |field|
    unless @instance.respond_to?("#{@name_string}_#{field}") && @instance.respond_to?("#{@name_string}_#{field}=")
      raise Paperclip::Error.new("#{@instance.class} model missing required attr_accessor for '#{@name_string}_#{field}'")
    end
  end
end

def ensure_required_validations!

def ensure_required_validations!
  if missing_required_validator?
    raise Paperclip::Errors::MissingRequiredValidatorError
  end
end

def errors

Returns an array containing the errors on this attachment.
def errors
  @errors
end

def expiring_url(time = 3600, style_name = default_style)

testing.
storage implementations, but keep using filesystem storage for development and
Alias to +url+ that allows using the expiring_url method provided by the cloud
def expiring_url(time = 3600, style_name = default_style)
  url(style_name)
end

def extra_options_for(style) #:nodoc:

:nodoc:
def extra_options_for(style) #:nodoc:
  process_options(:convert_options, style)
end

def extra_source_file_options_for(style) #:nodoc:

:nodoc:
def extra_source_file_options_for(style) #:nodoc:
  process_options(:source_file_options, style)
end

def file?

Returns true if a file has been assigned.
def file?
  original_filename.present?
end

def filename_cleaner

the filename that will be cleaned. It should return the cleaned filename.
:filename_cleaner object. This object needs to respond to #call and takes
You can either specifiy :restricted_characters or you can define your own
def filename_cleaner
  @options[:filename_cleaner] || FilenameCleaner.new(@options[:restricted_characters])
end

def fingerprint

stored in the _fingerprint attribute of the model.
Returns the fingerprint of the file, if one's defined. The fingerprint is
def fingerprint
  instance_read(:fingerprint)
end

def flush_errors #:nodoc:

:nodoc:
def flush_errors #:nodoc:
  @errors.each do |error, message|
    [message].flatten.each {|m| instance.errors.add(name, m) }
  end
end

def has_enabled_but_unset_created_at?

Check if attachment database table has a created_at field which is not yet set
def has_enabled_but_unset_created_at?
  able_to_store_created_at? && !instance_read(:created_at)
end

def hash_key(style_name = default_style)

publicly viewable attachment.
Returns a unique hash suitable for obfuscating the URL of an otherwise
def hash_key(style_name = default_style)
  raise ArgumentError, "Unable to generate hash without :hash_secret" unless @options[:hash_secret]
  require 'openssl' unless defined?(OpenSSL)
  data = interpolate(@options[:hash_data], style_name)
  OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@options[:hash_digest]).new, @options[:hash_secret], data)
end

def initialize(name, instance, options = {})

+escape_url+ - Perform URI escaping to URLs. Defaults to true
+url_generator+ - the object used to generate URLs, using the interpolator. Defaults to Paperclip::UrlGenerator
+interpolator+ - the object used to interpolate filenames and URLs. Defaults to Paperclip::Interpolations
+filename_cleaner+ - An object that responds to #call(filename) that will strip unacceptable charcters from filename
+preserve_files+ - whether to keep files on the filesystem when deleting or clearing the attachment. Defaults to false
+processors+ - classes that transform the attachment. Defaults to [:thumbnail]
+source_file_options+ - flags passed to the +convert+ command that controls how the file is read
+convert_options+ - flags passed to the +convert+ command for processing
+hash_secret+ - a secret passed to the +hash_digest+
+hash_data+ - the relative URL for the hash data. This is interpolated using +interpolator+
+hash_digest+ - a string representing a class that will be used to hash URLs for obfuscation
+use_default_time_zone+ - related to +use_timestamp+. Defaults to true
+whiny+, +whiny_thumbnails+ - whether to raise when thumbnailing fails
+use_timestamp+ - whether to append an anti-caching timestamp to image URLs. Defaults to true
+storage+ - the storage mechanism. Defaults to :filesystem
+default_style+ - the style to use when an argument is not specified e.g. #url, #path
+default_url+ - a URL for the missing image
a special case that indicates all styles should be processed)
+only_process+ - style args to be run through the post-processor. This defaults to the empty list (which is
+styles+ - a hash of options for processing the attachment. See +has_attached_file+ for the details
+path+ - where on the filesystem to store the attachment. This is interpolated using +interpolator+
+url+ - a relative URL of the attachment. This is interpolated using +interpolator+

Options include:

+options+ is the same as the hash passed to +has_attached_file+.
+instance+ is the model object instance it's attached to, and
Creates an Attachment object. +name+ is the name of the attachment,
def initialize(name, instance, options = {})
  @name              = name.to_sym
  @name_string       = name.to_s
  @instance          = instance
  options = self.class.default_options.deep_merge(options)
  @options               = options
  @post_processing       = true
  @queued_for_delete     = []
  @queued_for_write      = {}
  @errors                = {}
  @dirty                 = false
  @interpolator          = options[:interpolator]
  @url_generator         = options[:url_generator].new(self)
  @source_file_options   = options[:source_file_options]
  @whiny                 = options[:whiny]
  initialize_storage
end

def initialize_storage #:nodoc:

:nodoc:
def initialize_storage #:nodoc:
  storage_class_name = @options[:storage].to_s.downcase.camelize
  begin
    storage_module = Paperclip::Storage.const_get(storage_class_name)
  rescue NameError
    raise Errors::StorageMethodNotFound, "Cannot load storage module '#{storage_class_name}'"
  end
  self.extend(storage_module)
end

def instance_read(attr)

for more details.
Reads the attachment-specific attribute on the instance. See instance_write
def instance_read(attr)
  getter = :"#{@name_string}_#{attr}"
  if instance.respond_to?(getter)
    instance.send(getter)
  end
end

def instance_respond_to?(attr)

calculations on fields we won't even store.
Determines whether the instance responds to this attribute. Used to prevent
def instance_respond_to?(attr)
  instance.respond_to?(:"#{name}_#{attr}")
end

def instance_write(attr, value)

"avatar_file_name" field (assuming the attachment is called avatar).
instance_write(:file_name, "me.jpg") will write "me.jpg" to the instance's
Writes the attachment-specific attribute on the instance. For example,
def instance_write(attr, value)
  setter = :"#{@name_string}_#{attr}="
  if instance.respond_to?(setter)
    instance.send(setter, value)
  end
end

def interpolate(pattern, style_name = default_style) #:nodoc:

:nodoc:
def interpolate(pattern, style_name = default_style) #:nodoc:
  interpolator.interpolate(pattern, self, style_name)
end

def log message #:nodoc:

:nodoc:
def log message #:nodoc:
  Paperclip.log(message)
end

def missing_required_validator?

def missing_required_validator?
  (active_validator_classes.flat_map(&:ancestors) & Paperclip::REQUIRED_VALIDATORS).empty?
end

def only_process

def only_process
  only_process = @options[:only_process].dup
  only_process = only_process.call(self) if only_process.respond_to?(:call)
  only_process.map(&:to_sym)
end

def original_filename

_file_name attribute of the model.
Returns the name of the file as originally assigned, and lives in the
def original_filename
  instance_read(:file_name)
end

def path(style_name = default_style)

URL, and the :bucket option refers to the S3 bucket.
on disk. If the file is stored in S3, the path is the "key" part of the
file is stored in the filesystem the path refers to the path of the file
Returns the path of the attachment as defined by the :path option. If the
def path(style_name = default_style)
  path = original_filename.nil? ? nil : interpolate(path_option, style_name)
  path.respond_to?(:unescape) ? path.unescape : path
end

def path_option

def path_option
  @options[:path].respond_to?(:call) ? @options[:path].call(self) : @options[:path]
end

def post_process(*style_args) #:nodoc:

:nodoc:
def post_process(*style_args) #:nodoc:
  return if @queued_for_write[:original].nil?
  instance.run_paperclip_callbacks(:post_process) do
    instance.run_paperclip_callbacks(:"#{name}_post_process") do
      if !@options[:check_validity_before_processing] || !instance.errors.any?
        post_process_styles(*style_args)
      end
    end
  end
end

def post_process_file

def post_process_file
  dirty!
  if post_processing
    post_process(*only_process)
  end
end

def post_process_style(name, style) #:nodoc:

:nodoc:
def post_process_style(name, style) #:nodoc:
  begin
    raise RuntimeError.new("Style #{name} has no processors defined.") if style.processors.blank?
    intermediate_files = []
    original = @queued_for_write[:original]
    @queued_for_write[name] = style.processors.
      reduce(original) do |file, processor|
      file = Paperclip.processor(processor).make(file, style.processor_options, self)
      intermediate_files << file unless file == @queued_for_write[:original]
      # if we're processing the original, close + unlink the source tempfile
      if name == :original
        @queued_for_write[:original].close(true)
      end
      file
    end
    unadapted_file = @queued_for_write[name]
    @queued_for_write[name] = Paperclip.io_adapters.
      for(@queued_for_write[name], @options[:adapter_options])
    unadapted_file.close if unadapted_file.respond_to?(:close)
    @queued_for_write[name]
  rescue Paperclip::Errors::NotIdentifiedByImageMagickError => e
    log("An error was received while processing: #{e.inspect}")
    (@errors[:processing] ||= []) << e.message if @options[:whiny]
  ensure
    unlink_files(intermediate_files)
  end
end

def post_process_styles(*style_args) #:nodoc:

:nodoc:
def post_process_styles(*style_args) #:nodoc:
  post_process_style(:original, styles[:original]) if styles.include?(:original) && process_style?(:original, style_args)
  styles.reject{ |name, style| name == :original }.each do |name, style|
    post_process_style(name, style) if process_style?(name, style_args)
  end
end

def process_options(options_type, style) #:nodoc:

:nodoc:
def process_options(options_type, style) #:nodoc:
  all_options   = @options[options_type][:all]
  all_options   = all_options.call(instance)   if all_options.respond_to?(:call)
  style_options = @options[options_type][style]
  style_options = style_options.call(instance) if style_options.respond_to?(:call)
  [ style_options, all_options ].compact.join(" ")
end

def process_style?(style_name, style_args) #:nodoc:

:nodoc:
def process_style?(style_name, style_args) #:nodoc:
  style_args.empty? || style_args.include?(style_name)
end

def processors

def processors
  processing_option = @options[:processors]
  if processing_option.respond_to?(:call)
    processing_option.call(instance)
  else
    processing_option
  end
end

def queue_all_for_delete #:nodoc:

:nodoc:
def queue_all_for_delete #:nodoc:
  return if !file?
  unless @options[:preserve_files]
    @queued_for_delete += [:original, *styles.keys].uniq.map do |style|
      path(style) if exists?(style)
    end.compact
  end
  instance_write(:file_name, nil)
  instance_write(:content_type, nil)
  instance_write(:file_size, nil)
  instance_write(:fingerprint, nil)
  instance_write(:created_at, nil) if has_enabled_but_unset_created_at?
  instance_write(:updated_at, nil)
end

def queue_some_for_delete(*styles)

def queue_some_for_delete(*styles)
  @queued_for_delete += styles.uniq.map do |style|
    path(style) if exists?(style)
  end.compact
end

def reprocess!(*style_args)

#reprocess! will lose data if the files are not kept.
inconsistencies in timing of S3 commands. It's possible that calling
NOTE: Calling reprocess WILL NOT delete existing files. This is due to
the post-process again.
thumbnails forcefully, by reobtaining the original file and going through
in the paperclip:refresh rake task and that's it. It will regenerate all
This method really shouldn't be called that often. Its expected use is
def reprocess!(*style_args)
  saved_flags = @options.slice(
    :only_process,
    :preserve_files,
    :check_validity_before_processing
  )
  @options[:only_process] = style_args
  @options[:preserve_files] = true
  @options[:check_validity_before_processing] = false
  begin
    assign(self)
    save
    instance.save
  rescue Errno::EACCES => e
    warn "#{e} - skipping file."
    false
  ensure
    @options.merge!(saved_flags)
  end
end

def reset_file_if_original_reprocessed

def reset_file_if_original_reprocessed
  instance_write(:file_size, @queued_for_write[:original].size)
  assign_fingerprint { @queued_for_write[:original].fingerprint }
  reset_updater
end

def reset_updater

def reset_updater
  if instance.respond_to?(updater)
    instance.send(updater)
  end
end

def save

the instance's errors and returns false, cancelling the save.
Saves the file, if there are no errors. If there are, it flushes them to
def save
  flush_deletes unless @options[:keep_old_files]
  process = only_process
  if process.any? && !process.include?(:original)
    @queued_for_write.except!(:original)
  end
  flush_writes
  @dirty = false
  true
end

def size

_file_size attribute of the model.
Returns the size of the file as originally assigned, and lives in the
def size
  instance_read(:file_size) || (@queued_for_write[:original] && @queued_for_write[:original].size)
end

def staged?

:nodoc:
def staged?
  ! @queued_for_write.empty?
end

def staged_path(style_name = default_style)

:nodoc:
def staged_path(style_name = default_style)
  if staged?
    @queued_for_write[style_name].path
  end
end

def styles

def styles
  if @options[:styles].respond_to?(:call) || @normalized_styles.nil?
    styles = @options[:styles]
    styles = styles.call(self) if styles.respond_to?(:call)
    @normalized_styles = styles.dup
    styles.each_pair do |name, options|
      @normalized_styles[name.to_sym] = Paperclip::Style.new(name.to_sym, options.dup, self)
    end
  end
  @normalized_styles
end

def time_zone

time zone ensures that results are consistent across all threads.
The time zone to use for timestamp interpolation. Using the default
def time_zone
  @options[:use_default_time_zone] ? Time.zone_default : Time.zone
end

def to_s style_name = default_style

Alias to +url+
def to_s style_name = default_style
  url(style_name)
end

def unlink_files(files)

def unlink_files(files)
  Array(files).each do |file|
    file.close unless file.closed?
    begin
      file.unlink if file.respond_to?(:unlink)
    rescue Errno::ENOENT
    end
  end
end

def updated_at

lives in the _updated_at attribute of the model.
Returns the last modified time of the file as originally assigned, and
def updated_at
  time = instance_read(:updated_at)
  time && time.to_f.to_i
end

def updater

def updater
  :"#{name}_file_name_will_change!"
end

def uploaded_file

Returns the uploaded file if present.
def uploaded_file
  instance_read(:uploaded_file)
end

def url(style_name = default_style, options = {})

def url(style_name = default_style, options = {})
  if options == true || options == false # Backwards compatibility.
    @url_generator.for(style_name, default_options.merge(:timestamp => options))
  else
    @url_generator.for(style_name, default_options.merge(options))
  end
end