Problemas con Single Table Inheritance

Hola a todos,

Trabajando con STI (Single Table Inheritance) me he encontrado con
alguna cosa que otra bastante sorprendente.

Suponiendo la siguiente jerarquía:

class Persona < ActiveRecord:Base
end

class Empleado < Persona
end

class Tecnico < Empleado
end

class Auxiliar < Empleado
end

A) Persona.find(:all)

Si hacemos un Persona.find(:all) veremos a todas las personas, es decir,
Todos los Empleados y todos los Técnicos.

B) Tecnico.find(:all)

Si hacemos un Tecnico.find(:all) veremos solo a las personas del tipo
Tecnico.

C) Auxiliar.find(:all)

Si hacemos un Auxiliar.find(:all) veremos solo a las personas del tipo
Auxiliar.

D) Empleado.find(:all)

Si hacemos un Empleado.find(:all) veremos a las personas del tipo
Empleado, pero ¿por qué NO vemos las personas del tipo Tecnico y del
tipo Auxiliar?

Esto debería funcionar.

Solución FEA

He encontrado una forma para hacer que funcione, pero me parece
demasiado “mágica” como para utilizarla con convencimiento:

Fallo

Si en el controlador de Empleado, en la acción ‘list’ pongo:

def list
@empleados = Empleado.find(:all)
end

No veré a los Tecnicos, ni a los Auxiliares en la vista. Sólo veremos a
las personas del tipo Empleado.

OK

Si en el controlador de Empleado, en la acción ‘list’ pongo:

def list
@tecnicos = Tecnico.find(:all)
@auxiliares = Auxiliar.find(:all)
@empleados = Empleado.find(:all)
end

De este modo cuando pongo esto, sí que veré en la vista a las personas
del tipo Tecnico y del tipo Auxiliar, y además… ¡sólo recorriendo el
array @empleados!

DUDA

Tras leer mil sitios en la web, no he encontrado una solución al
problema (si es un problema)

¿Sabéis de algún sitio que explique porqué esto es así??? ¿lo sabéis
vosotros?
¿Es un bug de rails??
¿Existe alguna solución “más limpia”?? (así me parece demasiado “feo”)

Gracias por adelantado.
Un saludo!


Juan José Vidal [email protected]
ATICA - Universidad de Murcia

On 2/6/07, Juan José Vidal [email protected] wrote:

A) Persona.find(:all)

tipo Auxiliar?

OK
del tipo Tecnico y del tipo Auxiliar, y además… ¡sólo recorriendo el
¿Es un bug de rails??
¿Existe alguna solución “más limpia”?? (así me parece demasiado “feo”)

Hablo de cabeza, puedo meter la pata.
Para la STI creo que Rails incluye una columna “type” en la tabla que
guarda de que tipo de clase se trata (en tu caso Empleado, Tecnico,
Auxiliar). En las búsquedas el find de cada una de las clases
derivadas incluye una condición en plan “type = ‘Empleado’”, “type =
‘Tecnico’”, … mientras que para la clase padre la búsqueda no tiene
condiciones.

Por eso la busqueda por personas te funciona como esperas, pero la de
empleados no.

¿Bug? En cierto modo, digamos que Rails solo soporta particiones de un
solo nivel, no de dos como pretentes.

¿Una solución más limpia? Quizá redefinir el find de Empleado para que
las condiciones incluyan a los Tecnicos y a los auxiliares, pero hay
que hacerlo a mano y cada vez que incluyas una nueva subclase
tendrásque modificar el método.

On mar, 2007-02-06 at 14:10 +0100, Daniel R. Troitiño wrote:

end

Empleado, pero ¿por qué NO vemos las personas del tipo Tecnico y del
Fallo

De este modo cuando pongo esto, sí que veré en la vista a las personas
vosotros?
condiciones.
Migration ejemplo de Personas
====================================

create_table :personas do |t|
t.column :nombre, :string
t.column :identificador, :string <— para Empleado
t.column :carrera, :string <---------- para Técnico
t.column :estudios, :string <--------- para Auxiliar
t.column :type, :string
end

En la columna ‘type’ es dónde se guardan cada una de las clase.

Si insertamos datos en esta tabla (con diferentes vistas para Empleado,
Tecnico y Auxiliar), en la base de datos veremos en la columna ‘type’
los nombres de las clases pertinentes: Empleado, Tecnico y Auxiliar.

No existe a nivel de base de datos ninguna relación entre Empleado y
Técnico ó Empleado y Auxiliar.

Esto está claro.

Por eso la busqueda por personas te funciona como esperas, pero la de
empleados no.

¿Bug? En cierto modo, digamos que Rails solo soporta particiones de un
solo nivel, no de dos como pretentes.

¿Seguro? Lo digo porque he visto en muchos blogs como definen una
relación del tipo que digo, más no dicen nada de que les falle ni nada.
De hecho, en el ‘Agile Development with Rails’ sale un ejemplo… al
menos el diagrama de clases sale dibujado al estilo Martin F…

Como por ejemplo (Babboon < OldWorldMonkey < Primate):
http://rails.raaum.org/database-conventions.html#sti

¿Una solución más limpia? Quizá redefinir el find de Empleado para que
las condiciones incluyan a los Tecnicos y a los auxiliares, pero hay
que hacerlo a mano y cada vez que incluyas una nueva subclase tendrás
que modificar el método.

Eso no vale como solución. Sería muy engorrosa… además siempre está la
solución que he puesto en el primer correo.

Lo que más me sigue extrañando es este hecho (no termino de entenderlo):

del tipo Tecnico y del tipo Auxiliar, y además… ¡sólo recorriendo el
array @empleados!

Un saludo, y muchas gracias!

On mar, 2007-02-06 at 15:21 +0100, javier ramirez wrote:

Efectivamente, a nivel de base de datos no hay relación, pero ahí es
donde entra “la magia” de AR. Si miras el log de una query que tenga STI
verás que en el type le mete un “OR” por cada una de las ramificaciones
que se va encontrando desde la clase en la que buscas hacia sus
subclases. Aquí la limitación es que el nombre tiene que ser único en
cualquiera de los niveles porque si no vamos a tener conflictos… pero
es una solución lo suficientemente buena.

¿Seguro? Lo digo porque he visto en muchos blogs como definen una
relación del tipo que digo, más no dicen nada de que les falle ni nada.

Efectivamente, en el log sale:

Caso A)

Si en el controlador ponemos esto:

def list
@empleados = Empleado.find(:all)
end

En el log sale esto:

Empleado Load (0.001016) SELECT * FROM personas WHERE
( (personas.type = ‘Empleado’ ) )

Caso B)

Si en el controlador ponemos esto:

def list
@tecnicos = Tecnico.find(:all)
@auxiliares = Auxiliar.find(:all)
@empleados = Empleado.find(:all)
end

La salida del log es:

Empleado Load (0.000882) SELECT * FROM personas WHERE
( (personas.type = ‘Empleado’ OR personas.type = ‘Tecnico’ OR
personas.type = ‘Auxiliar’ ) )

En este caso perfecto.

Y tienes razón… tal cuál lo planteas en tu mail original debería
funcionar. Si no es así se me ocurre que pueda ser por una mala
definición de las tablas (¿tienen todas la columna type?) o porque estés
sobreescribiendo el campo type a mano… o si no sólo se me ocurre algún
tema de versiones.

Sólo hay una tabla definida (adjunto esquema de ejemplo), por principios
de STI:

create_table :personas do |t|
  t.column :nombre, :string
  t.column :apellidos, :string
  t.column :nif, :string
  t.column :telefono, :string
  t.column :telefono_movil, :string
  t.column :fax, :string
  t.column :email, :string
  t.column :observaciones, :text
  t.column :type, :string
end

Simplemente juego en las vistas con estos datos, poniéndole unos sí y
otros no a las diferentes clases Tecnico, Auxiliar.

Si miras en tu base de datos, ¿realmente todas las filas insertadas en
personas tienen el type bien puesto?

Sí, eso es lo raro.

En la base de datos sale:

Nombre | Apellidos | étc… | type
Pedro Pérez … Tecnico
Antonio García … Auxiliar
… … … …

Un saludo!

[email protected]
http://lists.simplelogica.net/mailman/listinfo/ror-es


Juan José Vidal Agustín [email protected]
Universidad de Murcia
ÁTICA - Área de Tecnologías de la Información y las Comunicaciones
Aplicadas
Proyecto SOFTLA - Software Libre y Abierto Universidad de Murcia
Edificio Ática, Campus Univ. de Espinardo
E-30100 Murcia (SPAIN)

Tlf.: +34 968 39 8741

@tecnicos = Tecnico.find(:all)
@auxiliares = Auxiliar.find(:all)        
@empleados = Empleado.find(:all)

end

La salida del log es: Empleado Load (0.000882) SELECT * FROM personas WHERE ( (personas.type = ‘Empleado’ OR personas.type = ‘Tecnico’ OR
personas.type = ‘Auxiliar’ ) )

En este caso perfecto.

Pues… me huele a que AR se está haciendo un lío al buscar las
subclasses y que sólo sabe encontrarlas si están cargadas en el espacio
de objetos. Imagino que haciendo un require inicial de cada una de las
implicadas (en el environment, por ejemplo), o lanzando un
Persona.find(:all) inicial para que te cargue todas las clases (por lo
que comentabas al hacer eso te encontraba todas) conseguirías tener las
clases en el scope. Pero desde luego no es muy elegante… parece un
bug, lo mismo mirando por el trac de RoR encuentras algo sobre el tema.

Claro que también puede pasar que no sea nada de eso, pero tal cual se
porta es la impresión que me da.

suerte,

j


Estamos de estreno… si necesitas llevar el control de tus gastos
visita http://www.gastosgem.com !!Es gratis!!

Hola,

En la columna ‘type’ es dónde se guardan cada una de las clase.

Si insertamos datos en esta tabla (con diferentes vistas para Empleado,
Tecnico y Auxiliar), en la base de datos veremos en la columna ‘type’
los nombres de las clases pertinentes: Empleado, Tecnico y Auxiliar.

No existe a nivel de base de datos ninguna relación entre Empleado y
Técnico ó Empleado y Auxiliar.

Efectivamente, a nivel de base de datos no hay relación, pero ahí es
donde entra “la magia” de AR. Si miras el log de una query que tenga STI
verás que en el type le mete un “OR” por cada una de las ramificaciones
que se va encontrando desde la clase en la que buscas hacia sus
subclases. Aquí la limitación es que el nombre tiene que ser único en
cualquiera de los niveles porque si no vamos a tener conflictos… pero
es una solución lo suficientemente buena.

¿Seguro? Lo digo porque he visto en muchos blogs como definen una
relación del tipo que digo, más no dicen nada de que les falle ni nada.

Y tienes razón… tal cuál lo planteas en tu mail original debería
funcionar. Si no es así se me ocurre que pueda ser por una mala
definición de las tablas (¿tienen todas la columna type?) o porque estés
sobreescribiendo el campo type a mano… o si no sólo se me ocurre algún
tema de versiones.

Si miras en tu base de datos, ¿realmente todas las filas insertadas en
personas tienen el type bien puesto?

Saludos,

javier ramirez


Estamos de estreno… si necesitas llevar el control de tus gastos
visita http://www.gastosgem.com !!Es gratis!!

Dije que iba a meter la pata y la metí. Como demuestras con los logs
efectivamente funciona como debería con particiones de varios niveles.
No me fijé en tu problema del modelo frente al controlador.

Parece que lo que te responde Javier sobre que las clases no parece
mal encaminado, pero en vez de en el environment o en el controlador
yo haría el require al final de la clase padre (haría el require
‘empleado’ en persona.rb y los require ‘tecnico’; require ‘auxiliar’
en empleado.rb), de esa forma entre la búsqueda de dependencias de
Rails y esos require cargando cualquier parte del arbol se deberían
cargar todas las clases del subarbol correctamente.

Ya nos contarás si lo consigues hacer funcionar.

Hola.

Parece que es un problema de momento sin resolver. Echale una ojeada a

http://dev.rubyonrails.org/ticket/4730

Al final tienes una sugerencia de solución.

Saludos

Daniel R. Troitiño escribió:

cargar todas las clases del subarbol correctamente.

Ya nos contarás si lo consigues hacer funcionar.


Ror-es mailing list
[email protected]
http://lists.simplelogica.net/mailman/listinfo/ror-es


Andrés Cirugeda E.
ASPgems
Email: andres at aspgems dot com

‘All we have to decide is what to do with the time that is given to us’.
Gandalf.