Testing Feedzirra

For one of my side-projects I’m using Feedzirra, a robust feed parsing library. Since we all TATFT I wanted to test parts of my code that depend on feed parsing.

The glitch is that Feedzirra doesn’t work with FakeWeb, since it’s using cURL for remote connections. However cURL supports file:// protocol, so you can fake external requests with local files. I’m using Shoulda in the following examples.

app/models/feed_storage.rb
# Excerpts extracted from model

class FeedStorage < ActiveRecord::Base
  def parse
    if self.marshaled.nil?
      parser = Feedzirra::Feed.fetch_and_parse self.feed.url
      entries = parser.entries
    else
      parser = Marshal.load self.marshaled
      parser = Feedzirra::Feed.update parser
      entries = parser.new_entries
    end

    self.update_attribute :marshaled, (Marshal.dump parser)
    entries
  end
end
test/unit/feed_storage_test.rb
# Excerpts extracted from model's tests
class FeedStorageTest < ActiveSupport::TestCase
  context "a new feed" do
    setup do
      @feed = Factory :feed
      @feed.update_attribute :url, "file://#{URI.escape(File.join(File.dirname(File.expand_path(__FILE__, Dir.getwd)), "..", "fixtures", "full_feed.rss"))}"
    end
    should "setup a new parser and parse all entries" do
      assert_equal 50, @feed.parser.parse.length
    end
  end

  context "a parsed feed" do
    setup do
      @feed_path = "#{URI.escape(File.join(File.dirname(File.expand_path(__FILE__, Dir.getwd)), "..", "fixtures"))}"
      `cp #{File.join @feed_path, "full_feed.rss"} #{File.join @feed_path, "feed.rss"}`

      @feed = Factory :feed
      @feed.update_attribute :url, "file://#{File.join @feed_path, "feed.rss"}"
      @feed.parser.parse

      `cp #{File.join @feed_path, "updated_feed.rss"} #{File.join @feed_path, "feed.rss"}`
    end
    should "parse all new entries" do
      assert_equal 1, @feed.parser.parse.length
    end
    teardown do
      `rm -f #{File.join @feed_path, "feed.rss"}`
    end
  end
end

The logic behing FeedStorage is quite simple - for every feed in the database I store its feed parser (a marshaled Ruby object, might switch to something else if I run into performance issues). Feed class requires valid urls for url field, hence the update_attribute call.

Feel free to drop me a line in the comments - I’m sure there’s some room for improvements!

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

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).

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!

Generating Ruby on Rails UTC-based migrations with TextMate

TextMate is a great tool for all Mac-based Ruby on Rails developers, but even if you’re using the Cutting-Edge updates you’re most likely still stuck with the old migrations generator.

Here’s a quick fix if you want to use UTC-based migrations (introduced in Rails 2.1). From your favourite Terminal application launch:

mate /Applications/TextMate.app/Contents/SharedSupport/Bundles/Ruby\ on\ Rails.tmbundle/Support/bin/generate_quick_migration.rb

Then just comment (or delete) the following code:

files = Dir.glob(File.join(migration_dir, "[0-9][0-9][0-9]_*"))
if files.empty?
  number = "001"
else
  number = File.basename(files[-1])[0..2].succ
end

And replace it with:

number = Time.now.utc.strftime("%Y%m%d%H%M%S")

There’s just one glitch with this solution - it’ll probably be overwritten whenever you update your Textmate application. However, this doesn’t happen very often, does it? If you have a better solution - let me know in the comments, I’m a TextMate beginner, so bear with me!

Discarding non-ASCII characters in Ruby on Rails

My current Ruby on Rails project requires SEO-friendly URLs. I’ve installed acts_as_slugable plugin, but soon I found out that all my slugs contained Polish characters (in UTF-8). After some googling (Polish Ruby on Rails forum) and some more sips of coffee I came up with some modifications to acts_as_slugable.

Here’s lib/acts_as_slugable_ascii.rb:

require 'string'

module Multiup
  module Acts
    module Slugable
      module InstanceMethods
        private
          def create_slug
            return if self.errors.length > 0

            if self[source_column].nil? or self[source_column].empty?
              return
            end

            if self[slug_column].to_s.empty?
              test_string = self[source_column]

              proposed_slug = test_string.strip.downcase.gsub(/[\'\"\#\$\,\.\!\?\%\@\(\)]+/, '').to_ascii
              proposed_slug = proposed_slug.gsub(/&/, 'and')
              proposed_slug = proposed_slug.gsub(/[\W^-_]+/, '-')
              proposed_slug = proposed_slug.gsub(/\-{2}/, '-')

              suffix = ""
              existing = true
              acts_as_slugable_class.transaction do
                while existing != nil
                  existing = acts_as_slugable_class.find(:first, :conditions => ["#{slug_column} = ? and #{slug_scope_condition}",  proposed_slug + suffix])
                  if existing
                    if suffix.empty?
                      suffix = "-0"
                    else
                      suffix.succ!
                    end
                  end
                end
              end
              self[slug_column] = proposed_slug + suffix
            end
        end
      end
    end
  end
end

ActiveRecord::Base.class_eval do
  include Multiup::Acts::Slugable
end

The only difference is the use of to_ascii method.

Now, we need to extend the String class. to_ascii method converts Polish UTF-8 characters to ASCII and discards all unknown characters:

require 'iconv'

class String
  def to_ascii
    ascii = 'acelnoszzACELNOSZZ'
    non_ascii = "\271\346\352\263\361\363\234\277\237"
    to_ascii_string = self
    begin
      result = Iconv.new("CP1250", "UTF-8").iconv(to_ascii_string)
    rescue Iconv::IllegalSequence => e
      failed = e.failed.chars.split(//, 2)
      to_ascii_string = to_ascii_string.gsub(failed[0], '')
    retry
    end
   result.tr!(non_ascii, ascii)
  end
end

And we’re done!

Just add the following line to your model:

require 'acts_as_slugable_ascii'

and configure acts_as_slugable as instructed in README file. Now you’re ready to face your users.

Nice URLs with observe_field

For the last couple of weeks I’ve been working on a small Ruby on Rails application (and learning both Ruby and Rails in the same time). Since everyone’s talking about the one-and-only Rails way, I wanted to make my little app as Rails as possible. That means that you have to write some workarounds to get things done the Rails way.

I wanted to add an onchange event to my collection_select. Normally you would put something like this in your template (that’s what I did after browsing through a couple of tutorial/helps):

<%= collection_select(:story, :license_id, License.find(:all), :id, :name) %>
<%= observe_field('story_license_id', :frequency =>; 0, :update => 'story_license_description', :url => {:controller => 'license', :action => 'description'}, :with => '"id="+value') %>

This code however results in ugly URLs (ie. /license/description?id=1). If you want to keep URLs nice, use the following method:

<%= collection_select(:story, :license_id, License.find(:all), :id, :name) %>
<%= observe_field('story_license_id', :frequency => 0, :update => 'story_license_description', :url => {:controller => 'license', :action => 'description', :id => "'+value+'"}) %>

This way you’ll keep your URLs beatiful throughout your application (ie. /license/description/1).

Oh, and forgive me the Rails way irony.

Deploying Ruby on Rails applications on Dreamhost with Capistrano

So, you have a nifty Ruby on Rails application, got yourself a cheap Dreamhost account and you want to give Capistrano a try. And what do you get? Errors.

This occurs if you have Ruby gems installed in your home directory (probably ~/.gems/). I’m assuming that you already have your environment configured. Despite that you still can get errors like no file to load -- json when executing cap deploy. That’s because you have your shell configured, but most likely non-interactive environment is left intact.

To fix this you have to modify your .bashrc file (since you probably added GEM_HOME and GEM_PATH to .bash_profile):

export GEM_HOME=$HOME/.gems
export GEM_PATH=$GEM_HOME:/usr/lib/ruby/gems/1.8

And you’re probably done.

You can also check these articles, they’ve been very helpful: Deploying Rails (2.0) to Mongrel with Capistrano 2.1, Using Capistrano with Rails and Ruby on Rails - Dreamhost.