Este artículo es una traducción de Living on the edge of Rails (20), publicado por Chu Yeow en su blog redemption in a blog.
El informe de esta semana cubre los cambios entre el 5 y el 11 de Mayo de 2008 en Rails.
script/dbconsole
Se ha añadido un script en scripts/dbconsole que nos permite conectarnos a la base de datos usando el cliente de consola de la misma. Si por ejemplo necesitásemos conectarnos al servidor de base de datos de producción (¡más vale que sepamos lo que estamos haciendo!) podríamos ejecutar RAILS_ENV=production script/dbconsole y nos conectará a la base de datos utilizando el cliente de comandos de MySQL. También funcionará con las bases de datos SQLite y PostgreSQL.
Como siempre, para disponer de este script en las aplicaciones que ya tengamos funcionando debemos recordar ejecutar rake rails:update:scripts después de actualizarnos a la última versión edge de Rails
Esta pequeña mejora es obra de Steve Purcell, que ya tenía un plugin similar.
Los cambios correspondientes son http://github.com/rails/rails/commit/4a07103687084496b773e18a03b1f2f5e686f7ad
flash.now en tests
Esto era algo con lo que posiblemente muchos desarrolladores de Rails nos hemos topado a la hora de escribir tests de los mensajes flash que envía nuestra aplicación mediante flash.now. Básicamente: uno no puede hacer pruebas sobre los contenidos de flash.now porque siempre se vaciaban antes de que el código del test se ejecutase sobre ellos.
# En el controlador
flash.now[:notice] = 'Debes de estar de broma!'
# En el test
assert_equal 'Debes de estar de broma!', flash.now[:notice]
# FALLA porque flash.now es nulo
Andreas Neuhaus le echó un buen vistazo al funcionamiento y se aseguró de que se puedan hacer tests sobre flash.now sin tener que recurrir a assert_select
El cambio en cuestión está en http://github.com/rails/rails/commit/74eed6290e63111d1aad2b181692a84f4f040aea
No hay mucho más que reseñar pero, como siempre, si queremos conocer hasta los más minimos detalles, lo mejor es repasar los logs de commit de Rails.
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:
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 entryHace 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.


