STI

1 view
Skip to first unread message

Luis Galnares

unread,
Dec 3, 2009, 10:38:02 PM12/3/09
to ru...@googlegroups.com
Hola

Les pido su opinion sobre lo siguiente. De repente es medio básico pero bueno, son mis primeros pasos

En mi aplicacion tengo una clase X de la que heredan 4 subclases A,B,C,D.
- La clase X no se instanciaría nunca, es una clase abstracta
- A,B,C,D comparten la mayoría de los atributos por eso los heredan de X

En varios sitios vi que en ese caso recomiendan implementar esto con STI (single table inheritance), o sea tener una sola tabla para todas las subclases en lugar de tener una tabla para cada subclase.

Supongo que eso tiene alguna ventaja pero en realidad no entiendo bien porqué ya que el manejo de la base de datos es transparente de todas formas cual es la ventaja que puedo tener?

Ademas me compica lo siguiente:
- El tema del controller y las views. Es decir quiero que todas las subclases usen el mismo index pero distindo edit
- Que las views de cada subclass sean lo mas DRY posible

Conocen algún ejemplo o tutorial que me pueda servir pasa aclararme estas dudas? Ya busque bastante y no encontre nada que me resultara claro en ese sentido

Nicolás Sanguinetti

unread,
Dec 4, 2009, 9:31:16 AM12/4/09
to ru...@googlegroups.com
2009/12/4 Luis Galnares <lgal...@gmail.com>:
> Hola
>
> Les pido su opinion sobre lo siguiente. De repente es medio básico pero
> bueno, son mis primeros pasos
>
> En mi aplicacion tengo una clase X de la que heredan 4 subclases A,B,C,D.
> - La clase X no se instanciaría nunca, es una clase abstracta
> - A,B,C,D comparten la mayoría de los atributos por eso los heredan de X
>
> En varios sitios vi que en ese caso recomiendan implementar esto con STI
> (single table inheritance), o sea tener una sola tabla para todas las
> subclases en lugar de tener una tabla para cada subclase.
>
> Supongo que eso tiene alguna ventaja pero en realidad no entiendo bien
> porqué ya que el manejo de la base de datos es transparente de todas formas
> cual es la ventaja que puedo tener?

La ventaja es que es la forma de heredar sin que AR se rompa. La
herencia, en ActiveRecord, está atada inexorablemente a STI, y
cualquier intento de hacer otra cosa requiere multiples hacks :)

> Ademas me compica lo siguiente:
> - El tema del controller y las views. Es decir quiero que todas las
> subclases usen el mismo index pero distindo edit
> - Que las views de cada subclass sean lo mas DRY posible

Herencia de controllers :)

Ponele que tus modelos fueran Assets, y las subclases que tenés son
Video, Image, Text.

Tendrías un AssetsController, y después VideosController,
ImagesController, y TextsController, y estos tres heredarían de
AssetsController.

Las rutas serían algo así:

map.resources :assets, :only => [:index, :destroy]

map.with_options :only => [:show, :edit, :update] do |asset|
asset.resources :videos
asset.resources :images
asset.resources :texts
end

En dónde van el :new y el :create va a depender de cuán similares sean
entre ellas las vistas, ponele que para este caso capaz que todos los
assets para crearlos simplemente tienen un file field, un nombre y una
descripción, y después lo separo como Video, Image o Text según el
content_type, entonces ahí el :new y :create irían en el
AssetsController, porque el usuario está, efectivamente, creando un
Asset sin importarle qué es.

Luego para las vistas, tu assets/index.html.erb tendría algo como esto:

<ul>
<%= render @assets %>
</ul>

Rails (2.3 y superior, la sintáxis es un poco más verbosa para
versiones más viejas) va a buscar un partial para cada elemento,
fijándose en el nombre de la clase del objeto, por lo que para que eso
funcione tenés que escribir partials para cada una de tus subclases:
videos/_video.html.erb, images/_image.html.erb y texts/_text.html.erb
que contengan el <li> con lo que vayas a mostrar adentro (o una tabla
en el index y los partials implementan los <tr>, se entiende la idea)

Después para las otras vistas cada controller implementa las suyas y
no te importa tanto.

Lo único que queda es ver qué pasa con los helpers para generar los
links. Si para cada asset tenés que hacer video_path(asset), o
image_paht(asset), o text_path(asset), obviamente va a quedar feísimo
y lleno de ifs en las vistas.

Para esto rails tiene lo que le llama "polymorphic url for". Vos
simplemente proveés un objeto, y rails adivina según el tipo de objeto
qué querés. Entonces, tus links al "show" de cada objeto serían <%=
link_to asset.name, asset %>. Y eso va a interpretarse como
video_path(asset) si asset.is_a?(Video), image_path(asset) si
asset.is_a?(Image), etc.

Cuando querés urls con más "partes" en el helper, le pasás un array:
[:edit, asset] se va a interpretar como edit_video_path(asset), etc
etc.

Cualquier duda preguntá!
-foca

Diego Algorta

unread,
Dec 4, 2009, 9:59:23 AM12/4/09
to ru...@googlegroups.com
2009/12/4 Nicolás Sanguinetti <god...@gmail.com>:

clap! clap! clap! clap! clap! clap! clap! clap! clap! clap!

Respuesta magistral.

Diego

Galni

unread,
Dec 5, 2009, 4:22:44 PM12/5/09
to Grupo de Usuarios Ruby del Uruguay
La verdad muy buena la respuesta. Te agradezco pila
Voy a digerirla y ponerla en práctica, pero capaz que los molesto con
alguna cosita más

Saludos

On 4 dic, 12:31, Nicolás Sanguinetti <godf...@gmail.com> wrote:
> 2009/12/4 Luis Galnares <lgalna...@gmail.com>:

Galni

unread,
Dec 6, 2009, 2:01:53 PM12/6/09
to Grupo de Usuarios Ruby del Uruguay
A ver que hice mal:

hice unas routes de prueba de esta forma:
map.resources :parents, :only => [:index, :destroy, :new, :create]
map.with_options :only => [:show, :update, :edit] do |parent|
parent.resources :sons
end

En el view del parent los links a los Show y Edit son los siguientes:
<td><%= link_to parent.name, parent %></td>
<td><%= link_to parent.name, [:edit,parent] %></td>

El link del show sigue direccionando al show del parent. Lo cual me
parece lógico ya que el 1er parametro es lo que despliega en pantalla
(el atributo nombre del parent "pepe") y el segundo parametro es "/
parent/1"

El link del edit directamente no funciona: undefined method
`edit_parent_path' for #<ActionView::Base:0x56c406c>

Disculpen la joda
Gracias


On 4 dic, 12:31, Nicolás Sanguinetti <godf...@gmail.com> wrote:
> 2009/12/4 Luis Galnares <lgalna...@gmail.com>:
>
>
>

Nicolás Sanguinetti

unread,
Dec 6, 2009, 10:44:46 PM12/6/09
to ru...@googlegroups.com
El tema es que estás pasando una instancia de Parent, y no de Son.
Básicamente, las polymorphic urls lo que hacen es agarrar el nombre de
clase y hacer underscore. Por ende, si pasás una instancia "x" de
ThisIsTheParent, url_for(x) va a generar this_is_the_parent_path(x), y
[:edit, x] genera edit_this_is_the_parent_path(x).

Es un buen gotcha, porque si en el índice vos cargás instancias de
Parent, entonces los links a los show/edit particulares no van a
andar. Sorry, no me avivé de que podía pasar esto :)

Igual AR tiene un método "becomes" [1], que lo que hace es convertir
un modelo a otra clase. Básicamente, instancia un objeto de la clase
nueva, y copia los atributos del objeto actual.

Por ejemplo a_parent.becomes(Son) devuelve una instancia de Son con
los atributos del objeto "a_parent".

Lo que yo haría (y espero alguien tenga una mejor idea, porque me
suena a medio chancho), es definir un método en Parent del estilo de:

def to_child_class
becomes(type.constantize)
end

Y en las vistas al link lo llamarías como:

<%= link_to "Edit", [:edit, parent.to_child_class] %>

Lo cual debería generar correctamente edit_son_path(parent).

Creo :P

[1] http://rails.rubyonrails.org/classes/ActiveRecord/Base.html#M002334

2009/12/6 Galni <lgal...@gmail.com>:

NachoKB

unread,
Dec 7, 2009, 10:47:00 AM12/7/09
to ru...@googlegroups.com
2009/12/7 Nicolás Sanguinetti <god...@gmail.com>

Lo que yo haría (y espero alguien tenga una mejor idea, porque me
suena a medio chancho), es definir un método en Parent del estilo de:

me gustaría encontrar la forma de forzar a polymorphic_url a que use un tipo específico (mil disculpas, no encontré nada en la doc, hay que probar) -- a fin de cuentas para lo único que se estaría usando el #becomes sería para forzar eso...

-- nachokb

Galni

unread,
Dec 9, 2009, 7:56:37 PM12/9/09
to Grupo de Usuarios Ruby del Uruguay
Para que quedara trate de hacer todo en una línea con:
<%= link_to "Edit", [:edit, parent.become(parent.type.constantize)]
%>

Pero no me funcionó, no le gusta el vector de parametros ese.
De todas formas me sirvió de pista para llegar a esto:

link_to 'Edit', url_for(:controller=>parent.type
+'s', :action=>'edit', :id=>parent.id)
(el polymorphic_url_for tampoco me dio bola y lo terminé haciendo con
el comun especificandole el controller)

No se si hay algun reparo filosofico con esa solución pero funciona y
es una sola línea.
Quedó bastante verbose pero anda :P
Gracias a todos

On 7 dic, 13:47, NachoKB <nach...@gmail.com> wrote:
> 2009/12/7 Nicolás Sanguinetti <godf...@gmail.com>

Nicolás Sanguinetti

unread,
Dec 10, 2009, 12:46:13 AM12/10/09
to ru...@googlegroups.com
2009/12/9 Galni <lgal...@gmail.com>:
>
> Para que quedara trate de hacer todo en una línea con:
> <%= link_to "Edit", [:edit, parent.become(parent.type.constantize)]
> %>
>
> Pero no me funcionó, no le gusta el vector de parametros ese.

Qué error te tira? Peganos lo que tira `rake routes` y el error que te tira.

> De todas formas me sirvió de pista para llegar a esto:
>
> link_to 'Edit', url_for(:controller=>parent.type
> +'s',  :action=>'edit', :id=>parent.id)
> (el polymorphic_url_for tampoco me dio bola y lo terminé haciendo con
> el comun especificandole el controller)

Es espantoso.

Galni

unread,
Dec 10, 2009, 8:00:37 PM12/10/09
to Grupo de Usuarios Ruby del Uruguay
Cheee es espantoso pero es working code, me sirve para ir tirando

El problema con la otra solucion:
<td><%= link_to 'Edit', [:edit, parent.becomes
(parent.type.constantize)] %></td>
error:
undefined method `edit_parent_path' for #<ActionView::Base:0x5804648>
(y eso que el type lo miro y dice "Son", pero despues arma el
edit_parent_path no se pq)

Los routes:
parents GET /parents(.:format)
{:action=>"index", :controller=>"parents"}
POST /parents(.:format)
{:action=>"create", :controller=>"parents"}
new_parent GET /parents/new(.:format)
{:action=>"new", :controller=>"parents"}
parent DELETE /parents/:id(.:format)
{:action=>"destroy", :controller=>"parents"}
edit_son GET /sons/:id/edit(.:format)
{:action=>"edit", :controller=>"sons"}
son GET /sons/:id(.:format)
{:action=>"show", :controller=>"sons"}
PUT /sons/:id(.:format)
{:action=>"update", :controller=>"sons"}

Los routes supuse que están bien porque la otra solución funciona

On 10 dic, 03:46, Nicolás Sanguinetti <godf...@gmail.com> wrote:
> 2009/12/9 Galni <lgalna...@gmail.com>:

Nicolás Sanguinetti

unread,
Dec 10, 2009, 9:35:58 PM12/10/09
to ru...@googlegroups.com
Mira, acabo de hacer una aplicación de prueba, con rails 2.3.4, y todo
anda como debería. Incluso mejor — o sea, es todo transparente, salvo
el Parents#create, donde a lo que manualmente estoy inicializando un
"Parent", tengo que castearlo al tipo que quiero para que redirija al
show apropiado.

No hay tests (oh the humanity), pero todo anda sin ningún problema[1],
y el código es como el que puse en mi primer post :)

http://nicolassanguinetti.info/files/sti-example.tar.bz2

Lo único que deberías necesitar es rake db:migrate y script/server

-foca

[1] bah, mentira, hay *un* problema, aunque no es estríctamente
relevante al ejemplo, en el form de crear un nuevo modelo, <%=
form.select :type, [["Image", Image], ["Video", Video]] %> no anda,
tenés que armar el select medio a mano, lo cual apesta.

Igual en una aplicación "de verdad" nunca dejarías que tus usuarios
elijan qué modelo vas a crear, sino que más bien detectarías a qué
tipo pertenece de acuerdo a alguna condición (por ejemplo, en el caso
de los assets que hay en la app de ejemplo, fijándote en el content
type, o alguna cosa así)

2009/12/10 Galni <lgal...@gmail.com>:
> --
> Grupo de Usuarios Ruby del Uruguay - http://groups.google.com/group/ruguy
> Anular suscripción - ruguy-un...@googlegroups.com

Galni

unread,
Dec 11, 2009, 6:44:49 PM12/11/09
to Grupo de Usuarios Ruby del Uruguay
Gracias por tomarte este trabajito
Un lujo la verdad

On 11 dic, 00:35, Nicolás Sanguinetti <godf...@gmail.com> wrote:
> Mira, acabo de hacer una aplicación de prueba, con rails 2.3.4, y todo
> anda como debería. Incluso mejor — o sea, es todo transparente, salvo
> el Parents#create, donde a lo que manualmente estoy inicializando un
> "Parent", tengo que castearlo al tipo que quiero para que redirija al
> show apropiado.
>
> No hay tests (oh the humanity), pero todo anda sin ningún problema[1],
> y el código es como el que puse en mi primer post :)
>
> http://nicolassanguinetti.info/files/sti-example.tar.bz2
>
> Lo único que deberías necesitar es rake db:migrate y script/server
>
> -foca
>
> [1] bah, mentira, hay *un* problema, aunque no es estríctamente
> relevante al ejemplo, en el form de crear un nuevo modelo, <%=
> form.select :type, [["Image", Image], ["Video", Video]] %> no anda,
> tenés que armar el select medio a mano, lo cual apesta.
>
> Igual en una aplicación "de verdad" nunca dejarías que tus usuarios
> elijan qué modelo vas a crear, sino que más bien detectarías a qué
> tipo pertenece de acuerdo a alguna condición (por ejemplo, en el caso
> de los assets que hay en la app de ejemplo, fijándote en el content
> type, o alguna cosa así)
>
> 2009/12/10 Galni <lgalna...@gmail.com>:> Cheee es espantoso pero es working code, me sirve para ir tirando
> > Grupo de Usuarios Ruby del Uruguay -http://groups.google.com/group/ruguy
> > Anular suscripción - ruguy-un...@googlegroups.com
>
>

Diego Algorta

unread,
Dec 12, 2009, 7:29:02 AM12/12/09
to ru...@googlegroups.com
2009/12/11 Galni <lgal...@gmail.com>:
> Gracias por tomarte este trabajito
> Un lujo la verdad

La verdá' ... que si.

Espero que todos los suscritos a esta lista que estén en proceso de
aprendizaje lo aprovechen.

Diego
Reply all
Reply to author
Forward
0 new messages