Using ActiveRecord Composed Of

1:02 PM EDT Saturday, April 19 2008

ActiveRecord has a feature called composed_of that allows you to take multiple fields of a model and treat them as one object. Let's say you have a requirement for your application to track the phone number of each user. Also, you need to be able to query for users by parts of the phone number like area code, prefix, line number and extenstion. So the phone number (212) 123-4567 x55 needs to be stored in separate fields as :area_code => 212, :prefix => 123, :line_number => 4567 and :extenstion => 55. So first we create a rails app with a user record with those fields:

$ rails myapp
$ cd myapp
$ script/generate model user name:string \
  phone_number_area_code:integer \
  phone_number_prefix:integer \
  phone_number_line_number:integer \
  phone_number_extension:integer
$ rake db:migrate

What we would like to do is treat all of those phone_number_* columns as one object. So first let's create a base object that has some of the plumbing functionality. Put this in app/models/value_object.rb:

class ValueObject

  class << self
    def has_fields(*args)
      (class << self; self; end).send(:define_method, :fields) do
        args.map(&:to_s)
      end
      fields.each{|f| attr_reader f}
    end
    def mapping
      fields.map{|e| ["#{name.underscore}_#{e}", e]}      
    end
    def mapper
      lambda do |params|
        PhoneNumber.new *PhoneNumber.fields.map{|e| params[e].to_i}
      end
    end
  end

  def initialize(*args)
    args.each_with_index do |a, i|
      instance_variable_set "@#{self.class.fields[i]}", args[i]
    end
  end

  def to_s
    "(#{area_code}) #{prefix}-#{line_number} x#{extension}"
  end

  def ==(o)
    o && self.class.fields.all?{|f| self.send(f) == o.send(f)}
  end

end

And here's our subclass of that specific for the phone number. Add the PhoneNumber class at app/models/phone_number.rb:

class PhoneNumber < ValueObject

  has_fields :area_code, :prefix, :line_number, :extension

  def to_s
    "(#{area_code}) #{prefix}-#{line_number} x#{extension}"
  end

end

Rather than explain what this does, I'll just give you the specs:

require File.dirname(__FILE__) + '/../spec_helper'

describe PhoneNumber do

  before(:each) do
    @phone_number = PhoneNumber.new(212, 123, 4567, 55)
  end  

  it "should have an area_code getter" do
    @phone_number.area_code.should == 212
  end

  it "should have an area_code getter" do
    @phone_number.prefix.should == 123
  end

  it "should have an area_code getter" do
    @phone_number.line_number.should == 4567
  end

  it "should have an area_code getter" do
    @phone_number.extension.should == 55
  end

  describe "#==" do
    it "should be equal if both number match" do
      @phone_number.==(PhoneNumber.new(212, 123, 4567, 55)).should be_true
    end
    it "should not be equal if both numbers do not match" do
      @phone_number.==(PhoneNumber.new(212, 123, 4567, 54)).should be_false
    end
  end

  describe "#to_s" do
    it "should format the phone number as (212) 123-4567 x55" do
      @phone_number.to_s.should == "(212) 123-4567 x55"
    end
  end

end

After you've had a chance to read through that, it should be pretty clear what the PhoneNumber class does. Next we modify the User model to use this class:

class User < ActiveRecord::Base
  composed_of :phone_number, :mapping => PhoneNumber.mapping, &PhoneNumber.mapper
end

The ValueObject base class provides us with the methods mapping and mapper, which are needed to get our User model's phone_number method to do what we want. Here's what we want it to do:

it "should be able to set phone number from hash" do
  @user.phone_number = {"area_code" => "212", "prefix" => "123", 
    "line_number" => "4567", "extension" => "55"}
  @user.phone_number.should == PhoneNumber.new(212, 123, 4567, 55)
end

it "should be able to find a user by area code" do
  @user.phone_number = PhoneNumber.new(212, 123, 4567, 55)
  @user.save
  @user.should == User.find_by_phone_number_area_code(212)
end

Now we can treat our phone number as a single object, but collect and store the values as separate fields.

Posted in  | Tags Rails, ComposedOf, Ruby, ValueObject | 0 Comments

Serving Images Stored in the Database with Rails

10:46 AM EDT Saturday, April 19 2008

Some Rails applications accept images uploaded by users via the application, such as profile images. You have a few choices as to how to store the image. I'm not going to go over them or debate the merits of each in this article. Instead I'm going to cover how to handle it if you've decided that you want to store your images in the database.

So to get started with the example, create a rails app and an image model to hold the image data:

$ rails myapp
$ cd myapp
$ script/generate model image file_name:string content_type:string \
file_size:integer file_data:binary
$ rake db:migrate

Ok, now let's put the awesomest image ever into our database:

$ script/console 

>> require 'net/http'
=> []

>> host = 'farm1.static.flickr.com'
=> "farm1.static.flickr.com"

>> path = '/62/163977533_55fd5ddd9b_o_d.jpg'
=> "/62/163977533_55fd5ddd9b_o_d.jpg"

>> res = Net::HTTP.get_response(host, path)
=> #<Net::HTTPOK 200 OK readbody=true>

>> Image.create!(:file_name => "awesome.jpg", 
  :content_type => res['Content-type'], 
  :file_size => res.body.size, 
  :file_data => res.body)
=> #<Image id: 1, file_name: "awesome.jpg"...

I've added some spacing for dramatic effect. Now, create an images controller at app/controllers/images_controller.rb:

class ImagesController < ApplicationController
  caches_page :show
  def show
    if @image = Image.find_by_file_name(params[:file_name])
      send_data(
        @image.file_data, 
        :type => @image.content_type,
        :filename => @image.file_name,
        :disposition => 'inline'
      )
    else
      render :file => "#{RAILS_ROOT}/public/404.html", :status => 404
    end
  end
end

Then setup a route:

map.connect "/images/*file_name", :controller => "images", :action => "show"

Next make sure caching is turned on for your development environment. Edit this line in config/environments/development.rb:

config.action_controller.perform_caching = true

And finally, start up rails and view the image in your browser at http://localhost:3000/images/awesome.jpg. If you look in your log, you will see a line like this:

Cached page: /images/awesome.jpg (0.00100)

But if you refresh your browser or hit the same URL from a different browser, you will not see that anymore. That's because we've cached the file to local disk and it can be served directly from there by the web server without going through rails. Mongrel does this by default on your local development setup, but your production environment might require some configuration to make sure that happens. The guys at Rails envy have a great article that has the details on how to configure our web and application servers to do that. The article provides more detail on page caching as well, including how to clear the cache when images change, so you should definitely check that out.

Posted in  | Tags Rails, Ruby, Caching | 3 Comments

Importing Data with Rails

8:54 AM EDT Saturday, April 19 2008

Often when working with Rails applications, you need to import data from other sources. A common source is an excel spreadsheet. A simple import consists of reading each line in the spreadsheet and creating a record in the database for each line. You could do this is a small Ruby script with SQL, you wouldn't need Rails. But sometimes the import is more complicated. For example, you may want to run your application validation logic on each record. Also, maybe you need to create associated record for each row.

To handle this kind of thing, it can be helpful to use your ActiveRecord data model. To do that, you can simply create a Ruby script and add these few lines at the top:

require File.join(File.dirname(__FILE__), "..", "..", "config", "boot")
require File.join(File.dirname(__FILE__), "..", "..", "config", "environment")

This will boot up the Rails environment when your script starts, and then you have full access to your Rails models. You could write a procedural script to handle that, but I've found that creating an object-oriented class gives you a little bit cleaner, more re-usable framework. So let's just get right to the code. Here is the code for a base class for your data import:

class DataImport

  attr_reader :file, :fields, :row_map, :default_e

  #Create DSL methods for subclasses
  class << self
    def default_environment(env)
      self.send(:define_method, :default_environment) do
        env
      end
    end
    def default_file(file_name)
      self.send(:define_method, :default_file) do
        file_name
      end
    end
  end

  def initialize(env, file)
    load_rails(env || respond_to?(:default_environment) ? 
      default_environment : "development")
    @file = file || default_file
    raise "You must specify a file" unless @file
  end

  def self.run(env, file)
    new(file, env).run
  end

  def run
    open(file).each_with_index do |line, i|
      initialize_row!(line, i)
    end
  end

  def initialize_row!(line, i)
    tokenize_row!(line)
    if i < 1
      initialize_fields!
    else
      initialize_row_map!
      process_row
    end
  end

  def process_row
    puts row_map.inspect
  end

  private

    def tokenize_row!(line)
      @row = line.split('|')
    end

    def initialize_fields!
      @fields = @row.map{|e| e.chomp.to_sym}
    end

    def initialize_row_map!
      @row_map = {}
      @row.each_with_index do |c, i|
        @row_map[fields[i]] = c.blank? ? nil : c.strip
      end
    end

    def load_rails(env)
      ENV['RAILS_ENV'] = env
      require File.join(File.dirname(__FILE__), "..", "..", "config", "boot")
      require File.join(File.dirname(__FILE__), "..", "..", "config", "environment")        
    end

end

Alright, that's a pretty big chunk of code, but this is the implementation of a base class that you will reuse. Don't worry, your actual import class will be much shorter. In other words, you can copy and paste this right into your app and use it as is, but if you are interested to find out how it works, read the next few paragraphs.

So the first interesting thing you'll encounter in this code is the DSL-ish methods. To understand how this works, you really need to read Why The Lucky Stiff's Seeing Metaclasses Clearly. The talk Dave Thomas gave just the other day at the NovaRUG would help too. But basically what it does is define 2 class methods that are intended to be used by subclasses during class definition. When called, they will define methods that the base class can then use. This the concept I blogged about the other day in action. They are conceptually the same thing as the definition of the belongs_to and has_many methods in ActiveRecord. It will make more sense when you see an implementation.

Next up is the constructor which handles setting the file instance variable for our data import class, as well as loading up rails with the right environment specified. After that are class and instance methods both called run. The idea here is that we want to work with an instance of the data import class, but it will be convenient to just call OurDataImport.run.

The work happens in the run instance method. This opens up the file and starts processing it line by line. In this method I'm trying to employ a technique, or more of a style I guess, that Marcel Molina spoke about at the DC Ruby Users Group. The idea is that you should strive to as much as possible have all of the code within a method be at the same level of abstraction. If you look at this whole method:

def initialize_row!(line, i)
  tokenize_row!(line)
  if i < 1
    initialize_fields!
  else
    initialize_row_map!
    process_row
  end
end

It's easy to read it and understand what it is going to do. First we are going to tokenize the row, then if it is the first row, we will initialize the fields, otherwise, we will initialize the row map and process the row. For example, this method could be written like this:

def initialize_row!(line, i)
  tokenize_row!(line)
  if i < 1
    initialize_fields!
  else
    @row_map = {}
    @row.each_with_index do |c, i|
      @row_map[fields[i]] = c.blank? ? nil : c.strip
    end
    process_row
  end
end

But there is an abstraction-level switching that you have to go through mentally once you get to the first line after the else. The rest of the method is composed of intent-revealing methods, but then we just have this lower-level chunk of code that deals with setting instance variables. So don't do that, the other implementation is cleaner, leads to code that is composed well and is easier to test and extend.

So the meat of what happens here is that the run method reads in the file row by row. It assumes the data will be pipe-separated (that is, records separated with the "|" character), because I find that to be easiest to parse. It's trival to convert an excel spreadsheet to a pipe-separated text file using OpenOffice. If your data is not pipe-separated, you could override tokenize_row to split up the row some other way. It assumes the first row contains the field names that each column will map to, so if we are on the first row, it just stores away the field names. Then, on each subsequent row it constructs a map (a.k.a hash) containing the column name and values. Then it calls the process_row method. The implementation of the process_row doesn't do anything interesting in this base class because the intent is for you to override that in your subclass.

Ok, so now let's put this to use. Create a rails app with a simple user model:

$ rails myapp
$ cd myapp
$ script/generate model user name:string email:string
$ rake db:migrate

Now copy the whole base DataImport class from above into db/data/data_import.rb. Then create a data file at db/data/users.txt with something like this:

name|email
Paul Barry|mail@paulbarry.com
Someone Else|someone_else@example.com

And then finally we'll create an implementation of our data import at db/data/user_data_import.rb

require 'data_import'
class UserDataImport < DataImport

  default_file "users.txt"

  def process_row
    user = User.create!(row_map)
    puts "Created => #{user.inspect}"
  end

end

UserDataImport.run(ARGV[0], ARGV[1])

So now we have a pretty clear, concise file that explains what we are doing. You can see the call to default_file that allows us to set our default file name using a clean, DSL-ish syntax. We could also call default_environment there as well if we wanted to, but we don't have to. This is a very simple import where we just create a user for each row. The last line of the script runs the import, passing in the command line arguments. If you pass no arguments, it will work, using "development" for the environment and "users.txt" for the file name. A real data import is likely to do some more interesting work with the data, but at least this gets all the plumbing of processing the data file out of the way for you and allows you to focus on the logic of what you need to do with the data. All that's left to do is simply run the db/data/user_data_import.rb script.

Sidenote: I've found that if your want to run the script from textmate, you need to add this line top of your script, due to a conflict in the ruby libraries provides with TextMate.

$:.reject! { |e| e.include? 'TextMate' }

Posted in  | Tags Rails, Ruby | 3 Comments

Sending Liquid-based Email with Rails

7:07 AM EDT Saturday, April 19 2008

Many applications send emails out to users when certain actions happen, such as placing an order. Sometimes the contents of those emails can change frequently, so it is a nice feature is to be able to give the administrative users of your system a way to change the contents of those emails.

One way to do it is to use the Liquid templating language. The reason Liquid is a good choice is that Liquid is "non-evaling", which means you can just execute arbitrary Ruby code from within a Liquid template. This isn't true of ERB, for example, where you could just put <% something_evil %> in the middle of a template and it would get executed. So if you were to give your users a way to edit the the template and it was using ERB, you would have a potential security problem.

So create a rails app:

$ rails liquid

Then make sure you have the liquid gem installed:

$ sudo gem install liquid

Then require liquid in your config/environment.rb (sidenote: edge rails has a better way of doing this)

require 'liquid'

Next we'll create the model and interface for letting users edit the mail templates:

$ script/generate scaffold mail_template name:string body:text
$ rake db:migrate

Now you can create a new mail template through the browser or just using IRB:

$ script/console
>> MailTemplate.create!(:name => "order_confirmation", 
  :body => "Dear {{name}},\n\nThank you for your order!")

There you can see the very basic syntax of a Liquid template. The {{name}} is a variable that will get replace with content when we render the template. Now we'll generate a mailer:

$ script/generate mailer OrderMailer order_confirmation

Now we have to modify the OrderMailer to use our Liquid template stored in the database. Edit app/models/order_mailer.rb so that it has the following contents:

class OrderMailer < ActionMailer::Base

  def order_confirmation(sent_at = Time.now)
    @from       = 'orders@yoursite.com'
    @recipients = 'some_customer@whatever.com'
    @subject    = 'Order Confirmation'
    @body       = {"name" => "Paul"}
    @sent_on    = sent_at
    @headers    = {}
  end

  def render_message(method_name, body)
    mail_template = MailTemplate.find_by_name(method_name)
    template = Liquid::Template.parse(mail_template.body)
    template.render body
  end

end

The order_confirmation method should look familiar, it is the almost the same as it would be for any regular mailer. One caveat is that Liquid expects the keys in the context hash that you pass to it to be strings, not symbols. The @body instance variable is the context hash that will be passed to Liquid. Those are the variables that you will have access to when the template renders.

The real work is happening in the render_message method. This is the method that the mailer calls when delivering your message. Normally you don't need to override this method, it takes care of finding the ERB template and rendering it for you. We can override it here to look for our Liquid template from the database. The first argument to the method will be set to the method name of the mailer, which in this case is order_confirmation. The second argument will be the value that we set @body to in the order_confirmation method. This makes for a pretty clean way of doing this, hooking in our Liquid functionality using the standard object-oriented technique of overriding a method, no monkey patching required, which should make Avdi proud!

Make sure you've started the app with script/server and then go into script/console and enter this:

>> OrderMailer.deliver_order_confirmation

Now in the output from script/server, you should see something like this:

Sent mail:
 Date: Sat, 19 Apr 2008 06:41:53 -0400
From: orders@yoursite.com
To: some_customer@whatever.com
Subject: Order Confirmation
Mime-Version: 1.0
Content-Type: text/plain; charset=utf-8

Dear Paul,

Thank you for your order!

Editorial: 4 years ago, Matt Raible posted an article to the The Server Side titled Sending Velocity-based Email with Spring. Consider this the Ruby equivalent to that article. Compare the amount of code/configuration required to accompish this task in Java/J2EE vs. Ruby on Rails. Also, the Ruby version is more complicated because it involves storing/retrieving the templates from the database rather than the file system. If we were just retrieving the templates from the file system, this probably would have been a 2 line article. This is just another example of how much simpler Rails makes things when compared to Java/J2EE.

Posted in  | Tags Rails, Liquid, Ruby | 1 Comments

merb.intro Video

12:09 PM EDT Thursday, April 17 2008

Here's the video from last night's Merb presentation:

Thanks to Evan for recording it!

Posted in  | Tags Rails, Ruby, merb, | 0 Comments

<< Previous Page    Next Page >>