ActiveRecord y herencia multitabla


#1

Hola gente,

Estaba yo tan contento intentando implementar una herencia con
ActiveRecord y veo que lo tiene complementamente magificado:
simplemente poniendo un campo type:string en la clase padre todo se
hace mágicamente.

Triste de mí al darme cuenta que cuando la documentación dice “Single
table inheritance” quiere decir exactamente eso: “Single table
inheritance”.

Y claro yo mirando los logs y viendo que sólo se hace un insert into
en la tabla de la clase padre y la tabla de la clase hijo es
completamente ignorada…

Esto está bien si la clase padre y la clase hijo comparten
absolutamente todos los atributos, pero qué hay si quiero, como es
completamente comprensible, incluir un atributo en una clase hija que
no quiero que esté en la clase padre… pues que no se puede…

Obie resuelve un poco esto con métodos de instancia… y aliases… [1]
pero no es lo mismo :slight_smile:

Verdaderamente me parece un implementación de herencia muy muy limitada.

Entonces vuelvo a google con la búsqueda más concreta “activerecord
multi table inheritance” y encuentro este currado post [2] a propósito
de como implementar este tipo de herencia pero me resulta demasiado
confuso, tortuoso y postgresql dependiente…

Mi pregunta es: ¿existe alguna manera sencilla y si es posible bien
testeada de implementar la herencia multitabla con Rails?

Por ahora estoy valorando usar relaciones polimórficas para que los
objetos que deberían ser los hijos pasen a ser contenedores de la
clase padre a través de un has_one polimófico… como se explica
aquí:[3], ésta es la aproximación que al principio se me indicó pero no me
parecía demasiado correcta… aunque ahora he cambiado de opinión: no
será la correcta pero es la más directa.

Cualquier comentario es bien venido.

Gracias.

f.

[1] http://www.jroller.com/obie/entry/some_ar_sugar_4_u
[2]
http://lindsaar.net/2008/3/12/multi-table-inheritance-in-rails-when-two-tables-are-one
[3] http://www.ibm.com/developerworks/web/library/wa-rails3/


Fernando
GuillénDesarrollador Web Freelance
http://www.fernandoguillen.info


#2

Hola Fernando,

La idea de STI es que la tabla padre tiene todos los atributos de
todas las clases ‘hijas’.

Si vees que las clases hijas no comparten muchos atributos con la
clase padre, es un code smell que significa que la herencia no te vale
en esta
ocasión.
Del libro del Agile Web Dev w/ rails:

David Says. . .
Won’t Subclasses Share All the Attributes in STI?
Yes, but it’s not as big of a problem as you think it would be. As
long as the sub-
classes are more similar than not, you can safely ignore the
reports_to attribute
when dealing with a customer. You simply just don’t use that attribute.
We’re trading the purity of the customer model for speed (selecting
just from
the people table is much faster than fetching from a join of people
and customers
tables) and for ease of implementation.
This works in a lot of cases, but not all. It doesn’t work too well
for abstract rela-
tionships with very little overlap between the subclasses. For
example, a con-
tent management system could declare a Content base class and have sub-
classes such as Article, Image, Page, and so forth. But these
subclasses are likely
to be wildly different, which will lead to an overly large base table
because it
has to encompass all the attributes from all the subclasses. In this
case, it would
be better to use polymorphic associations, which we describe next.

-christos


#3

Ultimamente yo también estoy pensando en cosas parecidas, y lo veo todo
muy oscuro.
Mi idea es hacer un clase abstracta, y que las subclases hereden de
ella, teniendo estas diferentes comportamientos en base a su naturaleza.

Estoy pensando en utilizar mixin a través de módulos paraa conseguir
“Herencia Múltiple”.
No tengo muy claro como hacerlo… No he encontrado ninguna aplicación
libre en rails que implemente algo así.

Por ejemplo: Una clase Proyecto y varias clases Subproyecto, cada una
con vistas diferentes.

¿Conocéis dónde puedo encontrar información relativa a esto?

Estoy leyendo concienzudamente “Enterprise Rails”, y parece que veo algo
de luz, pero algo se me escapa… no termino de verlo claro. Me gustaría
ver código Rails haciendo esto.

Un saludo!

Fernando G. escribió:


#4

2009/1/8 Roberto M. Oliva removed_email_address@domain.invalid:

Puedes hacer lo que buscas con STI. Basta que la tabla contenga el mayor
conjunto de campos posible entre las clases padre y las hijas. Aquellas
clases que no implementen ciertos atributos simplemente llenaran el
valor del campo con un valor por defecto, por ejemplo nulo.

Y utilizar STI con “aggregations”?


#5

Hola

Puedes hacer lo que buscas con STI. Basta que la tabla contenga el mayor
conjunto de campos posible entre las clases padre y las hijas. Aquellas
clases que no implementen ciertos atributos simplemente llenaran el
valor del campo con un valor por defecto, por ejemplo nulo.

Puede parecer una implementacion un tanto chapuza, pero la verdad es que
funciona bastante bien dentro de Rails y tiene mejor rendimiento que
utilizar MTI. El problema radica en el uso inútil de espacion en la BD
que sabes que nunca vas a llenar.

Un saludo
Roberto M. Oliva

Fernando G.
escribió:> Hola gente,


#6

El día 8 de enero de 2009 13:42, Roberto M. Oliva removed_email_address@domain.invalid
escribió:> Hola

Puedes hacer lo que buscas con STI. Basta que la tabla contenga el mayor
conjunto de campos posible entre las clases padre y las hijas. Aquellas
clases que no implementen ciertos atributos simplemente llenaran el
valor del campo con un valor por defecto, por ejemplo nulo.

Es que el uso de STI también tiene otra pega y es la imposibilidad de
usar validaciones a nivel BD de la no nulicidad de los campos: si un
campo es sólo usado por una clase hija pero quieres ponerle un null =>
false en la migración para asegurar de que este campo está bien
inicializado no vas a poder pues te petarán todas las clases
‘hermanas’. Sí ya sé que esto se puede proteger con validaciones de
rails, pero no las podrás poner a nivel de BD… aunque sé que para
muchos esto no es un problema… y para mí tampoco lo sería sino le
sumásemos todas las otras pegas que le encuentro.

Puede parecer una implementacion un tanto chapuza, pero la verdad es que
funciona bastante bien dentro de Rails y tiene mejor rendimiento que
utilizar MTI. El problema radica en el uso inútil de espacion en la BD
que sabes que nunca vas a llenar.

A parte de que la tabla padre queda hecha un burruño de campos
posiblemente muy inconexos… sin contar con el problema de no poder
usar el mismo nombre para dos atributos de clases hijas independientes
cuyo tipo de dato no es el mismo… o cualquier incompatibilidad.

Sin contar tampoco con no saber de ninguna manera qué campos se han
incluido para completar una de las clases hijas y cuales para la otra.

Además de andar todo el rato modificando la tabla padre cada vez que
aparece una nueva clase hija con atributos personalizados.

No sé… será más óptimo pero me parece una justificación un poco
paradógica partiendo de la base de que estamos usando un motor de
persistencia precisamente porque preferimos la agilidad, la
abstracción y la magia a la
optimización.
Saludetes
f.


#7

Desconozco alternativas mejores a las que habéis comentado (es posible
que otros patrones de acceso a datos los ofrezcan pero hablamos de
ActiveRecord), pero se me ocurre plantear: si las clases herederas son
tan independientes entre sí y te preocupa tanto su mantenimiento en un
futuro… ¿has pensado en manejar su estructura y almacenamiento en la
base de datos de forma independiente (en tablas separadas) y gestionar
su comportamiento el comportamiento común mediante mixins? De esta
forma podrías mantener el DRY donde IMHO de verdad importa (a nivel de
código) y no donde puede ser un estorbo (en la base de datos).


#8

2009/1/8 Raul M. removed_email_address@domain.invalid:

Desconozco alternativas mejores a las que habéis comentado (es posible
que otros patrones de acceso a datos los ofrezcan pero hablamos de
ActiveRecord), pero se me ocurre plantear: si las clases herederas son
tan independientes entre sí y te preocupa tanto su mantenimiento en un
futuro… ¿has pensado en manejar su estructura y almacenamiento en la
base de datos de forma independiente (en tablas separadas) y gestionar
su comportamiento el comportamiento común mediante mixins?

En realidad no son tan diferentes… la clase hijo sólo contiene un par
de métodos que no contiene la clase padre… por ahora. El ‘por ahora’
también es otra causa del porque no quiero usar STI pues no quiero que
porque un hijo más adelante requiera un nuevo atributo tener que
modificar la clase padre influyendo indirectamente a todas las clases
‘hermanas’ :confused:

Tu idea está en nuestra cabeza claro… lo que pasa que nos
gustaríatener en una sola tabla todas las referencias e este tipo de objetos,
que para hacerlo más claro se trata de referencias a ficheros en S3 y
los hijos serían Photo, UserFile, Avatar, …

La idea de usar mixins, includes, módulos y demás me parece un poco
liosa y críptica, no sé, seguramente porque no estoy acostumbrado y
todavía se me escapa de la cabeza… lo que me apetece es una
implementación que la leas y la entiendas a la primera :confused:

De esta
forma podrías mantener el DRY donde IMHO de verdad importa (a nivel de
código) y no donde puede ser un estorbo (en la base de datos).

Sip no me molesta tanto que la BD quede poco DRY… pero por lo que te
comento arriba perderíamos la centralización de este tipo de objetos.

Abrazotes.
f.


#9

A mi la primera vez que me planteé usar herencia con Rails me choco un
poco
pero lo hice con STI y ahora me arrepiento, pero tu idea de usar
polimorfismos
me parece bastante aceptable (aún que no correcta) para simularla.

Además no es una característica que se contemple implementar en
Rails… :frowning:


#10

El día 8 de enero de 2009 14:57, Francesc E.
removed_email_address@domain.invalid
escribió:> 2009/1/8 Roberto M. Oliva removed_email_address@domain.invalid:

Puedes hacer lo que buscas con STI. Basta que la tabla contenga el mayor
conjunto de campos posible entre las clases padre y las hijas. Aquellas
clases que no implementen ciertos atributos simplemente llenaran el
valor del campo con un valor por defecto, por ejemplo nulo.

Y utilizar STI con “aggregations”?

Uff, No llego Francesc, ya me lío para entender las aggregations como
para saber como se pueden usar para dar una solución elegante a este
problema… tienes algún sitio donde expliquen el acercamiento a esta
solución?
:confused:

f.


#11

Por si te sirve existe el plugin
http://research.inplanb.com/has_ancestor

Saludos.

El 08/01/2009, a las 16:18, Fernando G.
escribió:

2009/1/8 Raul M. removed_email_address@domain.invalid:


#12

El día 8 de enero de 2009 16:18, Fernando G.
removed_email_address@domain.invalid
escribió:> En realidad no son tan diferentes… la clase hijo sólo contiene un par

de métodos que no contiene la clase padre… por ahora. El ‘por ahora’
también es otra causa del porque no quiero usar STI pues no quiero que
porque un hijo más adelante requiera un nuevo atributo tener que
modificar la clase padre influyendo indirectamente a todas las clases
‘hermanas’ :confused:

Lo entiendo, yo también me lo pienso varias veces antes de aplicar STI :smiley:

Tu idea está en nuestra cabeza claro… lo que pasa que nos gustaría
tener en una sola tabla todas las referencias e este tipo de objetos,
que para hacerlo más claro se trata de referencias a ficheros en S3 y
los hijos serían Photo, UserFile, Avatar, …

Por curiosidad: ¿qué ventajas le veis? (aparte de tener menos tablas
en la base de datos, y por tanto en la cabeza?)

La idea de usar mixins, includes, módulos y demás me parece un poco
liosa y críptica, no sé, seguramente porque no estoy acostumbrado y
todavía se me escapa de la cabeza… lo que me apetece es una
implementación que la leas y la entiendas a la primera :confused:

Bueno, para quitarte un poco el miedo te animaría a que modelaras
cómoquedaría la cosa con un par de esas clases, por ejemplo Photo y
Avatar. A la hora de leerlo quizá tengas menos problemas de los que
crees, porque la distribución física del código es muy parecida a la
que tendrías si aplicaras herencia:

class S3

codigo comun

class Avatar < S3

codigo especifico

tendrías

module S3

codigo comun

class Avatar
include S3

codigo especifico

No sé, ya te digo que en mi caso suelo preferir la flexibilidad de los
mixins a la herencia, y más si es un objeto AR porque quedas atado a
la STI… aunque todo depende del caso concreto, claro :slight_smile:


#13

Hola Fernando

Tienes toda la razon en lo que comentas, pero muchas veces lo mejor es
enemigo de lo bueno.
No te digo que STI es la panacea, pero es una buena solucion para muchos
de los casos de herencia.

“partiendo de la base de que estamos usando un motor de persistencia
precisamente porque preferimos la agilidad, la abstracción y la magia a
la optimización.”

Date cuenta tambien que está el lado del propio desarrollador. Un
sistema STI tambien ayuda a que el codigo sea mas sencillo con lo bien
que esta integrado en Rails.
Si tan poco te preocupa el rendimiento de la aplicación es por que vas a
desarrollar muy rapidamente. Eso tambien es paradojico entre usar STI o
un plugin de terceros para implementar MTI (por ejemplo).

Vamos, en definitiva, lo correcto es lo que estás haciendo: sopesar los
pros y los contras pero, en mi opinion, STI no es tan malo como parece.

Un saludo
Roberto

Fernando G.
escribió:> El día 8 de enero de 2009 13:42, Roberto M. Oliva removed_email_address@domain.invalid escribió:


#14

El día 8 de enero de 2009 16:39, alarkspur removed_email_address@domain.invalid
escribió:> Por si te sirve existe el plugin http://research.inplanb.com/has_ancestor

Gracias Aitor, tiene buena pinta, lo probaré en algún proyecto
experimental… en el que estoy ahora no me atrevo bien sabes por qué
:), pero tiene pinta de conseguir muy bien lo que necesitaba.

f.


#15

El día 8 de enero de 2009 16:41, Raul M. removed_email_address@domain.invalid
escribió:>

Tu idea está en nuestra cabeza claro… lo que pasa que nos gustaría
tener en una sola tabla todas las referencias e este tipo de objetos,
que para hacerlo más claro se trata de referencias a ficheros en S3 y
los hijos serían Photo, UserFile, Avatar, …

Por curiosidad: ¿qué ventajas le veis? (aparte de tener menos tablas
en la base de datos, y por tanto en la cabeza?)

Pues la verdad cada vez le veo menos ventajas :)… no sé…

La idea de usar mixins, includes, módulos y demás me parece un poco
liosa y críptica, no sé, seguramente porque no estoy acostumbrado y
todavía se me escapa de la cabeza… lo que me apetece es una
implementación que la leas y la entiendas a la primera :confused:

Bueno, para quitarte un poco el miedo te animaría a que modelaras cómo
quedaría la cosa con un par de esas clases, por ejemplo Photo y
Avatar. A la hora de leerlo quizá tengas menos problemas de los que
crees, porque la distribución física del código es muy parecida a la
que tendrías si aplicaras herencia:

jaja… muchas gracias Raúl… la verdad que ayuda mucho ver como lo
explicas… lo tendré en cuenta.

No sé, ya te digo que en mi caso suelo preferir la flexibilidad de los
mixins a la herencia, y más si es un objeto AR porque quedas atado a
la STI… aunque todo depende del caso concreto, claro :slight_smile:

Gracias .

f.


#16

El 08/01/2009, a las 16:18, Fernando G. escribió:

Tu idea está en nuestra cabeza claro… lo que pasa que nos gustaría
tener en una sola tabla todas las referencias e este tipo de objetos,
que para hacerlo más claro se trata de referencias a ficheros en S3 y
los hijos serían Photo, UserFile, Avatar, …

A ver empezado por el problema concreto leches. Así es más fácil opinar.

La pregunta correcta sería si los campos que son diferentes han de ser
indexados (bien sea sphinx/indices mysql). De no ser así STI es la
mejor opción con las propiedades serializadas y un campo text a toda
la base.

Si ha de ser indexado tiraría yo por polimorfismo, dejando en la tabla
File los campos relacionados con el fichero y los metodos de
interacción con s3, un destroy debería borrar la fila y el archivo
remoto de s3.

En ambos casos haría delegación de method_missing a la asociación
polimórfica (en caso de usar polimorfismo).

f = File.first #
f.file_name # ‘foto.jpg’
f.mime # ‘image/jpeg2000.99’
f.read # #implementación rara de usar read pero el nombre del
metodo podría ser otro.
f.read.camera_info # ‘Foto hecha con un iphone mas’
f.camera_info # ‘Foto hecha con un iphone mas’ # delegate

p = Photo.first
p.camera_info # ‘Foto hecha un iphone mas’
p.file_name # ‘foto.jpg’ # Delegación inversa

Es como se me ocurre así a bote pronto.

Un Saludo


#17

Quizas he hecho una lectura un poco transversal, pero si lo que
quieres es tener varios ficheros adjuntos a un modelo, no te serviria
Paperclip? Nosotros lo utilizamos juntamemte con relaciones
polimorficas para solucionar un problema parecido al tuyo.

On 08/01/2009, at 17:47, Guillermo Álvarez Fernández
<guillermo@cientifico.n


#18

El día 8 de enero de 2009 16:43, Roberto M. Oliva removed_email_address@domain.invalid
escribió:> Hola Fernando

muchas veces lo mejor es
enemigo de lo bueno.

Tienes razón es éste y los posteriores comentarios…

f.


#19

El día 8 de enero de 2009 17:57, Francesc E.
removed_email_address@domain.invalid
escribió:>

Quizas he hecho una lectura un poco transversal, pero si lo que
quieres es tener varios ficheros adjuntos a un modelo, no te serviria
Paperclip? Nosotros lo utilizamos juntamemte con relaciones
polimorficas para solucionar un problema parecido al tuyo.

jaja… Francesc… sí un poco transversal si …

Saludete
f.