Este artículo es una traducción de Living on the edge of Rails (19), publicado por Chu Yeow en su blog redemption in a blog.

Esta entrada cubre los cambios realizados en Rails entre el 29 de Abril y el 4 de Mayo de 2008.

change_table para las migraciones ActiveRecord

Gracias a Jeff Dean, que también ha escrito sobre la nueva funcionalidad podemos modificar una tabla con un bloque de la siguiente manera:


change_table :videos do |t|
  t.add_timestamps
  t.add_belongs_to :goat
  t.add_string :name, :email, :limit => 20
  t.remove_column :name, :email # puede recibir varios argumentos
  t.rename :new_name
  t.string :new_string_column # se ejecuta contra el nombre cambiado
end

Las funcionalidades clave son:
  • add_XX añadirá una nueva columna. Por ejemplo, add_string añadirá una nueva columna de tipo string
  • Por supuesto add_timestamps añadirá los cambios mágicos created_at y updated_at
  • remove_column puede recibir varios argumentos
  • rename renombraría la tabla

Una excelente mejora, de nuevo gracias a Jeff Dean. El grupo de cambios correspondiente a este parche es http://github.com/rails/rails/commit/96980bd561d79824b6cb6efbcbecdcbf8785d452

ActiveRecord::Base recibe un bloque, al igual que ActiveRecord::Base.new

En efecto, ahora podemos crear objectos ActiveRecord pasando un bloque como argumento exactamente igual que con ActiveRecord::Base.new:


@person = Person.create(params[:person]) do |p|
  p.name = 'Konata Izumi'
  p.age = 17
end

El autor es Adam Meehan, y el grupo de cambios correspondiente a este parche es http://github.com/rails/rails/commit/dd120ede53eaf71dee76894998a81626b7a689fc

Arreglo: change_column debería poder usar :null => true en un campo que anteriormente estaba marcado como false

Ahora podemos usar change_column en nuestras migraciones para alterar una columan como nulificable si anteriormente estaba marcada como NOT NULL

Esta corrección es cortesía de Nate Wiger y el grupo de cambios relacionado es http://github.com/rails/rails/commit/10ef65a3b054270ed3d458ec8eb7c2b9a3e638f7

Ahí va una rápida que me ha costado trabajo encontrar (gracias a kithpom en #rubyonrails): cuando tenemos varias versiones de la gema de Rails instaladas en nuestro sistema (por ejemplo, la 2.0.2 y la 1.2.6), si ejecutamos rails en la consola se creará un esqueleto de aplicación 2.0 ¿Qué pasa si, por desdicha, necesitamos crear una aplicación 1.2.x?

rails _1.2.6_ myapp

La explicación, en cat `which rails`

Ya están disponibles las transparencias de mi presentación en la II Conferencia Rails Hispana.

Lamentablemente, el grueso de la presentación consistió en una demo en la que, con mayor o menor fortuna, traté de mostrar parte de la funcionalidad que Slingshot nos ofrece con muy poco esfuerzo por nuestra parte. Esa parte os la teneis que imaginar :)

Actualización: también están disponibles en Slideshare:

Como ya ha anunciado Sergio, el próximo jueves comienza la II Conferencia Rails Hispana en Madrid. Este año, al igual que el anterior, estaré por allí y haré una presentación.

En este caso se trata de una introducción a Joyent Slingshot, la plataforma para integrar aplicaciones Rails en el escritorio. ¡Nos vemos allí!

Una de las críticas más habituales que se hace a Ruby es el pobre rendimiento del intérprete Ruby frente a otros lenguajes rivales. Las cifras indican que por lo general para un mismo algoritmo el intérprete de Ruby ofrece un menor rendimiento que Python, PHP, Perl, ponga su lenguaje favorito.

La respuesta comunmente aceptada para esta penalización de rendimiento suele ser que Matz es un gran diseñador de lenguajes pero su labor como implementador no es tan brillante. El intérprete de Ruby escrito por Matz, conocido como MRI (de Matz Reference Implementation, o Implementación de Referencia de Matz) adolece de no pocos problemas (hay quien afirma que un lenguaje como C no es buena elección a la hora de implementar lenguajes dinámicos)

En todo caso, la comunidad de Rubistas (con Matz a la cabeza) hace tiempo que ha asumido el problema de rendimiento en la MRI y han surgido no pocos proyectos para corregir esta deficiencia. Uno de los problemas con los que se enfrentan los valientes que desean implementar un lenguaje es que, a diferencia de esfuerzos recientes como Perl 6, el lenguaje Ruby no dispone de una especificación funcional. Por tanto, la única fuente fiable de documentación sobre los entresijos del lenguaje Ruby es el estudio del código fuente en C del intérprete escrito por Matz (de ahí el nombre MRI)

YARV

El primero de estos esfuerzos es, por derecho propio, YARV de Sasada Koichi, la reimplementación de Ruby usando su propia máquina virtual y un compilador a código de bytes. El trabajo de Sasada Koichi, basado en un enfoque más moderno que la implementación de Matz, es tan brillante que la versión oficial 1.9.1 de Ruby estará basada en YARV. Aquí hay una serie de gráficos que muestran las mejoras de rendimiento de YARV frente a la MRI. Como puede verse, aún hay algún trabajo por hacer pero cabe destacar que, por lo general, una aplicación Rails verá mejoras de rendimiento del 15% sólo por usar YARV.

Como se ha visto, YARV divide el problema de la implementación del lenguaje en dos etapas: un compilador de código de bytes y una máquina virtual que ejecuta dicho bytecode. Surge de manera natural la pregunta de si no sería posible usar alguna máquina virtual ya existente y generar bytecode compatible con ella. Y, hablando de máquinas virtuales, la de Java está más que probada.

Ruby en Java

De aquí surge JRuby, que consiste en escribir un intérprete Ruby escrito en Java. Inicialmente un proyecto con pocos visos de ver la luz, al cabo de año y medio -y probablemente gracias al apoyo de Sun- en la actualidad es posible (como ya hemos visto) ejecutar aplicaciones Rails dentro de un contenedor de aplicaciones Java, lo que da una idea de la madurez del proyecto. La versión para Ruby de Netbeans soporta JRuby por defecto. Una de las ventajas de ejecutar Ruby en una máquina virtual de Java, por supuesto, es tener acceso a código ya existente escrito en Java lo cual hace a JRuby la implementación ideal para integrar Rails en entornos enterprise. Otra ventaja es que JRuby por definición se verá beneficiado de las mejoras posteriores que se realicen en el intérprete Java.

Y si JRuby es un intérprete de Ruby escrito en Java, XRuby es un traductor de Ruby a bytecode de Java (o lo que es lo mismo, de un fichero .rb generará un .class) Este traductor está basado en ANTLR. Aun en sus etapas iniciales, XRuby ya es capaz de generar bytecode Java para toda la librería estándar de Ruby y para Ruby on Rails 1.2.x

Rubinius

El siguiente en la lista esRubinius. El enfoque de esta iniciativa consiste en escribir la mayor parte del lenguaje en Ruby y dejar que la máquina virtual -escrita en C- ejecute el menor subconjunto posible de funcionalidad. En concreto, Rubinius pone el énfasis en el recolector de memoria y la implementación de forks y threads. A destacar que Rubinius parece surgir del mundo Rails (Evan Phoenix trabaja en Engine Yard, proveedores de alojamiento avanzado Rails) por tanto buena parte de las mejoras que promete en el intérprete atacan a los problemas de rendimiento que más pueden aquejar a nuestras aplicaciones Rails.

Ruby sobre .NET

Por supuesto que si hablamos de implementar Ruby sobre máquinas virtuales ya existentes sería pecado olvidarse de Microsoft. Microsoft, ya sea de manera oficiosa u oficial, ha estado detrás de dos proyectos.

El primero es Ruby.NET. Se trata de un proyecto de código abierto impulsado por Wayne Kelly y John Cough de la Queensland University of Technology (en Australia) y parcialmente financiado por Microsoft. Sin embargo, según los propios autores, la tarea de soportar RoR aún no está lista para su lanzamiento público.

IronRuby es la segunda implementación de Ruby sobre .NET más interesante quizá porque surge de un desarrollador de Microsoft, John Lam. Lam se ha basado en el Dynamic Language Runtime , originalmente pensado para Python y LISP. Parece ser que el desarrollo de IronRuby ha sido lo suficientemente complejo como para justificar cambios en el propio DLR oficial de Microsoft para acomodar a Ruby.

Si bien los esfuerzos en .NET como vemos parecen estar más verdes que sus equivalentes en Java, conviene tener en cuenta -sobre todo si desarrollamos sobre Windows- a estos dos proyectos.

Conclusión

Es evidente que vivimos tiempos interesantes. Si a un lenguaje que, no olvidemos, ya es suficientemente rápido como para ser competitivo por derecho propio le añadimos las mejoras y optimizaciones que están ahora mismo en los laboratorios de los desarrolladores arriba mencionados sólo pueden venir buenas noticias.

¿Ruby sobre C? ¿Sobre Java? ¿ Sobre .NET? ¿Ruby sobre Ruby? ¡Elige tus armas!

Corriendo aplicaciones Rails en JBoss

El objetivo de esta anotación es mostrar los pasos necesarios para lanzar una aplicación Rails dentro de un servidor de aplicaciones Java, en este caso el popular JBoss La intención última es comprobar los progresos está llevando a cabo el equipo de JRuby. Quién nos lo iba a decir hace un año, así que… ¿quién nos dirá qué va a ocurrir para el año que viene?

Instalación de JRuby

El primer paso será la instalación de JRuby , por supuesto. Damos por ya instalado Java en nuestra máquina. Para Mac OS X, la instalación por defecto de Java será suficiente (lo cual es raro)

1
2
3
  cd ~/jrubydemo/
  wget http://dist.codehaus.org/jruby/jruby-bin-1.0.1.tar.gz
  tar zxvf jruby-bin-1.0.1.tar.gz

Y se creará el directorio jruby-1.0.1. A continuación, añadiremos las siguientes variables al entorno de ejecución actual, una forma de hacerlo es la siguiente

1
2
3
Tamarindo:~/jrubydemo/ juan$ cat > entorno
export JRUBY_HOME=`pwd`/jruby-1.0.1
export PATH=$JRUBY_HOME/bin:$PATH

Cada vez que cambiemos este fichero tendremos que acordarnos de ejecutar el nuevo entorno en todas las sesiones que tengamos abiertas, hasta que añadamos estas variables al entorno por defecto.


. entorno

Ya podemos comprobar que las variables de entorno están presentes. Posteriormente podremos añadir la carga de este entorno a nuestro profile, pero de momento nos bastará con recordar que hay que evaluar este archivo de entorno para poder trabajar con JRuby. Ya podemos hacer nuestra primera, emocionante prueba:

1
2
Tamarindo:~ juan$ jruby -v
ruby 1.8.5 (2007-08-23 rev 4201) [i386-jruby1.0.1]
1
2
Tamarindo:~ juan$ which gem
/Users/juan/jrubydemo/jruby-1.0.1/bin/gem

JRuby incluye su propia versión de gem, y todas las gemas que instalemos con el entorno de JRuby quedarán instaladas en $JRUBY_HOME/lib/gems, de manera que no romperemos nuestra instalación estándar de Rails y otra gemas.

Instalación de Rails sobre JRuby

Merced a la flexibilidad del sistema de paquetes de Ruby, este paso será muy sencillo. Para instalar a los sospechosos habituales bastará con:

1
2
gem install rails -y --no-rdoc
gem install activerecord-jdbc --no-rdoc --no-ri

Esto instalará las gemas habituales: ActionMailer, ActionPack, ActiveSupport, ActiveRecord, Rails, Rake, RSpec, etc. asi como el adaptador JDBC para ActiveRecord. En este punto es cuando cualquier tutorial de Rails que se precie creará una aplicación de prueba (por ejemplo, un blog, un CMS, un mashup o cualquier otra cosa) en menos de 5 minutos. Nosotros, que somos algo más vagos, optaremos por instalar una aplicación que ya tenemos lista en su repositorio de Subversion:

1
2
3
4
cd ~/jbossdemo
svn checkout  http://mi-repositorio.com/myapp/trunk myapp
cd myapp
jruby script/server

Y, si la base de datos está arrancada y ’’config/database.yml’’ está bien configurado, la aplicación funcionará sin más dilación. El mismo código de nuestra aplicación funcionará tanto sobre la implementación de Ruby en C que sobre la implementación en Java. No es poco.

Más difícil todavía: JBoss

Descargaremos JBoss de Sourceforge. El paquete zip es algo más pequeño:

1
2
3
4
http://labs.jboss.com/jbossas/downloads/   (4.2.1GA)
cd ~/jrubydemo/
wget http://downloads.sourceforge.net/jboss/jboss-4.2.1.GA.zip?modtime=1184616808&big_mirror=1
unzip jboss-4.2.1.GA.zip  (la versión zip es más pequeña)

Y por supuesto a nuestro fichero de entorno añadiremos:

1
2
3
4
cd ~/jbossdemo
export JRUBY_HOME=`pwd`/jruby-1.0.1
export JBOSS_HOME=`pwd`/jboss-4.2.1.GA
export PATH=$JRUBY_HOME/bin:$PATH

Configurando el acceso a bases de datos en JBoss

Este paso es lógicamente específico de JBoss, pero es necesario para correr nuestra aplicación. Dado que en el mundo Java no está aún muy extendido lo de la convención sobre configuración, tendremos que explayarnos un poco en el acceso a la base de datos, creando el fichero ’’jboss-4.2.1.GA/server/default/deploy/mysql-ds.xml’‘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8"?>
<datasources>
  <local-tx-datasource>
    <jndi-name>MySqlDS</jndi-name>
    <connection-url>jdbc:mysql://mysql-hostname:3306/myapp</connection-url>
    <driver-class>com.mysql.jdbc.Driver</driver-class>
    <user-name>usuario</user-name>
    <password>clave</password>
    <exception-sorter-class-name>org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter</exception-sorter-class-name>

    <metadata>
       <type-mapping>mySQL</type-mapping>
    </metadata>
  </local-tx-datasource>
</datasources>

Con esta configuración le hemos dicho a JBoss que acceda a MySQL usando JDBC para acceder a la base de datos. Pero aún necesitamos instalar el correspondiente driver JDBC para Java, lo que MySQL denomina conector . De esta página podremos descargar el conector en forma de archivo .zip y la instalación es tan sencilla como copiar un archivo ’’.jar’’ al directorio lib del directorio de nuestro servidor.

1
2
3
4
cd ~/jbossdemo
wget http://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-java-5.1.3-rc.zip/from/http://mysql.rediris.es/
unzip mysql-connector-java-5.1.3-rc.zip
cp mysql-connector-java-5.1.3-rc/mysql-connector-java-5.1.3-rc-bin.jar  jboss-4.2.1.GA/server/default/lib/

Con todo esto, ya podremos probar si JBoss goza de buena salud.

cd $JBOSS_HOME
bin/run.sh

Tras varios mensajes esotéricos, JBoss cobra vida:

INFO  [Server] JBoss (MX MicroKernel) [4.2.1.GA (build: SVNTag=JBoss_4_2_1_GA date=200707131605)] Started in 16s:863ms

También nos interesa el mensaje

[ConnectionFactoryBindingService] Bound ConnectionManager 'jboss.jca:service=DataSourceBinding,name=MySqlDS' to JNDI name 'java:MySqlDS'

que indica que JBoss ha creado el gestor de conexiones MySQL. Es hora de mudar nuestra aplicación Rails.

Creando un archivo .war

La manera estándar de desplegar aplicaciones dentro de un contenedor Java es mediante el uso de archivos war” los cuales no son más que ficheros zip que contienen toda una jerarquía de directorios con clases y recursos estáticos (HTML, imágenes, etc) aderezados con ciertos metadatos. Cuando uno de estos ficheros aparece en el directorio adecuado del servidor, éste lo abre, analiza los metadatos y toma las acciones pertinentes para activar la aplicación.

Así, nos hace falta crear un fichero war que contenga nuestra aplicación Rails. Afortunadamente, no tenemos que preocuparnos mucho del asunto porque ya existe un plugin Rails que se encarga de ello, creado por Charles Nutter. Tenemos toda la información necesaria en este wiki

cd ~/jbossdemo/myapp
jruby script/plugin install svn://rubyforge.org/var/svn/jruby-extras/trunk/rails-integration/plugins/goldspike

Para configurar los contenidos de nuestro war no todo van a ser convenciones, tenemos que editar el archivo config/war.rb para referenciar el conector.

1
2
  
maven_library 'mysql', 'mysql-connector-java', '5.0.4'

Teniendo en cuenta que por defecto Goldspike ejecutará la aplicación Rails en modo producción, tendremos que configurar adecuadamente ’’config/database.yml’’ Por último:


rake war:standalone:create

Tras un rato de funcionamiento la tarea de Rake finalizará dejando un bonito myapp.war en nuestro directorio. Obsérvese que el fichero .war pesa la friolera de 18M: no en vano incluye todo JRuby, Rails y las gemas que tuviéramos instaladas. Si no hemos parado la anterior instancia de JBoss podemos copiar en caliente el war:

cp myapp.war /Users/juan/jbossdemo/jboss-4.2.1.GA/server/default/deploy/

Veremos que en JBoss comienzan a pasar cosas y nuestra aplicación cobra vida.

23:29:10,762 INFO  [TomcatDeployer] deploy, ctxPath=/myapp, warUrl=.../tmp/deploy/tmp22213myapp-exp.war/
23:29:11,322 INFO  [[/myapp]] Ruby is running in standalone mode
23:29:14,472 INFO  [[/myapp]] JRuby init time: 2082ms
23:29:26,946 INFO  [[/myapp]] Rails init time: 9818ms
23:29:26,947 INFO  [[/myapp]] Runtime 0 loaded
23:29:26,947 INFO  [[/myapp]] Requests can now be processed
23:29:35,874 INFO  [[/myapp]] Runtime 1 loaded

Básicamente, JBoss descomprime el war en un directorio temporal y lanza dos instancias de Rails, lo que sería equivalente a dos mongrels tradicionales (por supuesto todo esto en configurable, según se explca en el wiki de Headius)

Tras esto, y si no ha habido errores (es de suponer que no) ¡es el momento de probar nuestra aplicación! Sólo debemos tener en cuenta que la URL raíz es, por la configuración de JBoss por defecto, algo distinta: http://127.0.0.1:8080/myapp, por lo que las URLs absolutas de nuestra aplicación (si las hubiera, que no debería) dejarán de funcionar.

En este artículo haremos una breve introducción al servicio Amazon Elastic Computing Cloud (EC2) y describiremos los pasos necesarios para ejecutar nuestra propia instancia y lanzar una aplicación Rails.

Read the rest of this entry

Hace unos meses, los chicos de b-simple publicaron el tutorial en PDF RESTful Rails Development con licencia CC. Desde hoy, está disponible también la versión en castellano traducida por mí.

De la página del proyecto: NetworkFacade es una alternativa a DRb, XMLRPC, y REST, todo en uno.

Hay ejemplos de su uso como recubrimiento de sockets TCP, Unix y SSL, así como el acceso a las APIS REST de Flickr, Digg y Netvibes.

Mingle es una aplicación escrita con Ruby on Rails. Hasta aquí, nada de extrardinario.

Lo interesante llega cuando nos cuentan algunas de sus características: será una aplicación de escritorio para entornos corporativos y que deberá correr sobre Windows, Linux y Mac.

Y más interesante aún es la plataforma sobre la que corre esta aplicación Rails: usa como base de datos Derby y Jetty como servidor web. Nada que ver con los habituales mongrels y MySQL.

A estas alturas ya no nos sorprende saber que Mingle también se distribuirá como un fichero .war que se puede desplegar en cuaqluier contenedor J2EE.

Todo esto es posible gracias a JRuby

¿Por qué la siguiente expresión en Ruby no da ningún error


irb(main):001:0> puts x=1 if x.nil?
1
=> nil

... mientras que la siguiente falla estrepitosamente?


irb(main):001:0> puts "z is not defined" if z.nil?
NameError: undefined local variable or method `z' for main:Object
        from (irb):1

la diferencia ocurre en el análisis sintáctico que realiza el intérprete, que cuando se encuentra con un identificador (en nuestro caso, x y z) debe determinar si se trata de un objeto o bien de una invocación de un método definido en el ámbito de la clase o módulo actual (z.nil podría referirse a un método z que devuelve un objeto del que comprobamos si es nulo o no)

En el primer ejemplo, el intérprete de Ruby ve que hacemos referencia a una variable x en la expresión x=1 de manera que asume que x es un objeto, al que se le puede invocar el método x.nil?

En el segundo caso no es así: la referencia a z ocurre dentro de una cadena -donde el intérprete no husmea en busca de información sintáctica-, de manera que cuando llegamos a if z.nil? Ruby asume que se trata de una función o un método definido en el ámbito actual. Al no encontrarlos, devuelve el mensaje de error que, una vez considerado lo anterior, resulta ser bastante descriptivo.

Podemos comprobar que este proceso ocurre en el análisis sintáctico del fichero (en lugar de en tiempo de ejecución del código) con el siguiente código:


    irb(main):001:0> x.class
    NameError: undefined local variable or method `x' for main:Object
            from (irb):1
    irb(main):002:0> if false         
    irb(main):003:1>   x=0
    irb(main):004:1> end
    => nil
    irb(main):005:0> x.class
    => NilClass

La línea x=0 jamás se ejecuta, pero sí que es tenida en cuenta a la hora de determinar que x debe tratarse como un objeto en lugar de como una invocación de método.

Desde que trabajo con Ruby on Rails de manera intensiva cada día, una de las cosas que más me incordia es abandonar el cómodo entorno de script/console para lanzar la (al menos para mí) menos cómoda interfaz de mysql La razón por la que me veo obligado a hacer esto con cierta frecuencia es para encontrar los ids de los objetos ActiveRecord que me interesan para investigar o depurar alguna funcionalidad. Cuando la aplicación tiene cientos de registros en las tablas, viene muy bien poder hacer consultas por el nombre aproximado con la construcción LIKE de SQL. ActiveRecord nos permite escribir nuestro propio SQL pero como soy un vago me resulta bastante tedioso escribir la condición completa con la sintaxis que pide Rails, así que normalmente ando saliendo de la consola, escribiendo una sentencia SELECT ... LIKE en mysql y volviendo a la consola con el id apuntado.

Hasta hoy, que me he terminado de cansar y he escrito un plugin que permite hacer sentencias LIKE desde ActiveRecord de manera dinámica.

Finders dinámicos en ActiveRecord

Los finders dinámicos de ActiveRecord son una de las primeras perlas de Ruby con las que uno se encuentra cuando investiga las tripas de Rails. Supongo que serán conocidos, pero no está de más repasarlos un poco.

Supongamos que tenemos una clase ActiveRecord mapeada sobre una tabla MyTable con los atributos atributo1, atributo2 y atributo3, sabemos que podemos escribir:


Mytable.find_by_id(87)

Y ActiveRecord tratará de encontrar el registro identificado con el id 87. No parece nada del otro mundo, porque entre otras convenciones ActiveRecord asume que la clave primaria en nuestras tablas será una columna llamada id, por lo que podría ser que existiese un método find_by_id en ActiveRecord::Base

Pero resulta algo más sorprendente toparse con que sentencias como:


Mytable.find_by_atributo1('Valor')

también funcionan. Aquí comienza a saborearse el dinamismo de Ruby, y podemos asumir que ActiveRecord, de manera astuta, construye tantas funciones Mytable::find_by_... como atributos tengamos en la tabla, que lanzarán la correspondiente consulta SQL a la base de datos.

Pero.. ¡un momento!

1
2
Mytable.find_by_atributo1_and_atributo3('Valor', 58)
Mytable.find_by_atributo1_and_atributo2_and_atributo3('Valor',58,'Valor3')

¡También! funcionan. Y sería ridículo pensar que ActiveRecord construye funciones de manera dinámica para todas las combinaciones posibles de los atributos (y, además, en cualquier orden que queramos)

Nuestro propósito es añadir más finders dinámicos, que en lugar de búsquedas exactas hagan búsquedas aproximadas, invocándose de la siguiente manera:

Mytable.find_like_atributo1('al')

Para hacerlo, tenemos que entender bien qué hace ActiveRecord con estas misteriosas funciones dinámicas…

La magia de method_missing

A estas alturas ya nos imaginamos que no se añaden métodos para cada atributo, sino que hay algún otro mecanismo actuando en este caso. Se trata de method_missing que es el método que ejecuta una clase Ruby cuando se le invoca un método que no tiene implementado. Aquí podeis ver otro uso muy creativo de method_missing

ActiveRecord se aprovecha de method_missing para articular los finders dinámicos. Si abrís el fichero lib/active_record/base.rb vereis que la clase ActiveRecord::Base define un method_missing muy especial (si no estais viendo ese código, hacedlo ahora: leer código del core de Rails es siempre un ejercicio inspirador). Este método se activa cuando llamamos a Mytable.find_by_attributo('valor'), y lo primero que hace es comprobar con una expresión regular si el método invocado tenía la pinta find_by o find_all_by, en cuyo caso pasa a construir una sentencia SQL de búsqueda según los atributos y parámetros recibidos. Parece evidente que nosotros tendremos que puentear este método y hacer algo parecido por nuestra cuenta pero construyendo sentencias SQL con el modificador LIKE.

Cómo construir nuestro propio finder

Lo primero es preparar un plugin, lo cual es tan fácil como crear un directorio find_like en vendor/plugins, y ahí escribiremos un fichero init.rb con el siguiente contenido:


require 'find_like'

A continuación, creamos el directorio vendor/plugins/find_like/lib y ahí pondremos el código de nuestro plugin:

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
module ActiveRecord

class Base
  class << self
  
  private
      
  def construct_conditions_from_like_arguments(attribute_names, arguments)
    conditions = []
    attribute_names.each_with_index{ |name, idx| 
      conditions <<= "#{table_name}.#{connection.quote_column_name(name)} LIKE '%%" << arguments[idx] << "%%'"}
    [conditions.join(" AND "), *arguments[0..attribute_names.length]]
  end
      
  alias_method :previous_method_missing, :method_missing

  def method_missing(method_id, *arguments)
    if match = /find_(all_like|like)_([_a-zA-Z]\w*)/.match(method_id.to_s)
      finder = (match.captures.first == 'all_like' ? :find_every : :find_initial)
          
      attribute_names = extract_attribute_names_from_match(match)
      super unless all_attributes_exists?(attribute_names)
      conditions = construct_conditions_from_like_arguments (attribute_names, arguments)
      options = {:conditions => conditions}
      set_readonly_option!(options)
      send(finder, options)
    else
      previous_method_missing(method_id, *arguments)
    end
  end
end
end
end

El código más o menos es fácil de seguir: usamos class << self para abrir la clase ActiveRecord::Base y modificar sus propias tripas. Dado que estamos volviendo a escribir el método method_missing no querremos perder la funcionalidad ofrecida por el method_missing originalmente incluido con ActiveRecord::Base (el que veíamos antes en base.rb) así que simplemente haremos un alias a este método y lo llamaremos desde nuestro method_missing si detectamos que el método invocado no es uno de los que nosotros queremos controlar (find_like o find_all_like)

Tras esto, podemos lanzar una consola y comprobar que, por arte de birlibirloque, ya tenemos nuestros finders operativos:

1
2
3
4
5
6
7
8
9
Tamarindo:~/src/repos/dconrails juan$ script/console development
Loading development environment.

>> Algoritmo.find_all_like_nombre("oyal")

=> [#<Algoritmo:0x245c908 @attributes={"status"=>"BEGIN", "nombre"=>"RoyalRoadDemo"}>, #<Algoritmo:0x245c82c @attributes={"status"=>"RUN", "nombre"=>"RoyalRoad2">]

>> Algoritmo.find_all_like_nombre_and_status("oyal", "UN")
=> [#<Algoritmo:0x243dabc @attributes={"status"=>"RUN", "nombre"=>"RoyalRoad2"}>]

Repasando la lista ruby-talk me he topado con este artículo titulado REST eye for the SOA guy escrito por Steve Vinoski, que a la sazón trabaja(ba) para IONA, compañía con cuyo producto Orbix me las tuve que ver diariamente durante muchos años.

Creo que lo que hace más interesante el artículo es que Vinoski adopta un punto de vista neutro, al contrario de lo que suele leerse, lo que es de agradecer.

Sergio Gil publica en su blog un artículo describiendo el enfoque usado en TheCocktail para poder manejar varias bases de datos.

Eso es todo por el momento, permanezcan a la escucha.

Son conocidos los screencasts de Peepcode, obra del magno Topfunky. Diría que se tratan de un recurso imprescindible: por un puñado de dólares (algo menos de euros) uno recibe una densa hora de conceptos y consejos de alta calidad.

Pues bien, hoy he descubierto Railscasts, donde Ryan Bates publica screencasts breves con trucos y consejos de Rails. Uno puede, además, suscribirse en iTunes, lo que lo hace todo aún más fácil.

Y, sí, Peepcode también tiene algo que ver con esto.