Próxima reunión de Argentina on Rails

El próximo sábado (1 de noviembre) nos estamos reuniendo para compartir, aprender y divertirnos un rato con ruby. El que quiera aparecer, que aparezca y si quiere avisar que va, mejor así calculo bien la cantidad de mates que tenemos que tener :).

La cita es en la facultad de ingeniería de la UBA, aula 402 (4to piso) a partir de las 11hs. Más detalles de como se va a organizar la cosa acá (incluye un mapa para aquellos que no conozcan la facultad).

Usando Rails.cache en 2.1.x

Para uno de los sitios que tenemos montados llegó la hora de optimizar algunas partes para descargar la DB un poco. Lo primero (y por ahora único :P) que se hizo fue cachear la instancia que representa a la Etapa actual del juego en memoria, para así no cargarla en cada request.

El código original era :

class Stage  ["start_at = ?", t, t])
  end
end

y lo cambiamos para usar el nuevo sistema de cache de Rails 2.1.x que realmente fue simplificado. Hay varios sitio donde hablan sobre el cache en 2.1, les recomiendo mirar el screencast de RailsCasts y este y este post.

El código modificado quedó como se muestra a continuación.

# config.environment.rb
config.cache_store = :mem_cache_store

# app/models/stage.rb
class Stage  ["start_at = ?", t, t])
      Rails.cache.write('stage_current', @current)
    end
    @current
  end

La condición del if es necesaria por dos motivos : la primera por si el cache fue limpiado y la segunda para invalidar el valor guardado actualmente si la etapa eterminó, para que la nueva etapa pase a ocupar el cache.

En este caso estamos usando MemCache como CacheStore ya que se comparte entre más de un webserver y simplifica expirar el cache.

Lo único que resta es expirar el cache en caso de que cambie el modelo, que además de la fecha de finalización contiene textos que son usados en la web (como las bases y condiciones, premios, etc). Para eso creamos un Observer que se encargue de eso cuando una etapa es guardada y además es la etapa actual (si no, no tiene sentido hacer nada) :

# app/models/stage_observer.rb
class StageObserver < ActiveRecord::Observer
  def after_save(stage)
    Rails.cache.delete('stage_current') if stage.is_current?
  end
end

# config/environment.rb
config.active_record.observers = :stage_observer

Con esto bajó bastante el uso de la DB (prácticamente no se puede hacer nada en el sitio sin consultar la etapa actual). Ahora tengo que ver que le pasa a la DB que a veces respondes después de 3 segs :S, pero eso ya escapa a programar :).

Terminó el Rails Summit Brasil

Ya de vuelta en Argentina y descansando, puedo decir que el viaje a Sao Paulo fue muy productivo y entretenído. Del evento realmente no vale la pena que me extienda mucho más de lo que ya dijo Luis Lavena en su blog, por lo que les recomiendo leer lo que él dice :).

Durante el evento aproveché para conocer en persona algunos miembros de Ruby Argentina, como Luis y Pedro, con los que pasamos un momento realmente agradable durante el evento. También tuve el agrado de conversar con David Chelimsky (maintainer de rspec) y aprender más sobre BDD y cómo comenzar a usarlo.

Por el lado de la ciduad, Sao Paulo es realmente enorme y no tuve más que dos tardes para tratar de conocer algo, que aproveché en visitar algunas expos y museos que había cerca del hostel. Si van les recomiendo visitar el Museo de la lengua Portuguesa, realmente interesante las diferentes influencias que tiene su idioma y su cultura.

Hubo varias cosas que me sorprendieron, ¿será que estoy muy acostumbrado a vivir en argentina? Eso denlo por hecho :).

Lo primero fue que desde que llegué no pararon de darme monedas en todos lados :). Como segundo punto el que existan carriles exclusivos para buses y más sorprendente que los 5 taxistas con los que hablé están de acuerdo!. Suena irreal que pase acá, ¿no?.

El punto sin duda que más me gustó fue el metro. Una red realmente bien pensada (o eso pienso al menos) donde las combinaciones se hacen con solo unos pasos, y con frecuencias de un minuto o por ahí en hora pico. Un gusto andar en metro en Sao Pablo.

Esperemos que el año que viene vuelva este evento, aunque sería genial que sea en alguna ciudad con playa :).

Update: Algunas de las fotos.

http://picasaweb.google.com/s/c/bin/slideshow.swf

Rumbo a Brasil

En aproximadamente una hora ya estoy saliendo para Brasil. En este momento ya estoy esperando para abordar mi vuelo, luego de haber tomado un cafecito y fumado un cigarrillo (God bless smokerplaces 🙂

Sin dudas tomar un vuelo internacional no es nada cómodo ni divertido. Tuve que hacer 5 colas diferentes, preguntar 20 cosas diferentes (tal vez por falta de costumbre :P) y aún así en cada paso tenía esa sensación donde te dicen «Señor, hay un problema». Aunque finalmente no pasó nada :).

Estoy de acuerdo con la seguridad, y el control y bla bla bla bla bla bla, pero hay pasos que se podrían ahorrar, en fin, estoy aburrido y despotrico porque me levanté a una hora que no acostumbro.

Por lo menos tengo WiFi 😀

Refactoring de «Fat Methods» – Episodio 2

Continuando con esta serie de ejemplos de como refactorizar métodos de controladores vamos a seguir ahora con uno que cuando lo volví a ver apestaba feo feo.

El método en cuestión tiene por objetivo que el usuario actual se suscriba (es decir, anote) para jugar un partido determinado. Para eso el siguiente código es el que está siendo ejecutado actualmente :

  # app/controllers/matches_controllers.rb
  def subscribe
    flash[:notice] = "Ya formás parte del equipo!" and 
        redirect_to match_path(@match) and 
        return if @match.has_player? current_user

    flash[:error] = "El equipo está completo." 
      and redirect_to match_path(@match) and ]
      return if @match.available_places(params[:team].to_i)  current_user, :team => params[:team]

    redirect_to match_path(@match)
  end

Lo feo del problema se puede ver desde dos perspectivas : una que es una excusa y otra que es la razón de la desprolijidad. La excusa para este código tan horrible es que en caso de no poder agregar el jugador al equipo elegido tengo que mostrar un mensaje de error y redireccionar. Pero el redirect_to lo que hace es setear un header nada más, es decir que no hace un return auto-mágico, y de no hacerlo yo el segundo redirect_to (ubicado al final del código) haría que se lance una Exception por haber reenviado los headers.

El problema real es que estoy delegando en el controlador la responsabilidad de la clase Player para determinar si es válido crear una instancia para un partido y usuario dado.

Es por eso que lo primero que debemos hacer es usar los validators de Rails para esta tarea :

  # app/models/player.rb
  def validate
    errors.add_to_base("Ya formás parte del equipo!") if match.has_player? user
    errors.add_to_base("El equipo está completo.") if match.available_places(team) < = 0
  end

En este caso los errores no dependen de un atributo, por lo que usamos add_to_base para que los errores digan lo que queremos. Este método validate es llamado por Rails al crear o actualizar una instancia de Player, y si hay algún error nunca llega a la DB.

Habiendo quitado la validación del controlador ahora podemos escribirlo de una forma más prolija y entendible :

  # app/controllers/matches_controllers.rb
  def subscribe
    @player = @match.players.create :user => current_user, :team => params[:team].to_i

    flash[:notice] = @player.errors.full_messages.join(", ") if @player.errors.any?

    redirect_to match_path(@match)
  end

Lovely :). Se crea un Player para el Match, se prepara una alerta visual en caso de que haya algún error y luego se redirige a la página del partido. Si ven el antes y el después creo que nadie me va a negar de la mejora :).

Por hoy es todo, hasta la próxima entrega!.

Dave Matthews Band en Argentina

Dave Matthews Band

Creo que solo tengo una palabra : uno de los mejores shows!!!!! …. bueno, fueron más de una :). Fuera de joda lo que hacen en el escenario estos muchachos no tiene nombre. La química entre ellos logran que el espectáculo sea simplemente genial.

Llegamos temprano para ver varias bandas, entre ellas al Baiano y El cuarteto de Nos. Este último la verdad que no me gustó, muy simplón, la música muy placa y en vivo muy aburrido.

Fito paez era la otra «figurita difícil» de la noche que también dió un espectáculo muy bueno. Recibí muchos golpes cuando un integrante de Catupecu entró de sorpresa y todo el mundo se puso loco.

El concierto de DMB duró unas dos horas y casi casi que nos quedamos sin nada cuando en el segundo o tercer tema sorpresivamente se apagó todo y Dave miró al público con cara de «ups, y ahora?». El batero durante todo el corte siguió dándole duro para entretenernos.

Sin duda no veía una banda en vivo tan buena desde Pearl Jam.

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