sábado, 26 de noviembre de 2011

Rails legacy

¿Que tipos de legacy tenemos?
  1. El código que hice hace seis meses, cuando mis conocimientos eran inferiores a los actuales [fácil]
  2. El código que no he echo yo, sino un compañero, otro equipo o una empresa de terceros (en este orden) [moderado]
  3. El código o aplicación que ha estado o está funcionando desde hace "tiempo" con una tecnología distinta a rails, distinguiendo:
    1. Aplicación a recaer [moderado]
    2. Aplicación a heredar (los datos, el funcionamiento) o convivir [difícil]
Podemos entrar en muchos matices y seguramente definir una lista más completa, en todo caso para los dos primeros puntos, dos conejos rápidos:
  1. Si no tengo test añadirlos, si tengo entenderlos, añadir más tests... he dicho test? ¡Pues eso, test, test, test!
  2. Refactoring a demanda, cuando se tenga que modificar algo se aprovecha y se corrige.
Para el tercer punto, sigue leyendo ya que es el motivo principal de este articulo.

¿Rails es legacy?

Si!

¿Rails está pensado para legacy?

En mi opinión, no! La filosofía core de rails es "Convention over Configuration (CoC)". Una aplicación legacy raramente seguirá el convenio de rails, con lo que a priori ya perdemos o tenemos que añadir el coste de ir especificando todo lo que no siga el convenio, nombres de tablas, tipos, joins, etc... Lo segundo y derivado de este problema, es que al salirnos del core de rails y ampliar con gems, nos encontraremos que algunas de ellas están solo pensadas para aplicaciones que sigan el convenio y no tienen toda la especificación rails implementada. Si no tienes alternativa sigue leyendo.


Primera solución: mapeando con vistas

Como: creando vistas que renombren la tabla, las columna y los tipos al convenio de rails y luego creando modelos rails que nos permitan acceder a ellos.

Limitaciones: solo lectura (* dependiendo del SGBD).

Problemas habituales y posibles soluciones / alternativas: 
  • Indice compuesto por strings:
    1. Mantenemos el string como indice (nos saltamos el convenio)
    2. Creamos una tabla intermedia que traduzca de strings a integer y ligamos su mantenimiento con trigers.
    3. Creamos una función que traduzca de strings a integers (afecta al rendimiento y puedes tener claves muy largas)
  • Indice compuesto por múltiples columnas: juntamos las columans en la vista

Segunda solución: mapeado en modelos las cuatro coas más importantes

En el modelo:

nombre de la tabla personalizado:
        set_table_name "client"

nombre del identificador principal o clave primaria (sin clave compuesta)
set_primary_key "client_id"
* Tener en cuenta que la opción "find_by_id" deja de funcionar, podemos utilizar "find_by"o "find_by_client_id"
 nombre del identificador principal o clave primaria (con clave compuesta)
Instal·lamos la gem composite_primary_keys
Especificamos: "set_primary_keys :person_id, :group_id"
 renombrado columnas puntuales
alias_attibute "person_name_full", "name"
* Aquí los find funcionan para el alias.
definiendo relaciones con otros modelos
has_many :comments,
 :class_name => "WordpressComment"
 :foreign_key => "comment_post_ID"
* Ejemplo real: tabla "post" con la columna "comment_post_ID" que mapeamos contra la tabla del modelo "WordpressComment" con el nombre de tabla y el id que tenga definidos.
definiendo relaciones con otros modelos y claves compuestas:
Si la cosa se complica mucho, mi recomendación es utilizar métodos dentro del modelo que nos permitan realizar las búsquedas:
def comments
    WordpressComment.find_by_post_id(self.coment_post_id)
end
 Forzar los plurales "config/initializers/inflections.rb":

* Partiendo del nombre de la tabla y añadiendo una "s"
inflect.irregular 'imp_person', 'imp_persons'
inflect.irregular 'imp_comments', 'imp_commentss'
 En las migrations:

Siempre que quieras crearlas (lo normal es que venga hechas).

si el campo id no existe, especificarlo
def change
   create_table :imp_person, {:id => false} do |t|

...
si no hay fechas de control (eliminarlas)

t.timestamps
si tenemos clave primaria añadirle el indice con execute
def up
  execute "ALTER TABLE person ADD PRIMARY KEY (national_document);"
end
def down
  execute "ALTER TABLE person DROP PRIMARY KEY (national_document);"
end

Un par de recomendacoines más:
  1. Valorar el añadir un prefijo a los modelos, de esta forma juntamos toda la lógica.
  2. Dado el coste de definir, lo definimos todo, aún siguiendo el convenio de rails, té evitarás problemas raros.
  3. Si has generado el modelo con scaffold reviste las fixtures, es posible que no te sirvan (y escribe test!).

Otro escenario habitual en legacy: nueva aplicación que tiene que heredar los datos del sistema anterior. Aquí es habitual encontrarse con los mismos problemas de convenio, más las limitaciones derivadas de los nuevos modelos de datos, campos que se añaden, campos que se quitan, nuevas reglas de negocio que pueden provocar que nuestros datos no se validen, nuevas columnas que no podemos rellenar etc... La solución dependerá mucho de nuestro escenario, en general suele dar muy buen resultado crear modelos paralelos más o menos ligados con el principal, por ejemplo podemos tener el modelo "Client" y "ClientLegacy", aquí ya es la habilidad de cada uno para reducir código, solo una nota, en mi opinión es mejor mantener los dos modelos con dos tablas distintas (para mi la validación en BD es igual de importante que en el modelo y claramente ambos tendrán validaciones distintas).



Hasta aquí esta primera aproximación al mundo legacy, en futuros artículos más escenarios, soluciones y experiencias.

No hay comentarios:

Publicar un comentario