Closed nandosola closed 10 years ago
The parametrization has to do with how class hierarchies are stored, not with how roots are stored. For example:
class User < BaseEntity
children :computer
...
end
class AdminUser < User
...
end
class Computer < BaseEntity
parent :user
...
end
class Laptop < Computer
...
end
service.inheritance_mappers(
:BaseEntity => :class,
:User => :single
)
# Computer and Laptop are persisted using Class-Table Inheritance (the default)
# User and AdminUser are persisted using Single-Table Inheritance
service.repositories(
:BaseEntity => :pg_sql,
:User => :pg_json
)
# Computer and Laptop are persisted in pg_sql (the default)
# User and AdminUser are persisted in pg_json
Another GOTCHA here would be mapping the PKs to Integer
(RDBMS), Strings
(Mongo) or Ruby's Object#object_id
. Be careful with that. Oh! And what about composite PKs?
Anyhow, IMHO we could deal with this later....
When implementing the feature, please make sure Version
is dealt with as an Active Record object so that all locking behavior is confined in a single object (no mapper, no repository)
We should translate this and put it in the Wiki:
Por ejemplo:
class Person < Dilithium::BaseEntity
attribute :name, String
attribute :email, String
end
class User < Person
attribute :password, String
end
class Admin < User
attribute :organization, String
end
Leaf-table inheritance: Cada clase tiene todos sus datos en una tabla (que es lo que había antes de éste último cambio). Cada subclase tiene su propia secuencia de id's. En este caso tendríamos que tener las siguientes tablas:
persons: id, _version, active, name, email users: id, _version, active, name, email, password admins: id, _version, active, name, email, password, organization
Class-table inheritance: Cada clase tiene una tabla, con los datos comunes en la tabla de la superclase. Existe una única secuencia de id's para toda la jerarquía de clases. Las tablas serían las siguientes:
persons: id, _version, _type, active, name, email users: id, password admins: id, organization
En la tabla que representa la raíz de la jerarquía de herencia se agrega la columna _type que > contiene el nombre de la tabla que corresponde a la clase real del objeto. Por ejemplo, si un usuario es de tipo Admin, en la tabla persons la columna _type contendrá "admins". Es una columna de tipo varchar.
Para configurar el tipo de herencia, la issue https://github.com/nandosola/dilithium-rb/issues/40 pone algunas cosas a futuro y otras que fueron propuestas en su momento pero que han tenido ligeros cambios. Mejor mirar el ejemplo en spec/spec_base.rb. Queda algo así:
Dilithium::PersistenceService.configure do |config|
:'Dilithium::BaseEntity' => :leaf # Este será el default
:Person => :class
end
Como pone en el ejemplo, el tipo de herencia por defecto se configura para Dilithium::BaseEntity > (no existe un default, se tiene que configurar explícitamente). Sólo se permite configurar la herencia de subclases inmediatas de BaseEntity (es decir, no podemos, por ejemplo, dejar User con el default y configurar Person como :class, o configurar User como :class y luego Person como :leaf).
- Los finders
Los métodos fetch_by_id y fetch_all funcionan correctamente con herencia de tipo :class (de forma polimórfica), de forma que si hago un Person.fetch_by_id(3) y el tipo real del objeto es Admin, me devolverá un objeto de tipo Admin. Lo que hace por debajo es, primero buscar la tabla que corresponde a la raíz del árbol de herencia, obtener de ahí el tipo del objeto (mediante la columna _type) para saber qué tablas lo componen, y luego hace un join entre todas las tablas. En el caso de herencia de tipo :leaf, la herencia no es polimórfica. Para el caso de finders específicos, ya no vale nada más buscar en la tabla correspondiente (ya que se tiene que hacer un merge de todas las tablas de la jerarquía).
Se debe poner el finder en la clase donde se define un atributo. Por ejemplo, si quiero hacer un fetch_by_email, se debe definir en la clase User, no en la clase Person (ya que Person no tiene atributo email). Por ahora, hay que acceder a la tabla, obtener solamente la lista de id's, y luego hacer un fetch_by_id de cada resultado. Esto habrá que cambiarlo luego, hay que rehacer toda la estructura de los Finders.
module Dilithium::Repository
module Sequel
module UserCustomFinders
def fetch_by_email email
id_list = DB[:user].where(email: email).where(active:true).select_map(:id)
id_list.map do |id|
User.fetch_by_id(id)
end
end
end
end
end
Depends on #41
Proposed configuration from an API client:
No namespace-resolution is performed. The symbols are translated to class names at runtime. Please observe, that the only repository where inheritance mapping makes sense is a RDBMS (ie. Postgres) or a JSON-like repository (ie. MongoDB) since the :memory repository would have no persistence at all.
The Mapper uses a Strategy pattern for mapping entitites/attributes to tables. We would have an internal registry that maps inheritance roots to persistence types (which DB, which type of persistence, etc.). Registry keys have to be symbols, not classes, since all entity classes might not be loaded at configuration time.
One Strategy per inheritance hierarchy. We will not allow redefining per subclass (except for direct subclasses of BaseEntity). The default type of inheritance will be that assigned to BaseEntity.
Currently the Transaction constructor receives the Mapper instance. Probably change all references to @mapper.foo in the UoW to @mapper.for(klazz).foo
Finder classes (Repository) must inherit from parent class' finder classes but must redefine methods to get the correct class. Perhaps rethink the way Finders are implemented. (see #41)
We will probably need a type attribute even with CTI to make polymorphic finders work without having to read all tables in the inheritance hierarchy.
Careful with intermediate table names!