Dagi3d v4

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

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

Plugin para aplicaciones multilenguaje en Rails

En breve me pondré con el desarrollo de un site que necesitará estar disponible en varios idiomas, así que como me apetecía investigar el tema del desarrollo de plugins en RoR, decidí hacerme uno que me solucionase la papeleta siendo totalmente consciente de estar reinventando la rueda, ya que ya existen soluciones más que probadas como Globalize o Gettext, pero el caso era cacharrear, y la verdad es que he aprendido unas cuantas cosillas sobre la programación dinámica en Ruby, así que creo que a veces vale la pena repetir cosas, al menos cuando se está aprendiendo.

De momento ya trabaja con textos estáticos(en formato yaml) y con los modelos, aunque esta parte es algo rudimentaria. He subido el código a un repositorio de subversion y para instalarlo en la aplicación basta con teclear
script/plugin install http://svn.dagi3d.net/rails/wahrig/tags/wahrig-0.1.0/

Para que el plugin funcione con un modelo, tan sólo hay que llamar al método ‘acts_as_translatable’ junto con los nombres de los campos que queremos que sean ‘traducibles’ mientras que cada uno de estos campos deberá tener su correspondiente texto traducido para las ‘locales’ que queramos tener. Y para cambiar de locale, hay que llamar a ResourceBundle.set_locale():

app/models/foo.rb

class Foo < ActiveRecord::Base

  acts_as_translatable :bar

end

test/fixtures/foos.yml

foo1:
  id: 1
  bar: ejemplo
  bar_en_US: example
  bar_de_DE: Beispiel

vendor/plugins/wahrig/test/wahrig_test.rb

require File.dirname(__FILE__) + '/../../../../test/test_helper'

class WahrigTest < Test::Unit::TestCase

  fixtures :foos

  def test_model
    foo = Foo.find(1)

    assert_equal foo.bar, 'ejemplo'

    ResourceBundle.set_locale('en_US')
    assert_equal @foo.bar, 'example'

    ResourceBundle.set_locale('de_DE')
    assert_equal @foo.bar, 'Beispiel'

  end
end

Para utilizarlo en el controlador para usar textos estáticos basta con llamar al método ‘act_as_translator’, y luego llamar a ‘load_bundle’ en el método que queramos dentro del controlador, indicando el nombre del fichero yaml, la variable a la que queremos asignar el contenido y la locale que queremos utilizar:

app/controllers/foo_controller.rb

class FooController < ApplicationController

  acts_as_translator

  def index
    load_bundle("test", :foo, 'es_ES')
  end

end

config/test.es_ES.yml

foo: foo - español
bar: bar - español

config/test.en_US.yml

foo: foo - english
bar: bar - english

Esto lo que haría sería cargar el fichero RAILS_ROOT/config/test.es_ES.yml y asignarle el contenido a la variable @foo, pudiendo acceder desde la vista a todos los campos definidos en el fichero:

app/views/foo/index.rhtml

<%= @foo['foo'] %>
<%= @foo['bar'] %>