Isolating gem sets under Passenger with RVM 2010-05-29

For those who are thinking about deploying more than one ruby application on the same machine, and find it tricky to deal with different gem versions, here it is a short guide to having distinct gem paths for each project. I believe it suits small applications running under low load.

I started from a clean Debian installation, however I will assume here that apache2 is already installed and ruby is still not.

Installing RVM

As RVM documentation says, run:

bash < <( curl http://rvm.beginrescueend.com/releases/rvm-install-head )

then follow the instructions to add rvm to your path.

Installing Ruby

Once RVM is installed, running rvm notes will tell you what is required in order to install ruby versions. As I wanted to install the MRI interpreter (at first) I did install the following packages:

aptitude install curl bison build-essential zlib1g zlib1g-dev libssl-dev libreadline5-dev libreadline6-dev libxml2-dev git-core subversion autoconf

Now we are ready to compile our first ruby interpreter, I chose to install ruby 1.8.7

rvm install 1.8.7-head

I like to run my applications using the Ruby Enterprise Edition interpreter, therefore I installed it:

rvm install ree

I also set it as the default interpreter:

rvm ree --default --passenger

You may be wondering what we need the MRI interpreter for, well, I basically installed it because the REE installation script is written in Ruby.

Installing Passenger

In a nutshell:

gem install passenger
rvmsudo passenger-install-apache2-module

Edit your /etc/apache2/apache2.conf (or whatever file inside which you load the passenger module) and set:

PassengerRuby /home/user/.rvm/bin/passenger_ruby

Configuring an application

First of all, we need to create a gemset within which the application required gems will be installed:

rvm gemset create wadusappset
rvm gemset use wadusappset

Now we would do whatever it takes to have the needed gems properly installed (eg rake gems:install or bundle install).

Finally, we are going to tell Passenger where to find the gems. To find that place run:

rvm ree@wadusappset; rvm gemdir

And apply this formula:

GEM_HOME=`rvm ree@wadusappset; rvm gemdir`
GEM_PATH=$GEM_HOME:$GEM_PATH

Now, those environment values are set through the apache mod_env module:

<VirtualHost *:80>
  ServerName www.wadusapp.com

  DocumentRoot /var/www/wadusapp/current/public

  SetEnv GEM_HOME /home/user/.rvm/gems/ree-1.8.7-2010.01@wadusappset
  SetEnv GEM_PATH /home/user/.rvm/gems/ree-1.8.7-2010.01@wadusappset:/home/user/.rvm/gems/ree-1.8.7-2010.01:/home/user/.rvm/gems/ree-1.8.7-2010.01@global
</VirtualHost>

I did not find a better way of setting the variables so I went with copy & paste.

Fix a not too risky mess 2010-01-09

First of all, what I describe in this post was done in a concrete non critical situation. This worked fine for me but you should be careful.

What I needed to fix was just a typo and I didn't want to add another commit to my repository just to change one character, so I looked for a possible solution and I used a reduced version consisting of the following steps:

  1. Edit the files and make the correction. That is in case you want to fix your last commit. If you have more recent commits you will have to previously use git rebase as explained in the post linked earlier.
  2. Run git commit --amend and save the file. At this point the commit is fixed up.
  3. Once my local repository was as I wanted it to be I found that I could not push to the public repository. Then was when I reached the risky part. I did what is not recommended in this comment:

     $ git push origin +master:master
    

    Note: I guess this command is just a shortcut for git push -f origin master

Trivial friendly URL routing for Ruby on Rails 2009-08-28

Back to the times I was programming in PHP (it hasn't been that long but it feels like an eternity has passed), I worked in a company own made CMS. One of my biggest headaches was tuning and reimplementing the friendly URL generator.

Having the need to implement friendly URLs for a little and very specific CMS using Rails I found that it was a trivial matter. So I basically came up with a model acting as a tree (let's call it Page), a route and a class method.

First thing I did was creating a model Page with the fields I needed plus, a slug string field, plus a parent_id integer field. I installed the acts_as_tree plugin and made my model use it.

Then I set up a route:

   map.connect '*slugs', :controller => 'pages', :action => 'show'

Next thing was creating a class method to process the slugs chain:

class Page < ActiveRecord::Base
  def self.locate_by_slugs(slugs, parent_id=nil)
    page = if slugs.size == 1
      Page.find_by_slug_and_parent_id!(slugs.first, parent_id)
    else
      parent_page = Page.find_by_slug!(slugs.shift, :select => 'id')
      Page.locate_by_slugs(slugs, parent_page.id)
    end
  end
end

Lastly, I created the show action on PagesController:

class PagesController < ApplicationController  
  def show
    @page = Page.locate_by_slugs(params[:slugs].dup)
  end
end

Well, I haven't carried out a revolution, it's nothing fancy, it's been done a million times before, I don't even talk about the slug generation process. This post is just another evidence of how amazing the Ruby on Rails framework is.

Testear ActiveRecord sin Rails 2009-03-19

Hace unos días andaba algo ocioso y me puse a probar como escribir un plugin/gema para rails. Este plugin resultó en una muy sencilla extensión para modelos ActiveRecord, que simplemente mantiene una copia de los valores inversos de created_at y updated_at como enteros, con lo que podemos hacer consultas ordenando por estos campos de forma descendente.

Nada sofisticado. Pero a la hora de escribir los tests me encontré con que quería hacer esos tests independientes de Rails, pues solo afectan a ActiveRecord, y sin necesidad de ningún tipo de configuración. Para ello utilicé varios modelos de ActiveRecord y como base de datos sqlite.

Primero definimos un schema con las tablas de nuestros modelos:

ActiveRecord::Schema.define(:version => 0) do
  create_table :with_created_models, :force => true do |t|
    t.column :something, :string    
    t.column :created_at, :datetime
    t.column :updated_at, :datetime    
    t.column :created_at_inverse, :integer
  end
end

Una vez definido el schema, establecemos nuestro entorno de test, por ejemplo en test_helper.rb:

require 'test/unit'

require 'rubygems'
require 'active_record'
require 'shoulda'

require File.dirname(__FILE__) + '/../lib/inverse_sortable'

ActiveRecord::Base.establish_connection({
    :adapter => "sqlite3",
    :dbfile => "test/test.db"
})

class WithCreatedModel < ActiveRecord::Base
end

load(File.dirname(__FILE__) + "/schema.rb")

En este fichero, le decimos a ActiveRecord que utilice como base de datos un fichero de sqlite (test.db), definimos un modelo correspondiente a la tabla en nuestro schema.rb (declarando una clase que hereda de ActiveRecord::Base y con el nombre de la tabla en singular) y por último cargamos dicho schema.

Con esto ya disponemos de un modelo que podemos usar para testear el comportamiento de la extensión inverse_sortable, por ejemplo en inverse_sortable_test.rb:

require File.join(File.dirname(__FILE__), 'test_helper')

class WithCreatedModel
  acts_as_inverse_sortable
end

class InverseSortableTest < Test::Unit::TestCase
  context "A model that acts as an inverse sortable" do    
    should "have the inverse of its created_at time when is created if created_at_inverse is available" do
      with_created = WithCreatedModel.create(:something => "Testing")
      
      assert_not_nil with_created.created_at_inverse
      assert_equal -with_created.created_at.to_i, with_created.created_at_inverse
    end
    # ...
end

Para quién pueda interesar, tanto el plugin como los tests están en http://github.com/alvarobp/inverse_sortable, donde además hay un pequeño benchmark, sobre lo cual quizá comente en otro post.

Crear un repositorio Git remoto 2009-01-24

A modo de receta, he aquí la secuencia de comandos necesaria para la creación de un repositorio Git remoto, a través de SSH.

El primer paso es crear el repositorio en sí, para ello o bien creamos un directorio vacío donde empezar a meter el código de nuestra aplicación, o bien vamos al directorio donde tenemos ya nuestro código:

cd appname
git init

Acto seguido añadimos el contenido al repositorio y comiteamos el cambio:

git add .
git commit -m "inicilizacion del repositorio"

El último paso consiste en crear una copia del repositorio la cual subiremos al servidor (o moveremos dentro de la misma máquina, según proceda) y que contiene solamente la información interna de git y no una copia de trabajo:

git clone --bare appname appname.git
scp -r appname.git ssh://user@yourserver/path/to/git/

Por último, para comenzar a trabajar sobre el repositorio tenemos dos opciones, clonarlo desde su ubicación remota:

cd algunsitio
git clone ssh://user@yourserver/path/to/git/appname.git

lo que nos creará un directorio appname en algunsitio con la copia de trabajo, o bien añadimos el repositorio remoto a la copia actual (creada en el primer paso):

git remote add origin ssh://user@yourserver/path/to/git/appname.git