sábado 18 de julio de 2009

Conectar una aplicación rails a varias bases de datos simultáneamente

Se puede conectar una aplicación rails a varias bases de datos a la vez, eso sí, hay que tener un especial cuidado con el rendimiento de la aplicación, aunque ActiveRecord nos provee una interfaz transparente a la hora de acceder a los modelos.

Suponiendo como ejemplo que tenemos dos modelos (Post y Comment) y este último está localizado en una base de datos externa, se puede configurar rails de la siguiente forma:

1) Primero, creamos un fichero de configuración para la conexión a la nueva base de datos (config/external_database.yml).

development:
adapter: mysql
database: test
username: root
password:

production:
adapter: mysql
database: test
username: root
password:

2) Después, creamos un módulo para encapsular la lectura del fichero y la nueva conexión.

module ExternalDatabase
def self.included(base)
config = YAML.load(File.open('config/external_database.yml'))

if config and ENV['RAILS_ENV']
base.establish_connection(config[ENV['RAILS_ENV']])
else
raise 'External Database not configured'
end
end
end

3) Por último, incluimos en los modelos que van a la base de datos externa el módulo que hemos creado.

class Comment < ActiveRecord::Base
include ExternalDatabase

belongs_to :post
end

Con esto ya podemos utilizar los modelos aunque cada uno esté en una base de datos diferente.

Comment.find(:all, :include => :post)

sábado 11 de julio de 2009

Diferencias de rendimiento entre eval y send

La metaprogramación en ruby permite mucha expresividad y flexibilidad, aún así hace caer bastante el rendimiento.

A pesar de eso, lo más importante es evitar en la medida de lo posible eval ya que es bastante más lento.

Al hacer una llamada a send la máquina virtual de ruby tiene que buscar el nombre del método que se le pasa como parámetro y ejecutarlo.

En cambio, al hacer un eval además de lo anterior la máquina virtual necesita parsear la cadena y después ejecutar el código.


Benchmark.bm{|x| x.report{100000.times{eval('Post.scopes')}}}
user system total real
0.410000 0.000000 0.410000 ( 0.410306)

Benchmark.bm{|x| x.report{100000.times{Post.send('scopes')}}}
user system total real
0.140000 0.000000 0.140000 ( 0.144820)

viernes 26 de junio de 2009

Optimización de consultas en rails

Cuando en rails se hace una consulta a la base de datos sobre una tabla que contiene mucha información, no todo el tiempo se pierde en la consulta. Una parte importante del tiempo se pierde en la creación de cada objeto ActiveRecord.

Usando directamente el método select_all de la conexión ActiveRecord devolverá un array de hash, ahorrando el tiempo de construcción de objetos.

Benchmark.bm{|x| x.report{100.times{BigTable.find(:all)}}}
user system total real
10.090000 2.090000 12.180000 ( 19.199150)

Benchmark.bmbm{|x| x.report{100.times{BigTable.connection.select_all('select * from big_table')}}}
user system total real
7.350000 2.080000 9.430000 ( 16.646406)

En general, no es buena idea guardar todos los elementos de una tabla grande en un array, pero aún seleccionando tan solo el índice se obtiene una mejora sustancial.

Benchmark.bm{|x| x.report{100.times{BigTable.find(:all, :select => :id)}}}
user system total real
0.610000 0.010000 0.620000 ( 0.707702)

Benchmark.bmbm{|x| x.report{100.times{BigTable.connection.select_all('select id from big_table')}}}
user system total real
0.080000 0.000000 0.080000 ( 0.161878)

sábado 20 de junio de 2009

Crear extensiones de ruby en c

Crear extensiones en C del lenguaje ruby es bastante sencillo.

En primer lugar hay que crear un fichero para extconf, este nos generará el fichero makefile de forma automática.

En el fichero extconf.rb debería haber algo como:

#!/usr/bin/env ruby

require 'mkmf'
create_makefile("ruby1")

Donde ruby1 es el nombre de la extensión.

En segundo lugar hay que crear la extensión en código C, simplemente hay que incluir la cabecera ruby.h.

La función Init_ruby1 es la que se ejecutará al importar la extensión desde código ruby.

#include "ruby.h"

static VALUE c_printf(VALUE self, VALUE anObject){
printf("%s\n", STR2CSTR(anObject));
return Qnil;
}

static VALUE c_init(VALUE self){
return self;
}


VALUE FromC;

void Init_ruby1() {
rb_define_global_function("printfc", c_printf, 1);
FromC = rb_define_class("FromC", rb_cObject);
rb_define_method(FromC, "initialize", c_init, 0);
rb_define_method(FromC, "printfc", c_printf, 1);
}

En este ejemplo se define una función global printfc, se define la clase FromC, el método de instancia printfc de esa clase.

Para compilar e instalar este código hay que ejecutar ruby extconf.rb, make y make install.

Ya deberíamos tener disponible la librería ruby1, al ejecutar require 'ruby1' podremos ejecutar las funciones de nuestro código en C.

Hay una gran cantidad de funciones disponibles para crear extensiones en la siguiente dirección: http://www.rubycentral.com/book/ext_ruby.html

miércoles 3 de junio de 2009

Definición de named_scopes dependientes del tiempo actual

Si tenemos una clase ActiveRecord como la siguiente:

class Post < ActiveRecord::Base
named_scope :test, :conditions => ['updated_at < ?', Time.now]
end

En modo development funciona sin problemas, sin embargo en modo production la consulta que se genera no es la esperada.

Al arrancar rails en modo production se cachean las clases, al cargar este modelo en concreto se evalúa Time.now haciendo que para todas las consultas se coja como fecha la hora en la que se ha cargado el modelo.

El descrito anteriormente no es el comportamiento que esperaríamos al definir el named_scope, sino que debería obtener la fecha y hora actual para cada consulta, no en la definición.

Revisando la definición de los named_scopes nos encontramos lo siguiente:

def named_scope(name, options = {}, &block)
name = name.to_sym
scopes[name] = lambda do |parent_scope, *args|
Scope.new(parent_scope, case options
when Hash
options
when Proc
options.call(*args)
end, &block)
end
(class << self; self end).instance_eval do
define_method name do |*args|
scopes[name].call(self, *args)
end
end
end


En nuestro caso pasamos un hash como opciones, por lo que efectivamente se evaluará el Time.now solo en la definición de dicho hash.

Si en lugar de eso definimos un objeto de tipo Proc se evaluará cada vez.

Por lo tanto para arreglar el comportamiento del primer ejemplo deberíamos escribirlo como:

class Post < ActiveRecord::Base
named_scope :test, lambda{{:conditions => ['updated_at < ?', Time.now]}}
end

domingo 10 de mayo de 2009

Aplicación de consola para twitter

Acabo de lanzar un proyecto en github para interaccionar con twitter desde consola.

Por ahora incluye la funcionalidad básica: cambio de estado, consulta de estado de amigos, login y mensajería.

La interfaz de comandos es muy similar al clásico IRC.

La url del proyecto es la siguiente: twit

viernes 1 de mayo de 2009

Añadiendo estado a los elementos del DOM

En aplicaciones complejas con javascript para guardar el estado de cada elemento se suele modificar alguna propiedad del elemento que queramos manipular.

Sin embargo esta práctica es poco adecuada y no demasiado flexible, ya que no podemos guardar tipos de datos complejos.

jQuery ya prevee que podemos necesitar esta funcionalidad y nos proporciona la función data para guardar información de cada elemento.

Por ejemplo:

$('a').data('activated_links', false);
....
var footer_links = $('.footer_links');
if(!footer_links.data('activated_links')){
footer_links.data('activated_links', true);
...
}
...