martes, 25 de octubre de 2011

Creando PDF's con PRAWN

Si quieres crear informes o documents PDF's a demanda (dinamicamente) y aún no has escogido ninguna solución, no dejes de echarle un vistazo a PRAWN.

Simples pasos para la instalación:
  1. Editar el fichero "gemfile" y añadir "gem 'prawn' " 
  2. Linea de comandos y "bundle install"
  3. En "app" creamos una carpeta "app/reports" donde guardaremos nuestros generadores de PDF's
  4. En "app/reports" creamos una caperta "app/reports/images" donde guardaremos las imágenes que añadamos a nuestros PDF's
  5. Editamos nuestro fichero "application.rb" y añadimos la carpeta que hemos creado.
    config.autoload_paths << "#(Rails.root)/app/reports" #PDF custom generators
  6. Bajarnos el manual de http://prawn.majesticseacreature.com/manual.pdf y ponerlo en "doc/prawn_manual"
¿Cómo creamos nuestro primer report? 
  1. Creamos nuestro generador PDF en "app/reports/products_report1.rb" y "app/reports/products_report2.rb"
  2. Lo rellenamos con algún ejemplo dumy
    Ejemplo1:

    Ejemplo2:
  3. Creamos el punto de entrada en el controlador "app/controller/products_controller.rb"
  4. Añadimos la ruta en el "config/routes.rb"
  5. Añadimos un botón o link al informe en "app/views/products/index.html.erb"
    <%=link_to 'report1',  print1_products_url%> -
    <%=link_to 'report2',  print2_products_url%> <br />
Des de aquí es solo ampliar los generadores que hemos creado, para que mediante el controlador vaya renderizando la información que nos interese.

Para revisar el proyecto de ejemplo en profundidad puedes ir a https://github.com/micues/prawn_test

También te será útil revisar-te el manual que hemos adjuntado en la documentación para ver más en detalle que objectos y funciones puedes utilizar con prawn.

    viernes, 21 de octubre de 2011

    Ubuntu RoR + postgresSQL 9 -> sobre windows

    Si tu entorno "natural" es windows, pero tu desarrollo es rails, hay algunas situaciones donde te puede interesar tener una ubuntu con rails funcionando:
    • Para probar el sistema en un entorno parecido al de producción (en producción windows + rails está prohibido).
    • Para las gem que no se pueden compilar con el DevKit o no tienen versión "-x86-mingw32"(pre-compilada).
    • Para aumentar la velocidad en las pruebas en el servidor de desarrollo (sobretodo arranque)
    • Para aumentar la velocidad en los test (esta es mi principal)
    Para conseguirlo unos simples pasos:
    1. Descargamos virtual box (porqué es gratuito multi plataforma)
      https://www.virtualbox.org/wiki/Downloads
      I escogemos VirtualBox 4.1.x for Windows hosts x86/amd64
    2. Instalación dummy de virtual box (siguiente-siguiente)
    3. Descargamos la última versión de ubuntu
      1. Vamos a http://www.ubuntu.com/download/server/download
      2. Seleccionamos la versión a descargar. Mi recomendación la última LTS (Long-time-suport) que seguramente será la que tengamos en el servidor.
        Las versiones LTS (Long Term Support), que se liberan cada dos años, reciben soporte durante tres años en los sistemas de escritorio y cinco para la edición orientada a servidores
        Fuente: http://es.wikipedia.org/wiki/Ubuntu
      3. Guardamos la iso en un lugar conocido.
    4. Creamos la máquina virtual. Inicio -> Ejecutar-> "Oracle VM VirtualBox" y le damos a "New" o CTRL+N. A partir de aquí es un wizzard, nombre, versión... ram, disco, que tener en cuenta:
      1. Sistema operativo igual al que vamos a instalar Linux y ubuntu amb64 o ubunty 32 bits i386
      2. Disco tipo VDI (virtual box disk image) y "fixed size" en lugar de "dynamically allocated", para mejorar la velocidad.[recomendación]



        Si por equivocación le damos a cancelar al wizzard siempre podemos añadir la imagen des de "Devices" -> "CD/DVD's"

    5. Instalar el sistema operativo. Seleccionamos la máquina instalada y le damos al botón "Start" o "Run" y la primera vez no salé un wizzard. En la segunda pantalla del wizzard podemos escoger montar una unidad con la imagen que hemos bajado y de esta forma nos arranca la máquina con el wizzard de instalación de ubuntu.



    6. Instalación dummy de ubuntu (siguiente-siguiente)



      Del wizzard solo hace falta cambiar el teclado al que tengamos y escoger un nombre de usuario y contraseña.

    7. Para instalar rails (tiene que ser en una instalación limpia), linea de comandos y
      wget --no-check-certificate https://raw.github.com/joshfng/railsready/master/railsready.sh && bash railsready.sh
      + información en https://github.com/joshfng/railsready

      El script nos pregunta si queremos RVM (Ruby Version Manager) que nos permitiría en un futuro instalar en la misma máquina otra version de ruby que no sea la 1.9.2. Es muy recomendable trabajar en RVM pero si no lo ves claro (o quieres la máquina lo más simple posible) siempre puedes crear otra máquina virtual y instalar la versión de ruby superior cuando la necesites.

      Para que funcione necesitamos tener internet accesible des de la máquina virtual.

    8. Para instalar postgresSQL, linea de comandos y
      1. sudo apt-get install python-software-properties
      2. sudo add-apt-repository ppa:pitti/postgresql
      3. sudo apt-get update
      4. sudo apt-get install postgresql

      5. ¿Por qué? Porqué ubutnu no puede dar soporte a todas la versiones de PostgresSQL y la oficial es la 8.2 (a noviembre 2011), en futuras versiones se irá actualizando. Entonces para evitar tener que bajarnos la versión de la página oficial http://www.postgresql.org/download/ y compilarla por nosotros mismos, lo que hacemos es añadir un segundo respositorio de software (market / app store) y bajarnos la versión des de allí. 
        (http://www.dctrwatson.com/2010/09/installing-postgresql-9-0-on-ubuntu-10-04/)

    9. Des de aquí la instalacón es igual que en windows 
      1. linea de comandos y "gem install rails"

    En este punto deberías tener una máquina preparada para desarrollar, recuerda de realizar la performance / tuning básica al postgres (de serie viene preparada para un 386) y instalar GIT.

    Una buena solución que se integra muy bien con Windows es utilizar las "shared folders" de la virtual box, las encontrarás en "Devices" -> "Shared Folder" y te permiten mapear tus carpetas de windwos en ubuntu.

    Una última recomendación para VirtualBox es cambiar en "Devices" -> "Network Adapters" la conexión de "Nat" a "Bridge" (para que la máquina sea visible des de fuera) y instalar la "Guest additions" que encontramos en "Devices".

      miércoles, 19 de octubre de 2011

      Zona horaria en rails

      Si trabajas con fechas y horas y tienes configurada una zona horaria esto te puede interesar.

      Cuando inicies un proyecto en rails recuerda modificar:
      /config/application.rb

      Y substituir
      # config.time_zone = 'Central Time (US & Canada)'
      Por
      # More timezones on http://apidock.com/rails/TimeZone
      config.time_zone = 'Madrid'


      ¿Por qué no sumar +2UTC?

      Porqué en la zona horaria de Madrid el más n varía dependiendo del horario de verano y invierno, con lo que no podemos solo sumar n horas, tenemos que especificar la zona horaria, la cual ya gestiona la diferencia dependiendo del día del año.

      ¿Y si vivo fuera de España?

      Revisa http://apidock.com/rails/TimeZone y busca tu zona horaria.

      ¿Como detecto que la hora está mal?

      Dependiendo de la configuración del equipo o servidor, y de como nuestros usuarios entre las fechas, es posible que no nos demos cuenta, con lo que la forma más fácil es crear un registro y revisar las columnas "created_at" y "updated_at" y ver si coinciden con las actuales.

      Infraestructura con GIT y varios entornos

      De la creación a la actualidad.

      En la primera iteración, dios creó la programación y el primer programador programaba "a pelo" contra el entorno de producción.
      Todo funcionaba bien, hasta que un lunes, después de tener a nuestro programador trabajando dos días seguidos sin descanso, los usuarios vieron les cambios y le pidieron que los deshiciera. El pobre programador no sabía que hacer, porqué no recordaba que "coño" había tocado y el chico de sistemas estaba de vacaciones, con lo que no nadie cambiaba la cinta de la copia de seguridad, ergo no había copia.

      En la segunda iteración, dios se apiadó de el, y creó el entorno de producción, donde el desarrollador pudiera trabajar durante meses sin afectar a producción, como era un dios generoso, le facilito también un sistema de control de versiones, entonces creo GIT (subversión, SVN...), de esta forma podía hacer prototipos, desplegar en producción, subir y bajar de versión, cuando quisiera.

      Todo funcionaba bien y había mucho trabajo, pero a veces nuestro cowboy solitario tocaba partes del código y otras partes dejaban de funcionar.


      En la tercera iteración dios creo los test automatizados y el entorno de test.
      Todo funcionaba bien, y había mucho trabajo con lo que decidieron coger un segundo programador. Todo iba fenomenal, estaban compenetrados, bueno casi, excepto cuando los dos modificaban partes comunes, o cuando actualizaban el software de desarrollo, eran pequeños problemas que pasaban los test en la máquina de cada programador (cuando todos se acordaban de pasarlos), pero que al unirse en desarrollo no funcionaban.

      Dios lo vio y en la cuarta iteración decidió crear el ángel Jenkins, dueño y amo del servidor de integración continua, el cual siempre estaba atento a los nuevos cambios del GIT, los bajaba y volvía ha ejecutar los test y en caso de error se lo comunicaba al equipo de desarrollo.
      Todo funcionaba bien, hasta que llego el día en que su aplicación empezó ha comunicarse con otras aplicaciones y sistemas, los usuarios estaban integrados con el sistema de recursos humanos, la comunicación con el servidor de correo que sistemas había instalado, la explotación de datos con el DWH corporativo que se había comprado, había muchas cosas que estaban fuera de la lógica y los test de programación.

      Nuestro buen dios en su quinta iteración lo vio y decidió crear los MOCKs (objetos simulados) para que los desarrolladores y el ángel Jenkins pudieran probar el funcionamiento, así como también creó el entorno de pre-producción para que sistemas y el usuario final pudieran probar las funcionalidades en un entorno separado del de producción.

      Todo funcionaba bien, la empresa era un éxito y nuestro software estaba triunfando, con lo que un día decidieron contratar a un comercial para vender "la solución" a otros clientes, pero había un problema que le íbamos a enseñar a nuestros clientes?

      Era sábado y dios estaba de compras en su sexta iteración, con lo que decidió comprar un servidor, el servidor DEMO, donde el comercial podría hacer sus demos sin datos de los clientes y los desarrolladores podrían ver la última versión estable de todo el software (no solo del producto donde ellos trabajaban), de esta forma podría proponer mejoras y coger ideas de otros equipos.


      Era la séptima iteración y dio decidió descansar y contemplar su obra.

      sábado, 15 de octubre de 2011

      Como: crear un nuevo entorno en ROR?

      Hay varios escenarios donde te puede interesar crear un nuevo entorno (environment):
      1. Crear un entorno de pre-producción con variables a otros sitems de pre-producción.
      2. Tener un servidor Demo constantamente operativo.
      3. Tener dos entornos de producción distintos (dos clientes).
      Para hacerlo: ejemplo "pre_production"
      1. Copiamos de "config\environments\production.rb" a "config\environments\pre_production.rb", editamos el fichero y modificamos la variables que sean distintas.
      2. Editamos el fichero "config\database.yml", copiamos el bloque "production" y lo renombramos a pre_produciton
      3. Subimos al GIT
      Para probarlo
      1. Tenemos que ejecutar los trabajos rake con en el nuevo enriorment, ejemplo para migrate
        rake db:migrate RAILS_ENV=pre_production
      2. Para iniciar el servidor:
        rails server -e pre_production
      Extra (que tener en GIT y que no).
      1. Como parte de la configuración del entorno, recomiendo quitar del GIT el "database.yml" y en su lugar añadir el "database.yml.example" (que deber ser una copia del database.yml") de esta forma cada desarrollador puede tener una base de datos distinta y/o un usuario y contraseña específico.
      2. Los mismo que en el punto anterior se aplica a "config\environments\development.rb"
      3. En el caso de tener de "config\environments\production.rb" si tenéis dos entornos de producción y vais a tomar otras estrategias, pensad en mantener el proceso lo más automatizado posible y evitar las modificaciones a mano para el despliegue.

      Datepicker and timepicker more friendly

      Si tu usuarios se quejan de tener estás cajas:
       Y te reclaman un sistema más "moderno" esto te puede interesar.

      Problemas identificado de estás cajas:
      1. No hay garantía que la fecha se haya introducido correctamente. Y si no se introduce correctamente (ejemplo 30/02/2011) la base de datos la guardará como nulo (por defecto sin avisar).
      2. Es lento (desplegar y seleccionar requiere de 5 a 10 segundos, hacer la prueba con vuestros usuarios) 
      3. No tenemos forma de orientarnos,  la fecha seleccionada es hoy?... el martes que viene? ...
      4. Estamos informando un solo campo (fecha o fecha con hora) pero tenemos de 3 a 6 desplegables, con lo que  inconscientemente le estamos dando un peso (o importancia) mayor del que debería.
      5. Las 3-6 cajitas con sus separadores, sus flechas desplegables y su contenido ocupan un espacio que podríamos simplificar o ocupar con otro contenido.
      Solución propuesta:
      1. Convertimos los campos de "form_field_on" o "form_field_at" a "text_field".
      2. Añadimos un selector que nos facilite la vida.
      Resultado (desplegado):


      Como lo conseguimos:
      Si vas a trabajar con formatos de fecha no americanos, es posible que antes de empezar te interese adaptarlo (http://railsdynamics.blogspot.com/2011/09/cambiando-el-formato-de-fecha.html).
      Para los objectos date (de aquí en adelante "_on") tenemos el DatePicker de JQueryUI http://jqueryui.com/demos/datepicker/
      Para los objetos time (de aquí en adelante "_at") tenemos  el addon TimePikcer http://trentrichardson.com/examples/timepicker/ o https://github.com/trentrichardson/jQuery-Timepicker-Addon

      Pasos:
      1. Colocamos los javascript
        app/assets/javascripts/jquery-ui-1.8.16.custom.min.js
        app/assets/javascripts/jquery-ui-timepicker-addon.js
      2. Si queremos internacionalizar también añadimos los javascript de I18N
        app/assets/javascripts/jquery-ui-i18n.js
        app/assets/javascripts/jquery-ui-timepicker-addon.js
      3. Añadimos el CSS para que se vea correctament
        app/assets/stylesheets/jquery-ui-timepicker-addon.css
      4. Extendemos la clase form helper para añadir dos nuevos métodos: form_field_at y form_field_on
        app/helpers/form_helper.rb
        module FormHelper
          def self.included(base)
            ActionView::Helpers::FormBuilder.instance_eval do
              include FormBuilderMethods
            end
          end

          module FormBuilderMethods  
            def form_field_at(method, default_value = nil)
              script = '<script>'
              script << ' $(function(){$( "#' + @object_name.to_s + '_' + method.to_s + '" ).datetimepicker({ showOn: \'button\' });});'
              script << '</script>' + "\n"
                  
              value = @object.send(method).to_s
              value = default_value.to_s if value.blank?
            
              form_field_at = @template.text_field @object_name, method,
                    :value => value , :size => 16, :ondblclick => 'this.value="' + Time.now.to_s + '"'
            
              script.to_s.html_safe + '  ' + form_field_at.to_s.html_safe
            end

            def form_field_on(method, default_value = nil)
              script = '<script>'
              script << ' $(function(){$( "#' + @object_name.to_s + '_' + method.to_s + '" ).datepicker({ showOn: \'button\' });});'
              script << '</script>' + "\n"
                  
              value = @object.send(method).to_s
              value = default_value.to_s if value.blank?
            
              form_field_on = @template.text_field @object_name, method,
                    :value => value , :size => 10, :ondblclick => 'this.value="' + Time.now.to_date.to_s + '"'
            
              script.to_s.html_safe + '  ' + form_field_on.to_s.html_safe
            end  

          end
         
        end
      5. Cambiamos en la vista las cajas de texto de text_field a form_field_at y form_field_on
        app/views/events/_form.html.erb
          <div class="field">
            <%= f.label :start_at %><br />
            <%= f.form_field_at :start_at %>
          </div>
          <div class="field">
            <%= f.label :finish_on %><br />
            <%= f.form_field_on :finish_on %>
          </div>
      Con esto tendremos los date y time picker funcionando, ahora si queremos añadir validación también tenemos que:
      1. Añadir un helper para la fecha
        app/helpers/validation_helper.rb
        module ValidationHelper
          def conversion_to_date_at(method,field_at_before_type_cast)
            errors.add(method, ' (' + field_at_before_type_cast + ') formato incorrecto ' + DATE_AT_FORMAT_EXPLANATION + ' o valor fuera de rango') if is_invalid_date_at(field_at_before_type_cast)
          end

          def is_invalid_date_at(field)    
            if field == nil or field == ''
              false   #Null is not an invalid date. Valid date!
            else
              begin
                if defined?(DATE_AT_FORMAT)
                  format_to_validate = DATE_AT_FORMAT
                else
                  format_to_validate = I18n.t("time.formats.default")
                end
                DateTime.strptime(field, format_to_validate)              
                false  #Converstion without problems! Valid date!
              rescue
                true  #Converstion with problems! Invalid date!
              end
            end
          
          end

          def conversion_to_date_on(method,field_on_before_type_cast)
            errors.add(method, ' (' + field_on_before_type_cast + ') formato incorrecto ' + DATE_ON_FORMAT_EXPLANATION + ' o valor fuera de rango') if is_invalid_date_on(field_on_before_type_cast)
          end

          def is_invalid_date_on(field)    
            if field == nil or field == ''
              false   #Null is not an invalid date. Valid date!
            else
              begin
                if defined?(DATE_ON_FORMAT)
                  format_to_validate = DATE_ON_FORMAT
                else
                  format_to_validate = I18n.t("date.formats.default")
                end      
                DateTime.strptime(field, format_to_validate)              
                false  #Converstion without problems! Valid date!
              rescue
                true  #Converstion with problems! Invalid date!
              end
            end
          end  
        end
      2. Añadir la validación en el modelo
        app/models/event.rb
        class Event < ActiveRecord::Base
          include ValidationHelper
          require 'date'
          validate {conversion_to_date_at(:start_at, start_at_before_type_cast)}
          validate {conversion_to_date_on(:finish_on, finish_on_before_type_cast)}
        end