active_record ( Ruby ) sin Rails - Parte I
Saturday, 28 April , 2007
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á….

Leave a Reply