[Hint] Mocking Date.today in Ruby on Rails tests

Since for the last couple of months I’m all about test-driven development I wanted to test a couple of ActiveRecord callbacks that relied on Date.today method.

Here’s a quick hack for your test_helper.rb file that allows you to specify the date returned by Date.today.

class Date
  class << self
    @@mocked_today = nil
 
    alias :unmocked_today :today
 
    def today
      @@mocked_today || unmocked_today
    end
 
    def with_mocked_today(mocked_today)
      if block_given?
        @@mocked_today = mocked_today
        begin
          yield
        ensure
          @@mocked_today = nil
        end
      end
    end
  end
end

And a quick example how to use it:

Date.with_mocked_today Date.parse("2008-01-01") do
  // your code here...
end

[Hint] Testing Paperclip file uploads with Webrat

Webrat (acceptance framework for Ruby) has a nasty habit of sending text/plain Content-Type when testing file uploads. This causes validates_attachment_content_type Paperclip validation to fail.

Example test (using Shoulda):

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
# test
 
require File.dirname(__FILE__) + "/../test_helper"
 
class PhotoTest < ActionController::IntegrationTest
  context "any user" do
    should "be able to upload a photo" do
      visit new_photo_path()
      attach_file "photo_photo", File.dirname(__FILE__) + "/../uploads/photo.jpg"
      click_button "Add photo"
      assert_contain "Uploaded!"
    end
  end
end
 
# model
 
class Photo < ActiveRecord::Base
  has_attached_file :photo, :styles => { :main => "640x480#" },
    :path => ":rails_root/public/images/photo/:id/:style.:extension",
    :url => "/images/photo/:id/:style.:extension"
 
  validates_attachment_presence :photo
  validates_attachment_content_type :photo, :content_type => ['image/jpeg', 'image/pjpeg', 'image/jpg']
end

This will always fail (due to text/plain Content-Type).

Kinda nasty hack in my test solved the problem (line 3-7):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class PhotoTest < ActionController::IntegrationTest
  context "any user" do
    setup do
    Photo.class_eval do
        validates_attachment_content_type :photo, :content_type => ["text/plain"]
      end
    end
    should "be able to upload a photo" do
      visit new_photo_path()
      attach_file "photo_photo", File.dirname(__FILE__) + "/../uploads/photo.jpg"
      click_button "Add photo"
      assert_contain "Uploaded!"
    end
  end
end

Any better ideas? Hacking Webrat’s mime.rb was also an option, but I didn’t like it.

[Tool] Rails/Git application setup script

Recently I’ve been creating new Ruby on Rails applications like a mad-man. I like to keep my stuff in a repository from the very beginning and this ment setting up Git / Subversion for each application.

So came up with a simple script which runs like this:

./rails-git my_application

This sets up a Rails application, Git repository and freezes Rails gems. Enjoy!

#!/bin/bash
 
if [ "$1" = "" ]
then
    echo "usage: ./rails-git project_name"
    exit
fi
 
echo "Creating Rails app..."
 
rails $1 > /dev/null
 
echo "Initializing git repository..."
 
cd $1
git init > /dev/null
 
cat >> .gitignore << EOF
log/*.log
tmp/**/*
config/database.yml
EOF
 
touch log/.gitignore
touch tmp/.gitignore
 
mv config/database.yml config/database_example.yml
 
echo "Initial import..."
 
git add . > /dev/null
git commit -m "$1 initial import" . > /dev/null
 
echo "Freezing Rails gems..."
 
rake rails:freeze:gems > /dev/null 2>&1
git add vendor/rails > /dev/null
git commit -m "freezing rails gems" > /dev/null
 
echo "Done!"

[Hint] GNU sed on OS X with MacPorts

Should you require to use GNU sed instead of the default OS X BSD sed and you have MacPorts installed just go to your Terminal.app and type:

sudo port install gsed

Now you have GNU sed installed – though if you don’t want to type gsed instead of sed then create a symbolic link:

sudo ln -s /opt/local/bin/gsed /opt/local/bin/sed

No MacPorts? You can compile sed from scratch check Mark Chen’s post.

[Hint] Setting local Ruby on Rails development environment with Lighttpd and FastCGI

Tired of typing ./script/server? Yeah, me too. Especially with Lighttpd / FastCGI configured for PHP at hand on my development machine – pure envy. So I gave a try with my Ruby on Rails applications.

Easy as pie – first add a custom domain to your /etc/hosts:

127.0.0.1	myapp.local

Then – add FastCGI handler to your lighttpd.conf

$HTTP["host"] =~ "myapp.local" {
	server.indexfiles          = ( "dispatch.fcgi" )
	server.document-root       = "/path/to/myapp/public/"
	server.error-handler-404   = "/dispatch.fcgi"
	fastcgi.server = (
		".fcgi" =>
			( "localhost" =>
				(
					"socket" => "/tmp/myapp.socket",
					"bin-path" => "/path/to/myapp/public/dispatch.fcgi",
					"bin-environment" => ( "RAILS_ENV" => "development" )
				)
			)
	)
}

Restart your Lighttpd and your done. Unless…

Unless you require some custom libs in your Ruby on Rails application. Then you should modify your code (I keep all my require statements in myapp/config/initializers/requires.rb) from:

require "lib/mylib.rb"

to:

require Rails.path.join("mylib.rb")

No more those nasty 500’s! This setup will also work for subdomains, ie. thisis.myapp.local (as long you as handle them /etc/hosts or anywhere else).

[Hint] Launching onchange event with Prototype

Some of my current JavaScript development is done with Prototype. Launching default JavaScript events with Prototype seems to be kind of awkward – Element.fire method simply won’t work.

I came up with quite a nasty workaround:

Event.observe( window, 'load', function() {
	$$( '.my_element' ).each( function(element) { 
		Event.observe(element, 'onchange:fake', myMethod);
		element.fire( 'onchange:fake' );
	} );
} );

I wish this could be done in a more elegant way. Anybody?

[Hint] Fixing smtp_tls.rb for Ruby 1.8.7

I’ve been on-and-off developing a couple of Ruby On Rails applications and since most of them are in the “just for fun” stage I was using my Google Apps account for e-mail delivery. However, I kept on getting argument count error in smtp_tls.rb (a library required for proper e-mail delivery via smtp.gmail.com server) – both on my development and staging machine. I know, I know, I should get a dedicated box and become a real man, but for now my Dreamhost account should be enough (hey, it’s for blogging and fun, not production!).

But back to the problem – I kept on fixing smtp_tls.rb until this morning a lightbulb popped over my head. Yes, check_auth_args in Net::SMTP takes different arguments in Ruby 1.8.7 (which I’m using for development) and in Ruby 1.8.6 (staging).

In order to make things right just do some Ruby version checking (see lines 9 – 13):

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
68
69
70
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
 
    if RUBY_VERSION == "1.8.7"
      check_auth_args user, secret
    else
      check_auth_args user, secret, authtype if user or secret      
    end
 
    sock = timeout(@open_timeout) { TCPSocket.open(@address, @port) }
    @socket = Net::InternetMessageIO.new(sock)
    @socket.read_timeout = 60 #@read_timeout
 
    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
      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
    end
  end
end

[Hint] Viewing RI in a web browser with lighttpd

In his post Matthias Georgi showed how to view RI in a web browser. He’s using Apache, so I rolled out a couple of snippets for lighttpd users.

First I added a host to my /etc/hosts:

127.0.0.1     ri

And created a virtual host in my lighttpd.conf

$HTTP["host"] =~ "ri" {
	server.document-root = "/my/projects/path/ri/"
	cgi.assign = ( ".rb" => "/usr/bin/ruby" )
	url.rewrite-once = (
		"^(.*)$" => "ri.rb?$0"
	)
}

Since lighttpd has a diffrent way of handling mod_cgi request parameters Matthias’es script needed a little adjusting:

#!/usr/bin/env ruby
 
require 'rdoc/ri/ri_driver'
require 'rubygems'
 
print "Content-type: text/html\r\n\r\n"
 
ARGV << ENV["QUERY_STRING"].sub("/", "") << '-f' << 'html' <<
 
ri = RiDriver.new
 
print '<html><body style="width:600px; margin:auto; padding:20px"><pre>'
ri.process_args
print '</body></html>'

Restart your lighttpd, browse to http://ri/String.capitalize. That’s it!

[Release] Rails 2.2 compatible Prawnto

Some API changes in Ruby On Rails broke thorny-sun’s prawnto (Prawn based PDF templating plugin). I’ve just pushed a quick fix for it to github – you can pull it from http://github.com/filiptepper/prawnto/tree/master. Enjoy!

[Hint] Using localized_country_select with air_budd_form_builder in Ruby on Rails

For my current project instead of rolling out my own form builder I went for the github’s air_budd_form_builder. It generates nice, semantic HTML code. This project also required localized_country_select, so in order to make these plugins compatible I had to prepare this little snippet:

module AirBlade
  module AirBudd
    class FormBuilder
      def input_type_for(field_helper)
        case field_helper
        when 'text_field';     'text'
        when 'text_area';      'text'
        when 'password_field'; 'password'
        when 'file_field';     'file'
        when 'hidden_field';   'hidden'
        when 'check_box';      'checkbox'
        when 'radio_button';   'radio'
        when 'select';         'select'
        when 'date_select';    'select'
        when 'time_select';    'select'
        when 'country_select'; 'select'
        when 'localized_country_select'; 'select'
        else ''
        end
      end
    end
  end
end
 
module AirBlade
  module AirBudd
    class FormBuilder
      %w( localized_country_select ).each do |name|
        create_collection_field_helper name
      end
    end
  end
end

Put it in your application’s lib directory and enjoy!