Ruben On Rails

Creando un pequeño DSL en Ruby

Estaba aprovechando este fin de semana algo largo debido a la semana santa y recorde que tenia que revisar un tutorial muy importante que habia dado Chad Fowler y Marcel Molina Jr en el evento de Scotland On Rails sobre algunas cosas basicas y avanzadas que todo desarrollador en Ruby deberia manejar.

El primer tema que tocaron en el tutorial fueron sobre los Blocks y Closures, este es un punto muy fuerte para mi del lenguaje y junto al famoso method_missing permite que joyas como Rails salgan a la luz, bien aqui mi primer intento para resolver el ejercicio:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Configuration
  def initialize
    @options = {}
  end

  def method_missing(name, *args, &block)
    if block_given?
      @options[name.to_s] = Configuration.new
      block.call(@options[name.to_s])
    else
      if name.to_s =~ /=\Z/
        @options[name.to_s.gsub!(/=/,'')] = args.shift
      else
        @options[name.to_s]
      end
    end
  end
end

def configure
  conf = Configuration.new
  yield conf
  conf
end

# Aqui un ejemplo

configuration = configure do |config|
  config.tail_logs = true
  config.max_connections = 55
  config.app_server do |app_server_config|
    app_server_config.port = 8808
  end
end

configuration.class # => Configuration
configuration.tail_logs # => true
configuration.app_server.port # => 8808

Para esta version inicial decidí manejar las opciones de configuración en un Hash, basicamente lo que hago es crear una manera distinta de acceder a un Hash, de tal manera que pareciera que estamos accediendo a atributos de un objeto y no a los valores de un Hash.

Lo que uso en la solución es basicamente el poder de method_missing, si recibo un metodo que no existe para el objeto y este no tiene asociado un bloque, entonces sé que tengo que generar un nuevo valor en el Hash, por el contrario si recibo un metodo que no existe pero ademas este tiene asociado un bloque con el, entonces se que tengo que generar un nuevo valor en el Hash, pero este valor tiene que ser una nueva instancia de Configuration y tengo que pasar este objeto al bloque.

Para un segundo planteamiento del problema me acordé de que Ruby trae una libreria que permite hacer lo que necesito: trabajar con un Hash como si se tratara de atributos de un objeto, esta libreria se llama OpenStruct

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
require 'ostruct'

class Configuration
  def initialize
    @options = OpenStruct.new
  end

  def method_missing(name, *args, &block)
    if block_given?
      @options.send((name.to_s + '=').to_sym, Configuration.new)
      block.call(@options.send(name))
    else
      @options.send(name, args.shift)
    end
  end
end

def configure
  conf = Configuration.new
  yield conf
  conf
end

Con esto he ahorrado un par de lineas ;), pero lo que era mas importante era que tenia que aprender sobre esa libreria, ahora comprendo porque Ruby es el lenguaje perfecto para crear un DSL, aqui les dejo también un buen articulo que me ayudo en el tema: http://www.daniel-azuma.com/blog/view/z3bqa0t01uugg1/implementing_dsl_blocks

Happy hacking!

Mi presentación en el Barcamplima

En unos momentos me tocara exponer en el Barcamplima, hasta el momento todo va saliendo muy bien, pueden ver mi diapositiva si desean y espero que sigan viniendo mas eventos de este tipo!

Internacionalización en Rails: Gettext o I18n?

El dia de ayer, alguien hizo esta consulta en la lista de rails hispana en la cual participo y me sirvio bastante par tomar la decisión: Gettext.

Ahora tengo bien en claro que usare Gettext para la traducción de cadenas en mis templates y I18n para para la localización de las fechas y mensajes de error de ActiveRecord

Javascript no intrusivo con Jquery en Rails

He estado tratando de mejorar esta semana mi jquery-fu y una de las tareas que me propuse fue hacer que ciertas acciones comunes en mis controladores como el destroy y el create mejoraran la experiencia del usuario aprovechandose de Ajax y a la vez se porten bien con aquellos usuario que tienen Javascript desactivado, pues bien a la marcha, empezaremos con la acción ‘destroy’:

Cargando librerias de javascript

Lo primero es cargar las librerias javascripts que necesitemos, como estoy usando HAML, lo hago de la siguiente manera en mi layout:

1
= javascript_include_tag 'jquery-1.3.2.min', 'jquery.livequery', 'jquery-ui-1.7.custom.min', 'application'

Creando nueva accion y vista para destroy

He tomado como ejemplo un controlador para el manejo de categorias

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class CategoriesController < ApplicationController
  def destroy
    Category.destroy(params[:id])
    respond_to do |format|
      format.html do
        flash[:notice] = 'Categoria eliminada con exito'
        redirect_to categories_url
      end
      format.js { head :ok }
    end
  end

  def destroy_nojs
    @category = Category.find(params[:id])
  end
end

y la vista quedaria asi:

1
2
3
4
5
6
7
-# -#app/views/categories/destroy_nojs.html.haml
%h2== Eliminar categoria: #{@category.name.humanize}

¿Estas seguro que deseas eliminar esta categoria?

- form_for @category, :html => { :method => :delete } do |f|
  = f.submit 'Si, eliminar esta categoria'

Hasta aqui ya tendremos la accion destroy no intrusiva a diferencia de lo que nos genera esto:

1
= link_to 'Eliminar', category_path(category), :method => :delete, :confirm => '¿Seguro ...?'

Lo de arriba genera codigo Javascript intrusivo.

Ajaxificando la accion delete

Aqui viene la parte buena, pues necesitamos que eliminar una categoria sea una tarea al vuelo y si nos ponemos a pensar bien este puede ser un requerimiento que se repita en muchas partes de nuestra aplicación asi que necesitamos algo que sea reutilizable, pues bien, manos a la obra:

Suponiendo que tenemos una vista index para las categorias algo asi:

1
2
3
4
5
6
7
-#app/views/categories/index.html.haml

- for category in @categories
  %li
    == #{category.name} 
    = link_to 'Editar', edit_category_path(category)
    = link_to 'Eliminar', {:action => 'destroy_nojs', :id => category}, :class => 'delete_category'

Entonces nuestro archivo public/javascripts/application.js quedaria algo asi:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$.fn.delete_link = function(msg_confirm, message){
  $(this).livequery("click", function() {
    if (confirm(msg_confirm)) {
      var ajaxTarget = $(this).attr("href").replace(/destroy_nojs\//,"") + ".js";
      $.post(ajaxTarget, { _method: "delete" },
       function(data){
         alert(message);
       });
      $(this).parent().fadeOut("slow");
    }
    return false;
  });
};

$(document).ready(function(){
  $(".delete_ele").delete_link("Seguro que desea eliminar esta categoria?", "Categoria eliminada con éxito");
});

Como ven en el código de la funcion delete_link me he creado una especie de convención para los enlaces que son para eliminar recursos y la convención es que deben apuntar a la accion destroy_nojs la cual es la versión no intrusiva, de esta manera puedo crearme una función la cual puedo reutilizarla.

Ajaxificando el formulario de nueva categoria

Esto no podia ser mas sencillo, suponiendo que tenemos en el controlador de categorias lo siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class CategoriesController < ApplicationController
  def new
    @category = Category.new
    @categories = Category.all
  end

  def destroy
    Category.destroy(params[:id])
    respond_to do |format|
      format.html do
        flash[:notice] = 'Categoria eliminada con exito'
        redirect_to categories_url
      end
      format.js { head :ok }
    end
  end

  def destroy_nojs
    @category = Category.find(params[:id])
  end
end

la vista para la acción new quedaria algo asi:

1
2
3
4
5
6
7
8
9
10
-#app/views/categories/new.html.haml
%h2 Nueva categoria
- form_for(@category, :html => {:id => 'new_category'})  do |f|
  %p
    = f.label :name, 'Nombre:'
    = f.text_field :name
    = f.submit 'Agregar categoria'

%ul#categories
  = render :partial => @categories

y el partial para la categoria quedaria asi:

1
2
3
4
5
-#app/views/categories/_category.html.haml
%li[category]
  == #{category.name} 
  = link_to 'Editar', edit_category_path(category)
  = link_to 'Eliminar', {:action => 'destroy_nojs', :id => category}, :class => 'delete_category'

Entonces lo que faltaria seria ajaxificar el formulario y hacer algo despues que se creo la categoria, el archivo public/javascripts/application.js ahora quedaria asi:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$.fn.delete_link = function(msg_confirm, message){
  $(this).livequery("click", function() {
    if (confirm(msg_confirm)) {
      var ajaxTarget = $(this).attr("href").replace(/destroy_nojs\//,"") + ".js";
      $.post(ajaxTarget, { _method: "delete" },
       function(data){
         alert(message);
       });
      $(this).parent().fadeOut("slow");
    }
    return false;
  });
};

$(document).ready(function(){
  $("#new_category").submit(function() {
    $.post($(this).attr('action') + '.js', $(this).serializeArray(), null, 'script');
    return false;
  });

  $(".delete_category").delete_link("Seguro que desea eliminar esta categoria?", "Categoria eliminada con éxito");
});

Hasta aqui ya hemos ajaxificado el formulario, ahora falta realizar algo despues que se creo la categoria, lo que haremos es simplementemente agregar un nuevo elemento a la lista con identifciador #categories y agregarle un pequeño efecto, la vista para la acción create quedaria asi:

1
2
3
4
#app/views/categories/create.js.erb
$('#categories').append('<%= escape_javascript(render(:partial => @category)) %>');
$('#new_category_form  :text').val("");
$('#<%= dom_id(@category) %>').effect("highlight");

Y bien eso ha sido todo, no es nada dificil cuando se tiene un conocimiento adecuado de Jquery, el cual he estado obteniendo en estos ultimos dias y lo mas importante es que logras poner cada cosa en su lugar, el HTML donde debe estar, el CSS tambien y el Javascript igual, esto ayuda bastante cuando se termina la aplicación y hay que hacer mejoras o agregar nuevas cosas, espero que este articulo haya sido de utilidad a alguien.

Preguntas para contratar a un desarrollador ideal

Alexey Kovyrin uno de los desarrolladores de Scribd nos muestra algunas de las preguntas que ellos hacen para contratar a un desarrollador para este tipo de aplicaciones, osea aplicaciones que manejan un alto tráfico, esto nos sirve de mucho a los desarrolladores ya que nos permite saber en que estado se encuentran nuestros conocimientos y cuanto camino falta por recorrer para algún dia tomar una de estas posiciones.

Curso de git y github en 1 semana!

Me acabo de enterar algo tarde pero de todas maneras vale, el dia de mañana empezara un curso gratuito y muy bueno donde aprenderas cosas relacionadas a git y github, no dejes pasar esta oportunidad!

Curso excelente y gratuito de Ruby, JRuby y Rails

En la lista argentina de Ruby enviaron información sobre un excelente sitio donde oferecen varios cursos gratuitos y entre ellos enseñan Ruby, JRuby y Rails creo que esto le será de mucho provecho para aquellos que quieran iniciarse en este marvilloso lenguaje.

Detectando pais del usuario por IP

Obtener el pais del usuario a traves de su IP es una tarea realmente sencilla gracias a los servicios que proveen terceros para tal fin, pero lo que nos puede tomar tiempo es encontrar un servicio que sea decentemente fiable al momento de la detección del pais.

Como trabajan estos servicios?

Cada pais tiene un pool de rangos de IPs asignados para el, este pool esta en constante actualización supongo que es debido a que los usuarios de Internet van en aumento.

El navegador al momento de hacer una petición a un sitio envia ciertas cabeceras que es como un Hash que contiene información al estilo clave-valor, entre una de esas cabeceras se encuentra la ip del usuario quien visita el sitio, entonces al obtener la IP del usuario no queda mas que consultar contra ese pool de direcciones IP que tenemos y ver a que pais pertenece esa IP

Servicios que he probado y por tanto puedo recomendar

El primer servicio que probe se llama Find IP Address lo bueno que tiene este servicio es que te otorga el rango de IPs para el pais que tu eligas, pudiendo luego exportarlo a tu base de datos y realizar el trabajo de detección de país. El problema con este servicio es que no es tan fiable como parecia inicialmente, asi es que tuve que seguir “huaqueando”.

El otro servicio que probé y finalmente me convenció fue el de la empresa Maxmind, llegue a esta empresa por medio de la página de Mysql, que en algun lugar(en este momento no recuerdo) te da informacióm referente a tu IP y usa el servicio de esta empresa.

Ahora si al grano

Maxmind tiene un servicio denominado GeoIP Country el cual es de pago, pero tiene otro similar llamado Geolite la principal diferencia radica en la precisión 99.8% del servicio de pago contra 99.5% del gratuito, la verdad no se que tanto impacto pueda tener esta diferencia, pero he venido probando el Geolite y hasta ahora todo va de perlas.

Integrando el servicio con tu aplicación Rails

El servicio tiene disponibles “APIs para varios lenguajes:http://www.maxmind.com/app/api entre el cual esta nuestro favorito, pues bien a proceder con la integración:

Paso 1: Descargar la libreria escrita en C para el Geoip.

Luego procedemos con la instalacióm, lo cual es trivial para aquellos que vienen usando linux un par de meses:

1
2
3
4
5
tar -xvzf GeoIP.tar.gz
cd GeoIp
./configure
make
sudo make install

Paso 2: Descargar la libreria para Ruby.

Igualmente a lo anterior, en la consola lo descomprimimos y pasamos a la instalación:

1
2
3
4
5
tar -xvzf net-geoip-0.06.tar.gz
cd net-geoip-0.06
ruby extconf.rb --with-geoip-include=/usr/local/include
make
sudo make install

Si al momento de la instalación te sale algún error, entonces hay que editar el archivo Makefile y reemplazar una cadena que dice “Wall-g” por “Wall -g”, el error es simplemente por la falta de la separación mediante el espacio en blanco para los parametros.

Paso 3:

Finalmente tenemos que descargarnos la base de datos con los rangos de IPs en formato binario, igualmente procedemos a descomprimirlo y a copiarlo en algun lugar de nuestra aplicación:

1
2
gzip -d GeoIP.dat.dz
cp GeoIP.dat /ruta/a/tu/rails_app/db/

En este caso yo lo he copiado dentro del directorio db de mi aplicación, tu puedes copiarlo donde creas conveniente.

Paso 4:

Ahora crearemos un initializer donde declararemos una constante la cual servira de referencia para acceder a las funciones de la libreria:

1
2
#en config/initializers/geo_ip.rb
GEOIP = Net::GeoIP.open(File.join(Rails.root,'db','GeoIP.dat'), Net::GeoIP::TYPE_RAM)

Paso 5:

Finalmente podemos crear un filtro para tener disponible el codigo de pais del usuario en toda nuestra aplicación:

1
2
3
4
5
6
7
8
9
10
11
12
#en app/controllers/application.rb
class ApplicationController < ActionController::Base
  before_filter :current_country

  helper_method :current_country #por si quieres usarlo en tus vistas

  protected
  
  def current_country
    GEOIP.country_code_by_addr(request.remote_ip)
  end
end

Y bien, eso ha sido todo, como ven no es muy complicado poder saber el pais del visitante de nuestro sitio, espero que haya sido de utilidad este artículo.

Aqui estoy porque no me he ido

Asi es amables lectores, me he ausentado demasiado tiempo del blog y motivos he tenido, el principal es el trabajo y el secundario mi vida, creo que me tengo que dar mas tiempo aun para vivir el mundo real, pero bueno eso es tema muy aparte.

Lo importante es que he decidido volver a escribir en mi blog y en esta oportunidad de manera mas activa y con un sistema de blogs muy sencillo y extensible: Enki.

Bueno lo mio no es escribir este tipo de posts, me levanto, almuerzo y duermo pensando en Ruby, Rails y demas hierbas asi es que esten atentos que ahora si blogueare mas seguido.