lunes, 29 de febrero de 2016

Bundle exec

Verás que a veces aparece la secuencia "bundle exec" delante de los comandos que usamos para gestionar aspectos de una aplicación Ruby on Rails. Esto es debido a que queremos que todo ocurra en el contexto de las gems y las respectivas versiones que nos interesan. Estas son las declaradas en el Gemfile.

La localización del fichero Gemfile, puede venir determinada de muchas maneras. Te recomiendo que visites los enlaces siguientes:


Si no quieres leer los enlaces que te sugiero, te propongo una simplificación: considera que el fichero Gemfile está en el directorio desde el que estás trabajando o donde apunta la variable de entorno BUNDLE_GEMFILE.

Al hilo de esto, quiero recordarte que debes tener en cuenta que:

  • En un sistema pueden haber instaladas varias versiones de una misma gema. Usando bundle exec delante de la invocación a tu script te aseguras de que se usan las versiones que en el Gemfile se indican
  • Es posible que en algunos casos te funcionen las aplicaciones con cualquier versión de la gema que tengas instalada, pero el mantenimiento y despliegue rigurosos de la aplicación requieren que hagas un esfuerzo por delimitar con qué versión de gemas estás trabajando bien y declararlas en el Gemfile.
  • "bundle install" se encargará de recrear, instalando lo necesario, el entorno de tu aplicación.
  • También sirve para aplicaciones o scripts Ruby sin Rails, de manera que tiene sentido lanzar un script ruby con "bundle exec" y se usarán las gemas previstas en el "Gemfile".

sábado, 27 de febrero de 2016

Ruby sin rails: importar datos en sqlite3

El ejemplo propuesto en este post permite importar un fichero de texto, donde suponemos que cada linea se inserta directamente en la tabla. Los id consecutivos se calculan manualmente por lo que al principio se obtiene el mayor de la tabla.

Observa que:
- Se mete en un bloque que captura posibles excepciones
- En cuanto a la sentencia ten en cuenta que lo correcto es usarla parametrizada para evitar inyección de SQL y para favorecer la reutilización que algunos motores de base de datos hacen de sentencias precompiladas.
- Las sentencias que hacen "FETCH" de datos como las SELECT deben ser cerradas.
#!/bin/ruby
require 'sqlite3'

begin
    db = SQLite3::Database.open "/var/www/rails/muytest/db/development.sqlite3"
    puts db.get_first_value 'SELECT SQLITE_VERSION()'

    stm = db.prepare "SELECT max(id) as id FROM items"
    rs = stm.execute
    row = rs.next
    id = row[0].to_i
    stm.close

    
    sentencia = "INSERT INTO items(id, data) VALUES(?,?);"

    File.open('fichero.txt', 'r') do |f1|
       while linea = f1.gets
           id = id+1
           stm = db.prepare sentencia
           stm.bind_params(id,linea)
       end
    end
end


rescue SQLite3::Exception => e
    puts "Exception occurred"
    puts e
ensure


viernes, 26 de febrero de 2016

Ruby sin rails: uso de gemas


Las gemas son los componentes con los que ruby extiende la funcionalidad. Los podéis concebir como clases, paquetes, extensiones, etc.. La cosa es añadir componentes sobre los que construir nuestras aplicaciones.

Hay gente que en sus primeros pasos queda impresionada por la potencia de Rails y piensa que las "gems" le son propias, pero desconocen que esas gemas pueden ser usadas también cuando hacemos un programa o script, es decir, fuera de Rails.


¿ Dónde están las gemas ?

Preguntamos por el environment del comando "gem" y nos indica la ubícación del depósito de gemas:
$ gem env
RubyGems Environment:
- RUBYGEMS VERSION: 1.8.23
- RUBY VERSION: 1.9.3 (2013-11-22 patchlevel 484) [x86_64-linux]
- INSTALLATION DIRECTORY: /var/lib/gems/1.9.1
- RUBY EXECUTABLE: /usr/bin/ruby1.9.1
- EXECUTABLE DIRECTORY: /usr/local/bin
- RUBYGEMS PLATFORMS:
   - ruby
   - x86_64-linux
- GEM PATHS:
   - /var/lib/gems/1.9.1
   - /home/user/.gem/ruby/1.9.1
   - /usr/share/rubygems-integration/1.9.1
   - /usr/share/rubygems-integration/all
- GEM CONFIGURATION:
   - :update_sources => true
   - :verbose => true
   - :benchmark => false
   - :backtrace => false
   - :bulk_threshold => 1000
- REMOTE SOURCES:
   - http://rubygems.org/

El directorio /var/lib/gems/1.9.1 de forma genérica y el resto de los GEM PATHS pueden albergarlas. Las gemas estarán accesibles desde cualquiera de esas rutas.

Instalación de gemas
$ gem install mysql2
Si queremos instalar una versión concreta tenemos el modificador "-v"
$ gem install mysql2 -v 0.3.14

La ubicación será una de las anteriores. Depende de si eres el usuario root o no. El listado completo de gemas que un usuario tiene accesibles se puede obtener con el siguiente comando:
$ gem list --local

*** LOCAL GEMS ***

mysql2 (0.4.3, 0.3.16, 0.3.14)
actionmailer (4.0.3, 4.0.2)
actionpack (4.0.3, 4.0.2)
[...]
Si queremos eliminar una versión concreta de una gema
$ gem uninstall mysql2 -v 0.3.14

Uso de una gema en un script
El uso de la gema es tan sencillo como incluir "require" y el nombre de la gema.
#!/usr/bin/env ruby

require 'mysql2'
# Uso de la gema...

jueves, 25 de febrero de 2016

Gestión de rutas en Rails

Seguro que te has preguntado por los automatismos que Ruby on Rails tiene por todos los rincones del framework. Una de las primeras que llaman la atención son los nombres abreviados de las rutas tipo "new_user" o "edit_item". Cuando se está empezando con Rails, al poco de modificar lo que viene de serie con los scaffold, nos solemos encontrar con un batiburrillo de links con nombres de ruta como los anteriores y otros más manuales e improvisados o bien con una cadena fija o bien indicando :controller, :action, :id, etc... Vamos a intentar arrojar algo de luz a todo a la forma en que funcionan las rutas y los alias de cada una de ellas. 

Lo primero es que debemos conocer el contenido del fichero config/routes.rb. En este fichero se declaran las rutas o más bien el esquema de rutas que sigue Ruby on Rails para saber a qué método y de qué controller hay que redirigir la petición. Un error típico de una ruta no contemplada es el siguiente:
ActionController::RoutingError (No route matches [GET] "/items/0/unkownroute"):
Si miras tu config/routes.rb, un contenido típico sería del tipo:


Myrubyapp::Application.routes.draw do
    resources :items
    resources :users

    match '/users/:id/logout', :to => "users#logout" , :via => 'get', :as => 'user_logout'
    match "/users/:id/login", :to => "users#login" , :via => 'get'
    match "/users/:id/login", :to => "users#login" , :via => 'post'
    match "/users/:id/change_pass", :to => "users#change_pass"  , :via => 'post'
    
    #...

    get '/items/:id/preview', to: 'items#preview', as: 'preview_item'

    #...

    root :to => "items#index"

end
Primero lo primero 
¿ Adónde se encamina a un visitante que no sabe a donde va ? A donde indique la linea que comienza con "root". Cuando se crea una aplicación el primer controller puede que sea el destinatario de la URL root pero podemos cambiar facilmente este comportamiento editando esta linea. 

Sintaxis
Vemos 2 tipos de sintaxis: las lineas que empiezan por match y las que empiezan por "GET", "POST", etc... Son equivalentes. Sirva como ejemplo de la equivalencia el que las 2 siguientes lineas darían el mismo resultado:
match "/items/:id/preview", :to => "items#preview"  , :via => 'get', as: 'preview_item'
get '/items/:id/preview', to: 'items#preview', as: 'preview_item'

La parte mágica
Ya habrás adivinado que esos nombres mágicos ("edit_item", "new_user", etc..) los podemos crear con el atributo :as.

IMPORTANTE: el estandar HTTP
Un programador web debe ser consciente de los distintos métodos posibles de interacción entre el cliente y el servicio web. Se suele frivolizar con ello y se usa en demasía el GET para todo. Tampoco quiero ser purista aquí, aunque hay que serlo si se puede, pero qué menos que no usar GET cuando está estrictamente desaconsejado e incluso prohibido. 

Todo el mundo sabe como funcionan los buscadores. Van visitando enlaces y navagando de aquí para allá para realizar una reconstrucción de los contenidos de la web. No olvides qué usan el método "GET"!!!!. ¿ Qué pasa si pones una operación de escritura en tu sistema a tiro de GET ? Exacto, te van a bondardear y sin querer van a modificar el estado de tu sistema. El tema es importante y da para otra entrada en el blog. Me conformo de momento con que sepas que ninguna operación que suponga cambios del tipo que sea debe atender a una invocación con GET.
Me remito al fichero config/routes.rb donde verás que hay urls que se sirven indistintamente del método HTTP de invocación GET y POST
    match "/users/:id/login", :to => "users#login" , :via => 'get'
    match "/users/:id/login", :to => "users#login" , :via => 'post'
    match "/users/:id/change_pass", :to => "users#change_pass"  , :via => 'post'



otros solo para GET
get '/items/:id/preview', to: 'items#preview', as: 'preview_item'

y otro solo para POST

    match "/users/:id/change_pass", :to => "users#change_pass"  , :via => 'post'


¿ Cómo queda el resultado ?
El resultado ya te lo puedes imaginar, ¿ no ?. Ah, que no te lo imaginas. No pasa nada para eso está el comando siguiente:
$ bundle exec rake routes
          Prefix Verb   URI Pattern                                   Controller#Action
            items GET    /items(.:format)                              items#index
                  POST   /items(.:format)                              items#create
         new_item GET    /items/new(.:format)                          items#new
        edit_item GET    /items/:id/edit(.:format)                     items#edit
             item GET    /items/:id(.:format)                          items#show
                  PATCH  /items/:id(.:format)                          items#update
                  PUT    /items/:id(.:format)                          items#update
                  DELETE /items/:id(.:format)                          items#destroy
[...]

Creo que para una primera aproximación a las rutas en Rails no está mal.

miércoles, 24 de febrero de 2016

Generar un docx desde Rails

Una de las bondades de tener bases de datos y aplicaciones web que los gestionen es que aquellos pueden ser presentados de infinidad de formas. Pensemos por ejemplo en la generación de un informe que enviar a un cliente, presupuesto, etc... Los formatos estrella son PDF y documento de texto tipo DOC/DOCX (para Word o Libreoffice). En este post quiero dar unas directrices sencillas para que cualquiera pueda generar un documento tipo DOC o DOCX desde su aplicación Rails. Básicamente vamos a generar el documento y enviarlo al cliente web.


Para ello usaremos la gema "Caracal". La documentación oficial la puedes encontrar a partir de la URL de github https://github.com/trade-informatics/caracal.

Como es habitual en Rails, la incluimos en el Gemfile y ejecutamos el bundle install.


Generación de documento word

En la documentación de la gema hay ejemplos suficientes. Aquí muestro un ejemplo adaptado que podéis incluir tal cual en vuestro controller

def genera_word
  @items = Item.all
  Caracal::Document.save 'example.docx' do |docx|

    #Tamaño de página 
    docx.page_size do
          width   12240     # page width in twips.
          height  15840     # page height in twips.
    end

    #Fijamos los márgenes
    docx.page_margins do
      left    720     # left margin in twips.
      right   720     # right margin in twips.
      top     1440    # top margin in twips.
      bottom  1440    # bottom margin in twips.
    end

    #Colocamos el numero de página
    docx.page_numbers true do
      align :right    # accepts :left, :center, and :right.
    end
  
    #Cambiamos la fuente por defecto
    docx.font do
      name 'Droid Serif'
    end
  
    docx.h1 'Listado de items'
    cuenta = 0
    @items.order(:orden).each do |i|
      cuenta +=1
      docx.page if (cuenta%20==0)  #Página cada 20 lineas 
      docx."Item #{i.name}"
      docx.p  #Linea en blanco
    end
  end #Caracal
end #genera_word

Si has usado el nombre de función que te he propuesto acuérdate de incluir la ruta en config/routes.rb.

Descarga en el cliente

Tan sencillo como usar send_file. El método invocado en el controller contendrá algo como lo siguiente que envía el fichero al cliente.

def get_info_word
  #....

  send_file("example.docx" ,
     :filename      =>  "example.docx",
     :type          =>  'application/msword')
end
Ni qué decir tiene que send_file tiene diferentes opciones para optimizar la descarga. Aquí hemos mostrado la formulación básica.

Muchos lo tenéis claro, pero por si acaso, me comprometo mostrar en otro post como hacer esto mismo desde un script RUBY obviando el framework Ruby on Rails.