Railsconf 2008

Posted by topher
on Monday, May 12

I will be at Railsconf 2008 at Portland. I still haven’t finalized the sessions I’m attending but I’m sure it will be great.

RailsConf 2008

Notes on migrations

Posted by topher
on Tuesday, November 13

If you add columns to a model, and you want to use that model, call Base#reset_column_information.

If you save a model in a migration, and if you do not want the timestamps (created_at, updated_at) to be updated, put ActiveRecord::Base.record_timestamps = false.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class AddLastCommentAtToTopic < ActiveRecord::Migration
  class Topic < ActiveRecord::Base
    has_many :posts
  end
  
  class Post < ActiveRecord::Base
  end
  
  def self.up
    ActiveRecord::Base.record_timestamps = false
    add_column :topics :last_comment_at, :datetime
    Topic.reset_column_information
    Topic.find(:all).each do |topic|
      topic = topic.posts.find(:first, :order => "last_comment_at DESC")
      if topic
        topic.last_comment_at  = post.last_comment_at
        topic.save
      end
    end
    ActiveRecord::Base.record_timestamps = true
  end

  def self.down
    remove_column :topics, :last_comment_at
  end
end

Send emails in development environment using Gmail

Posted by topher
on Monday, November 12

If you don’t have a mail server in your development machine, you can use Gmail. Put the following code in RAILS_ROOT/lib/smtp_tls.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
require "openssl"
require "net/smtp"

Net::SMTP.class_eval do
  private
  def do_start(helodomain, user, secret, authtype)
    raise IOError, 'SMTP session already started' if @started
    check_auth_args user, secret, authtype if user or secret

    sock = timeout(@open_timeout) { TCPSocket.open(@address, @port) }
    @socket = Net::InternetMessageIO.new(sock)
    @socket.read_timeout = 60 #@read_timeout
    #@socket.debug_output = STDERR #@debug_output

    check_response(critical { recv_response() })
    do_helo(helodomain)

    if starttls
      raise 'openssl library not installed' unless defined?(OpenSSL)
      ssl = OpenSSL::SSL::SSLSocket.new(sock)
      ssl.sync_close = true
      ssl.connect
      @socket = Net::InternetMessageIO.new(ssl)
      @socket.read_timeout = 60 #@read_timeout
      #@socket.debug_output = STDERR #@debug_output
      do_helo(helodomain)
    end

    authenticate user, secret, authtype if user
    @started = true
  ensure
    unless @started
      # authentication failed, cancel connection.
      @socket.close if not @started and @socket and not @socket.closed?
      @socket = nil
    end
  end

  def do_helo(helodomain)
     begin
      if @esmtp
        ehlo helodomain
      else
        helo helodomain
      end
    rescue Net::ProtocolError
      if @esmtp
        @esmtp = false
        @error_occured = false
        retry
      end
      raise
    end
  end

  def starttls
    getok('STARTTLS') rescue return false
    return true
  end

  def quit
    begin
      getok('QUIT')
    rescue EOFError, OpenSSL::SSL::SSLError
    end
  end
end
Then, add these at the bottom of RAILS_ROOT/config/environments/development.rb
1
2
3
4
5
6
7
8
ActionMailer::Base.server_settings = {
  :address => "smtp.gmail.com",
  :port => 587,
  :domain => "mycompany.com",
  :authentication => :plain,
  :user_name => "username",
  :password => "password"
}

Add your username and password, restart the server and you’re good to go. Note that the emails your app sends might end up in the bulk folder.

Link How to use GMail SMTP server to send emails in Rails ActionMailer

Plugin: attachment_fu

Posted by topher
on Thursday, July 19
attachment_fu is a rails plugin written by Rick Olson. From the README,

attachment_fu facilitates file uploads in Ruby on Rails. There are a few storage options for the actual file data, but the plugin always at a minimum stores metadata for each file in the database.

Check out Mike Clark’s tutorial.

On windows, when I submit the form with the file upload, I get an error “Size not included in list”. Upon inspection, the size is zero. (The default minimum size is 1 byte.) The size of the file uploaded isn’t set properly. I found somewhere on the beast forum that you need to add “sleep 1” on your controller. I haven’t tried to use attachment_fu on my server (ubuntu) and I’m hoping this is only a problem with windows.

My friend Mon, also a rails developer, encountered another problem with attachment_fu. He added a user_id in the model which uses attachment_fu. When he saves the uploaded file, he gets an error saying that user_id is null. We’re absolutely sure there’s a user_id.

Here’s what happened: attachment_fu can create a thumbnail(s) for you when you save the uploaded image (you will need an image processor like image magick). If you don’t specify a thumbnail_class, attachment_fu will use the same model for the thumbnail. So if you have a Mugshot model, after submitting the form, 1 model will be created for the uploaded image, and another one will be created for the thumbnail. So even if you add user_id to your model, the thumbnail won’t have a user_id. You need to add a callback before_thumbnail_saved on your model.

1
2
3
before_thumbnail_saved do |record, thumbnail|
  thumbnail.user_id = record.user_id
end

Install the plugin with

ruby script/plugin install http://svn.techno-weenie.net/projects/plugins/attachment_fu/

Editing models in migrations

Posted by topher
on Friday, July 13

In migrations, you can also edit your models. I have a Topic and a Post. A topic has many posts. The models are already created using previous migrations. Now I want to add 3 new fields to Topic—posts_count, replied_by, and replied_at.

Here is the migration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class AddCachedFieldsOnTopic < ActiveRecord::Migration
  class Topic < ActiveRecord::Base
    has_many :posts, :order => "posts.created_at"
  end
  class Post < ActiveRecord::Base; end
  
  def self.up
    add_column :topics, :posts_count, :integer, :default => 0
    add_column :topics, :replied_by, :integer
    add_column :topics, :replied_at, :datetime
    
    Topic.find(:all, :include => :posts).each do |topic|
      last_post = topic.posts.last
      posts_count = topic.posts.size
      Topic.update_all(["posts_count = ?, replied_by = ?, replied_at = ?", posts_count, last_post.user_id, last_post.created_at], ["id = ?", topic.id]) if last_post
    end
  end

  def self.down
    remove_column :topics, :posts_count
    remove_column :topics, :replied_by
    remove_column :topics, :replied_at
  end
end
Notes
  • posts_count will be used by rails for caching. In the post model, I’ll put :belongs_to :topic, :counter_cache => true. Set the default to 0.
  • You can actually get these fields through the association—topic.posts.count, topic.posts.last.user, topic.posts.last.created_at – but I’m adding it to Topic to avoid the additional queries.
  • I’m looking at (copying) the beast application.

Server Setup

Posted by topher
on Thursday, July 05

I have a VPS from Rimuhosting and I’m using nginx and a cluster of mongrel for my rails applications. I followed this tutorial which guides you on setting up everything – ruby, rails, nginx, mongrel, postgresql, subversion and capistrano. I’m sharing my vps with some friends and ruby, rails, and mysql were already installed. I’m using Dreamhost for subversion. Setting up nginx and mongrel is easy. I just followed the tutorial and didn’t encounter any problem.

Mongrel is a fast HTTP library and server for Ruby that is intended for hosting Ruby web applications of any kind using plain HTTP rather than FastCGI or SCGI. It is framework agnostic and already supports Ruby On Rails, Og+Nitro, Camping, and IOWA frameworks.

I use nginx for this reason (taken from the faq page in mongrel website):

Ruby on Rails is not thread safe so there is a synchronized block around the calls to Dispatcher.dispatch. This means that everything is threaded right before and right after Rails runs. While Rails is running there is only one controller in operation at a time. This is why people typically have to run a small set of Mongrel processes (a “Pack of Mongrels”) to get good concurrency.

For a rails application, I run 2 mongrel servers. I use the gem mongrel_cluster to manage these mongrels (this is in the tutorial). For example, mongrels are running in port 7500 and 7501. When a request is made, nginx passes that request to one of the mongrels. For bigger applications, use more mongrels.

Since I’m sharing the VPS with my friends, I can’t change the web server easily. Besides, they are running php and though it’s possible to run php in nginx I haven’t researched that yet. We are using Apache 2.0 which is not easy to set up with mongrel. Apache 2.2 is recommended. I have to run nginx on a different port, say 8088. Then on the apache conf file, I put this

1
2
3
4
5
6
7
8
9
10
<VirtualHost *:80>
        ServerName topher.88-mph.net
        ServerAdmin crigor@gmail.com
        ProxyRequests Off
        ProxyPreserveHost on
        <Location />
          ProxyPass http://topher.88-mph.net:8088/
          ProxyPassReverse http://topher.88-mph.net:8088/
        </Location>
</VirtualHost>

When you go to topher.88-mph.net, apache gets the request then passes it to ngingx which in turns passes it to one of the mongrels. Apache is not really needed on this set up.

Ruby script for creating a rails app

Posted by topher
on Thursday, June 21

A few months ago I saw this ruby script by Akhil Bansal which creates a rails app and imports it to svn, ignoring and removing files like database.yml, tmp directory and log files.

I modified it to use the trunk, tags, and branches directories and updated some of the code. Here’s what the script does:
  • create a rails app in your local machine
  • create trunk, tags and branches directories in the svn repository
  • svn import the rails app
  • delete the app in your local machine
  • check out from svn
  • remove log files from svn
  • ignore log files
  • ignore tmp directory
  • remove tmp directory from svn
  • move database.yml to database.yml.sample
  • ignore database.yml

Download the script here.

Run it with “ruby create_rails_with_subversion.rb” on the command line. You need svn installed on your local machine and a subversion repository.

Exporting to CSV in Rails

Posted by topher
on Thursday, April 26

To export data to CSV, I use CSV::Writer.

1
2
3
4
5
6
7
8
9
10
11
12
13
def report
  CSV::Writer.generate(output="") do |csv|
    csv << %w(Name Price)
    @items.each do |item|
       csv << [item.name, item.price]
    end
  end

  send_data(output,
    :type => "text/csv",
    :filename => 'report.csv')
  end
end

You can also checkout FasterCSV.