Refactoring de “Fat Methods” – Episodio 1

Luego de escribir mucho código uno comienza a mirar a ver que hacen los de al lado, y al mirar se da cuenta de lo mal que uno escribe código cuando comienza con un nuevo lenguaje :). Algo de eso me pasa hoy en día con ¡Falta Uno!.

En el camino de emprolijar las cosas para liberar el código tuve la necesita de poner a refactorizar el código porque hay partes que no son nada bonito. Voy a tratar de plasmar en los siguientes posts algunas lecciones aprendidas junto con el paso a paso del refactoring del código.

Antes que nada y como buena práctica es ideal tener buenos test sobre los que se va a refactorizar ya que es muy fácil romper alguna funcionalidad que se suponía que andaba. Si bien para los ejemplos acá voy a ignorar esta buena práctica, no se los aconsejo :).

Vamos a empezar con el método create de controlador Matches, encargado de crear un nuevo partido :

  def create
    @match = current_user.matches.create(params[:match])
    if @match.errors.empty?
      Player.create :match => @match, :user => current_user

      @notifications = @match.owner.friends.select {|f| f.notify_new_matches? }.collect(&:email)
      Emailer.deliver_match_created(@match, @notifications) if @notifications.any?

      flash[:success] = "El partido fue creado."
      redirect_to matches_path
    else
      render :action => 'new'
    end
  rescue
    render :action => 'new'
  end

Asusta un poco, ¿no?. Para entender el problema, veamos primero como que involucra crear un nuevo partido :

  • Crear el partido
  • Anotar al usuario que creó el partido en uno de los equipos
  • Notificar a todos los amigos que se creó un nuevo partido

Dado esto, pasemos a analizar los problemas. La primer pregunta que habría que hacerse es : ¿Quién es el responsable de anotar al jugador?, ¿Tiene sentido que un usuario pueda crear un partido sin que esté anotado?. En el contexto nuestro, la respuesta a la última pregunta es “No”, solo porque así lo defino yo :). Entonces podemos responder a la primer pregunta : El Match debería ser el responsable de anotar al dueño del partido cuando es creado.

Para lograr esto último lo que debemos hacer es mover la lógica de creación del Player dentro de un callback en el modelo Match, quedando algo así :

class Match  self, :user => owner
  end
end

De esta forma nos aseguramos que se cumpla lo pedido sin correr el riesgo de que se puedan crear matches sin que el owner este anotado inicialmente. También ganaríamos en el caso de que exista alguna otra forma de crear un partido, por ejemplo si agregáramos la posibilidad de crear un partido enviando un email, no tendríamos la necesidad de repetir la lógica inicial en dos lugares diferentes.

Y ahora nuestro controlador queda algo cómo :

  def create
    @match = current_user.matches.create(params[:match])
    if @match.errors.empty?
      @notifications = @match.owner.friends.select {|f| f.notify_new_matches? }.collect(&:email)
      Emailer.deliver_match_created(@match, @notifications) if @notifications.any?

      flash[:success] = "El partido fue creado."
      redirect_to matches_path
    else
      render :action => 'new'
    end
  rescue
    render :action => 'new'
  end

Lo siguiente es limpiar un poco lo lógica del modelo. Una cosa que molesta a la vista son las dos llamadas a render, ¿son realmente necesarias? La respuesta en NO. Con el rescue lo que hacemos es que dada una Exception vuelva al formulario de creación, lo mismo que si el partido no es válido, por lo que simplemente podríamos haber usado el método create! que lanza una Exception si el modelo no es válido evitándonos así el molesto if. Quedando ahora :

  def create
    @match = current_user.matches.create!(params[:match])

    @notifications = @match.owner.friends.select {|f| f.notify_new_matches? }.collect(&:email)
    Emailer.deliver_match_created(@match, @notifications) if @notifications.any?

    flash[:success] = "El partido fue creado."
    redirect_to matches_path
  rescue
    render :action => 'new'
  end

Que si lo comparan con la versión inicial es mucho más legible. “Fat model, skinny controllers” es la clave a seguir. Todavía queda por mejorar la forma en que se genera la lista de emails de los amigos que desean recibir la notificación, pero no es algo que me moleste de momento.

En otras entregas seguiré mostrando el resto del refactoring de otro métodos que son aún peores que este :).

Simple Forum plugin for Rails

La semana pasada tenía la necesidad de agregar un foro super simple para un cliente, basicamente querían que los usuarios del sitio puedan dejar comentarios de la campaña, hablen entre ellos y no mucho más. Ya tenía uno hecho todo mezclado en otra aplicación por lo que decidí que era hora de reescribir menos :).

Lo primero que se me pasó por la mente “algo ya debe haber hecho” y si bien es cierto, lo poco que encontré se apoya en Engines lo cual para mi no es aceptable. Simplemente no me gusta Engines :). La mejor opción tal vez es Beast pero integrarlo con otra aplicación no es simple y tener el foro de forma externa no era una opción.

Por lo que decidí empezar un “site project” a partir del código que ya tenía, que al haberlo escrito hace mucho era horrible por lo que el resultado fue casi casi que codeado desde 0.

La idea es que en 3 pasos uno pueda tener el foro andando integrado en el sitio, y creo que mas o menos se logra con algunos “problemas”. El principal tema es que al estar los controllers en el plugin, y estos no se recargan en development, es medio molesto usarlo porque depende de que se haga hay que reiniciar el server a cada rato (tira unos stack level too deph”).

Acá está el código para el que quiera probarlo, testearlo y recomendar alguna alternativa. Sobre todo si encuentran errores de gramática/sintaxis en la poca documentación que hice se agradecería :).

Multiupload de imágenes con Prototype

Desde hace unos días que estoy haciendo un widget que soporte upload de múltiples archivos para una aplicación web. No fue fácil el comienzo pero despues de varias horas (unas 8 hasta este momento) ya va tomando forma.

Para poder trackear el upload de cada archivo utilizo apache_mod_upload_progress, un genio “Drogomir” :). Para compilarlo en OSX tuve algunos problemas ya que apache2 esta compilado en x86_64 y el default del apsx es x86 pero googleando se encuentra fácil como pasarle el parámetro al gcc. Lo otro que necesitamos tener instalado es mod_rails y apache 2.2. Cuando termine el código y lo publique estará todo explicado en detalle :).

El segundo problema grande fue el formulario. Para hacer el upload lo que hago es crear un iframe oculto y cambiar el target del formulario a ese frame (de esta menera si no tenemos javascript la aplicación degrada automáticamente al upload de imagenes individuales y el usuario no se entera), pero claro, necesitaba tener múltiples input:file, uno por cada archivo a subir. De ponerlos todos juntos tendríamos un POST super gigante que no era lo que se buscaba ya que no podría trackear cada upload por separado.

La solución fue, cada vez que se selecciona un archivo sacar el INPUT del form y guardarlo en un array. El espacio vacío se reemplaza con un nuevo INPUT y como todo es tán rápido, uno no se da cuenta. El problema llegó cuando terminaba el primer archivo, tenía que volver a agregar el siguiente file al formulario y hacer otra vez el submit. Pero si uno llama $(form).submit() desde javascript, el callback onSubmit no es ejecutado (defecto de las implementaciones de todos los navegadores que probé y parece que no va a cambiar) por lo que no era útil.

La solución finalmente fue simular el click enel botón enviar con un simple $(submit_button).click() que resuelve el problema anterior. Les dejo el video para que lo disfruten :).

httpv://www.youtube.com/watch?v=7PqZg_1Pi1w

Jornadas Regionales 2008 – Día 1

Día agotador, como hace mucho que no sufría :D.

Como todos los años arrancaron las Jornadas Regionales cuyo organizador en esta oportunidad es Cafelug. Los chicos arrancaron a pleno, super organizados, aunque como siempre sobre el final se olvidan de algunos detalles en la marea de gente consultando cosas.

Yo arranqué temprano dando una charla de Software Libre para Windows orientado a home/office user, sin tocar siquiera temas más de coding. No es una charla que me super guste pero fue “por encargo” y no me costaba nada sumar.

A la tarde fui a dar la segunda (y espero nunca repetir el error de dar dos charlas el mismo día, a mi garganta no le hace gracia :P) : SocialApps con Ruby On Rails. Fue un poco rara esta, porque iba a ser un taller pero no pude al final, así que hice un lindo cookbook y creo que a la gente le gustó. Estoy filmado en dos ángulos distintos, después tengo que conseguir los videos, y obviamente, destruirlos :D.

Como asistenmte estuve en muy pocas la verdad, más que nada porque me colgaba charlando con gente que hace mucho que no veo.

Vi un poco de Introduction to building RPM packages, a un amigo que debutaba en las jornadas con Desarrollo de aplicaciones Sociales. y la mitad de Mozilla and Browsers over the last 10 years. porque me tenía que ir a dar clases.

Este año el regalo por dar charlas me vino barbaro : un mate!. Si, el mío ya tenía que jubilarse por lo que me viene al pelo. Es lindo ver como estos eventos crecen año a año.

has_sitemap plugin

Este fin de semana tenía que agregar a 4 sitios de clientes un sitemap para mejorar la indexación por los motores de búsqueda, así que para hacerlo una vez y rápido, decidí aprender a hacer plugins para Rails.

No es que sea una magia absoluta, de hecho es muy tonto hacer uno. Pero cómo codeas adentro del plugin creo que es importante, porque la gente los usa como cajas negras, y si es un desastre, todo lo de afuera se vuelve un desastre.

El plugin permite agregar sitemaps tal cual lo define el protocolo, mediante un simple helper y una función adicional donde el controller debe generar la data que desea agregar. Lo que se encapsula es básicamente el generador del XML.

Todo se genera dinámicamente y por ahora solo es útil para sitios realmente chicos (< 100 urls) como los que yo necesitaba. El protocolo especifica que se pueden poner hasta 50k de URLs (o 10Mb) por lo que este plugin no es útil, al menos por ahora, para esos casos.

El código está hosteado en http://github.com/Gazer/has_sitemap/tree/master.

¡Falta Uno!

Hace un par de años, enojado por lo difícil que era organizar un partido de fútbol con mis amigos, me puse a desarrollar una paginita web pedorra en PHP para organizar partidos. Obviamente, como muchas de las cosas que empiezo, quedó en la nada. Si bien funcionaba, no me calente en ponerlo online ni comentarlo con mis amigos.

Hace dos semanas la idea reflotó, principalmente porque quería sumar experiencia en Ruby On Rails, por lo que decidí subir la apuesta. De eso se trata ¡Falta Uno!.

La idea es simple : te registrar, te relacionas con tus amigos, creas un partido y despues se van anotando. En la página se puede ver quiénes se anotaron y un pequeño board para postear mensajes por si acaso (vale putear, organizar quién juega con quién, etc), así como los datos de la cancha, hora y demás.

Ahora bien, no hace falta que todos los que van a jugar tengan cuenta. ¿Por qué? Simplemente porque si no la cosa no funcionaría :).

Con mi grupo de amigos suele pasar lo siguiente : 3 horas antes de que empiece el partido llega un mail diciendo “me bajo porque me duele la uña”, y le sigue un mail con “uhhh, che, falta uno, quien consigue ??”.

Este sujeto que aparece a último momento, es muy probable que sea el primo del tio de un amigo de un amigo, y casi seguro que no tendría cuenta y mucho menos relación de amistad con el creador de un partido (para poder anotarte a un partido tenés que ser amigo del creador por ahora).

Entonces?, bien, el sistema permite anotar a un “guest player“, poniendo solo el nombre y ocupando la plaza y así se mantiene simple la creación del partido.

Otro feature que se agrega, que es totalmente opcional y lo hice porque me gusta tenerlo en cuenta, es poder votar como jugaron tus amigos en ese partido. Hay 5 categorías para puntuar entre 1 y 5, y luego entre todos los votos se calcula el promedio por partido, y también un promedio general por usuario.

Este último feature es totalmente opcional y puede ser omitido si no tenés ni tiempo ni ganas de usarlo :).

En fin, el site no creo que tenga mucho éxito, si lo tiene, groso :), pero por lo menos mis amigos y yo vamos a dejar de putear para organizar un picadito.

Algunos datos “técnicos” sobre lo usado :

  • Ruby On Rails 2.0.2
  • Plugins
    • attachment_fu : Para upload de avatars
    • has_many_friends : Relaciones de amistad entre usuarios
    • restful_authentication : No puede faltasi hay un login 🙂
    • will_paginate : Tengo que migrar a usar la versión en gema.
    • calendar_helper : Para armar el calendario de partidos
    • simple_format : Para formatear el texto plano de los comentarios.
  • Server : mongrel/ apache (mod_balancer+ mod_rewrite) / mysql

Phusion Passenger (a.k.a. mod_rails)

Finalmente hoy leo que ya está disponible la primer versión estable de mod_rails, y si todo anda como dicen, no más problemas con fcgi o mod_balancer + mongrels. Simplemente deploy a la PHP. Será posible?

Ventaja inmensa cuando se tiene más de un sitio en el mismo host. Hoy en día (apacha+mongrel) debo recordar y documentar que rango de puertos usa cada aplicación (son 3 por ahora) y al hacer los updates tener que restartear N procesos diferentes y cosas así.

Voy a ponerlo en los servers de testing del trabajo a ver como se porta y si todo sale bien pasarlo a producción :).

Programando Rails con GEdit

Luego de buscar y buscar opciones para hacer el día a día programando en Ruby on Rails decidí quedarme con GEdit, por sobre mis otras opciones : Aptana Studio, vim+rails.vim, jedit.

Tanto jEdit como Aptana están escritos en Java, por lo que tienen todo lo malo de la JVM corriendo atrás y la verdad que molestaban más de lo que ayudaban. Aptana es un lindo plugin para Eclipse, pero no pienso actualizar la PC solo para poder sacarle el juego :).

Por el lado de vim, por primera vez que yo recuerde, queda chico. Esta muy bien para tocar 3 o 4 boludeces de vez en cuando. Pero para desarrollar a diario se me hace un martirio. El auto-complete de helpers, por decir algo, es super incomodo y yo lo quiero con un simple Tab :). No encontré si tiene la opción de Code Snippets.

Tengo que admitir que mi primer reacción fue : Quiero TextMate en Linux!. Quienes hayan usado TextMate lo entenderán, quienes nunca lo usaron, es como aquellos que no entiende como con un solo botón alcanza para hacer todo :). No vale la pena explicarlo.

No tuve que buscar demasiado la verdad y encontré varias fuentes para configurar GEdit de manera que quede suficientemente cómodo (como estaba acostumbrado con TextMate) para trabajar todos los días. El resultado :

GEdit en modo TextMate

GEdit es una de esas cosas que siempre fue “el editor pedorro ese que viene con GNOME”. Me sorprendió que se porte tan bien con varios plugins andando. Esperemos que a la larga no termine siendo un fracaso :).

/ URLs / Lindas / Con / Rails

Unos de los proyectos actuales en los que trabajo es un portal hecho en Ruby on Rails y tenía que mostrar el nombre de la seccion y subseccion en las URLs. La primer versión fue usar las rutas anidades que se incluyen en Rails 2.0 como sigue :

map.resources :sections do |section|
  section.resources :subsections do |subsec|
    subsec.resources :articles
  end
end

Sin embargo para Rails eso no son URls tan lindas como me (nos?) gustaría. Un ejemplo que daría la llamada a section_subsection_article_url sería : /sections/1/subsections/2/article/4, nada cómodo para alguien que quiere entrar a /sections/deportes/notas/yyyyy.

La solución llegó, en parte, al encontrar el plugin resource_hacks, que nos permite agregar un member_path a mano el cual Rails utilizaría para armar las rutas. Con el plugin instalado nuestro ejemplo anterior quedaría como :

map.resources :sections, :member_path => 'sections/:section' do |section|
  section.resources :subsections, :member_path => 'sections/:section/:subsection' do |subsec|
    subsec.resources :articles, :member_path =>; 'sections/:section/:subsection/:permalink'
  end
end

Y ahora si, si entramos a /sections/deportes/ veremos la portada del sitio de Deportes.

Este requerimiento era “importante” ya que el cliente quere manejar en forma dinámica el sitio. No todas las secciones tienen las mismas subsecciones y demás yerbas.

Una vez que tuve todo eso andando, mi problema fue que los helpers _url y _path dejaron de andar alegando que los parametros de entrada no concordaban con lo esperado.

Lo primero que encontré luego de un par de dias de buscar fue que había que redefinir el mítodo to_param de los modelos para que retornen el string que quería mostrar en la URL, ya que por default retornan el id del objeto. Sin embargo esto no alcanzó. La solución llegó cuando se me ocurrió pasar un string a mano con el parámetro :id a los helpers y todo mágicamente empezó a funcionar.

En lugar de :


Llamar a :

 "1") %>

Y todo va de pelos. No se si es un comportamiento esperado, o si los helpers con este hack no serían válidos, pero esto anda al menos hasta ahora. Si a alguien se le ocurre por qué puede pasar esto o alguna mejor solución, será bienvenido 🙂

En los controladores ahora debemos hacer las búsquedas por los strings que nos llegan mas el permalink del artículo, en lugar de utilizar el find por ID, por lo que es altamente recomendable agregar índices en las tablas :). Hay otras tantas optimizaciones que hacer, pero en general, la cosa funciona.