Batch Importing and Watermarking with Attachment_fu

In this exercise I had Posts with many image attachments attached with attachment_fu and rmagick. I also had a bunch of old data that i wanted to import in using a rake task and finally all image attachments needed to be watermarked.

First my assumptions:

  • “A Post can have many Image attachments”
  • You have the attachment_fu plugin installed already

Here is how it’s done.

Set constants for your image queue path, the directory for completed imports, and the watermark image path.

Add to bottom of /config/configuration/environment.rb:

  def logger
    RAILS_DEFAULT_LOGGER
  end

    # CONSTANTS ####################3
    IMAGES_QUEUE_PATH = "#{RAILS_ROOT}/images_import/queue/"
    IMAGES_DONE_PATH = "#{RAILS_ROOT}/images_import/done/"
    WATERMARK_IMAGE = "#{RAILS_ROOT}/config/watermark.png"

Configure your /models/image.rb as follows:

class Image  :image,
                 :storage => :file_system,
                 #:max_size => 500.kilobytes,
                 #:resize_to => '320x200>',
                 :thumbnails => { :thumb => '320x320>' }
  validates_as_attachment
  after_save :watermark_image

  def watermark_image
    unless self.thumbnail?
      image_to_be_watermarked = Magick::Image.read("#{RAILS_ROOT}/public/#{self.public_filename}").first
      watermark = Magick::Image.read(WATERMARK_IMAGE).first
      watermarked_image = image_to_be_watermarked.composite(watermark, Magick::SouthEastGravity, Magick::OverCompositeOp)
      watermarked_image.write("#{RAILS_ROOT}/public/#{self.public_filename}")
    end
  end
end

Setup /models/post.rb as follows:

class Post  :destroy

  def image_attributes=(image_attributes)
    image_attributes.each do |attributes|
      if attributes[:id].blank?
        images.build(attributes)
      else
        image = images.detect { |pic| pic.id == attributes[:id].to_i }
        logger.debug("image #{attributes[:id].to_i}, is_default as: #{attributes[:is_default].to_i}")
        image.attributes = attributes
      end
    end
  end

end

Create /models/local_file.rb and populate it with this:

require 'tempfile'
class LocalFile
 # The filename, *not* including the path, of the "uploaded" file
 attr_reader :original_filename
 # The content type of the "uploaded" file
 attr_reader :content_type

 def initialize(path)
  raise "#{path} file does not exist" unless File.exist?(path)
  content_type ||= @@image_mime_types[File.extname(path).downcase]
  raise "Unrecognized MIME type for #{path}" unless content_type
  @content_type = content_type
  @original_filename = File.basename(path)
  @tempfile = Tempfile.new(@original_filename)
  FileUtils.copy_file(path, @tempfile.path)
 end

 def path #:nodoc:
  @tempfile.path
 end
 alias local_path path

 def method_missing(method_name, *args, &block) #:nodoc:
  @tempfile.send(method_name, *args, &block)
 end

 @@image_mime_types ||= { ".gif" => "image/gif", ".ief" => "image/ief", ".jpe" => "image/jpeg", ".jpeg" => "image/jpeg", ".jpg" => "image/jpeg", ".pbm" => "image/x-portable-bitmap", ".pgm" => "image/x-portable-graymap", ".png" => "image/png", ".pnm" => "image/x-portable-anymap", ".ppm" => "image/x-portable-pixmap", ".ras" => "image/cmu-raster", ".rgb" => "image/x-rgb", ".tif" => "image/tiff", ".tiff" => "image/tiff", ".xbm" => "image/x-xbitmap", ".xpm" => "image/x-xpixmap", ".xwd" => "image/x-xwindowdump" }.freeze
end

Now we create a new rake file and task to do our importing.
Create /libs/tasks/import.rake

desc "Imports images/post content from set constant path"
task :do_gallery_import => :environment do
  require 'logger'
  ignore = ['.','..','body.txt','title.txt']
  directory = Dir.new(IMAGES_QUEUE_PATH)
  directory.each do |image_dir|
    unless ignore.include?(image_dir)
      gallery_path = IMAGES_QUEUE_PATH + image_dir
      logger.debug("Importing: " + gallery_path)

      if !create_new_post(gallery_path).nil?
        logger.debug("Import Complete.")
        #Move the completed import dir
        move_done(image_dir)
      end
    end
  end
end

#Creates a new post by reading the title.txt, body.txt and all images
def create_new_post(directory)
  p = Post.new
  p.title = File.new(directory + "/title.txt").read.chomp
  p.body = File.new(directory + "/body.txt").read.chomp

  get_pics_in_directory(directory).each do |image|
    p.image_attributes=[{"uploaded_data"=>LocalFile.new(image)}]
  end

  p.save

end

#Returns a list of all images in a "working" directory
def get_pics_in_directory(directory)
  ignore = ['.','..','body.txt','title.txt'] 

  file_list = []
  image_directory = Dir.new(directory)
  image_directory.each do |pic|
    unless ignore.include?(pic)
        file_list << directory + "/#{pic.to_s}"
    end
  end

  return file_list
end

def move_done(imported_directory)
  gallery_path = IMAGES_QUEUE_PATH + imported_directory
  gallery_done_path = IMAGES_DONE_PATH + imported_directory
  system "mv #{gallery_path} #{gallery_done_path}"
  logger.debug("Moved #{gallery_path} to #{gallery_done_path}")
end

Create the following directories:
/images_import/done/
/images_import/queue/