5 Rails Tips

4:39 PM EDT Saturday, April 19 2008

Ryan Bates, the author of Railscasts, just reached his 100th episode. Railscasts is a fantastic free resource for learning about Ruby on Rails. Congratulations, Ryan!

To celebrate his 100th episode, he's running a contest with some great prizes. The contest is to submit your own 5 Rails tips, much like Ryan did in Episode 100 of Railscasts. This blog post is my entry into that contest. I have posted 5 articles today which include tips on various aspects of Rails. They are:

  1. Sending Liquid-based Email with Rails

    Find out how to let the administrative users of your application update email templates.

  2. Importing Data with Rails

    Learn how to use Rails and object-oriented techniques to import data into your Rails app.

  3. Serving Images Stored in the Database with Rails

    See how to take advantage of Rails' page caching to serve images that are stored in your database.

  4. Using ActiveRecord Composed Of

    Make use of the composed_of feature in ActiveRecord to treat multiple fields within a model as one object.

  5. Using Lowpro To Create Autotab using Unobtrusive JavaScript and Rails

    Provide your users with an easy shortcut for entering in multi-field data types like phone numbers without filling up your views with lots of JavaScript code.

If you find these tips helpful, be sure to let Ryan know! :)

Posted in  | Tags Railscasts, Rails, Ruby, 5RailsTips | 0 Comments

Using Lowpro To Create Autotab using Unobtrusive JavaScript and Rails

4:16 PM EDT Saturday, April 19 2008

In the last article, I showed how to use composed_of to create a class to handle phone numbers. In this article, I'll show how you can use Unobtrusive JavaScript to make a spiffy interface for entering phone numbers. So first, here's the code we want for phone numbers:

Phone Number
<% f.fields_for :phone_number do |pn| %>
  (<%= pn.text_field :area_code, :size => 3, :class => "autotab" %>)
  <%= pn.text_field :prefix, :size => 3, :class => "autotab" %>
  -
  <%= pn.text_field :line_number, :size => 4, :class => "autotab" %>
  ext
  <%= pn.text_field :extension, :size => 4 %>
<% end %>

This goes in the form view code for the users resource. What this does is render separate fields for each part of the phone number. If you've generated the scaffolding for the users resource, you should be able to stick this in there and be able to create users with phone numbers using this form.

So that's nice, but it would be neat if we could have the cursor automatically jump from one field to the next once the correct number of digits have been entered? We've already got the first step of that in there by putting the class "autotab" on each of the first 3 fields. That is as much code as we are going to add into the actual view. We are going to do the rest unobtrusively, some might even say, Ninja-style.

The first step is to download Lowpro and put it into your public/javascripts directly. Lowpro is a library that enhances Prototype to make unobtrusive JavaScript easier. Once you've got that, make sure to include the javascript libraries in your layout:

<%= javascript_include_tag :defaults, "lowpro", "autotab" %>

Autotab is a JavaScript file that we are going to create specifically for the purpose of providing the "autotab" functionality, which is the name I'm giving to the feature where the cursor will automatically move to the next field once the correct number of characters have been entered. The correct number of characters is determined from the size attribute of the input field. For staters, make sure you have firebug installed and enter in this code to public/javascripts/autotab.js:

Event.onReady(function() {
  console.log("These are a few of my favorite things:");
  $$('input.autotab').each(function(e){
    console.log(e);
  });
});

When you load the form, you should see the output in the firebug console, with the 3 inputs that have the autotab class. This doesn't do anything interesting at this point, but just shows the basis of how unobtrusive JavaScript works. There are no event handlers directly inline in the HTML code, instead some JavaScript code execute once the page has finished loading and adds functionality to DOM elements based on something like their CSS class.

So in order to introduce the functionality we want, we'll use Lowpro's addBehavior method, which attaches an event handler to elements that match a given CSS tag. Replace the contents of public/javascript/autotab.js with this:

Event.addBehavior({
  'input.autotab:keyup' : function(e) {
    if(e.keyCode != 9 && e.keyCode != 16) {
      if(e.target.value.length == e.target.size) {
        e.target.next().focus();  
      }      
    }
  }
});

So once the page has loaded (or the DOM is "ready", I should say more correctly), this will attach this function to the keyup event handler of all of the elements on the page that match the CSS selector input.autotab. In the function, we first make sure the key being released isn't tab or shift. This is because if you are filling out the form and you press shift+tab to go back to the previous field, you don't want it to jump ahead, because you must want to edit that field if you are going back to it. After that if statement we just check to see if the length of the value in the input field matches its size. If it does, then we call next() on the target to get the next field, and call focus() to change the focus to that field.

Posted in  | Tags Rails, CSS, Javascript, Ruby, Autotab, Unobtrusive, Lowpro | 0 Comments

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

<< Previous Page    Next Page >>