Archive for the 'tecnologia' Category

Attachment_fu + S3 + Europa

Ya se que tengo pendiente un post sobre los controladores polimorficos en rails, pero hoy queria contar algo que creo es interesante.

Existe un plugin en Rails llamado attachment_fu, que permite de forma muy sencilla tener ficheros asociados a un modelo. Normalmente se utiliza para imagenes, ya que el propio plugin hace realmente sencilla la tarea de generar miniaturas (thumbnails) a diferentes tamaños para su posterior visualización.

Este plugin, además de permitir generar miniaturas y hacer senzillo el “upload” del fichero, permite guardar estos ficheros de distintas formas: en disco, en base de datos o en el famoso Amazon S3.

Lo recomendable es utilizar S3 por algunos motivos, entre ellos:

  • Espacio virtualmente ilimitado
  • Costes controlables y reducidos (pagas por uso y transferencia)
  • Despreocupate de los backups

Pero aqui empiezan los problemas, ya que attachment_fu utiliza el gem AWS::S3 para gestionar la relación con S3, y dicho gem tiene un pequeño bug que hace imposible subir contenido a un bucket europeo, solo funciona bien con los americanos. (El secreto es que el gem utiliza la API REST de Amazon, y siempre envia la cabecera “Host: s3.amazonaws.com”, cuando para los buckets europeos debe ser “nombre_bucket.s3.amazonaws.com”).

Otro aspecto negativo de attachment_fu es que solo te permite subir ficheros a un Bucket. Que pasa si quiero tener las fotos de los usuarios repartidas en distintos buckets? Pongamos un ejemplo práctico:

En moterus tenemos aspiración internacional, queremos tener usuarios de todo el mundo. Los usuarios pueden subir sus fotos, y queremos que las fotos se visualizen lo más rapido posible a todos los usuarios. Para que las fotos se visualizen rápido, es importante que estén en un servidor lo más cerca posible del usuario. Por este motivo Amazon tiene un datacenter en USA y otro en Europa. Tenemos que hacer que los usuarios americanos utilicen un bucket de USA y que los usuarios europeos utilizen un bucket de Europa.

Con el attachment_fu “estándar” esto no es posible. Pero por suerte existe GitHub, que nos permite hacer cosas como la que hemos hecho en VeSNe: un fork!

Ahi vamos. Si quieres utilizar distintos buckets de S3 en tus modelos utilizando attachment_fu o si deseas tener buckets europeos, utiliza nuestros forks en github:

AWS::S3: http://github.com/isaacfeliu/aws-s3

sudo gem install isaacfeliu-aws-s3 --source=http://gems.guithub.com

Attachment_fu: http://github.com/isaacfeliu/attachment_fu

script/plugin install git://github.com/isaacfeliu/attachment_fu.git

Para el AWS::S3 gem no hace falta nada en especial. Simplemente borrad el anterior si lo tuvierais (sudo gem uninstall aws-s3) y listo.

Para el attachment_fu hay algun cambio a destacar en el fichero config/amazon_s3.yml:

  • Ya no se acepta el parámetro “bucket_name”
  • Ya no se acepta el parámetro “server”
  • Hay un nuevo parámetro “buckets” donde se le pasa la lista de buckets con los que trabajar (separados por espacios), por ejemplo (fotos.moterus.es photos.moterus.com foto.moterus.it)
  • Hay un nuevo parámetro “use_vhosts” (true o false), para indicar si deseamos utilizar el nombre del bucket como dominio o se debe utilizar el dominio generico de amazon, es decir, si queremos que las urls sean del estilo: http://fotos.moterus.es/… o bien http://fotos.moterus.es.s3.amazonaws.com/… Para que nos funcione correctamente debemos tener en nuestro DNS un registro del tipo “fotos.moterus.es IN CNAME fotos.moterus.es.s3.amazonaws.com”

Además, en los modelos donde queramos trabajar con attachment_fu deberemos tener un campo en la tabla con el nombre “bucket_name” de tipo string, que es donde se guardará en qué bucket se encuentra su attachment. Y en el controlador que se encargue de crear el registro (Por ejemplo, photos_controller) deberemos pasarle el bucket en el metodo “create” del modelo, por ejemplo:

  def create
    Photo.create(params[:photo].merge(:bucket_name => 'fotos.moterus.es'))
  end

Y esto es todo! Te funcionará en buckets europeos y americanos, y podrás tener distintos buckets para cada modelo. Que mas quieres? Probad, probad, si todo funciona bien y no hay problemas, intentaremos que nos acepten el parche y lo implementen en el attachment_fu de verdad.

Dudas, comentarios?

Controladores Polimórficos y Ruby on Rails

Vamos a empezar con las cosas frikis en el blog, no lo he podido resistir.

Hoy lanzo una pregunta a la comunidad de desarrolladores Ruby on Rails de habla Hispana (¿Hay alguien?): ¿Cómo carajo os lo montais para mantener el código DRY con los controladores polimórficos?

Me explico con un ejemplo (real):

En moterus tenemos usuarios, cada usuario puede tener n motos y a la vez, tanto los usuarios como las motos pueden tener n fotos.

Por lo tanto tenemos un users_controller, un bikes_controller y un photos_controller (¿Hasta aqui todo bien, no?).

En el routes.rb tendriamos algo como:

map.resources :users do |user|
  user.resources :bikes do |bike|
    bike.resources :photos
  end
  user.resources :photos
end

Entonces, ¿que pasa con el photos_controller? ¿Cómo podemos saber si queremos mostrar las fotos de un usuario o de una moto? Si es de un usuario nos llegará el params[:user_id] lleno, si es de una moto tendremos params[:user_id] y params[:bike_id]…

Un lector avispado podria decirme: ¡fácil!

@user = User.find(params[:user_id])
if params[:bike_id]
  @bike = @user.bikes.find(params[:bike_id])
  @photos = @bike.photos
else
  @photos = @user.photos
end

Pues bien, va a ser que no. Porque? Pues por varios motivos:

1) Además de usuarios y motos, también tengo grupos, eventos, rutas…. lo cual convertiría este bloque de “ifs” en un infierno, y segundo (y más importante si cabe) porque luego en el template…. ¿como construyes los links a las fotos? Según el fichero de rutas que he puesto arriba, rails nos daria lo siguientes helpers:

user_photos_path, user_photo_path, new_user_photo_path....
user_bike_photos_path, user_bike_photo_path....

¿Que tendriamos que hacer en el template “photos/index” ?

link_to @photo.title, y la url???

Vale. Una vez puestos a todos en situación, esto es un problema. ¿Como lo hemos solucionado en moterus? Agárrense a sus asientos:

Para empezar las rutas de Rails (2.0.2) a mi entender tienen un fallo, y es que en los “resources” anidados no expande el path_prefix ni name_prefix, me explico: si tu tienes un fichero routes.rb como este:

map.resources :users do |user|
  user.deactivate 'deactivate', :controller => 'users', \
:action => 'deactivate'
end

si ejecutamos un rake routes nos dirá algo como esto:

...
user_deactivate  /users/:user_id/deactivate \
{:path_prefix=>"/users/:user_id", :name_prefix=>"user_", \
:controller=>"users", :action=>"deactivate", :namespace=>nil}

esto que implica? Pues básicamente que cuando se acceda a la url /users/33/deactivate tendremos automágicamente los siguientes parámetros:

params[:user_id] = 33
params[:path_prefix] = "/users/:user_id"
params[:name_prefix] = "user_"

en cambio, si tenemos un fichero routes.rb como este:

map.resources :users do |user|
  user.resources :comments
end

y ejecutamos de nuevo el rake routes, veremos que rails no nos expande el :name_prefix ni :path_prefix.

Creo (y digo creo) que no lo hace porque habria cierta “inconsistencia” con los helpers edit_xxx, new_xxx y formatted_xxx. La generación de “named routes” en rails tal como está montado hace que el parámetro “name_prefix” sea también el “prefijo” del helper, por lo que si en el ejeplo anterior se pusiera el name_prefix a “user_” provocaria que en lugar de generarnos helpers como “edit_user_comment” tendrian que ser “user_edit_comment”. Yo podria vivir con ello, o cambiar el sistema de generación de named routes para que el name_prefix no tuviera precedencia sobre la acción sino solo el controlador…. Pero las cosas son como son.

Así pues, una vez puestos en situación, explico como lo hemos “solucionado” en moterus:

Puesto que no tenemos la ayuda del (name/path)_prefix, nos lo hemos “currado” nosotros para implementarlo, de modo que en el photos_controller pueda saber a partir de la url sobre que objeto quiero ver las fotos….

Ooooh! Llevo tiempo ya dedicado a este post y la verdad es que me está quedando una explicación más compleja de lo que esperava, por lo que dejo el post así. Si alguien está realmente interesado en saber como lo hemos solucionado, que me mande un email o lo ponga en los comentarios, así veo si realmente vale la pena hacer el esfuerzo de explicarlo, y de paso explico también lo de las rutas multi-idioma (que tiene su miga….).

Así pues, a alguien le interesa realmente este tema? Que levanten la mano!

Vssss