Active Record

Visita este artí­culo en http://www.estadobeta.com/2006/05/02/active-record/

Por Ismael en Patrones de diseño, Ruby & Rails, artículos, educación

Descripción de Active Record y ejemplos de implementación y uso.

De todos los Patrones de Diseño, Active Record es probablemente el que recibe más atención recientemente.

Domain Model

Active Record es una extensión del patrón Domain Model (”Modelo de Dominio”), que se entiende como una clase o un grupo de clases que representan a “objetos” o responsabilidades particulares en la aplicación. Para una aplicación que despliega un catálogo de productos, por ejemplo, cada producto podría ser una instancia de la clase “Producto” (el catálogo mismo podría ser, a su vez, un objeto que contiene muchos objetos “Producto”).

Frecuentemente, una implementación de Domain Model representa los datos contenidos en una o más tablas de una base de datos. Por ejemplo, para una tabla que contiene productos en un catálogo (representada aquí gráficamente):

id precio nombre descripción
1 180.000 iPod Nano reproductor portátil de sonido
2 40.000 Adidas Chile 62 zapatillas deportivas
3 450.000 FujiPix F601Z cámara fotográfica digital

podemos escribir una clase que represente a cada producto y sus atributos*1 (Domain Model):


class Producto {
  attributo id
  attributo precio
  attributo nombre
  attributo descripción
}

Esta sencilla clase nos permitiría disponer de los productos en nuestra aplicación y acceder a sus atibutos:


ipod = new Producto //la creación de estos objetos es comúnmente manejada por otros patrones como Factory.
precio = ipod.precio
nombre = ipod.nombre
...

Active Record: Domain Model con vitaminas

Pues bien, Active Record abstrae aún más la base de datos al agregar a Domain Model la lógica necesaria para manipular esos datos. En el ejemplo:


ipod = Producto.find(1)
ipod.precio = 300.000
ipod.save()

vemos que ahora las instancias de Producto incluyen el método find() (que permite encontrar un producto por su ID en la base de datos) y el método save(), que actualiza los datos correspondientes a ese producto en la base de datos. Estos métodos comúnmente generan el SQL necesario e incluyen las utilidades para conectarse e interactuar con la base de datos. Así mismo, una implementación de Active Record puede tener utilidades que nos faciliten el trabajo en aplicaciones concretas. Imaginemos, por ejemplo, que de cada producto necesitamos obtener el precio + IVA (impuesto de 19% aquí en Chile). Para esto podemos agregar el método adecuado a nuestra clase Producto.


class Producto hereda de activeRecord {
  attributo id
  attributo precio
  attributo nombre
  attributo descripción
 
  metodo getPrecioConIva(){
    return (this.precio * 1.19)
  }
}

y creamos un objeto de clase Producto del que podemos obtener el valor total cómodamente:

ipod = Producto.find(34)
precio_con_iva = ipod.getPrecioConIva()

getPrecioConIva() se encarga de calcular el valor internamente, dejando el código cliente mínimo y claro. De esta forma se pueden encapsular operaciones mucho más sofisticadas sobre los datos tras una interfaz sencilla y fácil de usar.

ORM

Pero ¿ qué pasa con las tablas relacionadas? Normalmente un Modelo de Dominio estará compuesto por datos alojados en varias tablas de una base de datos relacional. Imaginemos que nuestros Productos pertenecen a una o más Categorías. Veamos la tabla “categorias” en nuestra base de datos.

id nombre descripción
1 linea blanca artículos de línea blanca
2 audio artículos de audio, música
3 ropa artículos de vestir

Un Producto puede pertenecer a una o más Categorías. Así mismo, cada categoría puede tener uno o más productos. Este tipo de relaciones se suelen articular por medio de una tabla intermedia:

Tabla categorias_productos

categoria_id producto_id
2 1
3 2

Ok. Las cosas se complican. Necesitamos 3 tablas en la base de datos para expresar la relación entre Productos y Categorías. Asumiendo que hemos creado una clase categoria similar a Producto para encapsular los registros de la tabla categorias, surge el problema de describir en nuestras clases la interdependencia entre productos y categorías.

Aquí es donde comienza a relucir el patrón Active Record. Una de las virtudes del patrón es representar de forma Orientada a Objetos los datos de una Base de Datos Relacional - modelo conocido también como ORM o “Object-Relational Mapping” - , definiendo interfaces sencillas para acceder y manipular esos datos.

Instancia de Producto con atributo categorias (una colección de objetos Categoria):


ipod = Producto.find(1)
categorias = ipod.categorias

En este otro ejemplo, buscamos todos los Productos pertenecientes a la categoría “audio” (id 2).


categoria = Categoria.find(2)
 
productos = categoria.productos

En la mayor parte de las implementaciones, Active Record se encarga internamente de recoger los objetos relacionados del modelo de datos, definiendo las colecciones de objetos dependientes automáticamente. La implementación del framework Ruby on Rails es especialmente interesante. En ella, el nombre de la clase se “mapea” directamente al nombre de la tabla en la base de datos*2, y en la definición de la clase se declaran los objetos dependientes, que a su vez encuentran sus tablas correspondientes de forma transparente:


# definición de la clase
class Producto < ActiveRecord::Base
  has_and_belongs_to_many :categorias
end
 
#código cliente de ejemplo
ipod = Producto.find_by_nombre("ipod")
 
#Creamos una nueva categoría y la agregamos al producto Ipod
nueva_categoria = Categoria.create("computación","Artículos informáticos")
ipod.categorias.add(nueva_categoria)
 
#actualizamos el producto en la base de datos.
#Esto creará la relación correpondiente en la tabla categorias_productos.
ipod.save()

La línea has_and_belongs_to_many :categorias declara el tipo de relación entre Productos y Categorias. Al momento de instanciar un objeto, Active Record asumirá que existe una tabla intermedia categorias_productos (en orden alfabético) y la usará para encontrar los registros dependientes en la tabla categorias, instanciando un objeto de clase Categoria para cada uno y agregándolos a la coleción Producto.categorias. La implementación de Active Record de Ruby on Rails también incluye declaraciones como has_many, belongs_to y has_one que expresan los tipode de relaciones entre tablas más comunes en el diseño de aplicaciones.

Observaciones

Active Record enfenta el problema de los ORM en forma pragmática al abstraer enormemente la complicada transición entre datos Relacionales y Orientados a Objetos. Metodologías más tradicionales prefieren mayor control sobre esta transición y para ello existen patrones como Data Gateway o Data Mapper. Active Record sobresale para resolver operaciones comunes sobre bases de datos pero puede legar a ser demasiado dependiente de la fuente de datos. Muchos critican que, al modelar en la lógica de aplicación las dependencias entre tablas, Active Record se arroga responsabilidades que son mejor gestionadas por la base de datos. David Heinemeyer Hansson, creador de Ruby on rails, tiene un artículo muy interesante sobre su postura al respecto en cuyos comentarios se desarrolla una buena discusión sobre el tema.

Links

*1 Los ejemplos están escritos en pseudo código y su función es ilustrar los conceptos expuestos.

*2 Ruby on Rails se basa por completo en la premisa de “Convención por sobre Configuración”, usando convenciones de nombres y Reflexión de clases para automatizar el desarrollo de aplicaciones lo más posible.

ruby on rails, design patterns, active record, domain model

5 comentarios para “Active Record”

  1. GravatarEstadoBeta » Archivo » Feliz Cumpleaños Ruby on Rails! Dice:

    […] Ingenieros del mundo: denle una oportunidad a Rails (o por lo menos a las ideas que lo sustentan). De todas maneras nada puede ser peor que lidiar con ASP cada dia. […]

  2. GravatarGerardo Dice:

    No me podia quedar callado y lo tengo que decir… buen artículo. Por cierto para todos aquellos que quieren probar algo similar a Ruby on Rails pero para el querido y muchas veces menospreciado PHP les recomiendo ampliamente CakePHP

  3. GravatargameXs Dice:

    Interesante artículo, pero tengo una duda en este pedazo de código:

    categoria = Categoria.find(2)
    productos = categoria.productos

    Como hago para obtener la lista productos ordenada por algún atributo??

    Gracias.

  4. GravatarIsmael Dice:

    gameX:
    Puedes ordenar la colección de resultados con Ruby

    productos = categoria.productos.sort{|a,b| a.nombre < => b.nombre }
    

    pero también puedes agregar SQL al “finder”

    productos = categoria.productos.find(:all,:order=>'nombre')
    

    En realidad, lo mejor que puedes hacer es definir el orden en la asociación misma:

    
    class Categoria < ActiveRecord::Base
        has_many :productos, :order=>‘nombre’
    end
    
    

    Hay mucho más en la documentación de ActiveRecord.

  5. GravatargameXs Dice:

    Muchas gracias Ismael. :)