The last post showed how the use of Test Driven Development can help create Rails apps that can receive email offline that can be plugged into one of the incoming email solutions to receive email. The example was a movie poster model that took the subject of the email and added it as the title of that movie. The thing is what’s a movie poster without images? In this post I am going to cover how you can extract an image out of an email and attach it to a model using Paperclip.
We’ve already covered how to receive the email using CloudMailin and the app’s controller and pass it onto a mailer so I’m only going to cover the code we need in our mailer and models here.
Getting Paperclip setup
The first step is to get paperclip installed into our app. I would recommend following the instructions in the Paperclip readme here to get your app set up with Paperclip and test everything works with a normal controller and form. Our movie poster will need the following setup:
We start by adding the migration to add the Paperclip columns to the migration
t.string :poster_file_name t.string :poster_content_type t.integer :poster_file_size t.datetime :poster_updated_at
We also need to add the magic Paperclip line to our Movie model
has_attached_file :poster, :styles => { :medium => "300x300>", :thumb => "100x100>" }
So far everything has been exactly the same as we would normally have to do if we were using the normal forms based approach to upload, next we’ll add the code to our mailer.
Setting up the Mailer to Receive Attachments
Normally when Rails receives an uploaded file it has to be through a multipart/form-data. When a Rails app receives these files it recognises the attachment parameter and behind the scenes a temporary file is created containing that content. This is to stop the attachment being stored in memory. This file is then extended by Paperclip to contain a few additional bits of information it’s original file name and the content type of the upload. Our email however is stored in memory and it’s attachments don’t get stored as files. For that reason we need to emulate the temporary file so that Paperclip can correctly work with out attachments.
In order to do this we can use the following mailer:
class MovieMailer < ActionMailer::Base # Create an attachment file with some paperclip aware features class AttachmentFile < Tempfile attr_accessor :original_filename, :content_type end # Called whenever a message is received on the movies controller def receive(message) # For now just take the first attachment and assume there is only one attachment = message.attachments.first # Create the movie itself Movie.create do |movie| movie.title = message.subject # Create an AttachmentFile subclass of a tempfile with paperclip aware features and add it poster_file = AttachmentFile.new('test.jpg') poster_file.write attachment.decoded poster_file.flush poster_file.original_filename = attachment.filename poster_file.content_type = attachment.mime_type movie.poster = poster_file end end end
Here we can see the AttachmentFile class that fakes the parameters needed by Paperclip to work with the file. We then create the AttachmentFile class passing in a filename to be used for the temporary file. Then we write to the file, flush the contents and set the required parameters for Paperclip.
Now when we receive an email the first attachment is taken from the email and stored as our movie poster with by Paperclip.
If you don’t want to use a temporary file you can also use a StringIO object and see the same benefits.
In addition to this @pedrodelgallego pointed out another posible solution that extends a StingIO class anonymously using class eval using the following code:
attachment = message.attachments.first file = StringIO.new(attachment.decoded) file.class.class_eval { attr_accessor :original_filename, :content_type } file.original_filename = attachment.filename file.content_type = attachment.mime_type
Hopefully this will be of use for anyone trying to store attachments extracted from emails. The real benefit is that Paperclip doesn’t only work with images, you can store all sorts of attachments and push them to S3 if you need to. If anyone has any other suggestions or better ways to achieve this I’d also love to hear them.