Dagi3d v4

Programa de facturación en Ruby On Rails

Hace poco para optar a un trabajo tuve que realizar como prueba una aplicación en Rails que gestionase un listado de facturas junto a sus clientes. El caso es que decidí añadirle alguna cosilla más y hacerle una interfaz más aceptable y liberar el código por si a alguien le podía interesar.
La aplicación puede exportar a pdf(aunque hace falta java para esto) y el diseño del pdf es totalmente personalizable a partir de un documento xhtml y css.
De momento no tiene mucha cosa, pero la idea es utilizarlo e irlo ampliando conforme lo vaya necesitando ahora que empiezo con el tema del freelanceo.

Se puede ver en funcionamiento en http://facturails.dagi3d.net/ y se puede descargar directamente desde el repositorio subversion en http://svn.dagi3d.net/rails/facturails/trunk (bajo licencia MIT)

Sumando varios elementos de un array

Realizando una aplicación en Rails necesitaba sumar varios atributos de los objetos almacenados en un array. El problema es que cuando se trata de sumar un único atributo, se suele utilizar el método inject:

total = mi_array.inject { |sum, obj| sum + obj.value }

pero en este caso no me terminaba de convecer estar llamando al método inject tantas veces como atributos quisiera sumar, así que en teoría la solución pasaría por iterar sobre el array e ir sumando:

var1 = 0
var2 = 0
var3 = 0
...
mi_array.each do |obj|
  var1 += obj.value1
  var2 += obj.value2
  var3 += obj.value3
  ...
end

Funcionar, funcionaba, pero digamos que el código quedaba algo feo, así que para seguir trasteando, intenté encontrar una solución más 'ruby' y esto fue lo que hice:

class Array
  
  def accumulate(fields)
    
    results = fields.dup
    
    self.each do |obj|
      fields.each_key do |key|
        results[key] += obj.send(key) if obj.respond_to?(key)
      end
    end
    
    results
  end
end

Ahora bastaba con llamar al método accumulate sobre el array de objetos e indicar qué atributos quería sumar para obtener un hash con los resultados:

# la lista de objetos
foos = [
  OpenStruct.new(:foo => 1, :bar => 2, :foobar => 3),
  OpenStruct.new(:foo => 3, :bar => 4, :foobar => 5),
  OpenStruct.new(:foo => 5, :bar => 6, :foobar => 7)
]

foos.accumulate(:foo =>­; 0, :bar => 0, :foobar => 0) 
# {:foo=>9, :bar=>12, :foobar=>15}­

Comparar gemas instaladas en desarrollo y producción

A raíz de un post en el blog de Nicolás Orellana, donde mencionaba el problema de no tener sincronizadas las gemas instaladas en la máquina de desarrollo con las que deberían estar también en la máquina de producción, me animé a escribir una pequeña tarea para Capistrano que intentase resolver este problema. La idea en principio era sencilla, tan sólo habría que obtener la lista de gemas instaladas en local utilizando el comando 'gem list' tal como mencionaba Nicolás, hacer lo mismo en remoto, compararlas para ver las diferencias y luego ofrecer la posibilidad de instalar aquellas que no estuviesen en la máquina de producción.

Y esta fue la solución a la que llegué (de momento no hace ningún control sobre las versiones, pero si tengo un rato intentaré completarla):

task :compare_gems do
  
  def parse_gem_line(line, gems)
    if line =~ /^([[:alnum:]]+)\s\((.*)\)/
      gem = $1
      gems[gem] = []
      versions = $2.gsub(/\s/, '').split(",")
      gems[gem] << versions
    end
  end
  
  local_gems = {}
  remote_gems = {}
  
  local_gem_list = %x{gem list}
  local_gem_list.each_line { |line| parse_gem_line(line, local_gems)}

  run "gem list" do |channel, stream, data|
    parse_gem_line(data, remote_gems)
  end
  
  not_installed_gems = local_gems.dup
  
  not_installed_gems.delete_if {|key, value| remote_gems.has_key? key }
  
  puts "== Not installed gems =="
  
  not_installed_gems.each_key do |gem|
    
    puts "* #{gem}"
    puts "Install? [yN]"
    answer = STDIN.gets.chomp
    
    if answer.downcase == "y"
      run "gem install #{gem} -y" do |channel, stream, data|
        puts data
        if data =~ /^>/
          install_option = STDIN.gets.chomp
          channel.send_data "#{install_option}\n"
        end
      end
    end
  end
  
  puts "========================"
end

Generar documentos pdf con Rails y Flying Saucer

En la aplicación que estaba haciendo en RoR necesitaba generar documentos en formato pdf a partir de los formularios rellenados por el usuario. Había comenzado a hacerlos usando la librería para ruby PDF::Writer y aparentemente funcionaba bastante bien pero resultaba un infierno el tener que maquetar toda la presentación desde código, aparte de que no me gustaba mucho la idea de hacerlo así de cara a posibles cambios de la plantilla.
De casualidad, a través de un enlace en Devzone di con un árticulo sobre la generación de pdf's en java usando las librerías Flying Saucer e iText.
Flying Saucer es una librería para renderizar documentos xhtml y css 2.1 y que ahora trabaja de manera conjunta a iText, que sirve para generar documentos pdf.
Así que la opción de tratar de integrar esta librería en la aplicación para así convertir la vista generada desde Rails en el pdf, era más que tentadora.

Para hacerlo, lo primero era descargar la librería Flyin Saucer(que ya viene con iText incluida) y generar la aplicación en Java que se encargase de convertir el documento xhtml en pdf(está copiada casi tal cual de un ejemplo del artículo):

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import org.xhtmlrenderer.pdf.ITextRenderer;
import com.lowagie.text.DocumentException;


public class Xhtml2Pdf {

    /**
     * @param args
     */
    public static void main(String[] args) throws IOException, DocumentException {
       
        if (args.length != 2) return;
       
        String inputFile = args[0];
        String url = new File(inputFile).toURI().toURL().toString();
       
        String outputFile = args[1];
       
        OutputStream os = new FileOutputStream(outputFile);

        ITextRenderer renderer = new ITextRenderer();
        renderer.setDocument(url);
        renderer.layout();
        renderer.createPDF(os);

        os.close();
    }
}

Luego escribí una sencilla función en ruby que se encargase de ejecutar la clase hecha en java pasándole como parámetros el archivo de origen con el documento xhtml y el archivo de destino donde se generaría el pdf(lo único que tiene así de 'chicha' es que genera el classpath de manera dinámica):

def xhtml2pdf(input_file, output_file)
  
  java_dir = File.join(File.expand_path(File.dirname(__FILE__)), "java")
  jar_dir = File.join(java_dir, "jar")
  
  class_path = ".:#{java_dir}"
  
  Dir.foreach(jar_dir) do |jar|
    class_path << ":#{jar_dir}/#{jar}" if jar.match(/\.jar/)  
  end
  
  command = "java -cp #{class_path} Xhtml2Pdf #{input_file} #{output_file}"
  system(command)
end

Para poder utilizar la librería desde el controlador, tan sólo quedaba copiar el fichero en ruby dentro la carpeta lib/ del proyecto Rails, la clase en java en lib/java y los jar's necesarios para ésta, en lib/java/jar:

Y en el controlador tan sólo tenía que generar la vista y guardarla en un fichero, convertirlo en pdf y mandarlo al cliente:

require 'xhtml2pdf'

class FooController < ApplicationController
 
  def generate_pdf
        
    @document = Document.find(params[:id])
    
    xhtml = "/tmp/foo.xhtml"
    pdf = "/tmp/foo.pdf"
    
    File.open(xhtml, "w") do |file|
      file << render_to_string(:template => "pdf/document", :layout => "../pdf/pdf")
    end

    xhtml2pdf(xhtml, pdf)
    
    send_file pdf,
      :filename => "document.pdf",
      :type => "application/pdf"
  end
end

Y si quisieramos ir viendo en el navegador cómo va a quedar sin necesidad de generar el pdf, bastaría con comentar el contenido de esta función(o hacer una nueva) y generar la vista como hariamos normalmente:

render :template => "pdf/document", :layout => "../pdf/pdf"

Por último, comentar que para que Flying Saucer pueda acceder a las imágenes y hojas de estilo del documento xhtml, las rutas de estos elementos deben estar de manera absoluta apuntando a un recurso local(file://<rutadelfichero>) o bien de manera relativa a dónde se encuentre físicamente el xhtml, por lo que igual habría que hacer un helper que modificase la ruta de los recursos dependiendo de si se está previsualizando desde el navegador o se está volcando en disco.

Aunque PDF::Writer parecía funcionar bastante bien e iba más rápido ya que escribe directamente el pdf sin necesidad de parsear un documento xhtml y sus hojas de estilo, creo que en este caso compensa tener un pequeño híbrido en rails y java por el tiempo ahorrado en maquetar el pdf directamente desde ruby.

Bantha Studio abre sus puertas

Bueno, pues parece que al final el señor King George se ha animado a dotar de presencia en internet al proyecto bajo el que estaba firmando sus últimos trabajos: Bantha Studio.

De momento tiene abierto un blog donde hablará principalmente sobre diseño, cómics y toys y aunque su portafolio todavía no está visible, ya se pueden ver algunas de sus ilustraciones dentro del propio blog, con una estética bastante influenciada por todos los tebeos que se debió de mamar de pequeño.

Esperemos que a partir de ahora le salgan proyectos interesantes. ¡Larga vida al rey!

www.banthastudio.com