Active Record fue descrito por Martin Fowler en su libro Patterns of Enterprise Application Architecture.
Un objeto transporta tanto datos como comportamiento. Gran parte de estos datos son persistentes y deben almacenarse en una base de datos. Active Record utiliza el enfoque más obvio, poniendo la lógica de acceso a los datos en el objeto de dominio.
En otras palabras, los objetos debe incluir funciones como por ejemplo insertar (CREATE), leer(READ), actualizar (UPDATE), eliminar (DELETE) y propiedades que correspondan de cierta manera directamente a las columnas de la base de datos asociada.
Este patrón de persistencia es quizás uno de los más habituales y es usado por frameworks como Rails(active record) o Laravel(eloquent).
En este post no vamos a usar una base de datos real, si no archivos JSON para mostrarte lo increíble que es el patron Active Record y para ello usaremos como lenguage de programación al gran y confiable ruby.
Iniciar el proyecto
Los archivos iniciales que vamos a necesitar para el proyecto son:
- Movie.rb
 - main.rb
 - movies.json
 
Primero vamos a crear un archivo ruby Movie.rb y un archivo json movies.json en donde almacenaremos nuestras películas(este archivo inicialmente solo tiene un [] como contenido).
En Movie.rb creamos una clase(que se comportará como un modelo), los atributos propios de Movie y unos cuantos métodos que nos permitiran acceder a nuestro archivo movies.json. Tambien importamos la librería json para poder parsear archivos json en hash y guardar jsons a partir de hashs :
require 'json'
class Movie
  @@file = 'movies.json'
  attr_accessor :id, :name, :description, :date, :genders
  def initialize(id = "", name = "", description = "", date = "", genders = [] )
    @id = id
    @name = name
    @description = description
    @date = date
    @genders = genders
  end
  def self.read
    JSON.parse(File.read(@@file))
  end
  def self.save_data_to_json(data)
    File.write(@@file, data)
  end
end
Nota: Todos los métodos que comienzen con la palabra
selfson métodos estáticos
Ahora en nuestra clase Movie vamos a crear un metodo que guarde los atributos del objeto(CREATE)
  def save()
    movies = Movie.read_json
    movies << ({
      name: @name,
      description: @description,
      date: @date,
      genders: @genders,
      id: (Time.now.to_f.round(2) * 100).to_i # generate fake id
    })
    Movie.save_data_to_json(movies.to_json)
  end
Nota: Cuando queremos usar un método estático dentro de un metodo normal, debemos hacer referencia al metodo con el nombre de la clase
Moviey no conself
Ahora necesitamos probar nuestro modelo, para ello creamos un archivo main.rb en la misma carpeta:
require_relative 'Movie'
movie = Movie.new
movie.name = "La chica que saltaba a traves del tiempo"
movie.description = "Una adolescente intenta usar a su favor su nueva capacidad para viajar en el tiempo"
movie.date = "09/12/2006"
movie.genders = ["drama", "seinen", "slice of life"]
movie.save()  # save object in movies.json
movie = Movie.new
movie.name = "El origen"
movie.description = "Esta película es una version corta de la película Paprika"
movie.date = "28/07/2010"
movie.genders = ["drama", "suspence", "acción"]
movie.save()
Si revisamos nuestro archivo movies.json debemos ver los objecto guardados.
Ahora en Movie.rb agregamos un método estático llamado all(READ) que nos retornará un array de objetos del tipo Movie:
  def self.all
    json_movies = self.read_json
    json_movies.map do |movie|
      self.new(
        movie["id"],
        movie["name"],
        movie["description"],
        movie["date"],
        movie["genders"]
      )
    end
  end
Usamos un metodo estático para poder llamarlo directamente desde la clase Movie y no crear un objecto de clase cada vez que necesitamos el metodo all
Ahora probamos el metodo all en main.rb para que nos liste todas las películas que han sido guardadas:
require_relative 'Movie'
movies = Movie.all
puts movies.inspect
Ahora vamos a crear un metodo estático que nos devuelva una película por un identificador unico(id) de nuestro archivo movies.json
  def self.find(id)
    movies = self.read_json
    movie = movies.detect{ |movie| movie["id"] == id}
    return nil if movie.nil?
    self.new(
      movie["id"],
      movie["name"],
      movie["description"],
      movie["date"],
      movie["genders"]
    )
  end
Ahora probamos el metodo find en main.rb, usamos un id de cualquier película que ha sido guardada:
require_relative 'Movie'
movie = Movie.find(155395102921)
puts movie.name
puts movie.description
Ahora procedemos a crear un metodo que nos permita actualizar a los registros guardados, para esto solo modificaremos nuestro metodo save:
  def save
    movies = Movie.read_json
    if @id == ""
      movies << ({
        name: @name,
        description: @description,
        date: @date,
        genders: @genders,
        id: (Time.now.to_f.round(2) * 100).to_i # generate fake unique id
      })
    else
      movies = movies.map do |movie|
        movie["id"] == @id ?
          {
            name: @name,
            description: @description,
            date: @date,
            genders: @genders,
            id: @id
          } : movie
      end
    end
    Movie.save_data_to_json(movies.to_json)
  end
Ahora probamos la actualizacion de un registro, primero usamos un id de cualquier película para encontrar a la película y después actualizamos sus atributos:
require_relative 'Movie'
movie = Movie.find(155395102921)
movie.name = "The best title"
movie.description = "Description update"
movie.save() # update movie
Por último vamos a implementar un metodo que nos permita eliminar(DELETE) registros:
  def self.delete(id)
    movies = self.read_json
    movies = movies.select{ |movie| movie["id"] != id}
    Movie.save_data_to_json(movies.to_json)
  end
Ahora probamos el metodo delete en main.rb, usamos un id de cualquier película que ha sido guardada:
require_relative 'Movie'
Movie.delete(155395357010)
puts Movie.all.inspect
Ahora somos capaces de eliminar cualquier película de nuestros registros.
Antes de terminar vamos a refactorizar todo nuestro modelo Movie aplicando el principio DRY:
require 'json'
class Movie
  @@file = 'movies.json'
  attr_accessor :id, :name, :description, :date, :genders
  def initialize(id = "", name = "", description = "", date = "", genders = [] )
    @id = id
    @name = name
    @description = description
    @date = date
    @genders = genders
  end
  def self.read_json
    JSON.parse(File.read(@@file))
  end
  def self.save_data_to_json(data)
    File.write(@@file, data)
  end
  def save
    movies = Movie.read_json
    if @id == ""
      @id = (Time.now.to_f.round(2) * 100).to_i # generate fake id
      movies << Movie.to_hash(self)
    else
      movies = movies.map do |movie|
        movie["id"] == @id ? Movie.to_hash(self) : movie
      end
    end
    Movie.save_data_to_json(movies.to_json)
  end
  def self.all
    json_movies = self.read_json
    json_movies.map do |movie|
      self.to_class(movie)
    end
  end
  def self.find(id)
    movies = self.read_json
    movie = movies.detect{ |movie| movie["id"] == id}
    return nil if movie.nil?
    self.to_class(movie)
  end
  def self.delete(id)
    movies = self.read_json
    movies = movies.select{ |movie| movie["id"] != id}
    Movie.save_data_to_json(movies.to_json)
  end
  def self.to_class(hash = {})
    self.new(hash["id"], hash["name"], hash["description"], hash["date"], hash["genders"])
  end
  def self.to_hash(movie)
    { id: movie.id, name: movie.name, description: movie.description, date: movie.date, genders: movie.genders}
  end
end
Como vemos agregamos dos métodos staticos to_class y to_hash porque ambas funcionalidades se repetian mas de unas vez en nuestro código, ahora tenemos un código reducido y mucho mas simple de leer.
Palabras finales
Existen varios patrones para la persistencia de datos, pero active record destaca por su facilidad de uso. Al usar active record nos aseguramos que nuestra aplicación quede completamente aislada del trabajo con SQL o como en este caso a la manipulación directa de los archivos de nuestra data.