Archive for January 2010

L33t Links #74

L33t Links #73

L33t Links #72

L33t Links #71

And another blog post that links to my BugMash articles:

Thanks Maxim Chernyak! (Aka hakunin)

L33t Links #70

The blog posts that link to one of or both of my BugMash articles:

These and the people that shared my articles on Twitter has generated a lot of traffic on my blog an won me a few more subscribers as well. Thanks!

Upgrading an Application to Rails 3 — Part 1

Almost a week ago I posted What I Will Do for Rails 3 in which I promised to “upgrade a Rails 2.3.5 application, publish an article on the process, and report any bugs I stumble upon”. This guide will walk you through the steps I took to upgrade a Rails 2.3.5 application of mine, called “The School Portal” (or “Skoleportalen” in Danish).

Re-generating the Application

The very first thing I did was to clone the Rails Git repository and use the rails executable to re-generate the application to get all files up-to-date with Rails 3.

git clone git://github.com/rails/rails.git
ruby rails/railties/bin/rails skoleportalen

The generator is smart enough to ask you for permission before overwriting the contents of a file. It will even let you see a diff of the current contents of the file and what Rails would like to insert. It’s important that you don’t let Rails delete code written by you. For example Rails wants to overwrite config/routes.rb but that would mean deleting your routes. So you should merge the two versions together to contain both your code and Rails’ updates to the file.

There is one exception, though: When Rails wants to delete the config.gem lines of config/environment.rb (and the configuration file for each environment) you can let it do so, given you have your application under version control (Of course you have, right?) so you can fetch the dependencies later and add them to the Gemfile.

When I was done I reviewed the changes, deleted public/index.html, and committed the current state of the process.

git diff
rm public/index.html
git add .
git commit -m "Rails 3 upgrade: Re-generated application"

You should consider doing the upgrade in a separate branch. In my case it was not necessary since my application was made as part of an assignment at school and therefore never went into production.

Booting the Application

The next step I took was to add the Rails Git repository that I just cloned to the Gemfile and bundle it.

gem 'rails', '3.0.pre', :git => '../rails/'

In my case, my clone of Rails and the application were both in my ~/code/ directory, but if you have arranged your directories differently you should update the reference to the Rails repository to reflect that.

gem bundle

Then it was time to try starting the server.

david@david-laptop:~/code/skoleportalen$ script/server
=> Booting Mongrel
=> Rails 3.0.pre application starting in development on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server
Exiting
/usr/lib/ruby/gems/1.8/gems/activesupport-3.0.pre/lib/active_support/dependencies.rb:456:in `load_missing_constant': uninitialized constant ActiveSupport::CoreExtensions (NameError)
...
	from /home/david/code/skoleportalen/config/initializers/date_formats.rb:1
...

In Active Support 3.0.pre all core extensions has been moved out of their respective modules and directly into the classes they extend. My config/initializers/date_formats.rb file looked like this:

ActiveSupport::CoreExtensions::Time::Conversions::DATE_FORMATS.merge!(
  :danish => "d. %d/%m-%Y"
)

And what I changed it to, in order to accomplish the same thing, was this:

Time::DATE_FORMATS.merge!(
  :danish => "d. %d/%m-%Y"
)

Then I tried starting the server again.

/home/david/code/skoleportalen/config/initializers/new_rails_defaults.rb:14: undefined method `generate_best_match=' for ActionDispatch::Routing:Module (NoMethodError)

The file that causes this error was intended to change the behavior of Rails 2 applications to what would be default in Rails 3, so I removed that.

git rm config/initializers/new_rails_defaults.rb

Now the server started successfully for the first time, so I committed the state of the upgrade once again.

git add .
git commit -m "Rails 3 upgrade: Bundled Rails and made the application boot-able"

A Bundler Bug?

Then I navigated to http://localhost:3000/ and concluded it was time to add the application’s dependencies to the Gemfile.

gem 'formtastic',     '~> 0.9'
gem 'wysihat-engine', '~> 0.1'
gem 'friendly_id',    '~> 2.2'
gem 'authlogic',      '~> 2.1'
gem 'cancan',         '~> 0.2'
gem 'paperclip',      '~> 2.3'
gem 'will_paginate',  '~> 2.3'

With a bit of search and replace-fu it was a piece of cake. I ran gem bundle again, restarted the server, and refreshed. But weirdly I got the same error again telling me that even though I had bundled the application’s dependencies they didn’t seem to get loaded. I went digging: The config/boot.rb file requires vendor/gems/environment.rb which, in my case, requires vendor/gems/ruby/1.8/environment.rb.

Inside that a Bundler.require_env method is defined which creates an anonymous class with a gem method that requires a specified gem. It then evaluates the Gemfile within the context of that class. Just for fun I added a call to it just below the definition and restarted the server.

One of the gems raised an exception which means, on the bright side, it got required. Of course, as a good citizen of the open source community, I reported the bug. I did not provide a patch because in cases like this the decision of how the bug should be fixed is really up to the project owners. Adding a call to require_env under the definition might not be ideal. Also, I didn’t know if it was Bundler’s or actually Rails’ responsibility to call the method.

Conclusion

When the dependencies of my application have all been fixed to work with Rails 3 I will post part 2 of this guide. The following is a list of issues I posted to the respective gems’ issue trackers:

UPDATE As pointed out by José Valim in the comments: These issues can simply be solved by calling Bundler.require_env later on in the process, making sure the components of Rails have been loaded. At the end of config/environment.rb would be a good place.

L33t Links #69

Making Generators for Rails 3 with Thor

Rails 3 replaces the old built-in generator system with Thor which is “a scripting framework that replaces rake and sake”. Not only that, because during Google Summer of Code José Valim extended it with Thor::Actions, a module with methods useful for generators, and integrated it with Rails. Some things have changed, others haven’t. This article will walk you through the process of making a gem with a built-in generator for Rails 3.

First Things First

The gem we will make is very simple. It’s job will be to copy one JavaScript (specifically the ColorBox jQuery plugin) into the public/javascripts/ directory of an application. Start out by creating a directory for the gem wherever you prefer to store your code:

mkdir colorbox_rails/

Previously Rails would search for generators in the rails_generators/ or just generators/ directory of a gem or plugin, but the new Thor-based system changes that. Now the locations are lib/generators/ or alternatively lib/rails_generators/.

cd colorbox_rails/
mkdir lib/
mkdir lib/generators/

Inside lib/generators/ the rules are as follows: One directory per generator, each of which should contain a file of which the filename is simply the name of the generator suffixed with _generator.rb. So in our case the file of the generator will be named colorbox_generator.rb.

cd lib/generators/
mkdir colorbox/
cd colorbox/
touch colorbox_generator.rb

The Generator

Open the newly created file in your favorite text-editor. The first line should require the generator system.

require 'rails/generators'

This little piece of code actually introduces another change, because the module used to be called Rails::Generator. Next, we need a class which inherits from Rails::Generators::Base.

class ColorboxGenerator < Rails::Generators::Base
end

The name of a generator class should simply be the name of the generator in CamelCase suffixed with Generator, staying consistent with the filename. Rails::Generators::Base inherits from Thor::Group which works in a special way. When invoked it will call all instance methods, or "tasks" in Thor terminology, of the class automatically, allowing you to divide the code of your generator in related parts — and naming them, notably. In our case we only need one task, though.

class ColorboxGenerator < Rails::Generators::Base

  def install_colorbox
  end

end

Now for the actual copying of the JavaScript. This is where Thor::Actions comes in. It has a few submodules with methods we can use in our generator. (Please refer to the Thor documentation for an overview.) In this case, what we're interested in is the copy_file method.

def install_colorbox
  copy_file(
    'jquery.colorbox-min.js',
    'public/javascripts/jquery.colorbox-min.js'
  )
end

UPDATE As José Valim has kindly pointed out in the comments: Rails::Generators::Base also has a set of Rails-related actions available which live in Rails::Generators::Actions which basically contains the Rails templates API. That means that generators and templates are merged in Rails 3! Check out the source of Rails::Generators::Actions.

Go ahead and download the jQuery plugin and copy the minified version into templates/, which should be located in the same directory as your generator file.

Before we make the generator a gem, we need to add a class method to the generator. It's intended to set the source root (where the generator will look for templates) to the templates/ directory of our generator. I honestly don't know why this is necessary — perhaps it isn't. I've been browsing through the source of the Rails generator system, trying to find a clear definition of where your templates should be located in order to not have to specify it manually, but with no success. If you can help me out here, please feel free to share your knowledge in the comments.

def self.source_root
  File.join(File.dirname(__FILE__), 'templates')
end

Making the Generator a Gem

In order to use our generator inside a Rails application we want to install it as a gem. All that takes, really, is a .gemspec file, and since this is just an example we will keep it as minimalistic as possible. Create a file called colorbox_rails.gemspec in the root of your gem directory and paste in the following:

Gem::Specification.new do |gem|
  gem.name    = 'colorbox_rails'
  gem.version = '0.0.0'
  gem.summary = 'The ColorBox jQuery plugin on Rails.'

  gem.files = Dir['lib/**/*']
  gem.add_dependency 'rails', '3.0.pre'
end

Before we install our gem we need the 3.0.pre version of Rails. Since it hasn't been released officially as a gem we have to install it from source. Active Record depends on Arel 2.0.pre which also hasn't been released as a gem, so first we'll install that from source, too.

git clone git://github.com/rails/arel.git
cd arel/
gem build arel.gemspec
gem install arel-0.2.pre.gem

git://github.com/rails/rails.git
cd rails/
sudo rake install

With that done we are ready to install our own generator as a gem.

cd colorbox_rails/
gem build colorbox_rails.gemspec
gem install colorbox_rails-0.0.0.gem

Using the Generator

In order to use our new generator we need a Rails 3.0.pre application. Unfortunately, even though we installed 3.0.pre as a gem we can't use the rails executable. Go ahead and try and observe the following error:

/usr/local/lib/site_ruby/1.8/rubygems.rb:384:in `bin_path': can't find executable rails for rails-3.0.pre (Gem::Exception)
	from /usr/local/bin/rails:19

Right... Luckily, the binary is (naturally) to be found in the Rails Git repository which we just cloned, so that's not a problem.

ruby rails/railties/bin/rails colorbox_rails_test
cd colorbox_rails_test/

To enable the generator we need to add the gem to the Gemfile.

gem "colorbox_rails", "0.0.0"

Now we just need to bundle the gem, before we should be able to run our generator.


gem bundle
script/generate colorbox

Tada! We have successfully created a Rails 3-compatible generator with Thor, installed it as a gem, and used it in a 3.0.pre application. But as good Ruby programmers there is one more thing we should do.

Testing the Generator

Head back to the root directory of our gem and create a test/ directory. Inside that we want a test_helper.rb file, and a generators/colorbox/ directory. Inside the latter we want a colorbox_generator_test.rb file, staying consistent with the structure of the lib/ directory. Open test_helper.rb and paste in the following:

require 'test/unit'
require 'rubygems'

Inside colorbox_generator_test.rb we want to require test_helper.rb and the generator file.

require 'test_helper'
require 'generators/colorbox/colorbox_generator'

Beneath we define our test case.

class ColorboxGeneratorTest < Test::Unit::TestCase
end

Inside we want one test method which tests for the presence and content of the jQuery plugin.

class ColorboxGeneratorTest < Test::Unit::TestCase

  def test_install_colorbox
    assert File.exists?(
      File.join(@destination, 'public', 'javascripts', 'jquery.colorbox-min.js')
    )

    assert_equal(
      File.read(File.join(@source, 'jquery.colorbox-min.js')),
      File.read(File.join(@destination, 'public', 'javascripts', 'jquery.colorbox-min.js'))
    )
  end

end

Obviously, a couple of instance variables are missing.

def setup
  @destination = File.join('tmp', 'test_app')
  @source = ColorboxGenerator.source_root

  ColorboxGenerator.start('', :destination_root => @destination)
end

Let us also clean up after ourselves, shall we?

def teardown
  FileUtils.rm_rf(@destination)
end

To run our tests let us define a task in Rakefile.

require 'rake/testtask'

Rake::TestTask.new do |test|
  test.pattern = 'test/**/*_test.rb'
  test.libs << 'test'
end

task :default => :test

Run rake and watch it go green.

Conclusion

This walk-through barely scratched the surface of the new generator system, but it hopefully gave you an idea of what has changed and what hasn't — and if you have never written a generator for Rails before, you can now! Don't forget to checkout the documentation of Thor.

L33t Links #68

L33t Links #67