Programación

10.lines do: Helpers bajo demanda en Rails

01.03.2010 0

Dentro de los objetivos en uno de nuestros desarrollos en Rails está el tener cuantas más vías mejor a la hora de personalizar la aplicación de cara al cliente. En una de esas últimas incursiones en la carga dinámica para cada cliente estuvimos jugando con el tema de que pudieran tener helpers propios.

Imaginemos que dentro de config/preinitializer.rb tenemos algo como esto:

NUESTRO_CLIENTE = 'foobar'

Podemos crear un fichero llamado config/initializers/helper_loader.rb (el nombre es un poco lo de menos), que contenga lo siguiente:

helper_load_path = File.join(RAILS_ROOT, 'app', 'customizations', NUESTRO_CLIENTE, 'helpers')
ActiveSupport::Dependencies.load_paths << helper_load_path

Por último, creamos un fichero llamado app/customizations/foobar/helpers/foobar_helper.rb parecido al siguiente:

module FoobarHelper
  def nuestro_metodo
    # lo que sea
  end
end

Ya solo nos quedaría meter en nuestro app/controllers/application_controller.rb la siguiente línea:

helper Module.const_get("#{NUESTRO_CLIENTE.titleize}Helper")

Con esto ya tendríamos disponible el helper nuestro_metodo solo en aquellas instancias en las que NUESTRO_CLIENTE fuese foobar y no en el resto. O también nos podría servir para poder definir en varias instancias de manera completamente distinta nuestro_metodo (por ejemplo, definir un ad_tag_helper que para un cliente estuviese optimizada para SmartAdServer, para otro con OpenX, para otro con Google Adsense…).

Las posibilidades que nos brinda estas pocas líneas son infinitas :)

10.lines do: Trucos para paperclip

12.10.2009 0

Paperclip es un plugin para Ruby on Rails que, debido a su potencia y sencillez, ha conseguido ser la elección de la mayoría de los desarrolladores a la hora de implementar de manera sencilla una solución a la hora de tener ficheros adjuntos a un modelo. Además está muy bien documentada tanto en su wiki como en los RDoc.

El ejemplo más básico de uso de paperclip sería el siguiente:

class User < ActiveRecord::Base
  has_attached_file :avatar
end

Aparte de facilitarnos la vida a la hora de la subida de ficheros, mediante su integración con ImageMagick nos permite poder generar todas las miniaturas que necesitemos de una imagen dada:

has_attached_file :image,
  :styles => { :normal => ["610x610>", :jpg], :mini => ["100x100#", :jpg] },
  :convert_options => { :all => "-strip" },
  :path => ":rails_root/public/images/articles/:id/:style.:extension",
  :url => "/images/articles/:id/:style.:extension"

En este ejemplo estamos definiendo que, para el atributo image de nuestro modelo generaremos dos miniaturas: normal, cuyo tamaño será nunca superior a 610 de ancho o de alto (lo que primero suceda) y un tamaño mini, el cual será una imagen a 100×100 px haciendo crop y en caso de que la imagen sea más ancha que alta o viceversa. Además, para ambas miniaturas se quitarán todos los datos extra de la imagen (como el EXIF) para asegurarnos de que generaremos el menor tamaño posible de imagen (más opciones para los estilos o la conversión en la documentación de ImageMagick.

Desde nuestras vistas para hacer referencia al estilo mini no tendríamos más que hacer algo como lo siguiente:

<%= image_tag article.image.url(:mini) %>

En los parámetros :path y :url vemos que se hacen uso de etiquetas del estilo :id, :style o :rails_root. Es lo que dentro de Paperclip se conoce como interpolaciones, y que a la hora de su uso se sustituyen sobre la marcha por el dato que corresponda (:style se sustituiría por "mini", basándonos en el ejemplo anterior).

Una de las opciones más interesantes, aunque quizá de las menos usadas por la gente, son la posibilidad de definir nuestras propias interpolaciones. Por ejemplo, podríamos definir una interpolación de nombre :username, incluyendo el siguiente código en el fichero config/initializers/paperclip.rb:

Paperclip.interpolates :username do |attachment, style|
  attachment.instance.username
end

El anterior código define que :username será sustituida por el atributo username de la instancia del modelo desde el que estemos usando Paperclip. De esta manera, si hiciesemos algo como lo siguiente:

class User < ActiveRecord::Base
  has_attached_file :avatar, :path => ":rails_root/public/images/:username.:extension"
end

Como resultado, al llamar a user.avatar.path tendríamos "./public/images/mayoral.png". Pensad por ejemplo en el uso de permalink_fu y una interpolación que hiciese referencia al campo que alberga el permalink de un artículo… :P

En mi caso, otra línea que suele estar frecuentemente en config/initializers/paperclip.rb es la siguiente:

Paperclip.options[:command_path] = '/opt/local/bin'

De esta manera le indico a Paperclip donde reside el ImageMagick que instalé a través de MacPorts.

Para una segunda parte de esta entrada me dejo pendiente el uso de Amazon S3 como almacenamiento y la personalización del post-procesado de los ficheros adjuntos, con los denominados processors.

Arañando segundos: Cachés y números primos

09.06.2009 12

Cuando te dedicas a crear sitios web que van a soportar tráficos de cientos de miles de usuarios únicos al mes con millones de impactos durante esos periodos, cualquier parte de la aplicación en el que consigas reducir milisegundos redundará en un mejor aprovechamiento de la infraestructura y por tanto en una reducción de los costes de la misma, al ser capaz de incrementar el número de peticiones por segundo que puede atender tu servidor. Hoy vamos a hablar de una técnica ya usada en otros ámbitos pero que pocas veces la he visto utilizada en desarrollos web, y es el uso de números primos en la expiración de las cachés.

A la hora de identificar las cachés que conforman una página, nos podemos encontrar con dos tipos distintos de caché:

  • Permantentes: No tienen tiempo de expiración. Normalmente o no son borradas o en caso de ser borradas se realiza desde código (por ejemplo, al añadir un nuevo artículo borramos la caché que contiene el listado de los últimos n artículos).
  • Temporales: Tienen tiempo de expiración. Dentro de este tipo de caché entran aquellos módulos que no merece la pena borrar desde código, como por ejemplo datos que muestres en la cabecera o el pie que con que se refresquen cada x minutos es más que suficiente.

Dentro de los cachés temporales es frecuente encontrarse con módulos que expiran transcurrida la misma cantidad de tiempo (2 horas), o módulos que expiran transcurrida una fracción del tiempo de los módulos más tardíos (10 minutos). En un caso que tuviésemos 4 módulos que caducan a las 2 horas y 3 módulos que caducan a los 10 minutos nos encontraríamos con que realmente cada 2 horas tenemos que estar regenerando 7 módulos.

Una manera de evitar una frecuencia tan alta de regenerar todos los módulos de caché es utilizar algo tan sencillo como números primos de minutos en vez de tiempos más “redondos”. En el caso que comentábamos antes cada dos horas se producía la regeneración ya que el mínimo común múltiplo de 10 (2 x 5) y 120 (2^3 x 3 x 5) es 120 (2^3 x 3 x 5). Sin embargo, si para los 3 módulos que caducaban a los 10 minutos hubiésemos escogido los números primos que rodean a 10 (7, 11 y 13) y para los 4 módulos que caducan a las dos horas hubiésemos escogido los primos que rodean a 120 (109, 113, 127, 131), nos encontraríamos con que nunca (o más estrictamente, cada 7 x 11 x 13 x 109 x 113 x 127 x 131 = 205.122.846.929 minutos) tendríamos que regenerar las 7 cachés a la vez, y no solo eso, sino que la gran mayoría de tiempo estaríamos sirviendo o de caché todo o como mucho regenerando un módulo o dos a la vez. Al fin y al cabo, los datos seguirían siendo aproximadamente igual de actuales que si tuvieran los valores más “redondos”.

Esto mismo que hemos explicado para cachés temporales se podría aprovechar para transformar cachés permanentes en temporales. Solo habría que cambiar la escala de “minutos” a “segundos”, y tendríamos datos tan en tiempo real como un usuario se podría dar cuenta ;) Y con estos pequeños cambios simplificaríamos de gran manera nuestros controladores, algo que a su vez implica que reducimos la cantidad de errores posibles en nuestro código y a la postre reducción también en el coste de mantenimiento del mismo :)

It’s a happy, happy week

18.04.2009 0

Cualquiera que esté metido en el mundo de Ruby on Rails, tenga el código de sus proyectos en GitHub y use de forma habitual GMail no se podría haber imaginado que en la misma semana recibiría los tres anuncios que vamos a comentar a continuación :)

Phusion Passenger para Nginx

Hace unos días la gente de Phusion, creadores del gran Passenger anunciaba que con motivo de su primer aniversario como empresa lanzaban la versión 2.2 de Passenger, cuya principal novedad es el soporte de Nginx.

Cualquiera que haya probado Passenger al poco tiempo se ha olvidado de Mongrels, Thins y compañía para poner en desarrollo y producción Passenger, al facilitarte la vida de forma increible a la hora de hacer un deploy de una aplicación Ruby. Y cualquiera que haya probado Nginx ha estado pensando en cómo poder deshacerse de Apache, que aunque es una maravilla y tremendamente potente, no es tan rápido, ligero y eficiente como es Nginx. Desde ahora no va a ser necesario elegir entre uno u otro :D

Sistema de gestión de incidencias integrado en GitHub

GitHub, uno de los mejores proveedores de repositorios Git que existen en la actualidad, anunció la integración de un sistema de incidencias en cada uno de los repositorios que tengas.

Con el soporte de etiquetas, cierre de tickets desde el propio mensaje de los commits y demás funcionalidades, ya no va a ser necesario andar montando una copia local que integrar con un Redmine o un Trac para poder gestionar las incidencias más técnicas.

Destinatarios sugeridos en GMail

A lo largo del día, ya sea mails a los socios, a los proveedores o a los clientes, acabo mandando una cantidad de mails tremenda. En la mayoría e las ocasiones no son mails a listas de correo, sino que son mails a varios destinatarios, que por lo general coinciden de mail a mail.

Google, desde la bitácora oficial de GMail, anunció una nueva característica en los Labs (también incluida en aquellos que utilizamos Google Apps para tu dominio), que no es ni más ni menos que la sugerencia de destinatarios conforme vas escribiendo un correo.

Si normalmente cuando escribes a A también mandas copia a B, C y D, al tener activada esta opción cada vez que escribas a A y añadas a B al correo, Google te sugerirá añadir como destinatario a C y a D. He estado haciendo pruebas con los mails a varios que más habitualmente mando y la verdad es que lo está bordando :)

Sin duda estas tres noticias van a suponer una mejora en la productividad y la calidad del trabajo tremendas. O eso espero :P

10.lines do: Limpiando comentarios HTML

25.02.2009 0

Otra nueva sección. Esta dedicada a esos pequeños truquillos que vas aplicando con el tiempo en tus proyectos, que no llegan a tener la entidad suficiente como para ser algo liberable en forma de proyecto, pero que seguro que a alguien más le puede resultar igual de útil que te resultó a ti en su momento.

La mayoría de la gente que se ha enfrentado a los “editores WYSIWYG” incrustados en una web se ha encontrado ante el caso en que los editores que lo han utilizado han copiado texto directamente desde Word y similares. Estos programas, en especial los de Microsoft, tienen la manía de insertar junto con el código que copias una ristra de comentarios HTML con definiciones de estilo, que al ser tratados por las diferentes funciones tanto del editor visual como del gestor de contenidos generan un alto riesgo de que el HTML resultante no valide o no cierre como es debido.

La solución que llevo aplicando durante un tiempo en mis proyectos consta de dos partes. La primera es extender la clase String para dotarla de un método que elimine los comentarios HTML

class String
  def strip_html_comments!
    self.gsub!(/\<![ \r\n\t]*(--([^\-]|[\r\n]|-[^\-])*--[ \r\n\t]*)\>/, '')
  end
end

Podéis comprobar desde irb que funciona:

>> "<!-- foo -->bar<!-- baz -->".strip_html_comments!
=> "bar"

La segunda parte es incorporar en nuestro modelo un método al que llamar antes de que se produzca el guardado de nuestro registro (ya sea en la creación o en la actualización) que aplique el filtro que acabamos de definir sobre los campos que nos interesen. Por ejemplo:

class Article < ActiveRecord::Base
  before_save :filter_bad_html

  private
    def filter_bad_html
      [excerpt, content].each(&:strip_html_comments!)
    end
end

En este caso estaría filtrando los campos excerpt y content del modelo Article.

Hay gente que suele hacer este filtrado en el controlador en vez de en el modelo. Personalmente prefiero el tan repetido “Fat model, skinny controller” que más de uno habréis oído. Los filtros before_* y after_* que incorpora ActiveRecord son unas herramientas bastante potentes que nos pueden ayudar a hacer una aplicación mucho más legible, más sencilla y más mantenible.

Aunque el código que hemos visto sea Ruby y utilice cosas de Rails, es bastante sencillo extrapolarlo a otros lenguajes como PHP (por ejemplo usando preg_replace).

Espero que esto os resulte de utilidad :)

Instalando Ruby 1.9 en Mac OS X

01.02.2009 13

Ruby 1.9 ya está aquí. Y por lo que podemos ver en el changelog, hay muchas novedades respecto de la rama 1.8 que nos lleva acompañando desde el año 2003. Aunque creo que lo que casi todos estábamos esperando era poder usar YARV en producción :P

Si todavía no habías visto ninguna comparativa de rendimiento de las diferentes máquinas virtuales, te recomiendo que eches un vistazo a The Great Ruby Shootout.

Paso 0: Requisitos previos

Antes de seguir, es básico que tengamos instalado XCode, para lo cual o bien lo hacemos desde nuestro DVD de instalación de Leopard o bien nos vamos al Mac Dev Center de la Apple Developer Connection y nos lo bajamos.

Todo lo que vamos a escribir lo haremos en un terminal. Mi recomendación es usar iTerm, aunque hay gente que está más habituada a utilizar la propia applicación Consola. A vuestra propia elección.

Comienza la diversión ;)

Paso 1: Descarga de Ruby 1.9

Si no existiese, crearemos una carpeta src desde la que funcionaremos a lo largo de todo este tutorial:

mkdir ~/src
cd ~/src

A continuación procedemos a la descarga y desempaquetado de Ruby 1.9:

curl -O ftp://ftp.ruby-lang.org/pub/ruby/ruby-1.9.1-p129.tar.bz2
tar xfj ruby-1.9.1-p129.tar.bz2
cd ruby-1.9.1-p129/

Ya estamos listos para compilar.

Paso 2: Compilación e instalación

Ya que en Leopard tenemos instalada una versión de Ruby 1.8, para no generar conflictos y poder funcionar con ambas versiones haremos que todos los binarios de Ruby 1.9 vayan con un 19 al final del nombre (ruby19, gem19, etc). Otra cosa que haremos es que todo Ruby 1.9 se instale dentro de /usr/local, para no mezclar estos binarios con los binarios propios del sistema:

autoconf
./configure --program-suffix=19 --prefix=/usr/local

Ya estamos listos para compilar:

make

Y por último, instalar:

sudo make install

Con esto ya tenemos Ruby 1.9 perfectamente operativo en nuestro sistema. Ahora nos tocará ir mirando con gem list las gemas que ya teníamos instaladas en el sistema y volverlas a instalar, esta vez usando gem19, por ejemplo sudo gem19 install rails.

¡A disfrutar!

Instalando git en Mac OS X

22.01.2009 0

Proyectos grandes como el propio kernel de Linux o más recientemente el framework Ruby on Rails usan este sistema distribuido de control de versiones. Y hace ya un tiempo que este blogger se unió a las legiones cada vez más numerosas de personas que han migrado de otros SCM como subversion a git.

Aunque existe la posibilidad de instalar git en tu Snow Leopard desde MacPorts o con alguno de los múltiples empaquetados que hay por internet, en esta entrada voy a aprovechar para explicaros cómo instalar git en tu equipo desde cero.

Paso 0: Requisitos previos

Antes de seguir, es básico que tengamos instalado XCode, para lo cual o bien lo hacemos desde nuestro DVD de instalación de Snow Leopard o bien nos vamos al Mac Dev Center de la Apple Developer Connection y nos lo bajamos.

Todo lo que vamos a escribir lo haremos en un terminal. Mi recomendación es usar iTerm, aunque hay gente que está más habituada a utilizar la propia applicación Consola. A vuestra propia elección.

Comienza la diversión ;)

Paso 1: Descarga de git

Este primer paso es un poco manía personal de tener organizado todo en carpetas. En este caso crearemos una carpeta src desde la que funcionaremos a lo largo de todo este tutorial:

mkdir src
cd src

A continuación procedemos a la descarga y desempaquetado de git:

curl -O http://kernel.org/pub/software/scm/git/git-1.6.6.1.tar.bz2
tar xvfj git-1.6.6.1.tar.bz2
cd git-1.6.6.1

Ya estamos listos para compilar.

Paso 2: Compilación e instalación

Leopard viene sin gettext, así que respecto del habitual make && sudo make install tendremos que añadir una opción para tener una compilación de todos los programas sin fallos:

NO_MSGFMT=yes make prefix=/usr/local all

Con esto estamos diciendo que a la hora de compilar no utilice msgfmt (parte de gettext) y que luego cuando instalemos lo haremos dentro de /usr/local.

Ya tenemos git compilado. Si queremos, antes de continuar podemos ejecutar una amplia batería de tests para asegurarnos de que no ha habido ningún fallo:

make prefix=/usr/local test && echo $?

Puede que no tengas la suficiente paciencia para ver como se ejecutan todos los tests (y no son precisamente pocos). Por ello, gracias a ese echo $? veremos si la ejecución de los tests ha ido bien (debería aparecer un 0 en la última línea antes del prompt del sistema).

Con git ya compilado y con todos los tests superados, procedemos a instalarlo:

sudo make prefix=/usr/local install

Ya tienes git instalado en tu equipo :)

Paso 3: Instalación del manual

Este paso es optativo, aunque siempre viene bien tener las manpages.

curl -O http://www.kernel.org/pub/software/scm/git/git-manpages-1.6.6.1.tar.bz2
sudo mkdir /usr/local/man
sudo tar xjv -C /usr/local/man -f git-manpages-1.6.6.1.tar.bz2

Con esto, estamos descargando las manpages correspondientes a la misma versión que hemos instalado de git y descomprimiéndolas en su sitio.

Ya solo nos queda abrir en nuestro editor favorito el fichero .bash_profile y añadir lo siguiente:

export MANPATH="/usr/local/man:$MANPATH"

Esto le decimos a man que también tenga en cuenta a la hora de buscar manpages la ruta /usr/local/man.

Primeros pasos con git

Lo primero que haremos será configurar git con nuestro nombre y el email que aparecerá como autor de los commits que hagamos a los repositorios:

git config --global user.name "Tu nombre"
git config --global user.email tu@correo.ejemplo.com

Ahora nos vamos a la carpeta de nuestro proyecto y ejecutamos lo siguiente:

git init
git add .
git commit

Ya tenemos nuestro proyecto bajo el control de git. Lo que hagas de aquí en adelante ya depende de ti.

Happy coding :)