Hola a todos.
A lo mejor es una pregunta tonta, pero no tengo muy claro cómo se reflejaría en las tablas de la base de datos una clase abstracta con subclases concretas, por ejemplo. Es que por lo que veo en el lilbro, la forma de trabajar es primero montar las tablas y luego trabajar con los modelos adaptándolos a esas tablas, y las relaciones más complejas entre clases no acabo de ver cómo se representan.
Estaba pensando, entre otras cosas, en lo de las clases abstractas y en aplicar el
patrón estado para guardar información sobre un pedido (nuevo, empaquetado, enviado, recibido...).
Muchas gracias.
Comentarios
Exitos...
Seguramente hay una forma habitual de hacerlo, ahora no tengo tiempo, pero luego googlearé un poco.
Bye!
<!-- m --><a class="postlink" href="http://www.abelgonzalez.com/entornosabiertos/modelando-subtipos-en-una-base-de-datos-relacional_p171.html">http://www.abelgonzalez.com/entornosabi ... _p171.html</a><!-- m -->
Pero si alguien puede poner un ejemplo práctico o explicar si el resultado es el esperado en Kumbia, se lo agradecería.
Gracias.
Puedes usar los metodos has_many, has_one, belongs_to para definir las asociaciones tambien.
Supongamos que tenemos una empresa con empleados de dos tipos: currantes y directivos, y cada uno de ellos tiene su sueldo, que se calcula de distinta forma: los currantes tienen un sueldo base (supondremos que cada currante tiene el suyo, dependiendo del tiempo que lleve en la empresa o lo que sea) más las horas extra que haga, mientras que un directivo tendrá un sueldo base (sustancialmente mayor que el del currante) más un "plus de egoísmo".
El modelo de clases en el que había pensado es el siguiente:
EMPLEADO (clase abstracta)
-nombre
-sueldo_base
+calcular_sueldo()
#calcular_sobresueldo() --> Operación abstracta o bien devuelve 0
CURRANTE (clase concreta)
-horas_extra
-precio_hora_extra
-calcular_sobresueldo() --> Redefine la operación de la superclase devolviendo importe horas extra
DIRECTIVO (clase concreta)
-plus_egoismo
-calcular_sobresueldo() --> Redefine la operación de la superclase devolviendo plus de egoismo
La función calcular_sueldo() de Empleado devuelve la suma del sueldo base más lo que devuelva la función calcular_sobresueldo(), que en la clase Empleado puede ser abstracta o bien devolver 0.
La función calcular_sobresueldo() de Currante devuelve el número de horas extra multiplicado por el precio de la hora extra, mientras que en la clase Directivo devuelve el plus de egoísmo.
Hasta ahí no es más que un ejemplo sencillo de herencia y sobrecarga de funciones. El problema viene al intentar hacer las tablas para guardar esa información. Esto es lo que he hecho:
empleado
id: int (clave)
nombre: char
sueldo_base: float
currante
horas_extra: int
precio_hora_extra: float
empleado_id: int (clave foránea)
directivo
plus_egoismo: float
empleado_id: int (clave foránea)
Me he encontrado con varios problemas:
* Parece que la clase abstracta no le ha gustado mucho a Kumbia, que me devuelve un error cuando intenta instanciarla en kumbia.php:110
[/list:u]* No consigo acceder al atributo nombre de Empleado desde Directivo o Currante (probablemente porque no he definido correctamente las tablas o me he dejado algo importante en los modelos que indique la herencia)[/list:u]
* Cuando en una vista intento mostrar información sobre los empleados sólo consigo acceder a los datos de Empleado, el cálculo de los sobresueldos no se llega a hacer. Claro que parece lógico, porque accediendo a la información de la tabla empleado no doy ninguna instrucción para que se acceda también a las tablas directivo o currante[/list:u]
Veo más o menos dónde están los errores, pero no encuentro la forma correcta de hacer esto en Kumbia. En el libro tampoco he encontrado ninguna referencia a clases abstractas ni a herencia aparte de los extends de ActiveRecord, ApplicationController...
Perdón por el rollo y gracias por la ayuda.
<!-- m --><a class="postlink" href="http://wiki.rubyonrails.org/rails/pages/Inheritance">http://wiki.rubyonrails.org/rails/pages/Inheritance</a><!-- m -->
<!-- m --><a class="postlink" href="http://wiki.rubyonrails.org/rails/pages/SingleTableInheritance">http://wiki.rubyonrails.org/rails/pages ... nheritance</a><!-- m -->
<!-- m --><a class="postlink" href="http://www.patchedsoftware.com/RailsEnvy-ActiveRecord.mov">http://www.patchedsoftware.com/RailsEnv ... Record.mov</a><!-- m --> (aproximadamente a las tres cuartas partes del vídeo hablan de relaciones polimórficas entre tablas)
¿Me equivoco o en Kumbia no hay implementada por ahora una solución a ese problema?
Gracias
El controlador "empleados_controller.php": El modelo "empleado.php": El modelo "directivo.php": El modelo "currante.php": Y la vista "empleados/sueldo.phtml":
Está claro que esto no funciona, en parte porque en ningún sitio le indico qué campos de la tabla "empleado" son de currante y cuáles de directivo. Pero el error que me da directamente al acceder a <!-- m --><a class="postlink" href="http://localhost/empresa/empleados/sueldo">http://localhost/empresa/empleados/sueldo</a><!-- m --> es el de que no puede instanciar el modelo "empleado" por ser abstracto, kumbia.php:110 (v0.4.6 RC6).
Si quito lo de "abstract" en la clase "empleado", entonces el resultado que obtengo es:
Pepe 0
Juan 0
Ricardo 0
Del mismo modo, al crear un formulario para "empleado" me permite introducir todos sus campos (y los de las subclases), siempre que la clase no sea abstracta, pero al hacer un formulario para "currante" me dice, obviamente, que no existe la tabla "currante" en la base de datos.
¿Cómo puedo usar clases abstractas, subclases y polimorfismo?
Gracias.
He colocado todas las clases en el mismo archivo models/empleado.php:
No he probado si se puede colocar en varios archivos, supongo que no habria problema:
Hasta aqui todo bien, la propiedad source le indica que trabaja con la tabla empleado, sin embargo ActiveRecord inicialmente no toma en cuenta source sino que da prioridad a el nombre de la clase. Ahora arreglamos esto en forms/db/active_record.php en el metodo model_name:
Con esto funcionaria,
Saludos[/code]
Yo creo que lo que hace falta es que Kumbia dé un soporte nativo para las clases abstractas y el polimorfismo, parecido a lo que hace Rails con el campo "type" de las tablas de la bd. No digo que sea fácil, pero yo lo pondría entre las prioridades, todo lo relacionado con la orientación a objetos es básico para un framework de PHP5. Me ofrecería para hacerlo, pero la verdad es que no tengo el nivel suficiente de php. <!-- s:oops: --><img src="{SMILIES_PATH}/icon_redface.gif" alt=":oops:" title="Embarassed" /><!-- s:oops: -->
Pepe 0
Juan 0
Ricardo 0
Es decir, que no ejecuta la función polimórficamente en las subclases sino que para todas ejecuta la de la superclase, que devuelve 0.
Aqui puedes utilizar un;
Gracias
Gracias por el tiempo, Andrés, de verdad. No acabo de entender lo de
De todos modos, puedo crear un Currante haciendo esto:
Eso funciona, aunque no me parece la mejor forma de crear una subclase, ya que ahí podría añadir tranquilamente atributos de Directivo junto con los de Currante. Lo lógico sería poder hacer new Currante() y que Kumbia crease un nuevo registro en la tabla Empleado con type="currante". No sé cómo se podría diferenciar los atributos que son de Currante, de Directivo y de Empleado para que funcionara el ActiveRecord correctamente e incluso para que el scaffold permitiera mostrar los atributos comunes de Empleado y los propios de cada subclase por separado.
El problema ahora mismo es que no puedo hacer cosas como:
porque no existe la tabla currante. Pero si lo dejo como:
Entonces al hacer en la vista:
ejecuta siempre estas dos funciones de Empleado:
Nunca se ejecuta el calcular_sobresueldo() de la clase Currante, que es lo que me interesaría.
Gracias de nuevo.
Probablemente con el $this->Currante no funcione si no colocas una a una las clases en archivos por separado en models/
De otro lado en PHP se pueden crear objetos dinamicamente usando el nombre de clase en alguna variable asi como estaba en el ejemplo de ruby on rails.
Si tipo es igual al nombre de la clase entonces creara el objeto indicado.
Saludos
Y lo de hacer lo entiendo pero no sé de qué sirve. Es decir, está bien para crear un objeto del mismo tipo que otro, pero el problema ahora mismo no es descubrir cómo se llama el tipo, que ya lo sé, sino que me permita crear un objeto de ese tipo, "sabiendo" Kumbia de algún modo que la información de Currante está definida dentro de la tabla de otra clase. Dicho de otro modo, igual que Kumbia identifica los campos llamados "id" y las claves foráneas, debería haber otra palabra reservada, "type", y que Kumbia supiera qué hacer cuando hay un campo con ese nombre, es decir, buscar un modelo con ese nombre y crear un objeto de esa clase.
Los pasos para crear un objeto de una subclase serían algo así: cuando se haga new Currante(), Kumbia ve a través del "extends" que Currante es una subclase de otro modelo llamado Empleado; luego crea una instancia php de la clase Currante y al hacer un save() debería añadir un registro a la tabla empleado que contenga type="currante". Y por supuesto, debería permitirme hacer esto aunque la clase Empleado fuese abstracta.
El principal problema estaría en saber indicarle qué campos de la tabla "empleado" son comunes para todos los empleados o propios de currante o directivo, porque yo podría hacer cuando el plus por egoísmo no pertenece a currante sino a directivo. Eso no tengo muy claro cómo lo hace Rails.
Saludos!
volvamos a <!-- m --><a class="postlink" href="http://wiki.rubyonrails.org/rails/pages/SingleTableInheritance">http://wiki.rubyonrails.org/rails/pages ... nheritance</a><!-- m -->:
El objeto es creado apartir de un campo cualquiera en este caso "type" que lo usan para internamente saber de que clase concreta es el objeto resultante.
En Ruby se puede cambiar el objeto devuelto en un constuctor por eso, si esperas que:
Sin embargo este codigo es invalido en PHP por lo tanto debes hacerlo:
No es crear un objeto del mismo tipo que otro, es si en $empleado->type es igual a "Currante" entonces $objeto = new $tipo(); creara un objeto de la clase correcta "Currante".
Por lo demas puedes colocar las clases en el mismo archivo models/empleado.php
Saludos
No me he rendido, no. Es que he andado liado.
Creo que voy pillando por dónde vas, lo que pasa es que yo intentaba verlo de otra forma y por eso no entendía lo que me querías decir. Lo que yo quería hacer era un new Currante() y luego un save() para guardar el nuevo objeto en la BD (cosa que no iba a suceder), mientras que, por lo que entiendo, tú lo que haces es recuperar los Empleados guardados para luego crear un objeto dependiendo del tipo y poder trabajar con ese objeto. Pero en ese caso, no sólo tengo que crear el currante (o directivo), sino que tengo que pasarle todos los datos del empleado así, ¿no?
Tengo que decir que eso por fin ha hecho que me funcionara el polimorfismo (gracias!! <!-- s:D --><img src="{SMILIES_PATH}/icon_biggrin.gif" alt=":D" title="Very Happy" /><!-- s:D --> ), pero la verdad es que lo de copiar los campos es bastante feo, y además tendría que empezar a hacer ifs para diferenciar los campos de cada subclase o bien hacer que todos tengan todos los campos, que no sé qué es más feo. Y por otro lado, no puedo luego consultar los currantes haciendo algo como $this->Currante, que sería lo ideal, sino que tengo que añadir dentro del bucle algo como un array:
para poder luego consultarlos.
Pero eso no me resuelve mi duda inicial: ¿y para crearlos? Es decir, ahora tengo un objeto Currante nuevo o modificado y lo tengo que almacenar en la base de datos. ¿Tengo que hacer el proceso inverso? O sea, crear un objeto Empleado, copiarle todos los atributos del Currante y luego hacer un save() del Empleado. No me convence demasiado...
Bueno, te agradezco las explicaciones y el interés. Y déjame que insista en una cosa: ¿no se podría intentar que Kumbia automatizara todo esto de alguna forma, dándole algún tipo de instrucciones para que sepa cuándo una clase es subclase de otra y con el campo "tipo" hacer que almacene las cosas correctamente (incluidas clases abstractas)? Sería un reto, pero le daría más poder a Kumbia.
Saludos a todos y gracias de nuevo!
Voy a trabajar en ello para 0.5
Saludos
En serio, sería un avance muy grande. Pero no lo veo muy fácil de implementar; lo que se gana por un lado se pierde por otro, hay mucha casuística y hay que tomar decisiones de diseño que luego a lo mejor no gustan a todos. Se me ocurren muchas dudas.
Por ejemplo, si tenemos una jerarquía en la que la clase Persona tenga dos subclases, Empleado y Cliente, y que de Empleado a su vez cuelguen Currante y Directivo, ¿todas estas clases deberían compartir la misma tabla Empleado? ¿Y eso sería así para todas las subclases que se deriven de cualquiera de éstas? ¿Cómo se indica que un campo es de la superclase Empleado o bien de una subclase cualquiera perdida por la jerarquía?
A lo mejor sería más fácil tener una tabla distinta para cada una de las subclases y otra con los atributos comunes, y que las demás se relacionen con ésta última con un campo id (caso 3 de esta explicación), pero habría que indicar de alguna forma que no es una relación asociativa sino de herencia. Lo del uso de tablas separadas es más parecido en lógica a la orientación a objetos, pero hay que consultar todas las tablas de la jerarquía de arriba abajo cada vez que se quieran conocer los atributos de una clase (parecido a como se haría a alto nivel), lo cual tampoco es muy deseable si esa jerarquía es muy grande. Pero si se hace todo en una única tabla se desperdiciaría mucho espacio en caso de jerarquías grandes.
Más dudas: ¿cómo se sabe que dos tablas son "hermanas", es decir, que pertenecen a una misma jerarquía y no a dos distintas que cuelguen de la misma clase? ¿Haría falta? ¿Habría que crear un campo para indicar que una clase es abstracta o se haría a nivel de php?
Un posible objetivo en el que se podría pensar es en hacer scaffolding de una clase abstracta, y que cambiando en un select box del formulario el tipo de subclase aparezcan para rellenar los campos de la clase seleccionada. Y si se cambia la selección por otra subclase, aparecen otros campos. Sería ideal para introducir datos de jerarquías. Por ejemplo, en una universidad quieren dar de alta a una persona, y sólo eligiendo con un clic entre Profesor o Estudiante aparecerían los campos específicos a rellenar. Si eso funcionase significaría que todo lo que hay por debajo es correcto.
Está claro que de fácil no tiene nada, pero si puedo echar una mano, estaré encantado.
Saludos!
PD: Ahora que lo pienso, esto debería ir al subforo de nuevas funcionalidades. <!-- s:? --><img src="{SMILIES_PATH}/icon_confused.gif" alt=":?" title="Confused" /><!-- s:? -->
Al ser orientado a objetos, existe herencia, polimorfismo, etc.
Para lograr esto, en Cache no se crean tablas, sino objetos que heredan de una superclase Persistent, la cual indica que este objeto será almacenado en disco (en nuestro caso habría de heredar de ActiveRecord):
Class Persona extends %Persistent{
Nombre As %String;
Apellidos as %String;
Edad as %Integer;
etc
}
Class Estudiante as Persona{
Escuela As %String;
Curso As %String;
etc
}
Pongamos que queremos crear una tabla por ejemplo Estudiante que hereda de Persona.
En Persona tenemos los atributos básicos (nombre, edad, domicilio, etc). La tabla Estudiante tiene los atributos extras (escuela, curso, etc). Al crear la tabla Estudiante, se crea automáticamente un id que referencia a Persona.
O sea, yo creo un estudiante:
estudiante = New Estudiante();
estudiante->nombre="Pepe";
estudiante->edad="23";
estudiante->curso="2º";
etc
Vamos, como cualquier lenguaje OOP.
A la hora de buscar, hay que tener bien definidos los índices tanto para los id de Estudiante, como los de estudiante->persona que contenga.
Me he inventado un poco el pseudocódigo pero creo que queda clara la idea.