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

10 Responses to “Controladores Polimórficos y Ruby on Rails”


  1. 1 Fernando

    Puedes solucionarlo con modelos astutos y polimorfismo de asociaciones.

    Create este modelo:
    Foto:
    id
    fotografiado_type
    fotografiado_id
    foto_bin

    Motero
    has_many :fotos,
    :class_name => Foto, #esta no es imprescindible
    :conditions => “fotografiado_type=’Motero’”,
    :foreign_key => :fotografiado_id

    Moto
    has_many :fotos,
    :class_name => Foto, #esta no es imprescindible
    :conditions => “fotografiado_type=’Moto’”,
    :foreign_key => :fotografiado_id

    Si necesitas el belongs_to en la foto entonces hay un plugin llamado has_many_polymorphic, pero es una pesadez, carga al arrancar los servidores.

    Puedes hacer un SingleTableInheritance en Foto:
    FotoMoto < Foto
    FotoMotero < Foto
    con el belongs_to en los subtipos, y la foto y la id en el supertipo.

    Puedes hacer un único campo fotos (sin modelo propio) en Moto y en Motero y guardar un array de fotos serializado con Marshal o con Yaml.

    def fotos
    return YAML.load(campofotos)
    end
    #que te dejaria hacer fotos.last, fotos.each…

    Espero que alguna de mis ideas te ayude.

    Si quieres más detalle sobre algo no dudes en escribirme.

    Un saludo

  2. 2 isaac

    Buenas Fernando,

    vaya, me sorprende que haya alguien aqui leyendo estas cosas. :) Por curiosidad, ¿Cómo me has encontrado?

    Y ahora al tema:

    El problema no está en los modelos, para esto rails se apaña muy bien… El problema está en las rutas y controladores. Es decir, en moterus tenemos rutas de este estilo:

    /usuarios/isaac/fotos/salida-a-ripoll (muestra la foto “salida a ripoll” del usuario “isaac”
    /usuarios/isaac/motos/fazerilla/fotos/pepe (muestra la foto “pepe” de la moto “fazerilla” del usuario “isaac”
    /grupos/che-passione/fotos/salvapantallas-1 (muestra la foto “salvapantallas-1″ del grupo “che-passione”.

    Además de mostrar la foto, tambien se permiten añadir comentarios a la foto, votaciones, etc… Todo desde un solo controlador (photos_controller). Cómo saber en el controlador si tenemos que buscar las fotos de un usuario, las de un grupo, las de una moto (de un usuario)…?

    Veo que como mínimo a álguien le interesa este tema… a ver si tengo algo de tiempo y escribo la segunda parte con “mi solución” y así la podemos debatir.

    Al loro!

  3. 3 isaac

    Ah, se me olvidaba… aparte de

    /usuarios/isaac/fotos… también se puede hacer
    /users/isaac/photos (ingles) o
    /benutzer/isaac/fotos (alemán) o
    /utenti/isaac/foto (italiano) o … etc…

    lo cual añade algo de complejidad más al de las rutas. Con “mi sistema”, en el template del show de la foto, es tan sencillo como hacer “photo_url(@photo.slug)” et voilà, construye el path correcto en función de sobre que objeto estemos mostrando las fotos y en el idioma del usuario…

    Me he animado, a ver si escribo la solución pronto!

  4. 4 Jorge Calás

    Hola Isaac, ante todo felicidades por moterus.es, me he dado una vuelta y me parece genial, con una interfaz de usuario sencilla e intuitiva.

    Sobre el post…. pues que me he quedado con las ganas de ver como termina el cuento. Hasta ahora no he tenido el problema con las dimensiones que cuentas pero alguna vez si que me he encontrado con la necesidad de hacerlo con el if, claro que como tu dices cuando tienes muchos recursos anidados esto se vuelve inmantenible.

    Animo (que con el exito de moterus debe tener de sobra me imagino) y cuentanos un poco más.

    Saludos,

    Jorge

  5. 5 Ricardo Gutiérrez

    Gracias por tomarte la molestia de compartir la solución en tu blog, o por lo menos mencionar la intención de hacerlo.

    Estoy desarrollando un sitio nuevo y por no conocer rails 2.0 quise aventurarme a empezar este desarrollo desde cero en dicha plataforma y los problemas que me he encontrado con las rutas son muchos.

    Tanto así que he pensado en bajar a la versión 1.2.6, en todo caso te tengo dos preguntas:

    - ¿Puedes compartir tu solución? En mi caso quiero hacer una interfaz de administradores y otra para los usuarios generales usando namespaces pero se me ha hecho imposible.

    - ¿Por que usar Rails 2.0?

    Saludos!!!!

  6. 6 Elías

    Para cuando el post porfavor!!!!

  7. 7 Elías

    y esperando…

  8. 8 Elías

    Hola,

    Estoy desesperado por una solución buena para este problema. Yo en mi caso tengo un controlador posts y tengo rutas como

    users/1/posts
    groups/2/posts
    category/1/posts
    tags/1/posts

    Es decir el controlador posts en su acción index, debería ejecutar una acción dependiendo de la ruta. Y además la vista también debe ser diferente.

    Esperamos tu solución con ansias,

    Elías

  9. 9 Elías

    Isaac tienes algún correo donde se te pueda contactar?

  10. 10 Elías

    Y no solo los controladores sino también las vistas tienen que cambiar pues tal vez quiera desplegar elementos adicionales dependiendo del caso.

Comments are currently closed.





All I Want Here