Mejorando algunos scopes de Rails

Muchas veces, para filtrar los resultados por una relación padre, he hecho o me encontrado scopes como el siguiente:

scope :for_user, -> (user) { where(user_id: user.id) }

Que me permite hacer cosas del estilo

MiModelo.for_user( User.first )

Pero tiene el inconveniente de que sólo funciona con un parámetro de tipo User (bueno, en realidad con un objeto que responda a #id). En multitud de ocasiones me he encontrado con un modelo que tiene una dependencia belongs_to sobre User, es decir, tiene un atributo user_id pero que no puedo usar directamente, porque el scope sólo acepta un objeto. Así, acabo haciendo cosas del estilo de

# membership.user carga el objeto user de BBDD sin necesidad
MiModelo.for_user(membership.user)

Sin embargo, Rails es lo suficientemente listo para discriminar qué parámetro se le pasa al where, y si no forzamos que el parámetro sea un objeto, ampliamos muchísmo la funcionalidad del scope:

scope :for_user, -> (user) { where(user_id: user) }

Simplemente quitando el .id ya podemos hacer las siguientes consultas:

MiModelo.for_user(User.first)         # Usa un objeto como parámetro
MiModelo.for_user(User.active)        # Usa una relación como parámetro
MiModelo.for_user(User.active.to_a)   # Usa un array de objetos como parámetro
MiModelo.for_user(membership.user_id) # Use un número como parámetro
MiModelo.for_user(Course.user_ids)    # Usa un array de números como parámetro
MiModelo.for_user(1..100])            # Usa un rango de números como parámetro

Actualizando un rails congelado bajo subversion

Últimamente estoy actualizando algunos proyectos que tienen rails congelado bajo el directorio vendor -que es lo habitual cuando tienes varios proyectos en una máquina y no quieres estar pendiente de actualizar la aplicación cada vez que rails se actualiza en la máquina-, y lo primero que estoy haciendo es actualizar la versión de rails.

A primera vista, la actualización parece algo trivial:

rake rails:freeze:gems
rake rails:update

Pero cuando tienes el proyecto bajo subversion, en seguida te das cuenta del error: la tarea de rake lo primero que hace es

rm -rf vendor/rails
mkdir -p vendor/rails

con lo que se ha cargado el directorio .svn con toda la información que subversion almacena allí. En cuanto intentas hacer algo con subversion, te encuentras con problemas:

$ svn st
~       rails
$ svn add rails/
svn: warning: 'rails' is already under version control
$ svn commit rails/
svn: '/home/frsantos/project/vendor/rails' is not a working copy
$ svn del rails/
svn: Directory 'rails/.svn' containing working copy admin area is missing

con lo que te toca volver a la versión anterior y volver a empezar:

rm -rv vendor/rails/
svn up vendor

Lo sencillo en este caso es hacer svn delete, svn commit y hacer el freeze de nuevo, pero con esto estamos introduciendo una versión en el repositorio que no es consistente. Para la gente, como yo, a los que les gusta hacer un commit atómico con todos los ficheros que se han modificado para una funcionalidad, he preparado un script de shell que actualiza el directorio rails con los cambios de la última versión rails.

[dm]1[/dm]

Además, tiene el extra añadido de que crea un changelist con todos los ficheros modificados en ese directorio, por lo que si usas un entorno gráfico como RubyMine, te saldrá automáticamente en la pestaña de Changes el changelist con el nombre apropiado con todos los ficheros modificados (si usas este entorno, un consejo: crea un changelist y márcalo como el changelist por defecto. Cuando acabe el proceso, todos los ficheros modificados que no se hayan marcado con un changelist en subversion irán automáticamente a ese sitio).

Usando múltiples Rubies con RVM

Lo primero que hice, incluso antes de empezar a trabajar en la empresa, fue instalarme Ruby 1.8.7 en mi Ubuntu, junto con Rails 2.3.5 y algunas gemas como mongrel. Para ello, seguí los howtos que fui encontrando por internet, como este de Hakido. Tras dejarlo todo configurado, al arrancar Eclipse te configura algunas gemas más como linecache, ruby-debug-ide y ruby-debug-base, con lo que ya tenía todo configurado para empezar a trabajar.

Sin embargo, en seguida te das cuenta de que si vas a tener que trabajar en varios proyectos, cada uno iniciado en un momento distinto con distintas versiones de Ruby y/o Rails, entonces cambiar de proyecto se puede hacer algo incómodo. La solución para ello, por supuesto, ya está pensada y es RVM (Ruby Version Manager).

RVM te permite tener distintas versiones de Ruby funcionando al mismo tiempo, con sus gemas particulares, y lo que es mejor, puedes tener todas las combinaciones de ruby/gemas que quieras. De esta forma, podemos tener un Ruby 1.8.6 con Rails 2.3.4, otro 1.8.6-2.3.5, otro 1.8.7-2.3.5 y otro 1.9.2-3.0.0. Simplemente cambias de configuración con un solo comando y a trabajar.

Al final, he desinstalado todo lo que tenía instalado de ruby en el sistema y lo he vuelto a instalar con RVM de la siguiente forma:

En primer lugar, instalamos las dependencias necesarias:

sudo apt-get install git-core curl zlib1g-dev libreadline5-dev

Después instalamos RMV. Como por ahora no hay un paquete .deb para ubuntu, lo bajamos desde Github:

mkdir -p ~/.rvm/src/rvm/
cd ~/.rvm/src
git clone http://github.com/wayneeseguin/rvm.git
cd rvm
./install

Ahora hay que añadir la siguiente línea en los ficheros ~/.bashrc y ~/.bash_profile

if [[ -s ~/.rvm/scripts/rvm ]] ; then source ~/.rvm/scripts/rvm ; fi

Cerramos y volvemos a abrir la shell para que nos pille los cambios, y ya estamos listos para instalar las versiones de ruby que necesitemos. Por ejemplo, si queremos instalar las versiones 1.8.7, 1.9.1 y jruby, haremos lo siguiente:

rvm install 1.8.7,1.9.1,jruby

Lo cual tardará un rato, pues debe descargar y compilar todas las versiones que les hemos dicho. Para seleccionar una versión en concreto:

rvm use 1.8.7

Y para hacer que una versión en concreto esté seleccionada siempre por defecto:

rvm use 1.8.7 --default

Ahora ya podemos instalar las gemas que necesitemos en esta versión de ruby, como rails, mongrel, …

gem install rails mongrel linecache ruby-debug-base ruby-debug-ide

que se instalarán sólo en la versión de ruby que esté actualmente activa.

Eclipse, sin embargo, tiene problemas para pillar el entorno de desarrollo correcto, por lo que tenemos que modificar el script de arranque y poner algo como esto para arrancar eclipse con la versión por defecto de ruby (podemos sustituir default por una versión concreta si queremos):

bash -cl "rvm default; eclipse"