Thinking on hiring me?

Please read

Fernando Guillén

a Freelance Web Developer

cabecera decorativa

software development as an artistic expression

Archive for the ‘how to’ Category

Lunes, Agosto 11th, 2008

Ruby on Rails: definir el tamaño de un índice MySQL en las migraciones

Revisando el log de tus queries te puedes encontrar que si estás buscando registros por un campo VARCHAR te salte un warning de “long key length“.

Esto es porque el índice que has puesto en este campo VARCHAR es muy grande, aunque MySQL soporta índices de hasta 1000 bytes si son muy grandes pueden ralentizar las búsquedas.

La solución está en limitar el tamaño del índice a un número bastante pequeño.

En MySQL esto se indica así:

ALTER TABLE mi_tabla ADD INDEX ( campo_del_indice(4) )

ó también:

CREATE  INDEX `nombre_del_indice` ON mi_tabla (`campo_del_indice`(4))

El problema es que si estamos usando ‘migrations‘ de Rails y queremos ser puristas nos encontramos con que la sintaxis estándar de las migraciones de Rails no soporta el campo ‘longitud’.

Como bien dice Jaime Iniesta existe un parche que da soporte a las migraciones para admitir el campo ‘length’.

Yo sin embargo he preferido no tocar el código de mis gemas y meter este fichero (rails_migraions_hack.rb) en el ‘/lib‘ de mi aplicación:

module ActiveRecord
  module ConnectionAdapters # :nodoc:
    module SchemaStatements
      def add_index(table_name, column_name, options = {})
        column_names = Array(column_name)
        index_name   = index_name(table_name, :column => column_names)
 
        if Hash === options # legacy support, since this param was a string
          index_type = options[:unique] ? "UNIQUE" : ""
          index_name = options[:name] || index_name
          index_length = options[:length] ? "(#{options[:length]})" : nil # only mysql
        else
          index_type = options
        end
        quoted_column_names = column_names.map { |e| quote_column_name(e) }.join(", ")
        # execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})"
        sql = "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{table_name} (#{quoted_column_names}#{index_length})"
        p "SQL: #{sql}"
        execute sql
      end
    end
  end
end

Y cargarlo con un:

require 'rails_migrations_hack'

En mi environment.rb.

Funciona perfectamente y vemos como saca por pantalla las querys de las creaciones de índices con cosas como esta:

"SQL: CREATE  INDEX `idx_forums_forums_nicetitle` ON forums_forums (`nicetitle`(4))"

Es interesante consultar toda la discusión en la lista de ror-es.

Actualizado (2008-08-13):

Al parecer no estan cubiertas todas las posibilidades.

En el caso de estar creando un índice compuesto el atributo length se lo asigna al último campo y esto puede no ser lo adecuado:

add_index :tabla, [:campo1, :campo2], :name => 'idx_tabla1', :length => '10'

lo que intenta es hacer:

CREATE  INDEX `idx_tabla1` ON tabla (`campo1`, `campo2`(10))

Si el campo varchar que queremos limitar es el primero la hemos cagado..

Habría que intentar modificar la sintaxis del length para que coja algo como esto:

add_index :tabla, [:campo1, :campo2], :name => 'idx_tabla1', :length => { :campo1 => '10' }
Lunes, Agosto 11th, 2008

Ruby, script generador de script javascript para precarga de imágenes en tu html

El truco más sencillo para precargar las imágenes de tu web y así no ver el horrible efecto de mouse-over hueco es hacer un script javascript como este:

img1 = new Image();
img1.src="imagen.jpg";

Esto invocará la imagen y el navegador, sino es muy tonto, la dejará en la caché para cuando verdaderamente la necesite.

Bueno, pues es lo que quiero hacer para una web que tiene un montón de background-image definidos en su css.

Para ello me he hecho un script en ruby que me busca todas las imágenes en todos los css y me genera un script de javascript con todo el rollito de los preload.

class Preloator
  def self.generate_script( css_directory )
    # take all .css on directory
    css_file_names = []
    Dir.foreach( css_directory ) do |file|
      if file =~ /.*\.css$/
        css_file_names << css_directory + '/' + file
      end
    end
 
    # take all the images by looking at 'url'
    image_names = []
    css_file_names.each do |file_name|
      self.grep( 'url', file_name ).each do |line|
        image_names << line.scan( /.*\((.*)\)/ ).flatten.first
      end
    end
 
    # generate the script
    script = "<script>\n"
    image_names.each_with_index do |image_name, index|
      script += "    img#{index} = new Image();\n"
      script += "    img#{index}.src = \"#{image_name}\";\n"
    end
    script += "</script>"
  end
 
  def self.grep( pattern, filename )
    matches = []
    regexp = Regexp.new( pattern )
    File.open(filename) do |file|
      file.each do |line|
        matches << "#{filename} #{file.lineno.to_s}: #{line}" if regexp.match(line)
      end
    end
    matches
  end
end
 
puts Preloator.generate_script( ARGV[0] )

Esto genera una salida como esta:

$ ruby etc/preloator.rb public/stylesheets/
<script>
img0 = new Image();
img0.src="/imgs/fondo.jpg";
img1 = new Image();
img1.src="/imgs/fondo_carpeta.png";
img2 = new Image();
img2.src="/imgs/blog_sombra_abajo.png";
img3 = new Image();
</script>;

Seguramente quieras mejorarlo un poquito: meterlo en un .js o invocarlo cuando el document.load.

El script ahorra trabajo si, como en mi caso, tienes más de 20 imágenes en el css.. y escribir el javascript a mano sería un coñazo.

Lunes, Agosto 4th, 2008

Ruby on Rails: expirar la caché desde un script ó como activar los Sweepers como Observers

No sé si es que soy yo el liante o es que me da por hacer las cosas raras.

El caso es que me he encontrado con otro dolor de cabeza haciendo algo, en principio, relativamente sencillo.

Se trata de insertar en la BD un modelo desde un script, que más adelante será un rake. Todo iba bien, no es complicado:

m = MiModelo.new( :nombre => 'el nombre' )
m.save

Pero, algo olía mal, pues los listados de este modelo tienen una vista, y esta vista está cacheada, y si insertaba el modelo desde el script la vista no cambiaba, osea: no estaba expirando la caché.

El problema estaba en que todos los engranajes de expiración de cachés estaban en los sweepers y éstos sólo se activan si el código pasa por el application.rb, padre de todos los controladores, concretamente con el método: cache_sweeper, y no encontraba manera de activar los sweeper sin pasar por un controlador.

Estuve dando unas buenas vueltas y preguntando a colegas, resulta que la caché en Rails está altamente integrada con el controlador. Y esto tiene su sentido pues no hay caché sin renderizado y no hay renderizado sin llamada a un controlador. Pero cuando hablamos de expirar cachés no veo tan claro que deba seguir estando integrado con el controlador, pues, como vemos en mi ejemplo del script, pueden existir muchos escenarios donde sea necesaria una expiración de caché sin necesidad de pasar por un controlador.

Googleando por ahí encontré un post que según creo entender también se queja un poco del exceso de integración entre la caché y los controladores.

La primero aproximación que parece ocurrírsenos es la de usar un observer en vez de un sweeper pero el sweeper tiene implícitamente un montón de métodos muy útiles para realizar la tarea de expiración de caché, como son los expire_* y los renderizadores de urls al estilo mi_modelo_url.

Además si empezaba a migrar todos los sweepers a observers empezaba a salirme del camino de como le gusta a Rails que se hagan las cosas.

Pero es que resulta que al parecer a Rails no le gusta que se expiren cachés desde un script y el workarround para conseguirlo no es del todo limpio pero se ha conseguido activar los sweepers desde un script para que se disparen al insertar un nuevo modelo en la BD.

El workarround es obra de Daniel Rodriguez Troitiño y aquí podéis revivir la lucha hasta conseguirlo.

Está funcionando para 2.0.1 y no sé si funciona en otras versiones.

require 'action_controller/test_process'
 
ActiveRecord::Base.observers = [MiSweeper]
ActiveRecord::Base.instantiate_observers
 
MiSweeper.instance.controller = ActionController::Base.new
 
tr = ActionController::TestRequest.new
MiSweeper.instance.controller.request = tr
MiSweeper.instance.controller.instance_eval( '@url = ActionController::UrlRewriter.new(tr, {})' )
 
m = MiModelo.new( :nombre => 'el nombre' )
m.save

Vemos que se activa el sweeper como un observer y luego se inicializa con todo lo necesario para que funcione.

Muchas gracias a Daniel que se lo ha tomado como reto personal ;).

Lunes, Abril 21st, 2008

El atajo shell del día.

Monitoreando un fichero y enviando alertas por email

Este script envía un email cada vez que aparece la palabra ‘now’ en el fichero stuff.

$ tail -f stuff | awk ' /now/ { system("mail -s \"Now Occured\"  mail@foo.com < msg") }'

Hay quién se anima el día leyendo la frase zen diaria en su calendario, o el consejo cristiano, o el chiste del pajares, son buenos recursos para comentar con los compañeros en la hora del café.

Y los hay quienes se activan con el comando o atajo shell del día.

[shell-fu]$, Recopilatorio de comandos y atajos shell.

Ya estás alimentando tu lector de feeds :)

Vía GenBeta.

Viernes, Abril 18th, 2008

Tomcat 5.5 nuevas features: no funciona nada.

¿Dónde demonios se ha metido todo? ¿El webapps? ¿El /manager? ¿El log? ¿Que demonios pasa con los VirtualHosts?

Todas estas preguntas que arrastro desde hace tiempo, desde que Tomcat 5.0 ya no está soportado en Ubuntu.

La 5.5 viene con un montón de features, la principal es que no funciona nada de lo que me funcionaba antes.

Alguna luz se me ha abierto.

Para que funcione el /manager hay que instalar:

$ sudo aptitude install tomcat5.5 tomcat5.5-admin tomcat5.5-webapps

Y lo encontrarás en:

http://localhost:8180/manager/html/

La seguridad como siempre nos juega una mala pasada y hay que mandarla a freir lechugas:

$ sudo vim /etc/default/tomcat5.5
TOMCAT5_SECURITY=no

El webapps se encuentra aquí:

/usr/share/tomcat5.5/webapps/

Este tutorial está bastante bien.

Actualizado 2008-05-14:

En Ubuntu 8.04 vuelve a fallar todo y aunque reinstale todo el tomcat no arranca

Solución: seguir este tutorial.

Si en el  /usr/share/tomcat5.5/logs/catalina.out te aparece algo así como esto:

java.lang.ClassNotFoundException: org.apache.catalina.startup.Bootstrap

Debes reinstalar libtomcat5.5-java:

 $ sudo aptitude reinstall libtomcat5.5-java

Suerte.. mucha suerte :)

Martes, Abril 8th, 2008

Poner una aplicación Rails en producción.

Esto no es un tutorial, es una nota mental.

Entorno
  • Ubuntu 7.10
  • MySql 5.0
Instalar capistrano

Tanto en cliente como en servidor.

$ gem install capistrano
Configurar aplicación rails para Capistrano

En cliente.

$ cd #{RAILS_ROOT}
$ capify .

Editar fichero /config/deploy.rb:

require 'mongrel_cluster/recipes'

set :user, "username"
set :runner, "username"

set :application, "miaplicacion"
set :repository,  "http://www.mirepositorio.com/svn/trunk/"
set :scm_username, "username"
set :scm_password, "userpass"

set :deploy_to, "/var/www/railsapps/#{application}"  # path en el servidor
set :mongrel_conf, "#{current_path}/config/mongrel_cluster.yml"  # no tocar

role :app, "miservidor.com"       # si quieres indicar puerto miservidor.com:puerto
role :web, "miservidor.com"       # si quieres indicar puerto miservidor.com:puerto
role :db,  "miservidor.com", :primary => true # si quieres indicar puerto miservidor.com:puerto

Tuve problemas con el username y el userpass y al final les puse el mismo username y pass al usuario del sistema como al usuario del svn.

Editar /config/mongrel_cluster.yml:

---
cwd: /var/www/railsapps/miaplicacion"  # path en el servidor
log_file: log/mongrel.log
port: "5000"
environment: production
address: 127.0.0.1
pid_file: tmp/pids/mongrel.pid
servers: 2      # puertos 5000 y 5001
Preparar servidor

En servidor.

Preparamos el directorio:

$ sudo mkdir -p /var/www/railsapps
$ sudo chown username /var/www/railsapps

Fichero configuración de VirtualHost /etc/apache/sites-available/miaplicacion.miservidor.com:

#we need this as on Ubuntu by default Proxy is not allowed
<Proxy *>
  Order allow,deny
  Allow from all
</Proxy>

#Proxy balancer section (create one for each ruby app cluster)
<Proxy balancer://miaplicacion.miservidor.com_cluster>
  BalancerMember http://127.0.0.1:5000
  BalancerMember http://127.0.0.1:5001
</Proxy>

#Virtual host section (create one for each ruby app you need to publish)
<VirtualHost *:80>
  ServerName miaplicacion.miservidor.com
  DocumentRoot /var/www/railsapps/miaplicacion/current/public/

  <Directory /var/www/railsapps/miaplicacion/current/public/ >
    Options Indexes FollowSymLinks MultiViews
    AllowOverride All
    Order allow,deny
    allow from all
  </Directory>

  ErrorLog /var/log/apache2/error_miaplicacion.miservidor.com_log
  CustomLog /var/log/apache2/access_miaplicacion.miservidor.com_log combined

  #Rewrite stuff
  RewriteEngine On

  # Check for maintenance file and redirect all requests
  RewriteCond %{DOCUMENT_ROOT}/system/maintenance.html -f
  RewriteCond %{SCRIPT_FILENAME} !maintenance.html
  RewriteRule ^.*$ /system/maintenance.html [L]

  # Rewrite index to check for static
  RewriteRule ^/$ /index.html [QSA]

  # Rewrite to check for Rails cached page
  RewriteRule ^([^.]+)$ $1.html [QSA]

  # Redirect all non-static requests to cluster
  RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f
  RewriteRule ^/(.*)$ balancer://miaplicacion.miservidor.com_cluster%{REQUEST_URI} [P,QSA,L]
</VirtualHost>

Activamos el VirtualHost:

$ sudo a2ensite miaplicacion.miservidor.com

Recargamos Apache:

$ sudo /etc/init.d/apache reload
Subir primera versión a servidor

En cliente.

$ cap deploy:setup
Preparar BD en servidor

En servidor.

Esto seguro que se puede hacer desde Capistrano:

$ mysql> create database midatabase;
$ mysql> grant all privileges on midatabase.* to username@localhost identified by 'userpass';
$ rake db:migrate
Próximos despliegues

En cliente.

$ cap deploy

Y si hay migraciones entonces hay que ejecutarlas en el servidor (aunque seguro que se puede hacer con el Capistrano).

Martes, Abril 8th, 2008

Procedimiento almacenado con cursor en MySQL

Aquí dejo el código de un procedimiento almacenado de MySQL que usa un cursor.

Esto no es un tutorial es una nota mental.

DROP procedure IF EXISTS test_cursor
/
 
CREATE procedure test_cursor()
 
begin
 
--
-- variables
--
declare hasMoreRows bool DEFAULT true;
declare _field1 varchar(20);
declare _field2 varchar(20);
 
--
-- the cursor
--
declare cur cursor FOR
 
  SELECT
      cl.LIBRARY_NAME,
      cl.LIBRARY_DESCRIPTION
  FROM
      CAT_LIBRARIES AS cl
  ;
 
declare continue handler FOR SQLSTATE '02000'
    SET hasMoreRows = false;
 
--
-- create table test
--
DROP TABLE IF EXISTS test_cursor;
 
CREATE TABLE `test_cursor` (
  `field1`  varchar(20) NULL,
  `field2`  varchar(20) NULL
);
 
--
-- open the cursor
--
open cur;
 
fetch cur INTO
  _field1,
  _field2;
 
while hasMoreRows do
 
--
-- ####### WALK:INI ########
--
 
  INSERT INTO
      test_cursor
  SET
    field1 = _field1,
    field2 = _field2
  ;
 
--
-- ####### WALK:END ########
--
 
  fetch cur INTO
    _field1,
    _field2;
 
end while;
 
close cur;
 
end;
/

Aquí está el fichero con el código fuente.

Viernes, Marzo 14th, 2008

Crear otro repositorio subversion.

Esto no es un How-To, es una nota mental.

Creando el repositorio

$ sudo svnadmin create  /home/svn/mirepositorio
$ sudo chown www-data.svn -R /home/svn/mirepositorio

Configuración del apache para el nuevo repositorio:

$ sudo vim /etc/apache2/mods-available/dav_svn.conf
<Location /svn/mirepositorio>
  DAV svn
  SVNPath /home/svn/mirepositorio
  AuthType Basic
  AuthName "mirepositorio's Subversion Repository"
  AuthUserFile /etc/apache2/htpasswd
  Require valid-user
</Location>

Añadimos un usuario:

$ sudo htpasswd -b /etc/apache2/htpasswd usuario clave

Recargamos apache:

$ sudo /etc/init.d/apache2 reload

Comprobamos:

navegador: http://fernandoguillen.info/svn/mirepositorio/
consola:  $ svn checkout http://fernandoguillen.info/svn/mirepositorio

Crear las carpetas iniciales:

$ svn mkdir http://fernandoguillen.info/svn/mirepositorio/trunk
$ svn mkdir http://fernandoguillen.info/svn/mirepositorio/branches
$ svn mkdir http://fernandoguillen.info/svn/mirepositorio/tags

Primer import:

$ svn import http://fernandoguillen.info/svn/mirepositorio/trunk

Mi sistema:

Ubuntu Gutsy Gibon 7.10
Subversion version 1.4.4
Apache 2
Jueves, Marzo 13th, 2008

Munin la bestia parda del monitoreo de servicios

Asistí a una de las aulas The Cocktail sobre Munin.

Me quedé impresionado, ahora mismo acabo de terminar de instalar y me ha costado 15 minuntos.

Es la gran bestia parda:

1) Apenas requiere configuración.

2) Permite sistema de nodos y master.

3) Monitorea cosas que nisiquiera entiendo como es capaz de monitorear.

4) Te autodetecta todo lo que tiene el servidor y se pone a monitorearlo todo sin que le tengas que explicar nada.

Increible, una joya.

Lunes, Octubre 1st, 2007

Google Analytics y PhpBB

Podemos usar las estadísticas de google analytics en nuestro foro basado en PhpBB fácilmente.

Editamos los ficheros:

  • simple_footer.tpl
  • overall_footer.tpl

De vuestra plantilla, por ejemplo:

  • /templates/subSilver/simple_footer.tpl
  • /templates/subSilver/overall_footer.tpl

Y ponemos el script del google analytics justo antes de la etiqueta de cierre del body: </body>

a Freelance Web Developer is proudly powered by WordPress
Entries (RSS) and Comments (RSS).

Creative Commons License
Fernando Guillen's blog by Fernando Guillen is licensed under a Creative Commons Attribution-NoDerivs 3.0 Unported License.