Saving TMail into an ActiveRecord model

2007-11-22 17:00:18 +0000

ActiveRecord works well when we are saving strings and integers, but what if you want to save a real, live, honest-to-God Ruby OBJECT like a TMail::Mail instance?? Well.. serialize to the rescue!

Today I had an interesting need.

I am making an Email transfer system, which, in part, needs to be able to save an Email for later inspection, retrieval or queries.

While this system is not a Ruby on Rails app, I decided to use ActiveRecord anyway as it is an awesome database interface, plugged in with a local copy of SQLite3, I had my entire datastore handy.

The system that this is replacing stores the objects on disk using Ruby’ marshalling methods, but ActiveRecord has a much cleaner implementation for use when you are saving to and from a database, it is invoked using the serialize method.

So, in my case I have a class called Email, which, when you create it with a raw source of an email text, initializes itself and parses that email text into a TMail::Mail object which it stores as an ActiveRecord attribute.

So, I wrote a spec like this:

1
2
3
4
5
6
email_text = IO.read("#{File.dirname(__FILE__)}/../fixtures/raw_email")

it "should create a tmail object as a TMail::Mail object" do
  mail = {:email => email_text}
  mail.email.class.should == TMail::Mail
end

To make this pass:

1
2
3
4
5
6
7
require 'tmail'
class Email < ActiveRecord::Base
  def initialize(email)
    super
    self.email = TMail::Mail.parse(email[:email])
  end
end

OK, that passes, pretty straight forward.

But now, what if I call:

1
2
3
mail.save
mail = Email.find(:first)
mail.email.class #=> String 

So that doesn’t work, what I want is to be able to save that object to a database, and then, when I recall it from the database, have the same model I started with.

OK, so first, we’ll write a spec for that:

1
2
3
4
5
6
it "should save itself to the and serialize it's mail component" do
  mail = create_email
  mail.save!
  mail = Mailer::Email.find(:first)
  mail.email.class.should == TMail::Mail
end

That fails, now we use the serialize method of ActiveRecord.

To do this, you need to make the “mail” record of the Emails table a TEXT field. This is important.

Then we go into the class definition and add the following:

1
2
3
4
5
6
7
8
9
10
require 'tmail'
class Email < ActiveRecord::Base
  
  serialize :email
    
  def initialize(email)
    super
    self.email = TMail::Mail.parse(email[:email])
  end
end

With that serialize :email, the ActiveRecord class will convert the TMail object into a text string through YAML and save it to the database as a text string. When it recalls it from the database, it will read this string via YAML and reinstate the object in all it’s ruby glory.

Pretty handy!

blogLater

Mikel