Dagi3d v4

Parche para acl_system

Ahora estoy desarrollando un programa de gestión con Ruby On Rails donde necesitaba usar algún sistema de control de privilegios dentro de la aplicación. Tras investigar un poco opté por usar acl_system, que parecía bastante sencillo de usar y era más que suficiente para lo que necesitaba, ya que permite restringir el acceso a los métodos del controlador y delimitar partes de la vista para usuarios que tengan un determinado 'rol' .
El único inconveniente era que si dentro del controlador quería preguntar por un rol en concreto para hacer cosas más específicas, tenía que hacer algo así:
if current_user.roles.map { |role| role.title }.include?("admin")
...
end
Aunque funcionaba perfectamente, pensaba que quedaría mejor si le añadía un pequeño parche al plugin acl_system que permitiese hacer algo del tipo
if current_user.has_admin_role?
...
end
Así que aprovechando el potencial de ruby, en unas pocas líneas de código conseguí dar con la solución:
module Dagi3d

  module RoleModel
    def self.included(mod)
      mod.extend(ClassMethods)
    end
    
    module ClassMethods
      
      def has_roles
        include InstanceMethods
      end
      
    end # ClassMethods 
    
    module InstanceMethods
      
      def method_missing(method, *args)
      
        if method.to_s =~ /has_(\w+)_role\?/
          self.roles.map {|role| role.title}.include? $1
        else
          super
        end
      end #method_missing
      
    end #InstanceMethods
  end
end
Para hacerlo andar, bastaría con añadir el módulo en la carpeta del plugin e incluirlo en el fichero init.rb:
require 'caboose/logic_parser'
require 'caboose/role_handler'
require 'caboose/access_control'
require 'caboose/role_model'
 
ActionController::Base.send :include, Caboose
ActionController::Base.send :include, Caboose::AccessControl
ActiveRecord::Base.send :include, Dagi3d::RoleModel

Y ya por último, llamar en nuestro modelo al método de clase 'has_roles':
class User < ActiveRecord::Base
  
  has_roles
  ...
end

Accesibilidad con Ajax y Ruby On Rails

Estaba desarrollando una página web con Ruby On Rails donde necesariamente tenía que utilizar Ajax para mostrar el contenido de las distintas secciones ya que ésta tenía un reproductor de audio hecho en Flash, por lo que el hecho de recargar la página entera suponía cortar la música cada vez que se cambiase de sección (o eso o utilizar iframes, así que la cosa estaba clara)

El 'problema' que me encontré con la función 'link_to_remote' es que por defecto no añade ninguna url en el atributo 'href' del enlace, lo que no sólo supone un problema para aquellos usuarios que no tengan javascript, si no a la hora de que los buscadores indexen el contenido, ya que en teoría no siguen los enlaces dentro de código javascript.

Para solucionarlo, tan sólo tuve que escribir una sencilla función en un helper que añadiese automáticamente al atributo 'href' la misma url a la que se iba a hacer la petición con Ajax:
module PublicHelper

def ajax_link(label, url = {}, options = {})

options.merge!({:href => url_for(url)})

link_to_remote label,
{
:url => url,
:update => "section_content",
:loading => "Element.hide('section'); Element.show('loading'); ",
:complete => "Element.hide('loading');"
},
options
end
end
Así, al llamar a la función ajax_link("enlace", :controller => "foo", :action => "bar"), se genería el código html <a href="/foo/bar" onclick=".." />

El siguiente incoveniente era distinguir en el controlador cuándo se estaba haciendo una petición con Ajax o se estaba pidiendo directamente la url para devolver sólo el trozo html con el contenido, o bien la página entera. Por defecto, Prototype, la librería utilizada en Rails para usar Ajax, realiza la petición a través del método POST, así que para decidir si se debía utilizar el layout tan sólo había que tener en cuenta el tipo de petición que se estaba haciendo:
class FooController < ApplicationController

layout "foo"

def bar
render(:action => "foobar", :layout => request.get?)
end
end
Otra opción hubiese sido mandar un parámetro adicional en la petición y luego comprobar en el controlador si éste existia y así saber si se trataba de una petición con Ajax o no.

Igual sería interesante hacer esto en forma de plugin e incluir alguna funcionalidad más como añadir el código javascript a los enlaces de manera no intrusiva.

Clases abstractas en ruby

Mientras seguía trasteando con ruby me percaté de que no existen determinadas características de la programación orientada a objetos que sí ofrece Java como son las clases abstractas. Así que como siempre, para seguir aprendiendo me puse a picar un poco de código para poder utilizar dicha funcionalidad en mis clases.

Como quería poder definir cualquier clase como abstracta de manera más o menos transparente, lo único que tenía que hacer era añadir un poco de código extra a la clase Class que aportase las siguientes funciones:
- Poder marcar una clase como abstracta.
- Poder definir dentro de la clase qué métodos serán abstractos.
- Lanzar una excepción en caso de instanciar una clase abstracta.
- Lanzar una excepción si se implementó un método abstracto dentro de la clase abstracta.
- Lanzar una excepción si no se implementaron los métodos abstractos heredados de la clase abstracta.

Y esta es la solución a la que llegúe: http://svn.dagi3d.net/rails/abstract_class/trunk/lib/abstract_class.rb

Para marcar una clase como abstracta, basta con llamar al método abstract_class y para definir un método como abstracto, a abstract_method :método:

class Foo
abstract_class
abstract_method :foo
end

# clase concreta
class Bar < Foo
def foo
end
end

Por si a alguien le interesa, el código está disponible en un repositorio de subversion en http://svn.dagi3d.net/rails/abstract_class/trunk/ y una gema ya preparada para instalar en http://dagi3d.net/temp/abstract_class-0.1.0.gem

Plugin para generar captchas

Hace unos meses implementé un sistema para generar captchas para el formulario de comentarios del blog y así evitar el spam que me estaba llegando. El “problema” de la solución a la que llegué era que no hacía ningún uso de las facilidades que ofrece Rails para crear plugins, por lo que la reutilización de todas las librerías se hacía un tanto ortopédica ya que obligaba a estar repitiendo código por todos lados. Por eso y para seguir trasteando con ruby, convertí todo el sistema en un plugin para RoR.

Para instalarlo basta con ejecutar el siguiente comando en nuestra aplicación Rails:
ruby script/plugin install http://svn.dagi3d.net/rails/rcaptcha/tags/rcaptcha-0.1.2

A continuación hay que llamar al método de clase ‘acts_as_captchable’ dentro de nuestro controlador. También se le pueden pasar parámetros para modificar la apariencia del captcha(son todos opcionales):

app/controllers/foo_controller.rb:

class FooController < ApplicationController

  acts_as_captchable :length => 5,  #número de caracteres del captcha
    :width => 300,  #ancho de la imagen generada
    :height => 30,  #altura de la imagen generada
    :step => 10, #ancho de las celdas de la rejilla de la imagen
    :font_color => 'black',  #color del texto. también admite valores hexadecimales
    :font_size => 25 #tamaño de la fuente

  def index
  end

  def validate_form
    render_text(valid_captcha_value?(params[:captcha_value]))
  end
end

Con el plugin también se crea automágicamente el helper ‘image_captcha_tag’ para pintar la imagen con el captcha. Hay que indicarle en el primer parámetro el nombre del controlador que se encargará de generar el captcha(al que hemos marcado como ‘captchable’). Opcionalmente se le puede pasar los atributos que queramos para el tag ‘img’ que se renderizará en la vista:

app/views/foo/index.rhtml:
<html>
  <body>
    <%= form_tag(:action => "validate_form") %>
      <%= image_captcha_tag("foo", :class => "captcha") %>
      <%= text_field_tag(:captcha_value) %>
      <%= submit_tag('enviar') %>
    </form>
  </body>
</html>

Finalmente, para comprobar si el valor enviado en el formulario coincide con el generado por el captcha, basta con llamar al método ‘valid_captcha_value?’ pasándole como parámetro el valor introducido en el textfield, tal como se indica en la clase FooController.
El resultado final con los valores por defecto sería algo así:

Mostrar el tamaño de un fichero de una manera más legible

Estaba haciendo una aplicación en rails donde necesitaba mostrar el tamaño del fichero que el usuario podía descargar. Ruby cuenta con el método File.size pero devuelve el tamaño en bytes y claro, que un archivo pese 3670016 bytes por ejemplo, no es que sea muy esclarecedor, así que añadí una función a la clase File que muestra el tamaño en la unidad más grande posible:

file_util.rb:

class File

  @@units = ['bytes', 'kb', 'mb', 'gb', 'tb']

  def File.readable_size(file_name)

    size = File.size(file_name).to_f

    pos = 0
    while size / 1024 >= 1
      size = size / 1024
      pos += 1
    end

    size = format("%.1f", size)

    return size.to_s + " " + @@units[pos] 
  end
end

Ejemplo de uso:
require 'file_util'

file_name = "foo" 
size = (1.5 * 1024 * 1024).to_i # 1.5 mb

#creamos un fichero de prueba con el tamaño indicado
File.open(file_name, "w") do 
  |file|
  size.times do
    file << 'x'
  end
end

puts File.size(file_name) # 1572864
puts File.readable_size(file_name) # 1.5 mb