attachment_fu Now With Local File Fu 10

Posted by Ben Reubenstein Fri, 04 Jan 2008 17:50:00 GMT

In the beginning there was file_column. It was an excellent plugin for handling file uploads and image processing with the added bonus of being able to simply pass a file to it and have it work without a file upload via a form. One thing that file_column didn't do was fill in your db with file attribute goodness that could be used to create logic around a particular file. attachment_fu handled this along with the ability to use multiple image processors. For detailed info on attachment_fu, check out Mike Clark's article.

In order to add some local_file_fu to attachment_fu so you can pass a local file directly to it, you have to take your local file and turn it into a temporary file that you can pass to attachment_fu's uploaded_data method. I altered the solution outlined here for my solution.

1. Create a class in your models directory in a file called local_file.rb.

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)]
  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
end


2. In order for attachment_fu to pass validations, you need to set the mime type of the file. This would usually come from the form when it is uploaded, but since we are using a local file, we'll set our mime types in environment.rb. At the end of the file add the various mime types you will need:

@@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

3. Now in your code that creates the model that has_attachments you can simply do the following:

model = Model.new()
model.uploaded_data = LocalFile.new(FULL_PATH_TO_FILE)
model.save


As always, comment on anything you have issues with or suggestions.
Comments

Leave a comment

  1. Avatar
    Micah Calabrese about 1 month later:

    This is great, thanks! One note, I had to downcase the file extensions to account for extensions in caps:

    contenttype ||= @@imagemime_types[File.extname(path).downcase]

  2. Avatar
    Andy Stewart 4 months later:

    You could also use the MIME::Types gem, so your code doesn't have to become expert on MIME types.

    There's an example in this post by, ahem, me, in the section Missing MIME Type.

  3. Avatar
    Nick 5 months later:

    Great one! I've spent ages trying to work this out.

  4. Avatar
    Jorge 6 months later:

    I'm using Paperclip and it has the same issue. I did what you guys did above and added a "to_tempfile" method that returns the tempfile as paperclip needs this in its Attachment.assign method. Everything works out fine except that it corrupts the original file. Any thoughts?

  5. Avatar
    Ben Reubenstein 6 months later:

    @jorge You might have better luck trying the paperclip google group. Would have to see some code to give some thoughts ;).

  6. Avatar
    Fred Z 6 months later:

    I was able to simplify the class a bit, and combine the mime types. Here it is:

    class Admin::LocalAttachment < File

    require 'mime/types'

    attrreader :originalfilename attrreader :contenttype

    def initialize( path ) super( path ) raise "#{path}: file does not exist" unless File.exist?( path ) @original_filename = File.basename( path ) mime = MIME::Types.typefor( @originalfilename )[0] raise "Unrecognized MIME type for #{path}" if mime.blank? @contenttype = mime.contenttype end

    alias local_path path

    def size #:nodoc File.size( self.path ) end

    end

  7. Avatar
    Foton 7 months later:

    Do you have any idea how add a attachment from DB. I have incoming attachments stored in MySql table (filename, content [blob],...) and I am processing them to be stored on filesystem. With plugin 'acts_as_attachement' I just use:

    Priloha.new({ :filename => p_incoming.filename, :attachmentdata => pincoming.content})

    but with plugin 'attachmentfu' this way no longer works. Using :uploadeddata needs something more, then just raw file data.

    Priloha.new({ :filename => p_incoming.filename, :ulploadeddata => pincoming.content})

  8. Avatar
    Foton 7 months later:

    Finaly I solved it. :

    Priloha.new({ :filename => p_incoming.filename, :tempdata => pincoming.content, :size => p_incoming.content.size, :contenttype => p.incoming.contenttype })

  9. Avatar
    Daniel 8 months later:

    How can I use it with server generated content (PDFs) without saving this PDF to the Filesystem. Can I create an empty asset and later saving the PDF document to this asset? Why I have to create a temp-file and afterwards coping it to the asset?

  10. Avatar
    findchris over 2 years later:

    Using this technique, is anyone ever getting this error message?

    > Errno::EMFILE: Too many open files - /tmp/icon.jpg >

    I supposed the tempfile isn't ever being closed explicitly; does it need to be?

    Cheers, Chris

Comments