Dagi3d v4

Cambiando la locale de una clase en Globalize2

En el último proyecto que estuve realizando con RoR necesitaba ofrecer soporte multiidioma para los modelos así que opté por utilizar Globalize2 ya que ya había estado trasteando con esta librería y me parecía muy cómoda de usar(aunque todavía le quedan algunas cosas por pulir).

El caso es que desde hace relativamente poco permite cambiar la locale de una clase en concreto sin necesidad de cambiar el idioma de toda la aplicación. Así se podría tener nuestra aplicación en un idioma determinado y trabajar con una instancia de un modelo en otro totalmente distinto.

El problema era que con la implementación actual, si se cambiaba la locale de una clase en concreto, se cambiaba automáticamente en todas las demás clases que tuvieran campos traducibles con Globalize2:­ 

­I18n.locale = 'es'
Post.locale = 'en'
Post.locale         # en
Category.locale     # en <- WTF?!

Esto sucede por la utilización de variables de clase y la implementación de éstas en Ruby­. Con Ruby, si se modifica el valor de una variable de clase que ha heredado de otra, cambiará en todas las demás clases que hereden de la misma:


class Polygon
  @@sides = 0

  def self.sides
    @@sides
  end
end

class Triangle < Polygon
  @@sides = 3
end

class Square < Polygon
­  @@sides = 4
end

Triangle.sides # 4 <- WTF?!­
Rails provee un mecanismo para solucionar esto a través de los métodos write_inheritable_attribute y read_inheritable_attribute, así que escribí un mini parche para este plugin de Rails. Aquí el commit del fork en Github

acts_as_flying_saucer: plugin para generar documentos pdf con Rails

flying-saucerParece que la cosa sigue yendo de reescribir código antiguo ya que a raiz de un comentario en  el post sobre ­cómo generar documentos pdf en una aplicación Rails con la librería Flying Saucer, he empaquetado el sistema en un plugin con lo que ahora resulta realmente sencillo poder convertir una vista en un pdf.
Además ya no hace falta preocuparse por las rutas de las hojas de estilos e imágenes(para escribir el pdf éstas tenían que apuntar a recursos locales) ya que cambiarán automáticamente dependiendo de si estamos renderizando la vista en el navegador o estamos guardando el pdf.

Para hacerlo funcionar basta con instalar el plugin:

­./script/plugin install git://github.com/dagi3d/acts_as_flying_saucer.git­­

y llamar al método de clase acts_as_flying_saucer en nuestro controlador. Entonces ya estará disponible el método render_pdf que admite las opciones de ActionController::Base#render. Además se puede indicar el nombre del fichero pdf que queramos generar así como mandarlo al cliente.

Más información en el repositorio de Github: ­http://github.com/dagi3d/acts_as_flying_saucer/

­­

Programa de facturación en Ruby On Rails II

Hace un tiempo escribí una aplicación bastante simple para generar facturas y aprovechando que salió hace poco la última versión estable de Rails, la modifiqué para hacerla funciona con la versión 2.2.x y ya de paso limpiar un poco el código. Se puede descargar desde github en http://github.com/dagi3d/facturails/ y la demo sigue estando en http://facturails.dagi3d.net/

Mi primer meme chispas

Aunque en el blog no suelo tratar estos temas, me parecía un poco feo no recoger el meme que me han pasado(ya me había hecho el sueco anteriormente) ya que esta vez me ha venido por partida doble(King George y mabarroso), así que aquí va:

1. Nombre completo.
Borja.

2. Por qué tienes ese nombre.
La verdad es que no tengo ni idea si tenía algún significado especial para mis padres. Lo que sí sé es que si no llega a ser por mi abuela me habría acabado llamando Jacobo ya que esa era la primera opción pero ella se negó en redondo porque decía que era nombre de mayordomo(eh, que lo dijo ella, no yo).

3. ¿Cuándo fue la última vez que lloraste?
Hace poco.

4. El pan te gusta ¿con qué?
Con casi cualquier cosa. Como mucho pan, diría que demasiado aunque últimamente me estoy cortando bastante. Si me descuido me puedo comer una barra de pan enterita yo solo. A veces creo que podría acabar comiéndome un bocadillo de pelos si se terciara.

5. ¿Te desabrochas los zapatos antes de sacártelos?
No porque ya los llevo desabrochados. Suelo llevar los zapatos sueltecicos y los cordones van por dentro y así no tengo que abrocharlos. Vago que es uno(aunque prefiero llamarlo optimización de recursos).

6. ¿A qué dos personas meterías en una habitación como la de Saw para que se aniquilasen entre ellas?
Así a bolapié, no odio a nadie tanto como para hacer algo así, pero si tuviera que elegir a algún personaje que me produce urticaría, creo que escogería a Mercedes Milá y Fedeguico Jiménez Losantos.

7. ¿A quién extrañas mucho?
A mi madre. Todos los días.

8. Lo último que comiste hoy.
Dos manzanas granny smith, uvas y un tazón de cereales para cenar.

9. ¿Qué estás escuchando en este momento?
El último de Guns n' Roses. No me gustan pero tenía curiosidad por saber cómo sonaba el disco más caro de la historia y ver qué habían hecho en los últimos 12 años, así que igual de rápido que llegó, acabará en la papelera de reciclaje.

11. ¿Cómo te cae la persona que te envió esto?
Estupendamente los dos.
Del señor King George destacar su sentido del humor y del señor Barroso su caracter afable. De hecho creo que es la única persona que no consigo imaginarme alzando la voz. Además son dos excelentes compis de curro y profesionales cada uno en lo suyo.

12. Comida favorita.
El arroz, ya sea en paella o arroz negre.

13. Última peli que viste en el cine.
Creo que fue la de Wall-E. Me encantan los trabajos de Pixar.

14. Postre preferido.
Todo lo que sea dulce me gusta pero diría que el que más son las natillas. De pequeño llegué a tomarme ocho en el buffete de un hotel.

15. ¿Qué libro estás leyendo?
En el cuarto de baño tengo 'The Rails Way' como libro de 'cabecera' y voy leyendo a ratos 'Hablemos de langostas', de David Foster Wallace.

16. ¿Qué hay en la pared de tu cuarto?
Un corcho con fotos y recuerdos y una diana eléctronica para jugar a los dardos.

17. ¿Qué viste anoche en la tele?
Nada, estaba haciendo el canelo por ahí.

18. ¿Cuál es la última comida que preparaste?
Merluza enterrada del libro 'Sigue cocinando en 22 minutos' del gran Julius para la hora de comer.

19. Un sitio para desaparecer.
Cualquier sitio con playa me vale.

20. Algo que no sepas hacer

Tirarme de cabeza en la piscina. Y no es algo de lo que me sienta precisamente orgulloso...

Por aquello de evitar la maldición que provocaría siete años de infelicidad si rompiese la cadena, le paso el meme a las siguientes personas:
Sowe
Josek
Edu
Adrián

 

Probando Globalize2

Vi que se había liberado hace poco una nueva versión de Globalize, el plugin para Rails que permite tener traducciones de nuestros modelos, así que me animé a probarlo. Ahora es compatible con Rails 2.2 y hace uso de la nueva api para la internacionalización de nuestras aplicaciones. Además, ahora en lugar de tener una única tabla con todas las traducciones, hay que crear una tabla adicional para cada modelo con los campos que queramos que sean traducibles(tal como hace el plugin translate_columns de Samuel Lown). Por ahora hay que escribir la migración a mano, pero se planea hacer un generator para automatizar este paso(si saco un momentillo igual me animo a escribirlo yo, que nunca hice uno).
De momento parece que funciona bastante bien, salvo un detalle que me tuvo entretenido un buen rato y que comento por si alguien se encuentra en la misma situación.

En todos los ejemplos de la documentación aparece que podemos indicar la nueva locale usando un símbolo, pero resulta que si lo hacemos así, no se puede recuperar luego el campo traducido(aunque sí que se guarda correctamente en la base de datos) y lo que hay que hacer es utilizar cadenas en su lugar.

Supongamos que tenemos la clase Post con la siguiente migración:

# Post
class Post < ActiveRecord::Base
  translates :title
end­
­­
# CreatePosts
class CreatePosts < ActiveRecord::Migration
  def self.up
    create_table :posts do |t|
      t.timestamps
    end
    create_table :post_translations, :force => true do |t|
      t.references :post
      t.string :locale
      t.string :title
      t.timestamps
    end
  end

  def self.down
    drop_table :post_translations
    drop_table :posts
  end
end

Podemos ver que si usa­­mos un símbolo para nuestra locale, el campo nos devuelve un nil, pero si usamos una cadena, devuelve el valor correcto:

gambitero:rails-test dagi3d$ ./script/console 
Loading development environment (Rails 2.2.0)
>> I18n.locale = :es
=> :es
>> post = Post.new(:title => 'titulo')
=> #
>> post.title
=> nil
>> I18n.locale = "es"
=> "es"
>> post.title = "titulo"
­=> "titulo"
>> post.title
=> "titulo"
>> I18n.locale = "en"
=> "en"
>> post.title = "title"
=> "title"
>> I18n.locale = "es"
=> "es"
>> post.title
=> "titulo"

También­ comentar que si tenemos traducido un campo en la locale por defecto(inicialmente ésta es 'en-US') e intentamos acceder a un atributo de una que todavía no tiene ningún valor asignado, se devolverá el valor de la locale por defecto. Si queremos evitar este comportamiento basta con comentar la línea 26 del fichero vendor/plugins/globalize2/lib/globalize/locale/fallbacks.rb para que no añada ésta a la lista de fallbacks(otra opción sería redefinir el constructor de la clase Globalize::Locale::FallBacks)

Por último decir que de momento la asociación que se crea entre nuestra clase y la generada por el plugin con las traducciones no se carga con 'eager loading' pero es una cosa que tienen prevista hacer.