Paperclip is a fantastic plugin for working with uploads from a user, but sometimes you need to manipulate the attachments outside of a web-browser-upload environment.

I needed to do that the other week and it was a complete pain, but I figured it out. The docs for paperclip mention that you can do

a.attachment = b.attachment

.. but that’s only useful if you want to move an attachment between two object and the attachment already exists. But what if you want to create the attachment from scratch? Paperclip::Attachment.assignment only works for re-assigning attachments that already are created.

After some poking around I found that the way (one way, anyway) is to make the object (where Paperclip has an attachment) think it’s being hit by a web browser:


data = method_that_reads_an_image_file_contents

tmpfile = Tempfile.new('temp')
file = File.new(tmpfile.path, 'w')
file.puts data
file.close

object.create(
:attachment => tmpfile
)

You can do similar for updating an existing object:

object.update_attributes(:attachment => tmpfile)

That worked almost perfectly, until I realized that the attachments were all being stored in Paperclip with the ‘application/octet-stream’ mime-type, which is a killer because the users web browser will, say, ask the user to download an image instead of just displaying it on screen.

And it’s no use just updating the ‘attachment_file_type’ column with the right mime type, either smart guy.

Instead I found I needed to do some hackery — Paperclip divines the file type partly by the file extension and the built-in Tempfile library in ruby does not allow you to set the extension of your tempfile. And with no extension set, Paperclip was using the default “octet-stream”.

In the end I had to resort to this:


def write_to_tmpfile(data,mime=nil)
tmpfile = Tempfile.new('tmp_'+rand(10).to_s)
file = File.new(tmpfile.path, 'w')
file.puts data
file.close

if mime
ext = mime.slice(mime.index('/')+1, mime.size)
File.rename tmpfile.path, "#{tmpfile.path}.#{ext}"
File.new("#{tmpfile.path}.#{ext}", 'r')
else
tmpfile
end
end

event.update_attributes(:attachment => write_to_tempfile(file_data))

The ‘write_to_tmpfile’ does the ugly work of renaming the temp file created by Tempfile to be the same as the file being inserted, and that in turn allows Paperclip to properly set the mime time.