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

Domingo, Febrero 1st, 2009

Ruby on Rails, Paperclip y validaciones de dimensiones

Me he picado una extensión de Paperclip para porveer de validaciones de dimensiones para las imágenes adjuntas aquí la comparto contigo por si te resulta útil.

No he hecho un parche para Paperclip pues eso supone un gran esfuerzo en tests y demás y eso tendrá que esperar un poco.

Lo he organizado todo para que copies y pegues el código en un fichero en /lib y lo requieras desde el environment.rb con eso debería valer pero úsalo bajo tu entera responsabilidad :)

La extensión provee de dos nuevos validates:

  • validates_attachment_width
  • validates_attachment_height

Que se pueden usar con toda la convinación de opciones que el nativo ‘validates_attachment_size‘.

Por ejemplo:

validates_attachment_width :photo, :greater_than => 576, :less_than => 1000
validates_attachment_height :photo, :greater_than => 150, :less_than => 300
 
validates_attachment_width :photo, :in => 576..1000
validates_attachment_height :photo, :in => 150..300, :message => "file height must be between :min and :max pixels."

Tenemos por ejemplo la validación del ancho:

    def validates_attachment_width name, options = {}
      min     = options[:greater_than] || (options[:in] && options[:in].first) || 0
      max     = options[:less_than]    || (options[:in] && options[:in].last)  || (1.0/0)
      range   = (min..max)
      message = options[:message] || "file width must be between :min and :max pixels."
 
      attachment_definitions[name][:validations][:width] = lambda do |attachment, instance|
        if attachment.file? && !range.include?( Geometry.from_file(attachment.queued_for_write[:original]).width.to_i )
          message.gsub(/:min/, min.to_s).gsub(/:max/, max.to_s)
        end
      end
    end

El código completo lo tienes aquí:  paperclip_validations_extended.rb.

Un ejemplo de tests y usos lo tienes aquí: paperclip_validations_extended_test.rb.

A mi me está funcionando.. y a tí?

Sábado, Diciembre 27th, 2008

Shell: script para borrar todos los delete pendientes con GIT

Tengo la mala costumbre de borrar los ficheros desde mi editor de texto así que cuando vuelvo a consola y hago un ‘git status‘ me salen un montón de ficheros borrados y no encuentro el comando de ‘git‘ para decirle: cárgatelos todos en el próximo commit.

Así que me he hecho este script para decirle exactamente eso:

$ git rm `git status | grep "deleted" | cut -f 2 -d ":" | cut -d " " -f 5`

Cuidadín con él eh :).. a ver si no va a estar bien..

Actualización: como dice Juanma en los comentarios podemos hacerlo mucho más elegantemente con:

git add -u
Jueves, Septiembre 18th, 2008

Port Forwarding: accediendo a las máquinas de una intranet desde una sóla máquina pública.

Las redes de los clientes son celosas y normalmente sólo se puede acceder a las máquinas de su red desde dentro de la red misma. Lo que viene a llamarse una intranet.

Pero muchas de mis tareas de mantenimiento requieren acceso a varias máquinas de una intranet y desplazarme al cliente no lo veo una opción factible ni acorde con nuestros tiempos.

Una de las soluciones que más me ha gustado es pedirle al cliente un sólo acceso, una sola máquina con IP pública.

Para dar seguridad se puede configurar para que sólo se pueda acceder desde la IP estática de tu oficina, y, para ofrecer flexibilidad, que también se pueda acceder desde la IP estática de tu servidor en un datacenter. Pero bueno todo este párrafo te lo puedes saltar si no hay mucho inconveniente con la seguridad.

La máquina con la IP pública mola que sea linux, más que nada porque si no lo es ya no hace falta que sigas leyendo pues el resto del artículo sobreentiende esta premisa.

Lo que vamos a hacer es lo que se ve en la ilustración.

port forwarding

Aquí me ves a mí con más pelo y sin barba deseando acceder a todos los servicios de las máquinas de la intranet 10.10.10.* y sin embargo sólo pudiendo acceder a la máquina con la IP pública 205.205.205.205.

Lo que necesito tiene un nombre y este es Port Forwarding, y la implementación de esto más sencilla que he visto se hace mediante IP Tables.

Va a ser más rápido escribir los comandos que hay que ejecutar para que esto funcione que toda la charla previa que te he contado.

Mi máquina con acceso público tiene la distribución de linux Gentoo así que los ejemplos son para esta distro pero no deberían variar mucho para otras distros.

Instalamos iptables (si no está ya)

# emerge iptables

Le decimos al kernel que permita ip-forwarding

# echo 1 > /proc/sys/net/ipv4/ip_forward

Flasheamos toda la configuración de iptables que haya por defecto

# iptables -F
# iptables -t nat -F

Permitimos el forward desde iptables (eth0 es la interface pública)

# iptables -A FORWARD -i eth0 -j ACCEPT
# iptables -A FORWARD -o eth0 -j ACCEPT

Esto hace que las ips se enmascaren para no liar a los routes y saltarse posibles filtros de seguridad por IP

# iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

Y aquí por fín las reglas del Port Forwarding

# iptables -t nat -A PREROUTING -p tcp -i eth0 --dport 213306 -j DNAT --to-destination 10.10.10.21:3306
# iptables -t nat -A PREROUTING -p tcp -i eth0 --dport 2180 -j DNAT --to-destination 10.10.10.21:80
# iptables -t nat -A PREROUTING -p tcp -i eth0 --dport 2280 -j DNAT --to-destination 10.10.10.22:80
# iptables -t nat -A PREROUTING -p tcp -i eth0 --dport 2221 -j DNAT --to-destination 10.10.10.22:21
# iptables -t nat -A PREROUTING -p tcp -i eth0 --dport 2322 -j DNAT --to-destination 10.10.10.23:22

Guardamos la configuración de iptables actual para que no haya que reescribirla al reiniciar

# /etc/init.d/iptables save

Ponemos el servicio de iptables para que arranque al inicio

# rc-update add iptables default

Creo que esto también hay que hacerlo

# vim /etc/sysctl.conf

Añadir/descomentar las siguientes líneas:

net.ipv4.ip_forward = 1
net.ipv4.conf.default.rp_filter = 1

Y ya debería funcionar. Si me voy a mi máquina en mi oficina y accedo a:

$ mysql -h205.205.205.205 -P 213306

Me encontraré con la mysql de la máquina 10.10.10.21.

Si accedo con un navegador a:

http://205.205.205.205:2280

Me encuentro con el Apache de la máquina 10.10.10.22.

Si no te funciona revisa la linkografía:

Jueves, Septiembre 4th, 2008

Ruby, sustituyendo matches de una regex en un String con matches de la misma regex

Madre mía que título bueno me ha quedado :)

Esto es una nota mental y puede que si no sabes de que hablo no te interese y si sabes de que hablo ya lo sepas.

Intento sustituir una parte de un String por otra parte que se encuentra en el mismo String.

Es decir, tengo esto “me gusta el heavy y no me gusta el country” y quiero obtener esto otro “me gusta el country y no me gusta el heavy”.

Se puede hacer así:

>> "me gusta el heavy y no me gusta el country".gsub( /me gusta (.*) y no me gusta el (.*)/, 'me gusta \2 y no me gusta el \1' )
=> "me gusta country y no me gusta el el heavy"

Donde \1 y \2 son las ocurrencias de los (.*).

El ejemplo puede parecer un poco tonto, pero esta utilidad me ha venido muy bien para quedarme con el contenido de una etiqueta html:

>> "<body>contenido</body>".gsub( /.*<body[^>]*>(.*)<\/body>.*/mix, '\1' ).strip
=> "contenido"

Que se puede solucionar de muchas otras maneras pero esta me ha parecido la más sencilla.

Escribo esta nota mental por lo poco intuitivo que es el uso de ‘\1′ como cadena sustitutiva pues se supone que las cadenas entre comillas simples no se interpretan…

Otra cosa es que google siempre me llevaba a soluciones como esta:

>> "me gusta el heavy y no me gusta el country".gsub( /me gusta (.*) y no me gusta el (.*)/, "me gusta #{$2} y no me gusta el #{$1}" )
=> "me gusta country y no me gusta el el heavy"

Y aunque parece que funciona no es así porque los contenidos de $1 y $2 los ha cogido del gsub anterior y no de éste:

>> "me gusta el musical y no me gusta el flamenco".gsub( /me gusta (.*) y no me gusta el (.*)/, "me gusta #{$2} y no me gusta el #{$1}" )
=> "me gusta country y no me gusta el el heavy"
>> "me gusta el musical y no me gusta el flamenco".gsub( /me gusta (.*) y no me gusta el (.*)/, "me gusta #{$2} y no me gusta el #{$1}" )
=> "me gusta flamenco y no me gusta el el musical"
Sábado, Agosto 30th, 2008

Ruby, sanitizando tus títulos en 2 líneas.

Ayer me acosté super contento, había conseguido escribir una función para sanitizar strings, o como dicen por ahí: crear un SLUG (que todavía no he encontrado la definición exacta).

Si no sabes de que hablo se trata de convertir un “Hola mundo!, qué tal?” en un “hola-mundo-que-tal” para las URLS bonitas y todo eso.

No había sido fácil pues Ruby se lleva mal con los caracteres no-ASCII y el castellano tiene muchos, había que hacer un pequeño malabarismo con la gema Unicode.

Al final mi función se veía así:

require 'unicode'
def to_slug( sentence, length = 64 )
  return if sentence.blank?
 
  wrong = ['á','é','í','ó','ú','ä','ë','ï','ö','ü','à','è','ì','ò','ù','ñ','ç','º','ª','_']
  right = ['a','e','i','o','u','a','e','i','o','u','a','e','i','o','u','n','s','o','a','-']
 
  sentence = sentence[0..length-1]
  sentence = Unicode.downcase( sentence )
 
  for i in 0..wrong.size-1
    sentence.gsub!( wrong[i], right[i] )
  end
 
  sentence.gsub!( /[^a-z0-9-]/, '-' ) # not letters of numbers
  sentence.gsub!( /-{2,}/, '-' )      # 2 or more '-' together becoming 1 '-'
  sentence.gsub!( /^-|-$/, '' ) unless sentence.size == 1 # '-' at begging or at end
  sentence
end

Estaba super orgulloso hasta que me despierto por la mañana y cambiando la pregunta a Google me encuentro con un… ‘inombrable’ que me hace esto:

require 'unicode'
def to_slug
  str = Unicode.normalize_KD(self).gsub(/[^\x00-\x7F]/n,'')
  str = str.gsub(/\W+/, '-').gsub(/^-+/,'').gsub(/-+$/,'').downcase
end

Exactamente (casi) lo que yo tenía pero en 2 líneas.

¡Así es Ruby!

Al final he hecho algún cambio y cogido lo bueno de uno y de otro y le he añadido soporte para STOPWORDS:

STOPWORDS = [
  'de','a','que','no','tiene','en','para',
  'por','le','la','lo','las','los','el',
  'una','un'
]
 
def to_slug( length = 64, drop_stopwords = false )
  return "" if self.length == 0
 
  str = Unicode.normalize_KD(self).gsub(/[^\x00-\x7F]/n,'').downcase
 
  # stopwords
  if drop_stopwords
    STOPWORDS.each do |stopword|
      str.gsub!( /\s#{stopword}\s|^#{stopword}\s/, ' ' )
    end
  end
 
  str = str.gsub(/[^A-Za-z0-9]/, '-').gsub(/^-+/,'').gsub(/-+$/,'').downcase
  str = str[0..length-1]
end

Por su puesto que se recomienda completar la lista de STOPWORDS con las que quieras.

Jueves, Agosto 28th, 2008

Ruby, el ‘print’ necesita que hagas un flush del $stdout

No conseguía hacer un indicador progresivo de un proceso, o lo que es lo mismo: el típtico ‘punto… punto… punto…‘ para indicar que el proceso sigue en marcha.

Si utilizada puts o p se me generaba un salto de carro al final de cada ‘punto‘. Y si utilizaba ‘print‘ simplemente no salía nada hasta que cancelaba el proceso… o terminaba.

Estaba claro que ‘print‘ estaba insertando los ‘puntos‘ en un bufer y había que ‘flusearlo‘.

La solución:

print '.'; $stdout.flush

Mira aquí y aquí.

Jueves, Agosto 28th, 2008

Ruby on Rails, no se me está inicializando el estado de las tablas al ejecutar un test

Estaba yo haciendo unos testecitos y veo que no se me estaban borrando los datos de la base de datos al ejecutar cada test lo cual me estaba generando unos poltergeist horribles.

Un comportamiento de Rails en el que confiaba como era este: “La base de datos vuelve al estado original en cada test“, me estaba traicionando.

Bueno el problema era que como mis tests no usaban fixturas no las estaba cargando y resulta que es la carga de las fixturas lo que borra la bd y no la propia suite de tests.

Solución:

fixtures :mimodelos

Aunque no use las fixturas es lo que he tenido que hacer para que la tabla se borrase con cada test.

¡Qué cosas!

Miércoles, Agosto 27th, 2008

Ruby, extraer la parte html del body de un email con parte html y part texto plano

Normalmente cuando se escribe un email en formato enriquecido el propio cliente de email se encarga de generar dos partes dentro del email, una con el texto en formato html (enriquecido) y otra en formato texto plano por si el cliente de correo con el que finalmente el destinatario lo abra así lo quiere.

Bien, para una aplicación en la que estoy trabajando se requiere que dado un email con varias partes (html y texto plano) pueda extraer sólo la parte html.

No he encontrado nada en la clase TMail::Mail de Ruby que me ofrezca esta funcionalidad.

No parece difícil en un principio, pero en realidad si profundizas un poco si que se vuelve lioso. Si cogemos el body del Mail lo tenemos todo junto y no sabemos donde cortar, ni siquiera si es requerido cortar. Si empezamos a recorrer las partes del Mail podemos empezar a rastrear los content_type de cada una pero esto puede resultar engañoso pues la parte con el body en formato enriquecido tiene el mismo content_type que un fichero html adjunto en el email.

Al final lo que me ha quedado es este método:

def body_html
  result = nil
  if multipart?
    parts.each do |part|
      if part.multipart?
        part.parts.each do |part2|
          result = part2.unquoted_body if part2.content_type =~ /html/i
        end
      elsif !attachment?(part)
        result = part.unquoted_body if part.content_type =~ /html/i
      end
    end
  else
    result = unquoted_body if content_type =~ /html/i
  end
  result
end

Es un método que extiende la clase TMail::Mail para ofrecer el método .body_html que devuelve la parte html o nil si no hay ninguna parte de texto enriquecido.

Puede parecer liosa pero en realidad está inspirada en el método .body de la propia clase TMail::Mail.

Podéis descargaros el parchecito de TMail::Mail y hacer un require del mismo, o también podéis mirar directamente el repositorio donde lo he subido junto con unos pocos tests.

Cualquier comentario es bienvenido.

Miércoles, Agosto 27th, 2008

Ruby, uso de caracteres no ASCII en la consola IRB del Mac OS X

Mis versiones:

  • Mac OS X 10.5.4
  • Ruby 1.8.6 patchlevel 114
  • Rails 2.1.0

Tengo problemas para introducir caracteres no ASCII ( caracteres especiales, acentos y ñs ) en la consola IRB de Ruby así como también en la consola script/console de Rails.

Gracias a las lista ror-es lo he podido solucionar.

Resumo aquí a mi manera el post con la solución:

Instalamos la versión universal de la librería readline mediante MacPorts:

$ sudo port install readline +universal

Si te dá error de:

-bash: port: command not found

Asegúrate que tienes instalados los MacPorts y que tienes esto en tu .bash_profile:

export PATH=$PATH:/opt/local/bin
export MANPATH=$MANPATH:/opt/local/share/man
export INFOPATH=$INFOPATH:/opt/local/share/info

Instalamos la extesión Ruby para readline. Vigila la versión que te bajas, debe coincidir con tu versión de Ruby exactamente:

$ ruby --version
ruby 1.8.6 (2008-03-03 patchlevel 114) [universal-darwin9.0]

v1_8_6_114

$ cd /tmp
$ svn co http://svn.ruby-lang.org/repos/ruby/tags/v1_8_6_114/ext/readline/ readline

Aplicamos un parchecito:

$ curl http://pastie.textmate.org/pastes/168767/download | patch readline/extconf.rb

Y compilamos, necesitarás tener instaladas las OS X developer tools:

$ cd readline
$ ruby extconf.rb
$ make
$ sudo make install

Si el script/console te sigue dando problemas asegúrate de tener bien la $KCODE:

$ script/console
Loading development environment (Rails 2.1.0)
>> puts $KCODE
UTF8
=> nil

Gracias de nuevo a Daniel Rodriguez Troitiño por la solución.

Jueves, Agosto 14th, 2008

Seudo soporte PNGs transparentes en IE6

Disclaimer: soy desarrollador web pero del lado oscuro: el de la parte servidor y, aunque me toca tocar de todo, el CSS, y el diseño web en general, no son mi especialidad. Así que si ves alguna página de algún maquetador especializado en la que se contradice algo de lo aquí digo hazle caso a él.

La pesadilla del maquetador web tiene nombre y éste es Internet Explorer 6. El Navegador más uebón de todos los navegadores. Y pese a que tiene más de 7 años, que está intentando ganar el récord a software con más agujeros de seguridad y que la versión 7 hace 2 años que está disponible, aún sigue siendo el navegador más usado… después de FireFox ;..)

Una de las mayores cerraduras de esfinter que nos encontramos al probar nuestra flamante web en un IE6 es que éste no soporta PNGs transparentes. :O

Bueno, sí que los muestra, pero en vez de la resultona transparencia aparece un desagradable tonillo gris con transparencia nula.

Existen muchos sitios donde te explican como hacer para que el IE6 soporte los PNGs transparentes.. pero la cruda realidad es que no hay manera, olvídate. Cambia todos los PNGs transparentes por PNGs no transparentes o JPGs o incluso GIFFs, pero no te metas en el lío de intentar conseguir que el IE6 te lea bien los PNGs transparentes..

Lo puedes hacer sólo para el IE6 con el hack de los comentarios condicionales para IE y cargando CSSs específicos para este navegador.

Si después de lo dicho te empecinas en hacerlo, allá tú, y aquí tienes la manera de acercarte lo más posible a conseguirlo.

Existen 2 hacks diferentes que tenemos que usar dependiendo de si el PNG transparente está incluido en la página mediante un <img> o mediante un background-image.

PNG transparente metido en una <img>

Éste es el más fácil y el soporte es casi total. A mi no me ha dado ningún problema.

Es muy fácil porque se utiliza un javascript que han hecho una gente muy simpática llamado pngfix.js.

Es una mala bestia de script en 30 líneas que selecciona todas los elementos <img> que contienen un PNG y los sustituye por elementos <span> con una imagen de fondo que es la misma PNG. Usa para ello el hack del IE6 para PNG en background-image que vemos a hora a continuación.

Para activarlo no hay más que descargarse el pngfix.js y llamarlo desde el HTML con:

<!--[if lt IE 7]>
  <script defer type="text/javascript" src="pngfix.js"></script>
<![endif]-->

Así sólo se activará si el navegador detectado es una versión anterior al IE7.

PNG transparente como background-image

Éste funciona siempre y cuando no queramos usar el background-repeat y/o el background-position. Si queremos usar estos atributos estamos jodidos y volvemos al punto del principio: olvídate.

Es decir sólo funciona en caso de que la imagen de fondo no queramos que se repita o que se coloque en una posición que no sea la 0,0.

El truco, o hack, fué ofrecido por la propia gente de IE6 que, diéndose cuenta de la carencia de su motor de renderizado, rebuscaron y rebuscaron y dieron con una posible solución: usar un filtro propietario llamado AlphaImageLoader que es capaz de cargar PNGs transparentes vía CSS.

Está claro que no vamos a usar este truco (chapuza) en todos los navegadores así que lo que hacemos en un CSS que sobrescriba los valores normales y los sustituya por los que necesita el IE6.

Así que generamos un CSS que sólo lo lea el IE6 y lo cargamos desde el html tal que así:

<!--[if IE 6]>
  <style type="text/css" media="all">@import "/stylesheets/ie6.css"</style>
<![endif]-->

En este CSS vamos a hacer todos los ajuste que el IE6 necesite. Entre ellos el del AlphaImageLoader.

Cogemos todos los elementos CSS que contengan un background-image y ponemos cosas coma ésta en nuestro ie6.css:

.clase_css{
  background-image: none;
  filter: none !important;
  filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="/imgs/imagen.png");
}
#id_css{
  background-image: none;
  filter: none !important;
  filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="/imgs/imagen.png");
}

Y repito: si requieres que la imagen se repita estás perdido, lo máximo que puedes hacer es que la imagen se estire con:

#id_css{
  background-image: none;
  filter: none !important;
  filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="/imgs/imagen.png", sizingMethod="scale" );
}

O si la imagen es más grande que el elemento que la contiene puedes cortarla:

#id_css{
  background-image: none;
  filter: none !important;
  filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="/imgs/imagen.png", sizingMethod="crop" );
}
Los links dejan de funcionar

O cuando parece que hay luz al final del tunel viene otro tren y nos aplasta.

Con el truco del AlphaImageLoader resulta que determinados links dejan de funcionar. También hay <p> que ya no se pueden seleccionar o formularios que no se dejan rellenar.

Ni el problema ni la solución está muy clara pero el workarround que se propone por ahí es buscar los componentes que fallan y ponerles un

position: relative

Con esto se suele arreglar bastantes cosas:

a {
  position: relative;
}

Para las demás deberá ir haciendo pruebas.

Los elementos no se estiran para contener su elementos hijos

Otro tren que pasa es que puede que los divs no crezcan hasta soportar todos los elementos que contienen, en caso de que ocurra esto hay que jugar con:

height: 100%;

y

height: auto !important;

Actualizado:

Parece que hay un script que intenta solucionar todos estos problemas el solito, se llama SuperSleight. Lo he probado pero ya tenía todo el tinglado montado y me funcionaba un poco mejor.

PD: hazte de la campaña SaveTheDevelopers o a la de EndIE6 y activa el script que ofrecen en tus páginas para incitar a que la gente que sigue usando IE6 se actualice .. porfavor¡¡

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.