Gastón Ramos

Blog personal

← Volver al blog

active_record ( Ruby ) sin Rails - Parte I

Recuperado del archivo original de WordPress.

Actualmente estoy trabajando en un módulo de una aplicación web cuyo objetivo es presentar reportes de accesos a un predio, la aplicación está desarrollada en php y postgresql, los reportes están basados en vistas, dónde tengo una "vista base" llamada "stats_ingresos", y las demás vistas hacen uso de "stats_ingresos". Todo muy lindo, pero actualmente este sistema es muy lento, las consultas a estas vistas demoran más 2 minutos lo cual es inaceptable para una aplicación web, entonces decidí pasar esta vista base a tabla, con lo cual el costo de la consulta se reduce a 0. Obviamente esta solución tiene una desvetaja, tengo que actualizar la tabla "stats_ingresos" perdiódicamente, la actualización se va a realizar una vez por día y se actualizarán todos los accesos del mes (ya sé que esto se puede mejorar). Para ir al grano, he decidido implentar este script de update con ruby + active_record y como metodología bdd usando rspec. Esta es la tabla que va a reemplazar a la "vista base":

CREATE TABLE stats_ingresos (
  id SERIAL NOT NULL,
  nombre VARCHAR(25),
  apellido VARCHAR(25),
  dni VARCHAR(12),
  id_institucion INTEGER,
  institucion VARCHAR(50),
  dia SMALLINT,
  mes SMALLINT,
  anio SMALLINT,
  ts timestamp,
  PRIMARY KEY  (id)
);

Entonces, resumiendo, la idea es reemplazar la vista "stats_ingresos" por una tabla exactamente igual (con los mismos campos), para que no tenga "efectos secundarios" en el código existente, y actualizar esta tabla todos los días. Para esto, voy a necesitar 2 clases una para la tabla "access" y otra para la nueva tabla "stats_ingresos" (la consulta que genera stats_ingresos está basada en la tabla "access")

require 'rubygems'
gem 'activerecord', '>= 1.15.2'
require 'active_record'
require 'yaml'

class Access < ActiveRecord::Base set_table_name "access" set_primary_key "access_id"

ActiveRecord::Base.pluralize_table_names = false Sql = "SELECT per.apellido, per.nombre, acc.pin as dni, soc.descripcion as institucion, soc.id_institucion, date_part('day'::text, acc.access_date) AS dia, date_part('month'::text, acc.access_date) AS mes, date_part('year'::text, acc.access_date) AS anio, acc.access_date AS ts FROM access as acc JOIN persona as per ON acc.pin::numeric = per.numero_documento JOIN socio_institucion as soc ON acc.pin::numeric = soc.numero_documento WHERE acc.pin != '------------' "

def Access.stats_ingresos_view date_cond = '' Access.find_by_sql("#{Sql}" + date_cond) end

def Access.sql_si_view hash_date @cond = " and date_part('year'::text, acc.access_date) = " \ "#{hash_date['anio']}" if not hash_date['anio'].nil? @cond = @cond + " and date_part('month'::text, acc.access_date) = " \ + "#{hash_date['mes']}" if not hash_date['mes'].nil? @cond end

def Access.method_missing(method_name, *args) if method_name.to_s =~ /^stats_ingresos_view/ hash_date = Hash.new hash_date["#{Access.por(method_name.to_s)[0]}"] = args[0] hash_date["#{Access.por(method_name.to_s).pop}"]= args.pop Access.stats_ingresos_view( sql_si_view(hash_date) ) end end

def Access.por str_por s = str_por.split('_por_').last s.split('_y_') end end

El método más importante en la clase anterior es Access.stats_ingresos_view, este hace la consulta en la tabla access y retorna un hash con los registros resultantes, este método toma como parámetro un string con las condiciones de búsqueda por fecha (año y mes), a su vez el método Access.sql_si_view toma como argumento hash con los valores de anio y mes y arma el string necesario para Access.stats_ingresos_view. Por otro lado redefino Access.method_missing para poder llamar a Access.stats_ingresos_view_por_anio_y_mes(2007, 10) y Access.stats_ingresos_view_por_anio(2006) por ejemplo. Estos son los specs para Access:

require '../models/access'

#FIXME -> ver el tema de flexmock

# ------------------ Conexión FIXME -------------------------------------

@config = YAML::load(File.open("../../config/database.yml"))["development"] ActiveRecord::Base.establish_connection( @config )

# -----------------------------------------------------------------------

context "Metodo 'por'" do

specify "Un string con anio y mes" do @arr_con = Access.por 'un_metodo_por_anio_y_mes' @arr_con.should == ['anio', 'mes'] end

specify "Un String con anio solo" do @arr_con = Access.por 'un_metodo_por_anio' @arr_con.should == ['anio'] end

end

context "Busco los ingresos por año" do

setup do `../../test/fixtures/delete_all.sh laptop` `../../test/fixtures/load_all.sh laptop` end

specify "Armo la condición con el anio 2007" do hash_anio_mes = {'anio' => '2007'} @cond = Access.sql_si_view hash_anio_mes @cond.should == " and date_part('year'::text, acc.access_date) = 2007" end

specify "Armo la condición con el anio 2006 y mes 11" do hash_anio_mes = {'anio' => '2006','mes' => '11'} @cond = Access.sql_si_view hash_anio_mes @cond.should == " and date_part('year'::text, acc.access_date) = " + \ "2006 and date_part('month'::text, acc.access_date) = 11" end

specify "Para el año 2007" do hash_anio_mes = {'anio' => '2007'} @accesos_view = Access.stats_ingresos_view( \ Access.sql_si_view(hash_anio_mes)).first @accesos_view.anio.should == '2007' end

specify "Para el año 2006 y mes 11" do hash_anio_mes = {'anio' => '2006','mes' => '10'} @accesos_view = Access.stats_ingresos_view( \ Access.sql_si_view(hash_anio_mes)).first @accesos_view.anio.should == '2006' @accesos_view.mes.should == '10' end

end

context "Busco los ingresos a la ruby-way" do

setup do `../../test/fixtures/delete_all.sh laptop` `../../test/fixtures/load_all.sh laptop` end

specify "Para el año 2007" do @accesos_view = Access.stats_ingresos_view_por_anio('2007').first @accesos_view.anio.should == '2007' end

specify "Para el año 2006 y mes 10" do @accesos_view = Access.stats_ingresos_view_por_anio_y_mes( \ '2006', '10').first @accesos_view.anio.should == '2006' @accesos_view.mes.should == '10'

end

specify "Para el mes 10 y año 2006" do @accesos_view = Access.stats_ingresos_view_por_mes_y_anio( \ '10', '2006').first @accesos_view.anio.should == '2006' @accesos_view.mes.should == '10' end

specify "Llamo a un método inexistente" do @no_existe = Access.esto_no_existe_ingresos_view_por_mes("10") @no_existe.should be_nil end

specify "Busco todos los ingresos" do @si_view = Access.stats_ingresos_view @si_view.length.should == 16 end

end

Esta es la clase para la nueva tabla:

require 'rubygems'
gem 'activerecord', '>= 1.15.2'
require 'active_record'
require 'yaml'

class StatsIngresos < ActiveRecord::Base set_table_name "stats_ingresos"

ActiveRecord::Base.pluralize_table_names = false

def StatsIngresos.update_view arr_view @insert_count = 0

arr_view.each do |@ing| @stat_ingresos = StatsIngresos.new @insert_count = @insert_count + 1 \ if @stat_ingresos.update_attributes @ing.attributes end

@insert_count end

end

Esto todo por ahora, se aceptan sugerencias y comentarios, continuará....

También puede interesarte

Los comentarios históricos se conservan como archivo. Para conversar hoy, podés escribirme.