Próximos Eventos

Blog

Colección de posts con temas relacionados con el lenguaje Ruby, el framework Ruby On Rails y desarrollo web en general. Los posts han sido creados y publicados mediantes contribuciones de la comunidad

Si quieres colaborar en la sección del Blog de Rails Mx, por favor revisa la guía de como hacerlo.

Desmitificando el manejo de fechas en Ruby on Rails

Junio 9, 2015 por mariochavez

Hace algunas semanas me tocó dar el Curso Profesional de Ruby on Rails a través de la plataforma Platzi. Una de las preguntas que surgieron durante el curso es sobre el manejo de las fechas y horas de forma correcta respetando las zonas horarias.

Durante el taller mencioné la posibilidad de crear un initializer donde configuráramos la zona horaria por omisión para nuestra aplicación de Ruby on Rails:

Rails.application.config.time_zone = 'Central Time (US & Canada)'

A manera de errata quiero aclarar que esto es incorrecto, ya que al momento en que Ruby on Rails carga los inicializadores la zona horaria ya fue definida en el servidor de Rails, por que queda en en ’UTC’. La zona horaria es uno de los valores de configuración que deben de definirse en el archivo application.rb para que tenga efecto en la aplicación.

¿Por qué definir una zona horaria en el servidor de Ruby on Rails?

Ruby on Rails independientemente de la zona horaria en que esté configurado siempre guarda la fechas y horas en ’UTC’ y al momento de desplegar una fecha realizar la conversión a la zona horaria en la aplicación de Ruby on Rails esté configurado.

Sin ésta configuración todas las fechas se desplegarían en ’UTC’:

Sun, 07 Jun 2015 22:00:00 UTC +00:00

Si tenemos configurada nuestra aplicación para la zona horaria del Pacífico la misma fecha se muestra de la siguiente forma:

Sun, 07 Jun 2015 15:00:00 PDT -07:00

Por ejemplo, es importante observar que Ruby on Rails entiende del horario del verano y automáticamente hace el ajuste cuando los cambios suceden, lo podemos notar porque la zona horaria se muestra como ’PDT’, donde la D indica Daylight saving.

Hay helpers como #datetime_select que también se benefician de tener configurada una zona horaria, que muestra la fecha y la hora de manera correcta.

Los valores que #time_zone puede aceptar los podemos obtener de ejecutar el siguiente comando de rake en la consola:

 $ rake time:zones:all

## Ruby on Rails parece ignorar mi zona horaria Cuando nuestra aplicación de Ruby on Rails está configurada con la misma zona horaria del servidor donde estamos afectando nuestra aplicación, el manejo de fechas y horas en Ruby on Rails es correcto.

Esto se debe a que en muchos de los casos hacemos uso de Time.now o DateTime.now pero dada la coincidencia de que la aplicación y el servidor están configurados en la misma zona horario todo se ve correcto. Pero si modificamos nuestra aplicación a una zona horaria diferente Time.now y DateTime.now ya no muestran la hora correcta.

Esto se debe a que Time y DateTime no entienden la configuración de la aplicación de Ruby on Rails y de hecho van y preguntas directamente al sistema operativo por fecha y hora.

Para trabajar de forma efectiva en Ruby on Rails con diferentes zonas horarias necesitamos de utilizar el soporte de ActiveSupport para el manejo de zonas horarias.

En el siguiente ejemplo el servidor está ejecutándose en la zona horaria ’CDT’ y la aplicación de Ruby on Rails en ’PDT’:

> DateTime.now
=> Tue, 09 Jun 2015 12:30:31 -0500
> Time.now
=> 2015-06-09 12:30:33 -0500

La forma efectiva de manejar fecha con Ruby on Rails es la siguiente:

> Time.zone.now
=> Tue, 09 Jun 2015 10:32:27 PDT -07:00
> DateTime.now.in_time_zone
=> Tue, 09 Jun 2015 10:32:43 PDT -07:00
> DateTime.current
=> Tue, 09 Jun 2015 10:32:47 -0700

Podemos observar que en el segundo ejemplo en todos los casos la fecha y hora se mostró de forma correcta para la zona horaria de la aplicación de Ruby on Rails.

Time cuenta con el método #zone con el cuál nos aseguramos de trabajar con las fechas de forma correcta. Por ejemplo si queremos parsear una fecha desde una cadena de texto lo hacemos de la siguiente forma:

> Time.zone.parse(str)

DateTime no cuenta con el método #zone pero sí con #in_time_zone que nos permite asegurarnos que las fechas y horas son manejadas adecuadamente.

¿Cómo puedo hacer para que Ruby on Rails muestra fechas y horas en la zona horaria de mis usuarios?

Es posible hacer que Ruby on Rails muestre la fecha y hora de manera diferente para cada usuario, por ejemplo en un país como México tenemos 5 diferentes zonas horarias. Si quisiéramos mostrar las fechas y horas de forma local para usuarios que se conectan desde estos diferentes lugares es necesario hacer un pequeño esfuerzo en Ruby on Rails.

Lo primero que tenemos que entender es que los navegadores no envían al servidor la zona horaria del usuario que está haciendo una solicitud, por lo que la única forma de mostrar la hora local a los usuarios es preguntándoles en que zona horaria están o bien mediante Geolocalización, por ejemplo tomar la IP desde donde se están conectado y resolverla a su ubicación. Esto es posible con una gema como geoip.

En la primera opción de preguntarle al usuario su zona horaria, podemos guardar esa información como parte de su perfil y así hacer uso de esa información para las siguientes ocasiones en que el usuario se conecte.

Pero bueno, ya tenemos la zona horaria del usuario y la cuál puede ser diferente a la zona horaria de la aplicación. Si tomamos los ejemplo previos, el hacer uso de #zone y #in_time_zone no nos ayuda mucho ya que siempre se mostrará la fecha y la hora resuelta para la configuración de la aplicación.

Lo debemos de hacer es pasar un parámetro a #in_time_zone:

> DateTime.now.in_time_zone('Eastern Time (US & Canada)')
=> Tue, 09 Jun 2015 13:47:50 EDT -04:00

De ésta forma Ruby on Rails desplegará la fecha y hora en la zona horaria del usuario. Otra forma de hacerlo es pasándole la zona horaria al método #zone= de la clase Time:

> Time.zone = "Eastern Time (US & Canada)"
=> "Eastern Time (US & Canada)"
> DateTime.now.in_time_zone
=> Tue, 09 Jun 2015 13:51:55 EDT -04:00

A nivel de un controlador de Ruby on Rails, podemos hacer uso de un #around_filter para configurar la zona horaria en cada solicitud de los usuarios:

class EventsController < ApplicactionController
  around_filter :set_time_zone

  private def set_time_zone
     if logged_in?
        Time.use_zone(current_user.time_zone) { yield }
     else
        yield
     end
  end 
end

De ésta forma cada que el usuario haga una petición al servidor la zona horaria se leerá del perfil del usuario o de un cookie, suponiendo que el perfil del usuario implementa el método #time_zone y en nuestras vistas sólo tenemos que preocuparnos por llamar al método #in_time_zone cada que despleguemos una fecha en nuestra aplicación.

Conclusiones

El manejo de zonas horarias de forma correcta en Ruby on Rails no es nada complicado, el error en que muchos caemos es el de utilizar Time y DateTime sin el soporte de ActiveSupport, el cuál entiende y nos ayuda a manejar las fechas de forma correcta en la aplicación.

Aprendiendo Ruby on Rails 4.

Libro Aprendiendo Ruby on Rails disponible en modo Early Access.

Formularios anidados

Marzo 10, 2014 por apux

En ocasiones, tenemos modelos asociados que necesitamos manipular en un único formulario en lugar de tener un formulario por cada uno de ellos. La gema nested_form nos permite crear formularios complejos cuando trabajamos con modelos anidados, su uso es realmente sencillo y su documentación cubre los aspectos más comunes. Sin embargo, existen algunos escenarios en los que debemos de tener algunas consideraciones adicionales para que todo funcione correctamente, especialmente con Rails 4.

En este tutorial, veremos cómo utilizar esta gema cuando incluimos relaciones uno a muchos y muchos a muchos en nuestros modelos.

Creación del proyecto

Para este tutorial, trabajaremos con un proyecto en Rails 4.1. Al momento de escribir este tutorial, es una versión que todavía está en RC, por lo que se se instala de la siguiente manera:

gem install rails --pre

Para empezar, agregamos la gema nested_form a nuestro proyecto.

echo "gem 'nested_form'" >> Gemfile

Y ejecutamos

bundle install

Ahora debemos agregar el javascript de la gema al asset pipeline. En el archivo application.js, ingresamos la siguiente línea:

//= require jquery_nested_form

Nota: La versión de nested_form utilizada para este post es 0.3.2.

Creación de modelos

Trabajaremos con tres modelos relacionados entre sí. El modelo Proyecto tiene muchas Tareas (relación uno a muchos), y las Tareas están asociadas a muchos Empleados (relación muchos a muchos).

Por comodidad, usaremos scaffold para generar los recursos de Proyecto y Empleado, mientras que Tarea no tendrá ni vista ni controlador propio, sino que se creará a través del formulario de Proyecto, que es donde utilizaremos la gema nested_form que acabamos de instalar.

rails generate scaffold proyecto nombre fecha_entrega:date
rails generate scaffold empleado nombre_completo
rails generate model tarea nombre prioridad:integer proyecto:references

Para la relación muchos a muchos entre tareas y empleados necesitamos una migración adicional para crear la tabla intermedia entre estos dos modelos. En Rails 4, podemos hacerlo de la siguiente manera:

rails generate migration create_join_table_empleados_tareas empleado tarea

Este comando nos genera una migración que crea la tabla intermedia para nuestra relación muchos a muchos:

class CreateJoinTableEmpleadosTareas < ActiveRecord::Migration
  def change
    create_join_table :empleados, :tareas do |t|
      # t.index [:empleado_id, :tarea_id]
      # t.index [:tarea_id, :empleado_id]
    end
  end
end

Ahora creamos la base de datos y generamos las tablas por medio de las migraciones. En este ejemplo, utilizaremos SQLite como manejador de base de datos, por lo que no es necesario configurar nada más. Si prefieres utilizar MySql, Postgres o algún otro manejador, asegúrate de incluir su gema correspondiente en el archivo Gemfile y adaptar el archivo config/database.yml.

rake db:create
rake db:migrate

Modificamos los modelos correspondientes para que incluyan las relaciones que hemos creado en la base de datos:

# app/models/proyecto.rb
class Proyecto < ActiveRecord::Base
  has_many :tareas
end

# app/models/tarea.rb
class Tarea < ActiveRecord::Base
  belongs_to :proyecto
  has_and_belongs_to_many :empleados
end

# app/models/empleado.rb
class Empleado < ActiveRecord::Base
  has_and_belongs_to_many :tareas
end

Datos de inicio

Agreamos empleados a la base de datos por medio del archivo db/seeds.rb.

# db/seeds.rb
Empleado.create! [
  {nombre_completo: 'Juan Pérez'},
  {nombre_completo: 'Pedro López'},
  {nombre_completo: 'María Hernández'},
  {nombre_completo: 'Carlos Sánchez'},
]

Para almacenar esa información en la base de datos:

rake db:seed

Por supuesto, también podemos agregar empleados manualmente desde nuestra aplicación, ejecutando rails server, y accediendo a la ruta empleados/new.

Formulario de proyectos

Ya con nuestros modelos creados, debemos trabajar con el formulario de proyectos, donde podemos crear un proyecto que contenga muchas tareas que a su vez estén asociadas con muchos empleados.

El formulario que nos creó el scaffold es el siguiente:

<%# app/views/proyectos/_form.html.erb %>
<%= form_for(@proyecto) do |f| %>
  <% if @proyecto.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@proyecto.errors.count, "error") %> prohibited this proyecto from being saved:</h2>

      <ul>
      <% @proyecto.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :nombre %><br>
    <%= f.text_field :nombre %>
  </div>
  <div class="field">
    <%= f.label :fecha_entrega %><br>
    <%= f.date_select :fecha_entrega %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

Para utilizar la gema, debemos utilizar el helper nested_form_for en lugar de form_for y agregar los campos de las tareas. Rails nos provee de un helper para agregar campos a objetos asociados: fields_for. El formulario quedaría de la siguiente manera:

<%# app/views/proyectos/_form.html.erb %>
<%= nested_form_for(@proyecto) do |f| %>
  <% if @proyecto.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@proyecto.errors.count, "error") %> prohibited this proyecto from being saved:</h2>

      <ul>
      <% @proyecto.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :nombre, 'Nombre del proyecto' %><br>
    <%= f.text_field :nombre %>
  </div>
  <div class="field">
    <%= f.label :fecha_entrega %><br>
    <%= f.date_select :fecha_entrega %>
  </div>
  <fieldset id="tareas">
    <%= f.fields_for :tareas do |tareas_form| %>
      <div class="field">
        <%= tareas_form.label :nombre, 'Nombre de la tarea' %><br>
        <%= tareas_form.text_field :nombre %>
      </div>
      <div class="field">      
        <%= tareas_form.label :prioridad %><br>
        <%= tareas_form.text_field :prioridad %>
      </div>
      <%= tareas_form.link_to_remove "Eliminar esta tarea" %>
    <% end %>
    <p><%= f.link_to_add "Agregar una tarea", :tareas %></p>
  </fieldset>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

Uso de accepts_nested_attributes_for

Si en estos momentos entramos a nuestra aplicación e intentamos crear un proyecto, obtendremos el siguiente error:

Invalid association. Make sure that accepts_nested_attributes_for is used for :tareas association.

Esto es porque nuestro formulario de Proyecto incluye ya los atributos anidados de Tareas, pero no hemos configurado nuestros modelos para que permita recibir estos campos.

En nuestro modelo Proyecto agregamos el accepts_nested_attributes_for:

# app/models/proyecto.rb
class Proyecto < ActiveRecord::Base
  has_many :tareas
  accepts_nested_attributes_for :tareas, allow_destroy: true
end

De esta manera, Proyecto puede recibir y procesar los atributos de tareas mediante la llave tareas_attributes. Ejemplo:

Proyecto.create nombre: 'Mi proyecto',
             fecha_entrega:  1.month.from_now,
             tareas_attributes: [
                                 {nombre: 'Tarea 1', prioridad: 5},
                                 {nombre: 'Tarea 2', prioridad: 3}
             ]

Con esto, le indicamos a ActiveRecord que cree un proyecto con dos tareas.

La opción allow_destroy nos permitirá eliminar alguna tarea de la lista de tareas agregando la bandera :_destroy. Ejemplo:

Proyecto.create nombre: 'Mi proyecto',
             fecha_entrega:  1.month.from_now,
             tareas_attributes: [
                                 {nombre: 'Tarea 1', prioridad: 5},
                                 {nombre: 'Tarea 2', prioridad: 3, _destroy: true }
             ]

Con esto, le indicamos a ActiveRecord que cree un proyecto con una sola tarea (Tarea 1), ya que Tarea 2 está marcada para ser eliminada y por lo tanto no se crea. Si la tarea ya existe previamente, al incluir la opción _destroy: true se borrará también de la base de datos, pero para eso habrá que especificar el id, como veremos más adelante.

Si tratamos de entrar a nuestro formulario de proyectos (/proyectos/new) veremos que el formulario se muestra correctamente y podemos agregar o eliminar tareas visualmente de manera dinámica.

Agregar tareas por omisión

Si queremos que nuestro proyecto se muestre inicialmente con algunas tareas asociadas, podemos agregar algunas desde el controlador. Ejemplo:

#app/controllers/proyectos_controller.rb
def new
  @proyecto = Proyecto.new
  2.times { @proyecto.tareas.build }
end

Parciales

En nuestro ejemplo, el modelo Proyecto tiene dos campos, y el modelo Tarea otros dos, pero en un caso real, ambos tendrían muchos más campos y mostrarlos todos dejaría un tanto sucio el código de nuestro formulario. En esos casos, es mejor utilizar parciales.

nested_form permite el uso de parciales de una manera elegante. Necesitamos crear una parcial con el nombre en singular de nuestro modelo más el sufijo _fields. En la nueva parcial, la variable del formulario anidado (tareas_form en nuestro caso), se pasa simplemente como f. Los archivos quedarían de la siguiente manera:

El formulario principal de proyectos de proyectos.

<%# app/views/proyectos/_form.html.erb %>
<%= nested_form_for(@proyecto) do |f| %>
  <% if @proyecto.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@proyecto.errors.count, "error") %> prohibited this proyecto from being saved:</h2>

      <ul>
      <% @proyecto.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :nombre, 'Nombre del proyecto' %><br>
    <%= f.text_field :nombre %>
  </div>
  <div class="field">
    <%= f.label :fecha_entrega %><br>
    <%= f.date_select :fecha_entrega %>
  </div>
  <fieldset id="tareas">
    <%= f.fields_for :tareas %>
    <p><%= f.link_to_add "Agregar una tarea", :tareas %></p>
  </fieldset>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

La nueva parcial:

<%# app/views/proyectos/_tarea_fields.html.erb %>
<div class="field">
  <%= f.label :nombre, 'Nombre de la tarea' %><br>
  <%= f.text_field :nombre %>
</div>
<div class="field">
  <%= f.label :prioridad %><br>
  <%= f.text_field :prioridad %>
</div>
<%= f.link_to_remove "Eliminar esta tarea" %>

Como vemos, queda más limpio y mejor organizado.

Show

Antes de crear un proyecto por medio del formulario, modificaremos el show para mostrar no sólo los datos del proyecto sino también los de las tareas asociadas. Así podremos saber si las tareas se crearon correctamente.

<%# app/views/proyectos/show.html.erb %>
<p id="notice"><%= notice %></p>

<p>
  <strong>Nombre:</strong>
  <%= @proyecto.nombre %>
</p>

<p>
  <strong>Fecha entrega:</strong>
  <%= @proyecto.fecha_entrega %>
</p>

<p>
  <strong>Tareas:</strong>
  <p>
    <% @proyecto.tareas.each do |tarea| %>
      Nombre: <%= tarea.nombre %><br/>
      Prioridad: <%= tarea.prioridad %>
      <hr/>
    <% end %>
  </p>
</p>

<%= link_to 'Edit', edit_proyecto_path(@proyecto) %> |
<%= link_to 'Back', proyectos_path %>

Strong parameters

Ahora sí, es momento de crear nuestro primer proyecto por medio del formulario. Ingresamos los datos tanto del proyecto como de las tareas, y damos clic en el botón de submit.

La página de show nos muestra el nombre del proyecto y la fecha de entrega pero la lista de tareas está vacía, lo que significa que algo falló en el proceso.

El problema se encuentra en el controlador. Rails 4 incluye strong_parameters, que valida que se reciban sólo atributos autorizados para un formulario. Por omisión, al ejecutar el scaffold, sólo se autorizan los campos que se incluyen en el modelo Proyecto, por lo que tenemos que agregar a mano los campos del modelo Tarea, incluyendo el atributo _destroy, para que una tarea pueda ser eliminada de la lista. Estos atributos se agregan a la lista que recibe el método permit como un hash, donde la llave es tareas_attributes y el valor es un arreglo con los nombres de los campos.

# app/controllers/proyectos_controller.rb

# [...]

# Never trust parameters from the scary internet, only allow the white list through.
def proyecto_params
  params.require(:proyecto).permit(
    :nombre, :fecha_entrega, tareas_attributes: [
      :nombre, :prioridad, :_destroy
    ]
  )
end

Y ahora sí, al crear un proyecto con tareas, éstas se crearán correctamente.

Update

Ahora bien, si queremos modificar un proyecto que ya hayamos ingresado, podemos hacerlo en el path correspondiente, por ejemplo, /proyectos/1/edit.

Sin embargo, aquí notamos un comportamiento extraño. En lugar de actualizar las tareas, crea siempre tareas nuevas. Es decir, mantiene las tareas creadas anteriormente y trata las tareas que estamos modificando como si las estuvieras agregando. Vemos también otro error: si queremos eliminar una tarea, ésta no se elimina, sino que sigue ahí.

Este comportamiento sucede porque para realizar la modificación, ActiveRecord espera recibir como parámetro, el id del modelo a modificar. Si el id corresponde a un registro de la base de datos, entonces lo modifica. Ejemplo:

Imaginemos un proyecto con dos tareas, las tareas tienen los ids 1 y 2 respectivamente.

proyecto = Proyecto.first
proyecto.update nombre: 'Mi nuevo proyecto',
             fecha_entrega:  1.week.from_now,
             tareas_attributes: [
                                 {nombre: 'Tarea nueva', prioridad: 1 },
                                 {id: 1, nombre: 'Tarea modificada', prioridad: 4 },
                                 {id: 2, nombre: 'Tarea 2', prioridad: 2, _destroy: true }
             ]

Este fragmento de código actualiza los datos del proyecto, incluyendo sus tareas. La primera tarea no lleva id, por lo que simplemente se crea y se asocia al proyecto. Las siguientes dos tareas sí llevan id, por lo que se entiende que ya existen previamente y están asociadas a ese proyecto; esas tareas se actualizan. En este caso, la tarea con id 1 sólo actualiza sus datos, mientras que la tarea con id 2 se elimina porque incluye la bandera _destroy como true.

Strong parameters

Conociendo la teoría, la solución es sencilla. Basta con agregar a la lista de parámetros aceptados, el id de las tareas. Para esto, modificamos de nuevo la lista de nuestros strong parameters.

# app/controllers/proyectos_controller.rb

# [...]

# Never trust parameters from the scary internet, only allow the white list through.
def proyecto_params
  params.require(:proyecto).permit(
    :nombre, :fecha_entrega, tareas_attributes: [
      :id, :nombre, :prioridad, :_destroy
    ]
  )
end

Y listo. Con eso podemos modificar y eliminar tareas correctamente desde nuestro formulario de proyectos.

Muchos a muchos

Ya hemos asociado correctamente proyectos y tareas desde un solo formulario, pero aún nos falta asociar tareas y empleados, que es una relación muchos a muchos.

Lo primero que modificaremos será nuestro show para incluir información sobre los empleados.

<%# app/views/proyectos/show.html.erb %>
<p id="notice"><%= notice %></p>

<p>
  <strong>Nombre:</strong>
  <%= @proyecto.nombre %>
</p>

<p>
  <strong>Fecha entrega:</strong>
  <%= @proyecto.fecha_entrega %>
</p>

<p>
  <strong>Tareas:</strong>
  <p>
    <% @proyecto.tareas.each do |tarea| %>
      Nombre: <%= tarea.nombre %><br/>
      Prioridad: <%= tarea.prioridad %><br/>
      Empleados: <%= tarea.empleados.map(&:nombre_completo).to_sentence %>
      <hr/>
    <% end %>
  </p>
</p>

<%= link_to 'Edit', edit_proyecto_path(@proyecto) %> |
<%= link_to 'Back', proyectos_path %>

Ahora procedemos a modifcar nuestro formulario para incluir empleados.

Como primer recurso, recurro a este railscast, donde se muestra una forma de hacerlo utilizando chec_kbox_tag.

En un formulario normal, la asociación podría hacerce de la siguiente manera:

<% Empleado.all.each do |empleado| %>
  <%= check_box_tag 'tarea[empleado_ids][]',
                    empleado.id,
                    @tarea.empleado_ids.include?(empleado.id) %>
  <%= empleado.nombre_completo %><br/>
<% end %>

Sin embargo, eso da por sentado que la tarea es el formulario del primer nivel, lo que es falso para nuestro caso. Nosotros necesitaríamos de una estructura más compleja que incluyera el proyecto y la tarea (mediante tareas_attributes).

Si analizamos el request que se hace cuando envía nuestro formulario, veremos que los parámetros llevan la siguiente estructura:

Started POST "/proyectos" for 127.0.0.1 at 2013-08-12 12:41:01 -0500
Processing by ProyectosController#create as HTML
  Parameters: {"utf8"=>"✓",
               "authenticity_token"=>"RtyGum+m6wIvaoOAvxcQnIlvMEPGStBmFvaTL+t+paQ=",
               "proyecto"=>{
                             "nombre"=>"Mi proyecto",
                             "fecha_entrega(1i)"=>"2013",
                             "fecha_entrega(2i)"=>"9",
                             "fecha_entrega(3i)"=>"22",
                             "tareas_attributes"=>{
                                                    "0"=>{
                                                           "nombre"=>"Tarea 1",
                                                           "prioridad"=>"1",
                                                           "_destroy"=>"false"},
                                                    "1"=>{
                                                           "nombre"=>"Tarea 2",
                                                           "prioridad"=>"",
                                                           "_destroy"=>"false"}
                                                  }
                           },
               "commit"=>"Create Proyecto"}

Como vemos, el formulario envía tareas_attributes no como un arreglo sino como un hash, donde la llave es un índice para cada tarea. Si quisiéramos incluir los checkboxes de empleados, tendríamos que indicar el número de cada tarea. Otro aspecto a tomar en cuenta es que no tenemos @tarea, sino @proyecto, por lo que tendríamos que tomarlo del objeto del formulario. Algo como esto:

<%= check_box_tag "proyecto[tareas_attributes][#{id_tarea}][empleado_ids][]",
                  empleado.id,
                  f.object.empleado_ids.include?(empleado.id) %>

Para el caso de nuestro ejemplo, es posible llevar el control del id de la tarea por medio de alguna variable generada en _form y pasarla a la parcial _tareas_fields, pero pensando en varios niveles de formularios anidados, sería muy complicado llevar el registro de cada uno de los ids de los modelos e irlos pasando entre parciales. Por ejemplo, si la tarea no estuviera relacionada directamente con empleados, sino con asignaciones, y las asignaciones con empleados, tendríamos un tag parecido a esto:

<%= check_box_tag(
    "proyecto[tareas_attributes][#{tarea_index}][asignaciones_attributes][#{asignacion_index}][empleado_ids][]",
    empleado.id,
    f.object.empleado_ids.include?(empleado.id)) %>

Lo que poco a poco se vuelve insostenible.

Afortunadamente, Rails 4 incluye un helper para asociaciones muchos a muchos a través de checkboxes, y eso nos facilita bastante las cosas. El helper es collection_check_boxes. Nuestra parcial de tareas quedaría de la siguiente manera:

<%# app/views/proyectos/_tarea_fields.html.erb %>
<div class="field">
  <%= f.label :nombre, 'Nombre de la tarea' %><br>
  <%= f.text_field :nombre %>
</div>
<div class="field">
  <%= f.label :prioridad %><br>
  <%= f.text_field :prioridad %>
</div>
<div class="field">
  <%= f.collection_check_boxes :empleado_ids, Empleado.all, :id, :nombre_completo %>
</div>
<%= f.link_to_remove "Eliminar esta tarea" %>

Haciendo uso de este helper, no tenemos que preocuparnos por nada más, Rails se encarga de llevar los índices por nosotros.

Strong parameters

Si probamos nuestro formulario ahora, funciona correctamente, los parámetros que genera son los correctos, pero aún no guarda las asociaciones con empleados. De nuevo, esto tiene que ver con strong parameters. Debemos indicar que acepte los campos correspondientes a empleado_ids.

Aquí, hay que observar un pequeño truco para hacer que strong parameters acepte la lista de ids que le mandamos desde nuestro formulario. Si agregamos simplemente el campo :empleado_ids, strong parameters lo filtrará como un campo cuando en realidad necesitamos que lo considere un arreglo. En este caso debemos indicarlo como un hash, donde la llave sea :empleado_ids y el valor un arreglo vacío.

# app/controllers/proyectos_controller.rb

# [...]

# Never trust parameters from the scary internet, only allow the white list through.
def proyecto_params
  params.require(:proyecto).permit(
    :nombre, :fecha_entrega, tareas_attributes: [
      :id, :nombre, :prioridad, :_destroy, empleado_ids: []
    ]
  )
end

Y ahora sí, nuestro formulario funciona correctamente asociando proyectos, tareas y empleados.

Conclusiones

Rails permite trabajar con formularios anidados de manera relativamente fácil. La gema nested_form hace que este trabajo sea todavía más sencillo, sin embargo, hay que conocer el funcionamiento de otros módulos, como strong parameters y acceptsnestedattributes_for para que todo funcione correctamente.

Este tutorial cubrió los aspectos básicos para el trabajo con formularios anidados, pero todavía quedan algunos detalles que cubrir. En el siguiente post explicaré cómo utilizar la gema cuando tenemos una estructura de modelos más compleja. En particular, cuando tenemos modelos dentro de un namespace y recursos anidados ( nested resources ) en nuestras rutas.

Recursos

Para quien esté interesado en conocer el funcionamiento interno de la gema, puede revisar este post. Además, estos railscasts son de mucha utilidad: Nested Model Form Part 1 y Nested Model Form Part 2. También hay una versión actualizada (de paga): Nested Model Form (revised).

Un post muy completo sobre Strong Parameters.

El railscast de habtm con checkboxes y su versión de pago.

La documentación de collection check boxes.

También creé un proyecto de ejemplo en github para que sea fácil revisar el código.

Crear y consumir API's en Rails

Marzo 3, 2014 por mariochavez

Un par de meses atrás me encontré trabajando en algunos proyectos donde se estaban utilizando API's creadas en Ruby on Rails sin embargo cuando yo me uní a los proyectos estos ya estaban bastante avanzados y yo siempre quise crear una API desde cero y que primero fuera publica y después poder agregar seguridad para que solo ciertos clientes pudieran acceder a la misma. Hola que tal mi nombre Jose Heriberto Perez actualmente trabajo como Software Engineer at Crowd Interactive Ecommerce Consultancy, en esta ocasión vamos a crear una API en Ruby on Rails desde cero que primero será publica y en la cual solo podremos leer y después vamos a agregar seguridad basada en tokens y también podremos crear, eliminar y actualizar registros, para lo cual también vamos a crear una aplicación cliente la cual será una SPA (Sigle Page Application) una aplicación de una sola pagina para que no recargue en ningún momento para lo cual vamos a utilizar las siguiente tecnologías resaltando que utilizaremos el framework de javascript Backbone:

Las tecnologías utilizadas para el cliente serán:

  • HTML
  • CSS
  • Bootstrap
  • Backbone
  • Handlebars
  • JQuery
  • JSONP
  • Responsive
  • Media Queries

Las tecnologías utilizadas para nuestra API en rails(backend) serán:

  • Rails version '4.0.2'
  • Kaminari para paginación
  • JSON format
  • rack-cors
  • Google OAUTH2 para Autenticación

Los repositorios de ejemplo los puedes encontrar en mi cuenta de heridev en github:

Para el cliente esta es la version final que se trabaja hasta el video final en la rama "episodio 7"

https://github.com/heridev/testing-episodes/tree/episode7

Para la parte de la API

https://github.com/heridev/railspublicapi

Sin embargo aprovecho para mencionarles que en este repositorio van a encontrar mucho mas código del que se muestra en los vídeos ya que después de terminar la serie de vídeos yo continúe actualizando este repo para cuestiones personales y requerimientos personales como nivel de privilegios de usuarios, envío de correos utilizando mandril, etcétera sin embargo si quieren ver el código que se creo hasta el "episodio 7" de los vídeos pueden hacer referencia a los commits:

https://github.com/heridev/railspublicapi/commits/master

Hasta el siguiente commit se incluyen los últimos cambios del episodio 7:

'Ensure google client id is set as environment variable' => 1233f526d878a22c84d774c20f5922783178bddf

En caso de que deseen ver solo el código que se trabaja en los videos pueden hacer lo siguiente: git clone git@github.com:heridev/rails_public_api.git luego cambiarse hasta ese commit que les comente git checkout 1233f526d878a22c84d774c20f5922783178bddf

y así podrán ver el código creado hasta el episodio 7, that's it

El curso completo esta divido en 7 vídeos a continuación les comparto los links esperando que les sea de mucha ayuda:

Episodio 1

Episodio 2

Episodio 3

Episodio 4

Episodio 5

Episodio 6

Episodio 7

Parte III: Publicación de la gema 'identificamex'

Octubre 15, 2013 por apux

Objetivo

Permitir que la gema identificamex que acabamos de crear, se pueda integrar a cualquier proyecto Rails que quiera hacer uso de ella, agregándola como dependencia a su Gemfile

gem 'identificamex'

Agregar la gema indicando el path

Hasta el momento, nuestro trabajo ha rendido frutos porque ya creamos nuestros validadores y ya tenemos una gema que los integra, pero todavía no hemos utilizado esa gema en un proyecto Rails. Ése es el siguiente paso.

Para esto, retomaremos el proyecto con el que empezamos a trabajar en la parte I de este tutorial, nuestro olvidado proyecto my_app.

Antes de intentar publicar nuestra gema, me gustaría que la probáramos para verificar que todo funcione correctamente. Para esto, vamos a utilizar una funcionalidad bastante útil de ruby gems, que es la posibilidad de indicar el path local donde se encuentra la gema.

En nuestra aplicación Rails my_app, elminamos el directorio lib/validators y eliminamos la línea que cargaba ese directorio, config.autoload_paths += %W(#{Rails.root}/lib/validators). Ahora agregamos la siguiente dependencia nuestro Gemfile:

gem 'identificamex', path: '../identificamex'

El path es la dirección relativa en donde se encuentra el código de nuestra gema.

Instalamos

bundle install

Y ejecutamos nuestras pruebas para verificar que todo funcione correctamente.

rspec spec/models

Finished in 0.02801 seconds
24 examples, 0 failures

Todo funciona correctamente. Sin embargo, me gustaría ver un escenario más complicado. A los validadores de ActiveRecord se les pueden indicar opciones, por ejemplo allow_blank, if, unless, etc. Y nosotros no hemos programado nada de eso en nuestros validadores. Veamos cómo se comporta.

Validadores con opciones

Para tener un panorama más completo, imaginemos que, por alguna razón, nuestra validación se complica un poco. Nuestro cliente nos dice que la CURP del empleado es opcional, y el RFC sólo se debe validar si el empleado es un contribuyente registrado.

Agregamos los ejemplos que prueban ese funcionamiento a nuestro conjunto de pruebas, que queda de la siguiente manera:

#specs/models/empleado_spec.rb
require 'spec_helper'

describe Empleado do

  def empleado_registrado(options)
    # método de utilería para crear empleados registrados
    Empleado.new(options.merge(registrado: true))
  end

  def empleado_no_registrado(options)
    # método de utilería para crear empleados no registrados
    Empleado.new(options.merge(registrado: false))
  end

  describe '#rfc' do
    context 'when registered (contribuyente registrado)' do
      context 'with valid data' do
        it 'accepts rfc (completo)' do
          expect(empleado_registrado(rfc: 'AAAA111111AAA')).to have(:no).errors_on(:rfc)
        end

        it 'accepts rfc (con 3 caracteres para el nombre)' do
          expect(empleado_registrado(rfc: 'AAA111111AAA')).to have(:no).errors_on(:rfc)
        end

        it 'accepts rfc (sin homoclave)' do
          expect(empleado_registrado(rfc: 'AAAA111111')).to have(:no).errors_on(:rfc)
        end

        it 'accepts rfc (sin homoclave y con 3 caracteres para el nombre)' do
          expect(empleado_registrado(rfc: 'AAA111111')).to have(:no).errors_on(:rfc)
        end

        it 'accepts rfc (datos de nombre con Ñ)' do
          expect(empleado_registrado(rfc: 'ÑAAA111111AAA')).to have(:no).errors_on(:rfc)
        end

        it 'accepts rfc (datos de nombre con &)' do
          expect(empleado_registrado(rfc: 'A&A111111AAA')).to have(:no).errors_on(:rfc)
        end
      end

      context 'with invalid data' do
        it 'refuses rfc (nombre con digito en lugar de letra)' do
          expect(empleado_registrado(rfc: '9AAA111111')).to have(1).error_on(:rfc)
        end

        it 'refuses rfc (caracter invalido)' do
          expect(empleado_registrado(rfc: 'A*AA111111')).to have(1).error_on(:rfc)
        end

        it 'refuses rfc (falta un digito en la fecha)' do
          expect(empleado_registrado(rfc: 'AAAA11111')).to have(1).error_on(:rfc)
        end

        it 'refuses rfc (falta un caracter en los datos del nombre)' do
          expect(empleado_registrado(rfc: 'AA111111')).to have(1).error_on(:rfc)
        end

        it 'refuses rfc (el dia es invalido 42)' do
          expect(empleado_registrado(rfc: 'AAAA111142')).to have(1).error_on(:rfc)
        end

        it 'refuses rfc (el mes es invaido 25)' do
          expect(empleado_registrado(rfc: 'AAAA112511')).to have(1).error_on(:rfc)
        end

        it 'refuses rfc (vacío)' do
          expect(empleado_registrado(rfc: '')).to have(1).error_on(:rfc)
        end
      end
    end

    context 'when no registered (contribuyente no registrado)' do
      it 'accepts RFC (válido)' do
        expect(empleado_no_registrado(rfc: 'AAAA111111AAA')).to have(:no).errors_on(:rfc)
      end

      it 'accepts RFC (inválido)' do
        expect(empleado_no_registrado(rfc: 'AA*A112511&XAL;I')).to have(:no).errors_on(:rfc)
      end

      it 'accepts rfc (vacío)' do
        expect(empleado_no_registrado(rfc: '')).to have(:no).errors_on(:rfc)
      end
    end
  end

  describe '#curp' do
    context 'with valid data' do
      it 'accepts curp (hombre)' do
        expect(Empleado.new(curp: 'AAAA111111HDFBBB01')).to have(:no).errors_on(:curp)
      end

      it 'accepts curp (mujer)' do
        expect(Empleado.new(curp: 'AAAA111111MDFBBB01')).to have(:no).errors_on(:curp)
      end

      it 'accepts curp (digito anti-duplicado alfanumérico)' do
        expect(Empleado.new(curp: 'AAAA111111HDFBBBA1')).to have(:no).errors_on(:curp)
      end

      it 'accepts curp as blank' do
        expect(Empleado.new(curp: '')).to have(:no).error_on(:curp)
      end

      it 'accepts curp as nil' do
        expect(Empleado.new(curp: nil)).to have(:no).error_on(:curp)
      end
    end

    context 'with invalid data' do
      it 'refuses curp (nombre con digito en lugar de letra)' do
        expect(Empleado.new(curp: '9AAA111111HDFBBB01')).to have(1).error_on(:curp)
      end

      it 'refuses curp (caracter invalido)' do
        expect(Empleado.new(curp: 'A*AA111111HDFBBB01')).to have(1).error_on(:curp)
      end

      it 'refuses curp (falta un digito en la fecha)' do
        expect(Empleado.new(curp: 'AAAA11111HDFBBB01')).to have(1).error_on(:curp)
      end

      it 'refuses curp (falta un caracter en los datos del nombre)' do
        expect(Empleado.new(curp: 'AAA111111HDFBBB01')).to have(1).error_on(:curp)
      end

      it 'refuses curp (el dia es invalido 42)' do
        expect(Empleado.new(curp: 'AAAA111142HDFBBB01')).to have(1).error_on(:curp)
      end

      it 'refuses curp (el mes es invaido 25)' do
        expect(Empleado.new(curp: 'AAAA112511HDFBBB01')).to have(1).error_on(:curp)
      end

      it 'refuses curp (sexo es inválido K)' do
        expect(Empleado.new(curp: 'AAAA111111KDFBBB01')).to have(1).error_on(:curp)
      end

      it 'refuses curp (caracteres de consonantes tiene alguna vocal)' do
        expect(Empleado.new(curp: 'AAAA111111HDFABB01')).to have(1).error_on(:curp)
        expect(Empleado.new(curp: 'AAAA111111HDFBAB01')).to have(1).error_on(:curp)
        expect(Empleado.new(curp: 'AAAA111111HDFBBA01')).to have(1).error_on(:curp)
      end

      it 'refuses curp (digito verificador alfanumérico)' do
        expect(Empleado.new(curp: 'AAAA111111HDFBBB0A')).to have(1).error_on(:curp)
      end
    end

  end
end

Lo que hicimos fue modificar los ejemplos anteriores que verificaban el comportamiento del RFC para contemplar el nuevo comportamiento que depende depende de que el empleado sea un contribuyente registrado o no. Eso nos llevó a separar los contextos: cuando es un contribuyente registado, las validaciones se mantienen, y cuando no es un contribuyente registrado, no importa qué valor se asigne al RFC, siempre será aceptado. Para crear esos objectos, hicimos uso de unos métodos de utilería empleado_registrado y empleado_no_registrado, que en un proyecto real se encargarían de generar empleados registrados y no registrados según complejas reglas de negocio, pero en nuestro caso sólo ponen la variable registrado en true o false según corresponda. También se agregaron ejemplos que prueban que se acepta la CURP vacía.

Es cierto que el código de nuestras pruebas no es el óptimo, se puede refactorizar para mejorarlo. Una buena alternativa sería usar factories para generar nuestros modelos en lugar de nuestros métodos de utilería, pero es un tema completo que nos desviaría mucho de nuestra tarea, por lo que lo mantendremos de esa manera porque creo que para fines del tutorial es suficientemente claro.

El código de nuestra clase `Empleado` quedaría como sigue:


class Empleado < ActiveRecord::Base
  attr_accessor :registrado

  validates :rfc, rfc_format: true, if: :contribuyente_registrado?
  validates :curp, curp_format: true, allow_blank: true

  def contribuyente_registrado?
    registrado
  end

end

De nuevo, el método contribuyente_registrado? es una simplificación del código real (que seguramente involucraría reglas de negocio complejas), pero funciona para probar nuestro ejemplo. Ejecutemos las pruebas para verificar que funcionen correctamente.

rspec spec/models

Finished in 0.05310 seconds
30 examples, 0 failures

Como vemos, nuestros validadores se integran sin ningún problema con las opciones de ActiveRecord que estamos acostumbrados a utilizar, y sin que tengamos que modificar el código, por lo que podemos decir que nuestra gema está completa. Ahora veremos cómo publicarla para que cualquier persona la pueda integrar en su proyecto.

Git y Github

Para esto, crearemos un proyecto en github. Si necesitas más información sobre cómo crear repositorios en github, revisa este tutorial.

Si no tienes configurado git aún, debes indicar tu nombre y correo electrónico

git config --global user.email "you@example.com"
git config --global user.name "Your Name"

Si sólo deseas configurar esos datos para este proyecto y no para todo tu entorno, no utilices la opcion --global.

Si estás utilizando git 1.8 o superior, se recomienda usar la siguiente configuración:

git config --global push.default simple

Ya que será el comportamiento por omisión para git 2.0. Esta opción le indica a git que cuando se haga push trabajará sólo con la rama actual, y no con todo el repositorio, como lo hacía con versiones anteriores.

Ahora sí, con git configurado, debemos agregar nuestros archivos a nuestro controlador de versiones. Aquí debemos indicar qué archivos compondrán parte de nuestra gema. En nuestro caso, vamos a incluir todos los archivos.

git add .

hacemos commit de los archivos

git commit -m "Primera versión de la gema."

y hacemos push de nuestro proyecto

git push origin master

Con esto, hemos conseguido un gran avance: cualquier usuario puede usar nuestra gema clonando nuestro proyecto en github y agregando la ruta en el Gemfile de su aplicación. Pero no es exactamente el mejor flujo para integrar gemas a un proyecto. Lo ideal es que simplemente agregue la gema como dependencia y rubygems se encargue de todo lo demás, sin que el usuario tenga que descargar manualmente el código. Veamos cómo podemos hacer esto.

Publicar en RubyGems

Algo que hasta el momento habíamos pasado por alto es la documentación de nuestra gema. Si la estamos compartiendo con el mundo, necesitamos indicar cómo debe ser utilizada. Dado que nuestra gema tiene una funcionalidad muy básica, no es mucho lo que tenemos que documentar por ahora, basta con explicar para qué sirve y cómo utilizarla. Para esto, nos basta el archivo de README.

Bundler nos generó una estructura bastante completa para este archivo, por lo que a nosotros nos corresponde sólo introducir la información adecuada.

Con la documentación de nuestra gema completa, podemos publicarla en RubyGems. Para esto, lo primero que tenemos que hacer es registrarnos en rubygems

Ahora sí, podemos publicar nuestra gema.

rake release

Este comando realiza dos tareas: crear un tag de git con la versión actual de nuestra gema y publicar esa versión en rubygems. Aquí es donde se vuelve importante actualizar la versión de nuestra gema cada que la liberemos, ya que de lo contrario obtendríamos un error al tratar de liberar dos veces la misma versión de la gema.

Si es la primera vez que trabajamos con rubygems, es probable que nos encontremos con este error:

rake aborted!
Your rubygems.org credentials aren't set. Run `gem push` to set them.

Si esto sucede, basta con seguir la instrucción que nos indica el mensaje de error, introducir nuestras credenciales (email y password) y... ¡listo!. Con eso podemos publicar nuestra gema cada vez con rake release.

Integrar la gema con la aplicación

A estas alturas, ya no hay mucho trabajo por hacer, lo único que nos falta es delegar completamente la carga de nuestra gema a rubygems, sin que tengamos que indicarle el directorio. Como nuestra gema ya está publicada, no nos queda más que eliminar el path en nuestro Gemfile. La línea quedaría de la siguiente manera:

gem 'identificamex'

Para asegurarnos que estamos tomando la versión publicada, debemos actualizar nuestra gema con bunlder.

bundle update identificamex

Por último, volvemos a ejecutar nuestras pruebas:

rspec spec/models

Finished in 0.05518 seconds
30 examples, 0 failures

Todo sigue funcionando, así que podemos dar por finalizada nuestra tarea.

Si te interesa revisar el código real de la gema, puedes verlo en github, sólo toma en cuenta que la gema ha crecido un poco e incluye más funcionalidad de la que se muestra en el tutorial. Además, en la gema utilicé MiniTest en lugar de RSpec, pero el principio es el mismo.

Cualquier corrección a este tutorial o a la gema, será bienvenida.

Azarel Doroteo Pacheco es albañil de software en LogicalBricks.

Parte II: Creación de una gema que incorpore los validadores de CURP y RFC

Octubre 1, 2013 por apux

Objetivo

Crear una gema que incluya los validadores de CURP y RFC que se generaron en la Parte I de este tutorial.

Creación de la gema

Una de las tareas más difíciles al momento de crear una gema es escoger un nombre. Al menos, es una de las más complicadas para mí. Para simplificar, llamémosle identificamex.

Lo primero que debemos hacer es crear la gema. Existen varias formas de hacer esto, una de las más fáciles es utilizar bundler.

bundle gem identificamex

Y listo. Bundler se encargó de crear toda la estructura necesaria de una gema genérica. Ahora sólo tenemos que agregar el contenido propio de la nuestra.

Los archivos que incluye la gema son el Gemfile, el Rakefile, el README, la licencia y un archivo .gemspec. Además, creó un directorio llamado lib y dentro de ese directorio un archivo llamado identificamex.rb, que define un módulo vacío. También creó otro archivo, lib/identificamex/version.rb, que tiene la versión de la gema (por omisión, '0.0.1').

De todos estos archivos, el que más nos intriga es identificamex.gemspec, de los demás se puede inferir fácilmente su utilidad.

Gemspec

El archivo identificamex.gemspec es el archivo básico de configuración para nuestra gema. Cada gema tiene un archivo con el nombre de la gema y la extensión .gemspec. En este archivo se configura el nombre, la descripción y la versión de la gema, los archivos que la componen, datos de los autores, etc.

Nota cómo hay un pequeño truco para indicar qué archivos conforman la gema. En lugar de listarlos todos, se hace uso de git. Es decir, la gema se compondrá de los archivos que nosotros agreguemos a la estructura de git. Ojo, esto significa que si nosotros creamos un archivo nuevo, y no lo agregamos a git, nuestras pruebas locales pueden funcionar correctamente, pero al liberar la gema ésta fallará porque le harán falta archivos.

Si no conoces git, puedes empezar revisando el sitio oficial (hay una sección de tutoriales), y practicando en el sitio interactivo.

Otro detalle a tomar en cuenta es que la versión de la gema no se especifica directamente en el archivo (aunque puede hacerse). En lugar de eso, se hace referencia al valor de una variable: Identificamex::VERSION. Esa variable la podremos encontrar en el archivo lib/identificamex/version.rb que se creó automáticamente al crear la gema. Cada nueva versión de nuestra gema, habremos de cambiar el valor de esa variable.

El resto de parámetros de configuración son bastante intuitivos.

Dependecias

Es probable que nuestra gema dependa de que existan otras gemas para que funcione correctamente, de ser así, debemos especificar esas dependencias. Al igual que un proyecto Rails, nuestra gema tiene un archivo Gemfile, donde podríamos agregar las gemas de las que depende, sin embargo, en el caso de las gemas, hay un mejor archivo para hacer esto, y ese archivo es identificamex.gemspec. Este es el archivo general para la configuración de nuestra gema. Y en nuestro caso, agregaremos también la dependencia de ActiveModel, ya que esta gema es necesaria para que nuestros validadores funcionen.

Al momento, nuestro archivo gemspec contiene lo siguiente.

# coding: utf-8
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'identificamex/version'

Gem::Specification.new do |spec|
    spec.name          = "identificamex"
    spec.version       = Identificamex::VERSION
    spec.authors       = ["Azarel Doroteo Pacheco"]
    spec.email         = ["azarel.doroteo@logicalbricks.com"]
    spec.description   = %q{Validadores para los formatos de CURP y RFC}
    spec.summary       = %q{Validadores sencillos para los formatos de la Clave Única de Registro de Población (CURP) y el Registro Federal de Contribuyentes (RFC) utilizados en México}
    spec.homepage      = "https://github.com/LogicalBricks/identificamex"
    spec.license       = "MIT"

    spec.files         = `git ls-files`.split($/)
    spec.executables   = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
    spec.test_files    = spec.files.grep(%r{^(test|spec|features)/})
    spec.require_paths = ["lib"]

    spec.add_development_dependency "bundler", "~> 1.3"
    spec.add_development_dependency "rake"

    spec.add_dependency 'activemodel'
end

Dependencias de desarrollo

Ahora bien, existen dependencias que sólo afectan al momento del desarrollo pero no a la funcionalidad de la gema en sí. Por ejemplo, las bibliotecas de pruebas (en nuestro caso RSpec). Si agregamos rspec como dependencia, se instalará automáticamente cuando se instale nuestra gema y no queremos eso. Esta dependencia sólo afecta si alguien descarga nuestro código con la intensión de modificarlo, en cuyo caso sí se debe instalar la biblioteca de pruebas; pero si un usuario sólo quiere instalar nuestra gema para usarla y no para modificarla, la biblioteca de pruebas no debe considerarse dependencia. Es el mismo caso que presenta bundler y rake.

Para estos casos, la dependencia se debe especificar de la siguiente manera:

spec.add_development_dependency 'rspec'

Y por último instalamos las dependencias con bundler:

bundle install

Validadores

Como ya creamos nuestros validadores para nuestra aplicación Rails, nos tomamos la libertad de copiarlos al directorio lib. Siendo estrictos con TDD, primero deberíamos realizar las pruebas y luego programar la funcionalidad, pero en este caso, como la funcionalidad ya la tenemos, podemos tomarnos ese permiso.

#lib/rfc_format_validator.rb
class RfcFormatValidator < ActiveModel::EachValidator
  def validate_each(object, attribute, value)
    unless value =~ /\A[A-ZÑ&]{3,4}[0-9]{2}[0-1][0-9][0-3][0-9]([A-Z0-9]{3})?\z/i
      object.errors[attribute] << (options[:message] || "no es un RFC válido") 
    end
  end
end

#lib/curp_format_validator.rb
class CurpFormatValidator < ActiveModel::EachValidator
  def validate_each(object, attribute, value)
    unless value =~ /\A[A-Z][AEIOUX][A-Z]{2}[0-9]{2}[0-1][0-9][0-3][0-9][MH][A-Z][BCDFGHJKLMNÑPQRSTVWXYZ]{4}[0-9A-Z][0-9]\z/i
      object.errors[attribute] << (options[:message] || "no es una CURP válida") 
    end
  end
end

Pruebas

De nuevo, es necesario agregar pruebas a nuestra biblioteca para asegurarnos que todo esté funcionando como debe y que las futuras modificaciones no rompan nada. Gracias a nuestro conjunto de pruebas que creamos en la primera parte de este tutorial, esta tarea se simplifica un poco. Desafortunadamente, la transición no será tan sencilla, tendremos que sortear algunos problemas antes de conseguir que las pruebas funcionen adecuadamente.

Lo primero que hay que tomar en cuenta es que la funcionalidad que debemos de probar no es una funcionalidad aislada, sino una funcionalidad que se agrega a un modelo de Rails. Por lo tanto, necesitaremos crear un modelo Rails para poder probar nuestros validadores.

¿Significa que dependemos de que Rails esté instalado para que podamos probar nuestra gema? ¿Tenemos que agregar la dependencia a 'rails' en lugar de 'activemodel'? ¿Debemos agregar 'rails' como dependencia de desarrollo? Afortunadamente, la respuesta a todas estas preguntas es 'No'. No necesitamos en lo absoluto de Rails en nuestra gema. Rails agrega la funcionalidad de validadores mediante la gema de ActiveModel, que nosotros ya tenemos instalada.

Ahora bien, si revisamos el código de nuestra aplicación, veremos que nuestra clase Empleado hereda de ActiveRecord::Base. Pero, de nuevo, nosotros no tenemos accesso a ActiveRecord, sólo a ActiveModel. Podríamos agregar la dependencia a ActiveRecord directamente, en lugar de ActiveModel, pero no sería lo adecuado.

ActiveRecord vs ActiveModel

¿Qué es exactamente lo que hace ActiveRecord y qué es lo que hace ActiveModel? La funcionalidad de ambas bibliotecas puede confundirnos un poco. No es la intensión de este tutorial estudiar a fondo el comportamiento de estas herramientas, pero sí necesitamos conocer los aspectos básicos que nos permitan seguir avanzando en la creación de nuestra gema.

Anteriormente, la funcionalidad de los modelos de Rails recaía por completo en ActiveRecord, pero a partir de Rails 3, esta funcionalidad se agregó un nivel más de abstracción con ActiveModel. ActiveRecord realiza muchas tareas muy importantes, como mapear nuestro modelo a una base de datos, realizar las consultas y actualización, validar las entradas, etc. Sólo que ahora, las tareas que no corresponden a la base de datos son delegadas a ActiveModel. De esta manera, podemos tener un modelo con la funcionalidad completa de un modelo de Rails, pero sin la parte de la base de datos. Esto nos beneficia mucho, ya que para el desarrollo de nuestra gema, no necesitamos que los valores sean persistentes: sólo necesitamos que se puedan validar y saber si hay errores o no.

Nuestro instinto nos podría indicar que si no queremos la funcionalidad de ActiveRecord, y sólo queremos quedarnos con ActiveModel, podríamos crear nuestra clase de la siguiente manera:

class Empleado < ActiveModel::Base
end

Desafortunadamente, esto no funciona porque no existe una clase Base en el módulo ActiveModel. En estos casos, lo que se necesita hacer es agregar la funcionalidad como un módulo. El módulo completo es ActiveModel::Model, pero tampoco necesitamos del módulo completo, para nuestra gema es suficiente trabajar con ActiveModel::Validations. Así, nuestra clase queda de la siguiente manera:

class Empleado
   include ActiveModel::Validations
end

Y ahora sí, tenemos nuestro modelo que permite agregar validaciones.

Regresemos entonces a nuestras pruebas. Para empezar, necesitamos crear un archivo spec/spec_helper.rb. Este archivo es similar al que la gema de rspec-rails creó por nosotros en nuestra aplicación, pero para nuestra gema, vamos a crear una versión mucho más básica:

# spec/spec_helper.rb
require 'identificamex'

Lo único que incluye el spec_helper es una línea que requiere el archivo importador. Este archivo es el que se creó al inicio de este proceso, con el módulo vacío. Modificamos ese archivo para incluir los archivos de nuestros validadores.

require 'active_model'
require "identificamex/version"
require "curp_format_validator"
require "rfc_format_validator"

module Identificamex
end

Specs

Ahora sí, ya podemos programar nuestros specs. Nuestro primer intento, como buenos desarrolladores, será reutilizar las mismas pruebas que teníamos en nuestra aplicación. Desafortunadamente eso no será posible, ya que hay algunos problemas que necesitamos solucionar. Primero, las pruebas en la aplicación son bastante elegantes, con sintaxis de este tipo:

expect(Empleado.new(rfc: 'AAAA111111AAA')).to have(:no).errors_on(:rfc)
expect(Empleado.new(curp: 'AAAA111111KDFBBB01')).to have(1).error_on(:curp)

Pero esto no funciona dentro de nuestra gema, porque los métodos errors_on y error_on no existen en rspec: son agregados por la gema rspec-rails, que no tenemos en nuestra gema. Por lo tanto, tenemos que cambiar un poco la forma de los specs.

La idea sería escribir nuestros specs de la siguiente manera:

expect(Empleado.new(rfc: 'AAAA111111AAA')).to be_valid
expect(Empleado.new(curp: 'AAAA111111KDFBBB01')).to_not be_valid

La clase Empleado

Para esto, tenemos que modificar la clase Empleado que creamos anteriormente para que acepte parámetros. Además, para asegurarnos que el modelo sea válido por omisión, en caso de no recibir un parámetro, se inicializará con un valor válido.

class Empleado
  include ActiveModel::Validations
  attr_accessor :curp, :rfc
  validates :curp, curp_format: true
  validates :rfc, rfc_format: true

  def initialize(options)
    @curp = options[:curp] || 'AAAA111111HDFBBB01'
    @rfc  = options[:rfc]  || 'AAAA111111AAA'
  end
end

Como no dependemos de una base de datos, los campos se han agregado como accessors a las varibales curp y rfc, se han agregado las validaciones de formato a esas variables, y se ha agregado un método initialize que inicializa las variables correctamente.

El archivo de specs completo queda de la siguiente manera:

#spec/validators_spec.rb

require 'spec_helper'

class Empleado
  include ActiveModel::Validations
  attr_accessor :curp, :rfc
  validates :curp, curp_format: true
  validates :rfc, rfc_format: true

  def initialize(options)
    @curp = options[:curp] || 'AAAA111111HDFBBB01'
    @rfc  = options[:rfc]  || 'AAAA111111AAA'
  end
end

describe 'rfc validator' do

  context 'with valid data' do
    it 'accepts rfc (completo)' do
      expect(Empleado.new(rfc: 'AAAA111111AAA')).to be_valid
    end

    it 'accepts rfc (con 3 caracteres para el nombre)' do
      expect(Empleado.new(rfc: 'AAA111111AAA')).to be_valid
    end

    it 'accepts rfc (sin homoclave)' do
      expect(Empleado.new(rfc: 'AAAA111111')).to be_valid
    end

    it 'accepts rfc (sin homoclave y con 3 caracteres para el nombre)' do
      expect(Empleado.new(rfc: 'AAA111111')).to be_valid
    end

    it 'accepts rfc (datos de nombre con Ñ)' do
      expect(Empleado.new(rfc: 'ÑAAA111111AAA')).to be_valid
    end

    it 'accepts rfc (datos de nombre con &)' do
      expect(Empleado.new(rfc: 'A&A111111AAA')).to be_valid
    end
  end

  context 'with invalid data' do
    it 'refuses rfc (nombre con digito en lugar de letra)' do
      expect(Empleado.new(rfc: '9AAA111111')).to_not be_valid
    end

    it 'refuses rfc (caracter invalido)' do
      expect(Empleado.new(rfc: 'A*AA111111')).to_not be_valid
    end

    it 'refuses rfc (falta un digito en la fecha)' do
      expect(Empleado.new(rfc: 'AAAA11111')).to_not be_valid
    end

    it 'refuses rfc (falta un caracter en los datos del nombre)' do
      expect(Empleado.new(rfc: 'AA111111')).to_not be_valid
    end

    it 'refuses rfc (el dia es invalido 42)' do
      expect(Empleado.new(rfc: 'AAAA111142')).to_not be_valid
    end

    it 'refuses rfc (el mes es invaido 25)' do
      expect(Empleado.new(rfc: 'AAAA112511')).to_not be_valid
    end
  end

end

describe 'curp validator' do

  context 'with valid data' do
    it 'accepts curp (hombre)' do
      expect(Empleado.new(curp: 'AAAA111111HDFBBB01')).to be_valid
    end

    it 'accepts curp (mujer)' do
      expect(Empleado.new(curp: 'AAAA111111MDFBBB01')).to be_valid
    end

    it 'accepts curp (digito anti-duplicado alfanumérico)' do
      expect(Empleado.new(curp: 'AAAA111111HDFBBBA1')).to be_valid
    end

  end

  context 'with invalid data' do
    it 'refuses curp (nombre con digito en lugar de letra)' do
      expect(Empleado.new(curp: '9AAA111111HDFBBB01')).to_not be_valid
    end

    it 'refuses curp (caracter invalido)' do
      expect(Empleado.new(curp: 'A*AA111111HDFBBB01')).to_not be_valid
    end

    it 'refuses curp (falta un digito en la fecha)' do
      expect(Empleado.new(curp: 'AAAA11111HDFBBB01')).to_not be_valid
    end

    it 'refuses curp (falta un caracter en los datos del nombre)' do
      expect(Empleado.new(curp: 'AAA111111HDFBBB01')).to_not be_valid
    end

    it 'refuses curp (el dia es invalido 42)' do
      expect(Empleado.new(curp: 'AAAA111142HDFBBB01')).to_not be_valid
    end

    it 'refuses curp (el mes es invaido 25)' do
      expect(Empleado.new(curp: 'AAAA112511HDFBBB01')).to_not be_valid
    end

    it 'refuses curp (sexo es inválido K)' do
      expect(Empleado.new(curp: 'AAAA111111KDFBBB01')).to_not be_valid
    end

    it 'refuses curp (caracteres de consonantes tiene alguna vocal)' do
      expect(Empleado.new(curp: 'AAAA111111HDFABB01')).to_not be_valid
      expect(Empleado.new(curp: 'AAAA111111HDFBAB01')).to_not be_valid
      expect(Empleado.new(curp: 'AAAA111111HDFBBA01')).to_not be_valid
    end

    it 'refuses curp (digito verificador alfanumérico)' do
      expect(Empleado.new(curp: 'AAAA111111HDFBBB0A')).to_not be_valid
    end

  end

end

Es básicamente el mismo conjunto de pruebas de nuestra aplicación pero adaptado para nuestra gema. Por supuesto, sería mejor que las pruebas estuvieran en archivos separados y el modelo auxiliar también, pero para fines del tutorial, podemos trabajar con ese archivo.

Si ejecutamos nuestro conjunto de pruebas:

rspec .

Finished in 0.00984 seconds
24 examples, 0 failures

Como podemos ver, todo ha salido de maravilla. Ahora tenemos una gema que agrega los validadores. Sin embargo, aún nos falta publicar nuestra gema para que se pueda utilizar en otros proyectos Rails. De eso trata la última parte de este tutorial.

Azarel Doroteo Pacheco es albañil de software en LogicalBricks.

Parte I: Creación de validadores para CURP y RFC

Septiembre 24, 2013 por apux

Una de características más potentes del ecosistema Ruby es la facilidad de compartir bibliotecas con funcionalidades que pueden ser (re)utilizadas en varios proyectos mediante gemas. La creación de gemas puede parecer una labor muy complicada y reservada sólo para los gurús de Ruby, pero no es así. Es posible crear nuestras propias gemas sin demasiado esfuerzo.

Este tutorial se compone de tres partes, en la primera parte crearemos los validadores para la Clave Única de Registro de Población (CURP) y el Registro Federal de Contribuyentes (RFC) que son los formatos más comunes de identificación en México, e integraremos estos nuevos validadores a la lista de validadores que ya incorpora ActiveRecord. En la segunda parte veremos cómo crear una gema con estos validadores y en la tercera parte veremos cómo publicar la gema e integrarla a una aplicación Rails.

En caso que deseen reproducir el ejemplo, las versiones de las herramientas que utilizaremos en este tutorial son: Ruby 2.0, Rails 4.0, Bundler 1.3.5, RubyGems 2.0.7 y RSpec 2.14.

Parte I: Creación de validadores para CURP y RFC

Objetivo

Se pretende crear validadores que permitan escribir la validación de CURP y RFC de la siguiente manera:

validates :rfc, rfc_format: true
validates :curp, curp_format: true

Aplicación Rails

Lo primero que haremos es crear una aplicación Rails con un modelo llamado Empleado, que incluya los campos curp y rfc.

rails new my_app -T
cd my_app

La opción -T indica que no usaremos el framework de pruebas por omisión, así que antes de generar el modelo, instalamos la gema de rspec-rails que es la que vamos a ocupar en el proyecto. La agregamos como dependencia al Gemfile

group :development, :test do
  gem 'rspec-rails', '~> 2.0'
end

y la instalamos por medio de bundler

bundle install

Ahora sí, podemos crear nuestro modelo y correr las migraciones de nuestra base de datos.

rails generate scaffold empleado curp rfc
rake db:migrate

Pruebas

Ahora, crearemos las pruebas para definir el comportamiento de nuestras validaciones. Existen varias herramientas para hacer TDD/BDD en Ruby, como TestUnit, MiniTest, Shoulda, Cucumber, Bacon, etc. En nuestro caso, usaremos RSpec ya que es una herramienta muy potente, bastante difundida entre los desarrolladores Rails y fácil de utilizar. Si no estás familiarizado con RSpec, las siguientes ligas pueden resultarte útiles: info, rspec expectations, better specs y el libro de RSpec

Ya instalada la gema, necesitamos generar el archivo spec/spec_helper.rb. Lo podemos crear desde cero o bien utilizar el generador que la gema nos proporciona. Por comodidad, haremos esto último.

rails generate rspec:install

De esta manera, se genera el archivo spec/spec_helper.rb con el contenido necesario para nuestra aplicación.

Specs

El comportamiento que esperamos para los campos de curp y rfc es el siguiente:

#spec/models/empleado_spec.rb
require 'spec_helper'

describe Empleado do
  describe '#rfc' do
    context 'with valid data' do
      it 'accepts rfc (completo)' do
        expect(Empleado.new(rfc: 'AAAA111111AAA')).to have(:no).errors_on(:rfc)
      end

      it 'accepts rfc (con 3 caracteres para el nombre)' do
        expect(Empleado.new(rfc: 'AAA111111AAA')).to have(:no).errors_on(:rfc)
      end

      it 'accepts rfc (sin homoclave)' do
        expect(Empleado.new(rfc: 'AAAA111111')).to have(:no).errors_on(:rfc)
      end

      it 'accepts rfc (sin homoclave y con 3 caracteres para el nombre)' do
        expect(Empleado.new(rfc: 'AAA111111')).to have(:no).errors_on(:rfc)
      end

      it 'accepts rfc (datos de nombre con Ñ)' do
        expect(Empleado.new(rfc: 'ÑAAA111111AAA')).to have(:no).errors_on(:rfc)
      end

      it 'accepts rfc (datos de nombre con &)' do
        expect(Empleado.new(rfc: 'A&A111111AAA')).to have(:no).errors_on(:rfc)
      end
    end

    context 'with invalid data' do
      it 'refuses rfc (nombre con digito en lugar de letra)' do
        expect(Empleado.new(rfc: '9AAA111111')).to have(1).error_on(:rfc)
      end

      it 'refuses rfc (caracter invalido)' do
        expect(Empleado.new(rfc: 'A*AA111111')).to have(1).error_on(:rfc)
      end

      it 'refuses rfc (falta un digito en la fecha)' do
        expect(Empleado.new(rfc: 'AAAA11111')).to have(1).error_on(:rfc)
      end

      it 'refuses rfc (falta un caracter en los datos del nombre)' do
        expect(Empleado.new(rfc: 'AA111111')).to have(1).error_on(:rfc)
      end

      it 'refuses rfc (el dia es invalido 42)' do
        expect(Empleado.new(rfc: 'AAAA111142')).to have(1).error_on(:rfc)
      end

      it 'refuses rfc (el mes es invaido 25)' do
        expect(Empleado.new(rfc: 'AAAA112511')).to have(1).error_on(:rfc)
      end
    end
  end

  describe '#curp' do
    context 'with valid data' do
      it 'accepts curp (hombre)' do
        expect(Empleado.new(curp: 'AAAA111111HDFBBB01')).to have(:no).errors_on(:curp)
      end

      it 'accepts curp (mujer)' do
        expect(Empleado.new(curp: 'AAAA111111MDFBBB01')).to have(:no).errors_on(:curp)
      end

      it 'accepts curp (digito anti-duplicado alfanumérico)' do
        expect(Empleado.new(curp: 'AAAA111111HDFBBBA1')).to have(:no).errors_on(:curp)
      end
    end

    context 'with invalid data' do
      it 'refuses curp (nombre con digito en lugar de letra)' do
        expect(Empleado.new(curp: '9AAA111111HDFBBB01')).to have(1).error_on(:curp)
      end

      it 'refuses curp (caracter invalido)' do
        expect(Empleado.new(curp: 'A*AA111111HDFBBB01')).to have(1).error_on(:curp)
      end

      it 'refuses curp (falta un digito en la fecha)' do
        expect(Empleado.new(curp: 'AAAA11111HDFBBB01')).to have(1).error_on(:curp)
      end

      it 'refuses curp (falta un caracter en los datos del nombre)' do
        expect(Empleado.new(curp: 'AAA111111HDFBBB01')).to have(1).error_on(:curp)
      end

      it 'refuses curp (el dia es invalido 42)' do
        expect(Empleado.new(curp: 'AAAA111142HDFBBB01')).to have(1).error_on(:curp)
      end

      it 'refuses curp (el mes es invaido 25)' do
        expect(Empleado.new(curp: 'AAAA112511HDFBBB01')).to have(1).error_on(:curp)
      end

      it 'refuses curp (sexo es inválido K)' do
        expect(Empleado.new(curp: 'AAAA111111KDFBBB01')).to have(1).error_on(:curp)
      end

      it 'refuses curp (caracteres de consonantes tiene alguna vocal)' do
        expect(Empleado.new(curp: 'AAAA111111HDFABB01')).to have(1).error_on(:curp)
        expect(Empleado.new(curp: 'AAAA111111HDFBAB01')).to have(1).error_on(:curp)
        expect(Empleado.new(curp: 'AAAA111111HDFBBA01')).to have(1).error_on(:curp)
      end

      it 'refuses curp (digito verificador alfanumérico)' do
        expect(Empleado.new(curp: 'AAAA111111HDFBBB0A')).to have(1).error_on(:curp)
      end
    end
  end
end

El archivo puede parecer extenso, pero es realmente muy sencillo. Lo único que hemos incluido son algunos ejemplos que verifican que se acepten RFC's y CRURP's válidos y se rechacen los inválidos. Por supuesto que este conjunto de pruebas se puede incrementar para agregar más casos, pero por el momento podemos darnos por satisfechos con los que tenemos.

NOTA: Los que ya utilizan RSpec habrán notado que hemos utilizado la sintaxis de expect en lugar de should. Personalmente me gusta más la sintaxis de should porque es más elegante y más fácil de leer, pero actualmente se recomienda usar expect ya que será la sintaxis por omisión para RSpec 3.

Para ejecutar nuestras pruebas, es necesario configurar nuestra base de datos de prueba:

rake db:test:prepare

Ahora, ejecutamos nuestro conjunto de pruebas.

rspec spec/models


Finished in 0.02662 seconds
24 examples, 15 failures

Muchas de las pruebas fallan. Algunas otras pasan, pero en realidad son falsos positivos, ya que no hemos escrito la funcionalidad aún. Las pruebas que pasan son las que validan que un valor en particular sea aceptado, y como aún no agregamos las validaciones, todos los valores son aceptados, así que, lo que haremos a continuación será agregar esa funcionalidad.

Expresiones regulares

Deberíamos detenernos un momento para solucionar el problema de la validación en sí: generar las expresiones regulares que empaten con el formato de la CURP y el RFC. Considero que no es necesario entrar en detalles sobre las reglas para generar y validar la CURP y el RFC, si se necesita más información al respecto, es posible consultarla en la wikipedia (curp, rfc) y en la conducef (curp, rfc)

Las expresiones regulares generadas son las siguientes:

# curp
/\A[A-Z][AEIOUX][A-Z]{2}[0-9]{2}[0-1][0-9][0-3][0-9][MH][A-Z][BCDFGHJKLMNÑPQRSTVWXYZ]{4}[0-9A-Z][0-9]\z/i
# rfc
/\A[A-ZÑ&]{3,4}[0-9]{2}[0-1][0-9][0-3][0-9]([A-Z0-9]{3})?\z/i

No nos detendremos mucho para analizar estas expresiones, si no estás familiarizado con expresiones regulares en Ruby, puedes revisar estas ligas: la documentación oficial y el libro de cabecera de ruby.

Es posible que a estas expresiones regulares se les pueda hacer alguna optimización, pero por el momento las usaremos tal como están.

Validar el formato

Una primera aproximación es realizar la validación de estos campos por medio del validador de formato que ya incorpora Rails, de tal manera que tendríamos un modelo parecido al siguiente:

class Empleado ActiveRecord::Base
  validates :rfc, format: { with: /\A[A-ZÑ&]{3,4}[0-9]{2}[0-1][0-9][0-3][0-9]([A-Z0-9]{3})?\z/i, message: 'no es un formato de RFC válido' }
  validates :curp, format: { with: /\A[A-Z][AEIOUX][A-Z]{2}[0-9]{2}[0-1][0-9][0-3][0-9][MH][A-Z][BCDFGHJKLMNÑPQRSTVWXYZ]{4}[0-9A-Z][0-9]\z/i, message: 'no es un formato de CURP válido' }
end

Si ejecutamos nuevamente nuestro conjunto de pruebas

rspec spec/models


Finished in 0.05323 seconds
24 examples, 0 failures

Esto significa que todo ha funcionado como esperamos.

Validadores

Ahora bien, si la misma validación la realizamos en más de un modelo (por ejemplo, Empresa, Cliente, Representante, etc.), lo mejor es extraer la funcionalidad en un validador propio con el fin de reutilizar el código. Afortunadamente, Rails nos facilita mucho el trabajo, estableciendo una estructura que nosotros debemos de seguir si queremos agregar validadores. Así que, manos a la obra.

Antes de crear los validadores, debemos decidir en qué directorio colocarlos. Una opción sería agregarlos a la carpeta de modelos, pero al ser una funcionalidad que no es exclusiva de nuestra aplicación sino una biblioteca general, lo correcto sería ubicarlos en la carpeta lib.

Primero, creamos el directorio lib/validators y creamos los archivos rfc_format_validator.rb y curp_format_validator.rb dentro de ese directorio.

El contenido de ambos archivos se lista a continuación

#lib/validators/rfc_format_validator.rb
class RfcFormatValidator < ActiveModel::EachValidator
  def validate_each(object, attribute, value)
    unless value =~ /\A[A-ZÑ&]{3,4}[0-9]{2}[0-1][0-9][0-3][0-9]([A-Z0-9]{3})?\z/i
      object.errors[attribute] << (options[:message] || "no es un RFC válido") 
    end
  end
end



#lib/validators/curp_format_validator.rb
class CurpFormatValidator < ActiveModel::EachValidator
  def validate_each(object, attribute, value)
    unless value =~ /\A[A-Z][AEIOUX][A-Z]{2}[0-9]{2}[0-1][0-9][0-3][0-9][MH][A-Z][BCDFGHJKLMNÑPQRSTVWXYZ]{4}[0-9A-Z][0-9]\z/i
      object.errors[attribute] << (options[:message] || "no es una CURP válida") 
    end
  end
end

Poner nuestro código dentro del directorio lib trae consigo un pequeño problema: a partir de Rails 3, este directorio no se carga automáticamente, así que tendremos que indicarle a Rails que queremos que cargue automáticamente nuestros validadores.

En el archivo config/application.rb, agregamos la siguiente línea

config.autoload_paths += %W(#{Rails.root}/lib/validators)

Si queremos que cargue varios directorios, basta con que los agreguemos a la lista.

Refactor

Y listo, con esto podemos refactorizar el código de nuestro modelo para incluir los validadores que acabamos de crear

class Empleado < ActiveRecord::Base
  validates :rfc, rfc_format: true
  validates :curp, curp_format: true
end

Volvemos a ejecutar nuestras pruebas para asegurarnos que todo funcione correctamente:

rspec spec/models

Finished in 0.05323 seconds
24 examples, 0 failures

Todo funciona perfectamente. Sin embargo, hasta el momento, todo nuestro trabajo lo hemos hecho dentro de nuestra aplicación Rails. ¿Qué sucede si en un nuevo proyecto necesitamos hacer estas validaciones? Una opción sería copiar estos archivos a todos los proyectos que requieran validar CURP y RFC, pero si de pronto encontramos que nuestra expresión regular tiene un error y debemos corregirla, o si encontramos una optimización que hacer, tendríamos que modificar estos archivos en todos los proyectos en los que los estemos utilizando. Una mejor opción sería crear una gema y reutilizar nuestros validadores a través de ella. Y eso es precisamente lo que haremos en la segunda parte de este tutorial.

Azarel Doroteo Pacheco es albañil de software en LogicalBricks.

Nitrous.IO un ambiente fácil de instalar para Ruby on Rails.

Septiembre 9, 2013 por mariochavez

Configurar un ambiente de desarrollo para Ruby on Rails es todo menos una tarea sencilla. No es "rocket science", pero generalmente se presentan problemas que arruinan la experiencia de configuración.

Cuando he tenido que dar un taller, generalmente comparto mi guía de instalación para Windows, Linux o OSX. El problema de mi guía es que los pasos pueden cambiar con el tiempo y que algunas otras aplicaciones previamente instaladas en los equipos de quien intenta seguirlas, causan conflictos que una vez más arruinan la experiencia.

En el caso de Windows y OSX el uso de RailsInstaller es una gran ayuda, el único problema es que la experiencia de desarrollar en Windows puede no ser la optima con gemas o librerías que no funcionan correctamente o donde se tiene que hacer trabajo manual de compilación.

Para OSX aparte de RailsInstaller hay trabajo realizado con Tokaido que promete una instalación muy sencilla, pero que aún no es una realidad del todo.

Afortunadamente no todo esta perdido y parece que "la nube" puede ser una solución bastante buena.

Nitrous.IO

Nitrous.IO los conocí hace tiempo cuando se llamaban Action.IO. Nitrous.IO ofrece la posibilidad de crear "Boxes" para desarrollo pre configuradas con Ruby on Rails, pero también es posible utilizarlas para desarrollo con Python, Go y NodeJs.

Cada Box es una virtualización de Linux a la cual se le pueden agregar recursos como espacio en disco y memoria, pero además ofrece las siguientes características que lo ponen como una opción seria de desarrollo.

Editor Web

Este es un editor totalmente en linea, cuenta con un navegador de archivos, un editor multi tab y acceso a la linea de comandos.

image alt

Desde la interface web es posible navegar los directorios en nuestra máquina virtual y abrir cualquiera de ellos con un simple "click". También es posible cargar nuevos archivos desde nuestra máquina a Nitrous.

Otra de las funcionalidades consiste en poder tener una consola de comandos tipo "\nix"* donde podemos ejecutar cualquier comando de la máquina virtual de Nitrous que no requiera de permisos de root.

Finalmente en el editor tenemos la opción de colaborar con la cual podemos invitar por medio de email a otras personas de colaborar en tiempo real, ademas de contar con un chat donde es posible conversar con las personas que estén colaborando.

Todo nuestro código se guarda en la máquina virtual de Nitrous y si usamos un sistema de control de código fuente, como por ejemplo git, desde el panel de terminal podemos realizar commit y empujar esos cambios a un servidor remoto.

Nota: Para este caso es necesario agregar las llaves que se encuentran en ~/.ssh a el control de acceso de nuestro repositorio.

Acceso SSH

Esta quizás es una de las funcionalidades mas poderosas para mi, ya que la máquina virtual de Nitrous permite acceso mediante SSH a la misma; esto nos da la posibilidad de usar la terminal nativa de nuestro sistema operativo y utilizar herramientas como Vim y Tmux, que viene instalados por defecto.

image alt

En mi caso utilizo una configuración especifica de Vim y Tmux, la cual me fue posible instalar sin problemas. Inclusive cree unas recetas de Chef que me permiten configurar la máquina virtual de Nitrous a mi gusto.

Todo es tan simple como clonar mis recetas a la máquina virtual ir al directorio chef-solo y ejecutar el siguiente comando para tener una personalización de Vim y Tmux:

$ chef-solo -c solo.rb -j web.json

El acceso mediante SSH abre la posibilidad, por ejemplo, de utilizar un iPad con un teclado externo para desarrollar, solo es necesario instalar un cliente de SSH.

image alt

Mac app

Si cuentas con una Mac, Nitrous ofrece una aplicación para la barra de notificaciones desde donde puedes ver:

  • Cuantas máquinas virtuales tienes
  • El estado de las mismas (encendidas|apagadas)
  • Manejar las máquinas virtuales
  • Abrir una sesión de SSH para alguna de las máquinas
  • Abrir el IDE web para alguna de las máquinas
  • Activar la opción de sincronizar archivos (* mas detalle adelante)
  • Hacer port-forward (* mas detalle adelante)

image alt

Sincronizar archivos

Con la aplicación Mac es posible activar la opción de sincronizar archivos entre un directorio local y la máquina virtual, este permite el poder usar algún otro editor, como TextMate por ejemplo, y cada que se guarde un archivo se sincronice con Nitrous y poder ejecutar las pruebas o ejecutar la aplicación desde allá de forma transparente.

Port-forward

Esta funcionalidad permite conectar un puerto de la máquina virtual, por ejemplo el 3000, como un puerto local, con lo cual si queremos ver nuestra aplicación ejecutándose, en lugar de usar la URL larga que proporciona Nitrous, podemos navegar a http://localhost:3000

¿Qué viene instalado en la máquina virtual?

Al momento de crear una máquina de Nitrous, configurada para Ruby on Rails, esta viene configurada con:

  • Ubuntu OS
  • Ruby 2.0.0-p247 (Máquinas nuevas de hace una semana)
  • Rails 4.0.0
  • RVM
  • Vim
  • Tmux

La máquina virtual no nos ofrece acceso de root, por lo que no podemos instalar paquetes al sistema, pero si viene con los compiladores y headers necesarios, por ejemplo, para descargar el código fuente de alguna versión de Ruby e instalarla.

Tampoco viene con base de datos instalada, por lo que es necesario usar algún servicio de Cloud, como por ejemplo Heroku Postgres.

Aunque para algunos usuarios nos llego la invitación beta para un servicio con el cual es posible instalar motores de base de datos de la máquina virtual y de esa forma contar con un ambiente completo de desarrollo.

¿Cuanto cuesta Nitrous?

Para tener una máquina virtual en Cloud con 512 Mb de RAM y 750Mb de espacio, el costo es gratuito, para agregar más recursos el costo varia dependiendo de estos y son en modalidad de renta mensual.

Si quieres registrarte y tener acceso a una máquina virtual, puedes usar mi código o bien ir directamente a Nitrous.io.

Comentarios finales

Si quieres contar con una instalación de Ruby on Rails simple y que este disponible desde donde te conectes con la máquina que te conectes, no puedes dejar de pasar la oportunidad de probar y desarrollar en Nitrous, no encontraras forma mas simple de tener un ambiente de desarrollo.

Como trabajar con asociaciones con Ruby on Rails

Agosto 20, 2013 por apux

Mapeo objeto-relacional

Como desarrolladores en Ruby on Rails, algunas veces olvidamos de la capa de persistencia gracias a que ActiveRecord se encarga de mapear objetos Ruby a tablas de la base de datos y viceversa (a esto se le conoce como Mapeo objeto_relacional). Nosotros trabajamos con objectos Ruby, con la lógica de negocios que contienen, y delegamos por completo al framework la forma en la que esos objetos se guardan (salvo en ocasiones especiales, que tenemos que ensuciarnos las manos con SQL).

Sin embargo, como toda herramienta, es necesario conocer cómo trabaja para poder sacarle el mayor provecho, sobre todo en ocasiones especiales, donde el comportamiento no es siempre el esperado. La intensión del post es conocer cómo se comportan los modelos asociados cuando se realizan asignaciones, ya que en ciertas ocasiones la asociación se guarda automáticamente y en otras no. También veremos cómo 'forzar' el comportamiento que deseamos.

Aunque el tema principal del post es verificar el comportamiento de las asociaciones para casos específicos, en los ejemplos también se cubrirán aspectos más básicos para que una persona con escaso conocimiento de asociaciones en Rails pueda seguirlos sin problema. De cualquier forma, si se requiere documentación básica más detallada, la guía de asociaciones en Rails es un buen lugar para comenzar.

Las asociaciones

Imaginemos que tenemos los siguientes modelos asociados:

#app/models/empleado.rb
class Empleado < ActiveRecord::Base
  has_one :puesto
  has_many :computadoras
  has_and_belongs_to_many :proyectos
end

#app/models/puesto.rb
class Puesto < ActiveRecord::Base
  belongs_to :empleado
end

#app/models/computadora.rb
  class Computadora < ActiveRecord::Base
  belongs_to :empleado
end

#app/models/proyecto.rb
class Proyecto < ActiveRecord::Base
  has_and_belongs_to_many :empleados
end

Como podemos ver, tenemos cuatro modelos con diversas asociaciones: uno a uno, uno a muchos y muchos a muchos. Por comodidad, vamos a pasar por alto las validaciones de estos modelos y centrarnos únicamente en las relaciones.

Inicialización de modelos

Para empezar, veamos cómo instanciar un Empleado. Esto lo podemos hacer en dos pasos:

empleado = Empleado.new nombre_completo: 'Juan Pérez'
empleado.save

O bien, en uno solo:

empleado = Empleado.create nombre_completo: 'Juan Pérez'

En el primer ejemplo, se instancia el modelo Empleado y se guarda después. En el segundo ejemplo se instancia el modelo Empleado y se guarda automáticamente en la base de datos.

También podemos crear por separado un puesto, un proyecto y una computadora.

puesto = Puesto.create nombre: 'Programador'
proyecto = Proyecto.create nombre: 'Proyecto Importante'
computadora = Computadora.create modelo: 'laptop 15"'

Hasta aquí, hemos instanciado cuatro modelos por separado y todo ha funcionado correctamente. Es momento de asociarlos.

Asociación uno a muchos

Empezaremos analizando cómo se comporta la relación uno a muchos al momento de realizar una asignación. Rails nos permite asociar nuestros modelos por medio de los ids ( foreign keys ), pero una forma más natural es asociar los modelos directamente. Como ya tenemos creados nuestros modelos, basta con asignar uno al otro. Ahora bien, las asignaciones de uno a muchos pueden hacerse de dos maneras: asignar el empleado a la computadora, o bien, agregar la computadora a la lista de computadoras que tiene el empleado. Empecemos por hacer lo primero:

computadora.empleado = empleado

Una vez hecha la asignación, verificamos que efectivamente estén asociados.

computadora.empleado
# => #<Empleado id: 1, nombre_completo: "Juan Pérez">
empleado.computadoras
# => #<ActiveRecord::Associations::CollectionProxy []>

La asignación funcionó correctamente, pero al tratar de acceder a la asociación contraria, nos regresa un arreglo vacío.

Guardado manual

La razón por la que la lista de computadoras regresó vacía es sencilla: no hemos salvado nuestra relación, por lo que los cambios todavía no se han reflejado en la base de datos y por tanto, el empleado no se ha enterado que tiene una computadora asignada. Guardemos la instancia del modelo y veamos cómo se comporta.

computadora.save
Computadora.find(1).empleado
# => #<Empleado id: 1, nombre_completo: "Juan Pérez">
Empleado.find(1).computadoras
# => [#<Computadora id: 1, modelo: 'laptop 15"', empleado_id: 1>]

Hecho esto, las asociaciones funcionan correctamente. Para comprobarlo, hemos consultado directamente la información de la base de datos en lugar de los objetos que tenemos en memoria, y la asociación se mantiene ya que se ha guardado en la tabla correspondiente. Eso también lo podemos ver en el campo empleado_id de la computadora, que ahora tiene el id del empleado (en este caso, 1).

Guardado automático

Veamos ahora cómo se comporta la asignación si la hacemos al revés, es decir, agregando una computadora a la lista de computadoras de un empleado.

computadora_2 = Computadora.create modelo: 'desktop 24"'
empleado.computadoras << computadora_2
Computadora.find(2).empleado
# => #<Empleado id: 1, nombre_completo: "Juan Pérez">
Empleado.find(1).computadoras
# => [#<Computadora id: 1, modelo: 'laptop 15"', empleado_id: 1>,
#     #<Computadora id: 2, modelo: 'desktop 24"', empleado_id: 1>]

Lo primero que hicimos fue crear una nueva computadora y luego agregarla a la lista de computadoras del empleado, después comprobamos el estado de las asociaciones y todo funciona correctamente. Lo interesante es que no necesitamos guardar ninguno de los dos modelos después de la asociación, Rails lo hizo automáticamente por nosotros.

Asignación de un modelo que no ha sido guardado

Veamos un tercer caso: en los dos casos anteriores, el modelo computadora ya existía en la base de datos, ahora veremos qué sucede con un modelo que aún no ha sido guardado:

empleado.computadoras << Computadora.new(modelo: 'netbook') 
Computadora.find(3).empleado
# => #<Empleado id: 1, nombre_completo: "Juan Pérez">
Empleado.find(1).computadoras
# => [#<Computadora id: 1, modelo: 'laptop 15"', empleado_id: 1>,
#     #<Computadora id: 2, modelo: 'desktop 24"', empleado_id: 1>,
#     #<Computadora id: 3, modelo: 'netbook', empleado_id: 1>]

La nueva computadora aparece en la lista de computadoras asociadas, con el campo empleado_id: 1, lo que significa que está asociado al empleado que creamos previamente, y con el id: 3, lo que significa que la computadora no sólo fue asignada, sino que al momento de realizar la asignación, Rails la guardó automáticamente.

Este comportamiento puede ser el esperado en muchas de las ocasiones, pero en otras no. A veces preferimos asociar un modelo nuevo a uno existente sin que el nuevo modelo se guarde automáticamente, por ejemplo, cuando hay validaciones que todavía no se satisfacen, o cuando tenemos que hacer un procesamiento posterior sobre el modelo a agregar antes de que éste sea guardado. Si ese es nuestro caso, ¿qué podemos hacer?

Create, Build y New a través de la clase Proxy

Nuestra relación computadoras dentro de un modelo empleado es una clase proxy que incluye métodos que nos permiten algunas operaciones. Una pequeña aclaración sobre la clase de la asociación: en Rails 3, si ejecutábamos empleado.computadoras.class, obteníamos como resultado Array, pero era en realidad información falsa, porque empleado.computadoras es en realidad una clase proxy. En Rails 4, la información es más adecuada: ActiveRecord::Associations::CollectionProxy::ActiveRecord_Associations_CollectionProxy_Computadora.

Esta clase nos provee de un par de métodos más para asociar dos modelos en una relación uno a muchos: los métodos build y create. El método build construye y asigna el modelo pero no lo guarda (debemos llamar a save manualmente si queremos guardarlo). El método create, en cambio, guarda automáticamente el modelo asociado.

 computadora_sin_guardar = empleado.computadoras.build modelo: 'server'
 computadora_sin_guardar.new_record?
 # => true
 computadora_sin_guardar.empleado_id
 # => 1
 empleado.computadoras
 # => [#<Computadora id: 1, modelo: 'laptop 15"', empleado_id: 1>,
 #     #<Computadora id: 2, modelo: 'desktop 24', empleado_id: 1>,
#     #<Computadora id: 3, modelo: "netbook", empleado_id: 1>,
#     #<Computadora id: nil, modelo: "server", empleado_id: 1>]
empleado.save
Empleado.find(1).computadoras
# => [#<Computadora id: 1, modelo: 'laptop 15"', empleado_id: 1>,
#     #<Computadora id: 2, modelo: 'desktop 24', empleado_id: 1>,
#     #<Computadora id: 3, modelo: "netbook", empleado_id: 1>,
#     #<Computadora id: 4, modelo: "server", empleado_id: 1>]

Como la asociación no se guarda automáticamente, hay que hacerlo de manera manual llamando al método save. Esto se puede hacer ya sea desde el objecto empleado (empleado.save) o desde el objeto computadora_sin_guardar (computadora_sin_guardar.save). En ambos casos, se guarda también la relación.

Existe también el método new, que es simplemente un alias para el build.

Como regla general, para las asociaciones uno a muchos, podemos decir que si la asignación se hace del modelo con belongs_to al modelo con has_many, la asociación se guarda automáticamente, mientras que si se hace a la inversa, la asociación no será guardada mientras no se guarde alguno de los dos modelos.

Asociación muchos a muchos

Las asociaciones muchos a muchos se guardan automáticamente, sin importar la dirección de la asignación. Si el modelo que se asigna no está guardado y el receptor sí, el nuevo modelo se guarda automáticamente. Este comportamiento lo podemos ver en el siguiente código.

empleado.proyectos << proyecto
# => [#<Proyecto id: 1, nombre: "Proyecto Importante">]
proyecto.empleados << Empleado.new(nombre_completo: 'Guadalupe Martínez')
 Proyecto.find(1).empleados
# => [#<Empleado id: 1, nombre_completo: "Juan Pérez">,
#     #<Empleado id: 2, nombre_completo: "Guadalupe Martínez">]

De nuevo, si queremos asignar un proyecto a un empleado (o viceversa) sin que la asignación provoque que el modelo se guarde automáticamente, podemos usar los métodos build o new de la clase proxy de la asociación, y guardar el modelo posteriormente. Por ejemplo:

empleado_sin_guardar = proyecto.empleados.build nombre_completo: 'Carlos López'
proyecto.empleados
# => [#<Empleado id: 1, nombre_completo: "Juan Pérez">,
#     #<Empleado id: 2, nombre_completo: "Guadalupe Martínez">,
#     #<Empleado id: nil, nombre_completo: "Carlos López">]
proyecto.save
Proyecto.find(1).empleados
# => [#<Empleado id: 1, nombre_completo: "Juan Pérez">,
#     #<Empleado id: 2, nombre_completo: "Guadalupe Martínez">,
#     #<Empleado id: 3, nombre_completo: "Carlos López">]

Como la asociación no guarda automáticamente, hay que hacerlo de manera manual llamando al método save, similar a como se hace en las asociaciones uno a muchos. La diferencia es que en este caso se tiene que llamar al método save desde el objeto que recibe la asignación, en este caso, proyecto. Si se llama a save desde el modelo nuevo (empleado_sin_guardar.save) sólo se guarda este objeto y no la relación.

También se puede usar el método build desde la otra asociación, por ejemplo: empleado.proyectos.build(nombre: 'secreto').

Existe, como cabe suponer, un método create en ambas relaciones que construye y guarda al mismo tiempo el modelo asociado.

Asociación uno a uno

Conociendo el comportamiento de las asociaciones uno a muchos y muchos a muchos, la asociación uno a uno parece más sencilla. Reproduciremos los mismos escenarios de los casos anteriores. En esta ocasión trabajaremos con los modelos Empleado y Puesto. Como ya tenemos creados nuestros modelos, simplemente los asociamos.

puesto.empleado = empleado
puesto.empleado
# => #<Empleado id: 1, nombre_completo: "Juan Pérez"> 
empleado.puesto
# => nil

Cuando asignamos el modelo que tiene el has_one al modelo que tiene el belongs_to, la relación no se guarda automáticamente. Veamos la asignación contraria:

empleado.puesto = puesto
Puesto.find(1).empleado
# => #<Empleado id: 1, nombre_completo: "Juan Pérez">
Empleado.find(1).puesto
# => #<Puesto id: 1, empleado_id: 1, nombre: "Programador">

El comportamiento es el esperado, al hacer la asignación contraria, la asociación se guarda automáticamente. Probemos ahora cómo se comporta cuando se asocia un modelo que no ha sido guardado:

empleado.puesto = Puesto.new(nombre: 'Programador Sr')
Empleado.find(1).puesto
# => #<Puesto id: 2, empleado_id: 1, nombre: "Programador Sr">

De nuevo, se mantiene el comportamiento visto en la relación uno a muchos, es decir, el nuevo modelo se guarda automáticamente al asignarse a un modelo existente.

Build_association y create_association

Si queremos asociar un nuevo modelo Puesto al modelo de Empleado, pero sin guardarlo, buscaríamos hacer algo como esto: empleado.puesto.build(nombre: 'jefe'), desafortunadamente eso no funciona, nos arroja un error que dice NoMethodError: undefined method 'build' for nil:NilClass. Esto es porque la asociación puesto no corresponde a una clase proxy como en la relación uno a muchos, sino que es ya propiamente el modelo Puesto que en este caso es nil (o puede ser un objeto Puesto en caso de que ya tenga un puesto asociado).

Afortunadamente, gracias a un poco de metaprogramación, Rails nos ofrece un método que hace lo que necesitamos. Por cada asociación uno a uno que tengamos, se genera un método. El nombre del método varía según el nombre de la asociación. En nuestro caso, el método se llama build_puesto. Usaremos este método para construir nuestro modelo.

 puesto_sin_gurdar = empleado.build_puesto nombre: 'Jefe'
 empleado.puesto
 # => #<Puesto id: nil, empleado_id: 1, nombre: "Jefe">

Aquí, al igual que en las relaciones uno a muchos, podemos guardar cualquiera de los dos modelos (empleado o puesto_sin_guardar) y en ambos casos, se guardará correctamente la asociación.

Así, podemos instanciar y asociar un modelo sin que sea guardado automáticamente.

De manera similar, existe un método que empieza por create_ (create_puesto en nuestro caso) que realiza la asociación y la guarda automáticamente, como la asignación directa. Podemos usar este método para crear y asociar automáticamente un empleado a un puesto, por ejemplo:

puesto_nuevo = Puesto.create 'nuevo'
puesto_nuevo.create_empleado(nombre_completo: 'Mario Guerrero')

Conclusiones

Las asociaciones en Rails son una parte fundamental en el framework y conocer su funcionamiento resulta muy útil en varios escenarios, particularmente en aquellos en los que el comportamiento, aunque consistente, no siempre resulta intuitivo.

Trataré de resumir los aspectos importantes que vimos:

  • Las asociaciones uno a uno y uno a muchos tienen tienen un modelo que especifica el belongs_to y otro con has_one o has_many respectivamente. Cuando la asignación se hace del modelo con belongs_to al modelo con has_*, la asignación guarda automáticamente la relación y el modelo asignado en caso de que no exista en la base de datos.
  • Si la asignación se hace en el otro sentido, la relación no se guarda automáticamente.
  • Las asociaciones muchos a muchos salvan automáticamente la relación y el modelo, sin importar en qué sentido se haga la asignación.
  • Para las asociaciones uno a muchos y muchos a muchos, existen los métodos build y new en la asociación (por medio de una clase proxy ), que nos permiten construir modelos asociados sin salvarlos.
  • Para las asociaciones uno a uno, el método existe directamente en el modelo (cualquiera de los dos) y se llama build_#{nombre_de_la_asociacion}.

Azarel Doroteo Pacheco es albañil de software en LogicalBricks. Guillermo Moreno y Angel Solorio de Crowd Interactive colaboraron en la revisión de este post.

Flujo colaborativo de trabajo con Git y Github

Agosto 9, 2013 por FerPerales

Probablemente ya hemos escuchado acerca de Git y de cómo se ha ido convirtiendo en el sistema de control de versiones favorito entre las empresas de TI ya que hace que el trabajar en equipo sea relativamente fácil gracias a los comandos que nos proporciona.

Dependiendo de las características del proyecto, pueden presentarse algunas de estas opciones:

  • Repositorio público al que no tienes acceso para empujar código
  • Repositorio privado al que tienes acceso

En este post, vamos a dar un vistazo al primer flujo

Repositorio público al que no tienes acceso para empujar código

La mayoría de aportaciones a open source entran en este caso: existen un proyecto en Github y quieres mandar alguna propuesta de mejora, agregar una nueva funcionalidad o corregir un bug pero no tienes acceso al repositorio para clonarlo y empujar directamente los cambios.

Fork

Lo primero que debemos hacer es hacer una copia del repositorio a nuestra cuenta A esto se le conoce en Github como hacer un 'fork' y se hace desde la interfaz de Github

image alt

Esto hará que el repositorio se copie a nuestra cuenta conservando el mismo nombre y agregando el origen del repositorio.

image alt

Clonamos el repositorio a nuestra máquina usando el siguiente comando:

git clone git@github.com:TU_NOMBRE_DE_USUARIO/NOMBRE_DEL_REPO.git

image alt

Siempre es conveniente revisar en el repositorio si hay reglas para realizar las contribuciones. Estas reglas pueden incluir desde seguir ciertas convenciones de nombres para las ramas creadas o para los commits, que la nueva funcionalidad tenga pruebas (que es bastante recomendable aunque no sea requerido) o alguna otra regla que el autor del código original considere necesaria. Si no nos apegamos a esas reglas, es muy probable que nuestro pull request sea rechazado.

Considerando que no hay alguna regla especial para este ejemplo, procedemos a trabajar:

Una vez que tienes tu copia local, es hora de realizar una nueva rama para nuestros cambios.

git checkout -b nombre-de-la-rama

Estando en la nueva rama, podemos trabajar libremente en nuestro código.

Una vez que hemos terminado de hacer los cambios correspondientes, es hora de subir nuestra rama a Github.

git push origin nombre-de-la-rama

Ahora nuestra rama aparecerá en nuestro repositorio junto a las otras ramas que el proyecto pueda tener.

image alt

Pull Request

Ya que tenemos nuestra rama en nuestro repositorio en Github, es hora de integrarla con el repositorio original para lo que tenemos que hacer un Pull Request. Un Pull Request (comúnmente abreviado como PR) es la manera en que solicitamos agregar los commits realizados en una rama a otra.

Para esto, vamos al repositorio original y buscamos el botón de 'Compare and Pull Request'.

image alt

En esta sección, podemos ver tres partes principales:

  • Ramas involucradas

Indica el repositorio y la rama que queremos comparar así como el nombre de la rama base con la que se realizará la comparación. Si alguna de las ramas no fuera la deseada, podemos seleccionar otra en esta sección.

image alt

La segunda parte sirve para agregar comentarios respecto al PR que haremos así como si este podrá ser integrado sin conflictos con la rama base.

image alt

Por último, tenemos una sección que nos muestra tanto los archivos como los cambios realizados a los mismos para dar un vistazo antes de realizar el PR.

image alt

Finalizando

Una vez que verifiquemos que todo está en orden, procedemos a crear el pull request.

image alt

Lo que sigue es esperar a que el código que enviamos sea revisado ya que es probable que alguien deba revisar el código y tenga dudas o sugerencias al respecto del mismo o que tu PR sea rechazado por alguna razón.

image alt

Nuestro final feliz llegará el día en que nuestro código sea integrado en el proyecto original.

image alt

Conclusiones

Como comenté al inicio de la entrada, este flujo es el más común para realizar contribuciones a proyectos de open source.

Fernando Perales,es Ingeniero en Crowd Interactive.

Refactorizar a un helper

Julio 18, 2013 por joseluistorres

En días pasados, después de encontrar la gema de railsbestpractices como les explicaba en el post. Estuve revisando nuestro código en powhow.com e inmediatamente me di cuenta que teniamos tantísimos cosas que arreglar que de plano no terminaríamos refactorizando todo para usar las mejores prácticas.

En la reunión pasada de @ruby_gdl lo comentaba, la gema nos encontró 3200 warnings. Triste pero cierto, indudablemente nos hace falta mucho por aprender y sin duda esta herramienta es un buen comienzo. Ya entrados en materia encontré que uno de los warnings o sugerencias para refactorizar era mover código similar en una vista y moverlo a un helper. Lo había escuchado sí, lo había visto sí, lo había hecho yo mismo, no.

Veamos este es el código de diferentes vistas con una lógica similar:

Vista 1:

<% image_dimensions = process.transcoding_in_progress ? '144x81' : (process.schedule_id.nil? or !process.is_image_from_workshop) ? '144x81' : '66x81' %>
<% thumbnail_class = process.transcoding_in_progress ? 'image-container' : (process.schedule_id.nil? or !process.is_image_from_workshop) ? 'image-container video' : 'image-container process letterbox-tiny' %>
<%= image_tag !process.transcoding_in_progress ? process.thumbnail_url : Process::DEFAULT_IMAGE, :size => image_dimensions, :id => 'processplay-' + process.id.to_s, :class => thumbnail_class %>

Vista 2:

<% image_dimensions = (process.schedule_id.nil? or process.is_image_from_workshop) ? '192x108' : '88x108' %>
<% thumbnail_class = (process.schedule_id.nil? or process.is_image_from_workshop) ? 'image-container process' : 'image-container process letterbox-vsmall' %>        
<%= image_tag process.thumbnail_url, :size => image_dimensions, :class => thumbnail_class, :id => "" %> 

Vista 3

<% image_dimensions = (@process.transcoding_in_progress or @process.is_image_from_workshop) ? '192x108' : @process.schedule_id.nil? ? '192x108' : '88x108' %>
<% thumbnail_class = (@process.transcoding_in_progress or @process.is_image_from_workshop) ? 'image-container' : @process.schedule_id.nil? ? 'image-container video' : 'image-container process letterbox-vsmall' %>            
<%= image_tag !@process.transcoding_in_progress ? @process.thumbnail_url : asset_path('thumb-video.jpg'), :class => thumbnail_class, :size => image_dimensions, :id => "videoplay-" + @process.id.to_s %>

Entonces tenemos 3 vistas con lógicas similares que puede ser un buen candidato para refactorizar. ¿Por donde empezar? Pues como dice @amyhoy : empecemos por el final. No teniamos un helper para esta entidad por lo que primero creamos el archivo helper de process:

module ProcessHelper
end

Excelente, pero ¿y luego que? Pensé crear un método que tuviera la salida de la etiqueta de image_tag

def image_tag_for_process(process, place_used)
  image_dimensions = process.transcoding_in_progress ? '144x81' : (process.schedule_id.nil? or !process.is_image_from_workshop) ? '144x81' : '66x81'
  thumbnail_class = process.transcoding_in_progress ? 'image-container' : (process.schedule_id.nil? or !process.is_image_from_workshop) ? 'image-container video' : 'image-container process letterbox-tiny'
  image_tag !process.transcoding_in_progress ? process.thumbnail_url : Process::DEFAULT_IMAGE, :size => image_dimensions, :id => 'processplay-' + process.id.to_s, :class => thumbnail_class %>
end

Claro me di cuenta que necesitaba una forma para validar el alto y ancho en cada caso y además la clase de CSS que debía utilizar. Empecé por la clases de CSS, y luego el tamaño y finalmente la fuente de la imagen.

module VideosHelper
  def image_tag_for_process(process, place_used)
    image_dimensions = process.transcoding_in_progress ? '144x81' : (process.schedule_id.nil? or !process.is_image_from_workshop) ? '144x81' : '66x81'
    image_tag thumbnail_image_url_process(process.transcoding_in_progress, process.thumbnail_url, place_used),
      :size => image_dimensions,
      :id => "videoplay-#{process.id.to_s}",
      :class => thumbnail_class_process(process.transcoding_in_progress, process.is_image_from_workshop, place_used)
  end

  def thumbnail_image_url_process(transcoding_in_progress, thumbnail_url, place_used)
    !transcoding_in_progress ? thumbnail_url : Process::DEFAULT_THUMBNAIL_IMAGE
  end

  def thumbnail_class_process(transcoding_in_progress, is_image_from_workshop, place_used)
    if place_used=='send_video_invite'
      if transcoding_in_progress or !is_image_from_workshop
        'image-container process process_thumb_for_mailing'
      else
        'image-container process process_thumb_for_mailing_with_letterbox'
      end
    elsif transcoding_in_progress
      'image-container'
    elsif !is_image_from_workshop
      'image-container process'
    else
      "image-container process #{letter_box_class(place_used)}"
    end
  end

  def letter_box_class(place_used)
    case place_used
    when 'manage_processes'
      'letterbox-tiny'
    when 'library'
      'letterbox-small'
    when 'locked_process'
      'letterbox-vsmall'
    when 'show'
      'letterbox'
    end
  end
end

Finalmente agregué este método al helper

def image_dimensions_process(transcoding_in_progress, is_image_from_workshop, place_used)
  case place_used
  when 'manage_processes'
    if transcoding_in_progress or !is_image_from_workshop
      '144x81'
    else
      '66x81'
    end
  when 'library'
    if transcoding_in_progress or !is_image_from_workshop
      '284x160'
    else
      '131x160'
    end
  when 'locked_process'
    if transcoding_in_progress or !is_image_from_workshop
      '192x108'
    else
      '88x108'
    end
  when 'show'
    if transcoding_in_progress or !is_image_from_workshop
      '728x410'
    else
      '335x410'
    end
  when 'send_process_invite'
    if transcoding_in_progress or !is_image_from_workshop
      '550x310'
    else
      '252x310'
    end
  end
end

Entonces finalmente la refactorización quedó así:

module VideosHelper
  def image_tag_for_process(process, place_used)
    image_dimensions = process.transcoding_in_progress ? '144x81' : (process.schedule_id.nil? or !process.is_image_from_workshop) ? '144x81' : '66x81'
    image_tag thumbnail_image_url_process(process.transcoding_in_progress, process.thumbnail_url, place_used),
      :size => image_dimensions,
      :id => "videoplay-#{process.id.to_s}",
      :class => thumbnail_class_process(process.transcoding_in_progress, process.is_image_from_workshop, place_used)
  end

  def thumbnail_image_url_process(transcoding_in_progress, thumbnail_url, place_used)
    !transcoding_in_progress ? thumbnail_url : Process::DEFAULT_THUMBNAIL_IMAGE
  end

  def thumbnail_class_process(transcoding_in_progress, is_image_from_workshop, place_used)
    if place_used=='send_video_invite'
      if transcoding_in_progress or !is_image_from_workshop
        'image-container process process_thumb_for_mailing'
      else
        'image-container process process_thumb_for_mailing_with_letterbox'
      end
    elsif transcoding_in_progress
      'image-container'
    elsif !is_image_from_workshop
      'image-container process'
    else
      "image-container process #{letter_box_class(place_used)}"
    end
  end

  def letter_box_class(place_used)
    case place_used
    when 'manage_processes'
      'letterbox-tiny'
    when 'library'
      'letterbox-small'
    when 'locked_process'
      'letterbox-vsmall'
    when 'show'
      'letterbox'
    end
  end

  def image_dimensions_process(transcoding_in_progress, is_image_from_workshop, place_used)
    case place_used
    when 'manage_processes'
      if transcoding_in_progress or !is_image_from_workshop
        '144x81'
      else
        '66x81'
      end
    when 'library'
      if transcoding_in_progress or !is_image_from_workshop
        '284x160'
      else
        '131x160'
      end
    when 'locked_process'
      if transcoding_in_progress or !is_image_from_workshop
        '192x108'
      else
        '88x108'
      end
    when 'show'
      if transcoding_in_progress or !is_image_from_workshop
        '728x410'
      else
        '335x410'
      end
    when 'send_process_invite'
      if transcoding_in_progress or !is_image_from_workshop
        '550x310'
      else
        '252x310'
      end
    end
  end

end

Y la implementación:

<%= image_tag_for_video video, 'manage_videos' %>
<%= image_tag_for_video video, 'library' %>
<%= image_tag_for_video video, 'locked_video' %>

Aún no sé si es la mejor opción pero por ahora se ve mejor y entiendo mejor que estaba haciendo. ¿Y ustedes como lo mejorarían?

José Luis Torres, trabaja como Ingeniero Ruby on Rails en Powhow, sigue a José Luis en twitter @joseluis_torres

Contenido para Rails MX

Julio 15, 2013 por mariochavez

Este post es para recordarles que siempre estamos buscando contenido que sea interesante para la comunidad de desarrollo con Ruby y Ruby on Rails en México y habla hispana.

Pero soy nuevo en Ruby no creo que pueda ayudar.

Estamos buscando contenido para todos los niveles, ya que a Rails Mx, llega gente que apenas va empezando, así como gente con experiencia.

Recuerden que un post no es para decir cuanto sabes sobre un tema, es para compartir algo nuevo que descubrimos, que aprendimos y bien comunicar experiencias positiva o negativas en nuestro día a día.

Siempre hay algo que otras personas no sepan.

Hay mucha gente nueva que está aprendiendo, hay muchas nueva experiencia que esta misma gente esta generando por sí misma, ayuda a otros nuevos con los problemas o cosas que descubriste y que te sorprendieron, ser un "newbie" no implica que lo que tengas que decir no le importe a nadie, y si dices algo incorrecto, no te preocupes, la comunidad de Ruby es muy amigable, siempre habrá alguién dispuesto a ayudar.

Agradecimiento

En agradecimiento a tu esfuerzo para generar contenido, cada mes escogeremos uno de los posts publicados para recibir de regalo una camiseta de Rails MX.

Tenemos una limitada cantidad de camisetas, patrocinadas por Crowd Interactive. Queremos regalar más, así que si tu empresa pueda ayudarnos patrocinando más camisetas y quizás regalar una por post, ponte en contacto con nosotros.

No lo pienses más

Si ya terminaste de leer éste post, es hora de que empieces a escribir tu post, aquí esta la guía de como contribuir, y si necesitas ayuda para revisar tu post, mandalo usando esa guía y pidiendo la ayuda para revisarlo.

Trabajar efectivamente con ActiveRecord

Julio 8, 2013 por joseluistorres

Estoy seguro que todos han enfrentado la siguiente situación: un equipo de desarrollo que es una mezcla de programadores experimentados y no tanto en Ruby on Rails; trabajando en el mismo proyecto con muchas funcionalidades en varios estados de avance o progreso, muchas páginas de inicio o entrada(landing pages), muchas más con contenido dinámico, diferentes tipos de búsquedas, etc.

Todo esto en el contexto de que la mayoría de desarrolladores prefirieron Ruby o Ruby on Rails porque es un lenguaje elegante, divertido de trabajar y además fácil de leer. Y esto sin duda es cierto, sin embargo, el hecho de que pequeñas o grandes aplicaciones puedan convertirse en lentas y poco responsivas, la experiencia combinada de todos los desarrolladores no los para, por ejemplo, de utilizar algo como User.where("all the criteria").profile.tickets.review para mostrar todos los reviews disponibles que un clase, producto o servicio tiene.

Sin importar la experiencia del equipo, entre más pronto nos demos cuenta que a pesar que la sintaxis del Active Record es glamorosa y agradable al leer, puede dejar mucho que desear en términos de rendimiento. Continuando con el ejemplo anterior para mostrar todos los reviews de una clase, producto o servicio, no deberíamos de usar más de una relación para encontrar algo vía Active Record, ¿por que?

Permítanme explicarme:

  1. Si utilizan algo como User.find(1).profile estarán cargando automáticamente todas las columnas de user y profile. Como todos sabemos, esas tablas pueden llegar a contener muchos campos que muy probablemente no necesites en el escenario arriba descrito.

  2. Si tratas de hacer con 3,4 o 5 tablas tendrá ese mismo efecto pero magnificará el tiempo de respuesta de una manera considerable, porque estás agregando un query adicional por cada una.

  3. Tu jefe de proyecto seguramente te gritará: ¿por qué está tan lento?…

¿Que debemos hacer?

Primero que nada, a mi no me crean. Chequen ustedes mismos sus logs, tiempos de respuesta en firebug, las dev tools de Chrome, etc, instalen New Relic si aún no lo han hecho. Vean los miles de queries que hace su aplicación de manera innecesaria para tomar ese dato que se requiere pintar en la pantalla o es utilizado para una columna en una fila de resultados.

Una vez que ya hayan verificado, es tiempo de pensar en la consulta SQL que necesitan para obtener los datos. Les sugiero las siguientes preguntas para que les ayude en el diseño de su query:

  • ¿Que columnas necesito para mostrar la información que quiero? Escojan de manera quisquillosa, y recuerden que Model.select() o .pluck() son sus amigos

  • ¿Hay alguna operación matemática requerida para poder mostrar el la información correctamente? Vean la documentación de su BD y vean si pueden utilizar alguna de las funciones predefinidas en el manejador de base de datos de su preferencia

  • ¿Necesitan incluir una concatenación de columnas de otra tabla que no necesariamente está relacionada con un simple "inner join" y además con un formato específico? Ej. select concat(firstname, ' ', lastname) from anothertable; Si es así, utilicen los subqueries

  • Hagan su query en su editor de SQL favorito, corranlo local para verificar

  • Ahora generen el SQL con sus parametros correctos. Y recuerden no utilizar más de una relación en la sintaxis de Active Record

Esto ayudará a reducir el tiempo consumido en cada petición, porque de entrada no se generará el efecto query +1 cada vez al navegar en los resultados. Recuerden también que no es necesario agregar el Model.select("*") ya que esto es lo mismo que omitir el select(), generando así un query que selecciona todas las columnas. Cuando tengan duda, preferible remover todas las columnas e irlas agregando una por una hasta que obtengan el resultado adecuado.

Eso es todo. Por supuesto esto puede aplicar solo a algunos escenarios, ustedes escogen cuando y donde funciona mejor en sus proyectos. Comenten por favor, su opinión es bienvenida.

Los invito a consultar Active Record Basics y Active Record querying para mayor información.

También escuchar: http://rubyrogues.com/110rr-activerecord-with-ernie-miller/#more-1368

José Luis Torres, trabaja como Ingeniero Ruby on Rails en Powhow

RailsBridge Mexico: Mujeres aprendiendo Ruby on Rails.

Junio 25, 2013 por ana-ci

Hace un par de semanas tuvimos nuestro primer taller de RailsBridge Open Workshop en las instalaciones de Crowd Interactive. El evento de dos días reunió a mujeres en su mayoría, y (algunos) hombres invitados, interesados en aprender a desarrollar una aplicación en Ruby on Rails desde cero. Entre los asistentes se encontraban personas que nunca había programado, estudiantes, diseñadores y personas con amplia experiencia en el área. Todos igual de entusiasmados de estar ahí para aprender y compartir experiencias con los demás asistentes.

image alt

¿Qué es RailsBridge?

Es una comunidad enfocada en diversificar la tecnología, proporcionando herramientas y recursos para todos aquellos interesados en la enseñanza y el aprendizaje, especialmente mujeres. Nuestro taller, como parte de este movimiento, se dedicó a la enseñanza de Ruby on Rails, guiando a los asistentes paso a paso a través de la construcción de una aplicación web.

El desarrollo de software y la tecnología en general, son campos principalmente dominados por hombres, en el que las mujeres tienen muy poca representación. Es por eso que RailsBridge trabaja para reducir la brecha entre las mujeres y la tecnología.

Día uno: Installfest

El primer día del taller fue dedicado exclusivamente a la instalación de las herramientas necesarias para el desarrollo y publicación de las aplicaciones: Ruby, Ruby on Rails, gemas, etc. De esta manera, al siguiente día, todos estarían listos para empezar a codificar desde el primer momento.

Día dos: ¡Código!

Muy temprano iniciamos el taller con todos los asistentes y sus equipos configurados con el entorno de Ruby on Rails. Desi McAdam, nuestra invitada de honor y miembro de la comunidad RailsBridge, dio las instrucciones para iniciar con la guía de desarrollo, y posteriormente los Teaching Assistants explicaron los conceptos a los grupos para que cada miembro codificara lo que era necesario. De esta manera se iba llevando paso a paso el desarrollo con la asesoría de personas dispuestas a enseñar y resolver cualquier duda que surgiera en el camino.

image alt

Se explicaron conceptos básicos, desde MVC y gemas, pasando por scaffolding, testing, control de versiones de código fuente, hasta el deploy de las aplicaciones en Heroku, asegurándonos de que cada asistente entendiera lo que estaba haciendo y por qué se hacía de esa manera.

A la par de Desi, gente de Crowd Interactive estuvo al pendiente de resolver y atender las dudas, así como de explicar a detalle cada uno de los temas que se iban tocando.

Nuestro granito de arena

Finalmente el objetivo del taller se cumplió, pues los asistentes se mostraron contentos de haber aprendido y de haber construido su propia aplicación web y verla funcionar "en vivo". El plus fue haber conocido gente interesante, y haber compartido y recibido conocimiento de todos, independientemente si tenían experiencia programando o no.

Creemos que ha sido un paso importante para la comunidad tecnológica femenina de nuestra región, empezando de esta manera, con la ayuda de Desi, reduciendo la brecha entre las mujeres y la tecnología, proporcionando herramientas para fortalecer el rol de la mujer en un campo comunmente dominado por hombres.

Eventualmente estaremos realizando mas talleres de este tipo, para que todos los que no pudieron asistir esta vez, puedan hacerlo. Los eventos y reuniones serán anunciados en el Meetup de RailsBridgeMx y Codificadas. También en las cuentas de Facebook y Twitter.

image alt

Agradecemos a todos los asistentes y los invitamos a que sigan aprendiendo y que no se queden con ninguna duda. Internet está lleno de recursos gratuitos para el aprendizaje. Y de igual manera no duden en acercarse a la comunidad de Ruby y Ruby on Rails en México. Siempre habrá alguien dispuesto a ayudar y compartir conocimiento.

Agradecimiento especial a Esteban Cortés, Mariana Hernández, Adrián Castillo, Fernando Perales, Ismael Marín y, por supuesto Desi McAdam, quienes voluntariamente apoyaron el taller con su tiempo, ideas y entusiasmo.

Ana Castro es Ingeniero en Crowd Interactive.

Encuesta de Rails Mx

Junio 18, 2013 por mariochavez

La última semana de Mayo lanzamos una encuesta para conocer un poco a los desarrolladores de Ruby on Rails en México. La encuesta estuvo abierta por poco más de una semana, durante este tiempo recibió 231 visitas aunque únicamente 73 personas la contestaron.

A continuación les muestro el análisis de las respuestas de la misma, aclarando que la encuesta fue anónima y no se capturo información personal de quienes respondieron.

Resultados

Como se comento anteriormente, la encuesta recibió 231 visitas aunque solamente 73 personas la contestaron, es difícil conocer porque el 68% no se animo a contestar, pero gracias al 32% que si lo hizo.

image alt

La primer pregunta de la encuesta tenia la intención de conocer el nivel de experiencia usando Ruby on Rails, de las respuesta se puede ver que 73% tiene 2 años o menos desde que se inicio en Ruby on Rails, de hecho el 41% apenas lo esta aprendiendo.

image alt

La segunda pregunta estaba relacionada con la primera. Solo el 16% cree que tiene un nivel avanzado de conocimiento de Ruby on Rails, el 42% aun lo esta aprendiendo.

image alt

Las siguientes 2 preguntas eran en relación a conocer que tanto se participa en proyectos Open Source, dado el nivel de experiencia contestado en las preguntas previas, el resultado de esta 2 preguntas no causa sorpresa. Para las personas que tienen algún proyecto o librería, háganos un favor y publiquen un post en el Blog para darlas a conocer, si necesitan información de como publicar en el blog, aquí pueden encontrar la guía.

image alt

Ahora que ya conocemos el nivel de conocimiento de Ruby on Rails, nos interesaba conocer quienes realmente trabajan en Ruby on Rails. Resulta que el 47% de los encuestados trabajan de tiempo completo y el 29% lo hace por hobby. También conocimos que el 44% trabajan en una empresa de consultoría y el 21% son freelance.

Para los que trabajan en consultoría seria interesante saber cuales son estas, y hasta que punto apoyan cuestiones de desarrollo de comunidades. En el caso de los freelance el dato importante es conocer como se promocionan o como consiguen a sus clientes.

image alt

Con la intención de conocer donde están la mayoría de los desarrolladores de Ruby on Rails - al menos de entre los que contestaron la encuesta -, se pregunto de que estado/ciudad eran, siendo la mayoría de Guadalajara, DF y Colima en ese orden.

Guadalajara => 14 
DF => 8
Colima => 7
Hermosillo => 5
NO RESPONDIO => 5
Monterrey => 4
Fuera de México => 4
Chihuahua => 3
Aguascalientes => 2
Guaymas => 2
León => 2
Puebla => 2
Campeche => 1
Cancún => 1
Ciudad Guzmán => 1
Cuernavaca => 1
Estado de México => 1
Jalisco => 1
Mexicali => 1
Morelos => 1
Manzanillo => 1
Naucalpan => 1
Oaxaca => 1
Salamanca => 1
Tijuana => 1
Tuxtla Gutiérrez => 1
Veracruz => 1
Xalapa => 1

Finalmente cuando se pregunto sobre lo que hacia falta en términos de recursos para trabajar con Ruby on Rails, las respuestas obtenidas a forma de sumario son:

  • Más recursos en español
    • Blog posts con temas básico y avanzados
    • Vídeos y tutoriales
    • Blog posts sobre testing y mejores prácticas
    • Blog posts no técnicos con entrevistas y consejos.
    • Blog posts para presentar aplicaciones hechas en Ruby on Rails en México
  • Eventos
    • Realizar eventos presenciales de comunidades
    • Realizar eventos en línea
    • Talleres prácticos

En general se pide la generación de contenido en español para diferentes niveles, obviamente para poder cumplir con tales expectativas es necesario que todos nos involucremos en esta tarea, la ultima pregunta de la encuesta fue como ¿Cómo puedes ayudar a Rails Mx?, solo 50 personas respondieron a esta pregunta, es un buen numero para comenzar.

Para quienes estén interesados en participar, no duden en contactarnos por Twitter @railsmx o la forma de contacto o a través de Google Plus.

En el keynote de Bryan Liles en MagmaConf el cerro diciendo "... Es un buen momento para ser un desarrollador Web, es un buen momento para ser un desarrollador de Ruby on Rails ...", esperemos mostrar a través de la comunidad que tiene razón.

¿Cómo formar una comunidad?

Junio 17, 2013 por mariochavez

Una comunidad de desarrolladores es una de los formas más sencilla de relacionarte con otros desarrolladores en tu ciudad. Es una manera simple de compartir y aprender nuevos conocimientos, pero más importante es una excelente herramienta para conocer personas.

Comunidades enfocadas en Ruby y Ruby on Rails hay varias en México, aunque su nivel de actividad varía entre ellas.

Si aún no formas parte de una comunidad y te gustaría hacerlo, no lo pienses más y actúa. Lo primero que tienes que hacer es buscar si en tu ciudad ya hay comunidades activas o que en algún momento funcionaron, ésta es una pequeña lista que te puede servir como referencia, iniciando desde el norte hasta el sur:

Si tienes la suerte de que vives en alguna de esta ciudades, únete a ellas. Si no están activas, trata de buscar la forma de organizar una reunión.

¿Cómo puedo ser un organizador?

Contrario a lo que se pueda llegar a creer, ser el organizador de una comunidad no implica que se tiene que ser quien más conoce del tema, realmente sólo se necesita tener el interés de aprender y juntar a las personas interesadas.

Parte importante de ser un organizador, consiste en hacer llegar la invitación a quienes les puede interesar unirse a la comunidad y buscar un lugar para llevar a cabo las reuniones.

Como dato curioso: cuando organizamos la primera reunión de Tijuana.rb, solamente esperábamos alrededor de 4 personas. Elegimos un Starbucks céntrico en la ciudad de Tijuana, y para nuestra sorpresa, llegaron más de 15 personas.

Ser un organizador también consiste en mantener activa la comunidad, buscando la manera de reunirse regularmente de preferencia. Esto es importante porque de esta forma, los posibles asistentes ya saben que mes con mes viene la reunión de la comunidad y pueden apartar ese tiempo para estar presentes, a diferencia de no saber con ciencia cierta cuando será la siguiente reunión.

Otro punto importante es convencer a los miembros que todos pueden aportar y dar una plática de vez en cuando, no todos los temas tienen que ser súper complejos: hay que recordar que es posible que asistan personas con diferentes niveles de conocimientos, así que tener pláticas básicas o de introducción es importante.

¿Cómo formo una comunidad?

Sólo es necesario encontrar a un grupo de amigos o conocidos con el mismo interés: crea un cuenta de Twitter o un Google Group o una comunidad en Google Plus, organiza la primera reunión, sigue las recomendaciones de ¿Cómo puedo ser un organizador? y estarás listo.

¿Qué más puedo hacer por una comunidad?

Es importante el reunirse y tener la oportunidad de presentar temas, pero también es importante el relacionarse con los miembros de las mismas, como lo mencioné anteriormente, conocer a la gente. Por lo tanto si organizas una comunidad, ten en cuenta que en cada reunión, aparte del tiempo para las presentaciones, es importante hacer un espacio para que la gente platique y se conozca.

De igual forma es posible organizar pequeños talleres de un par de horas en donde una vez más, se pueda compartir un poco del conocimiento que exista en la comunidad.

Otra manera de extender la comunidad es invitando a las otras comunidades a participar en las reuniones, puede ser invitando a alguien a dar una plática mediante Skype o Google Hangout.

Comentarios finales

Ser parte de una comunidad le agrega valor a tu actividad profesional, ya sea como asistente u organizador, es una buena forma de conocer otras personas y lo que están haciendo, y ¿quién sabe? hasta puede ser una opción de encontrar mejores oportunidades de trabajo.

MagmaConf 2013: Keynote día 3: Bryan Liles

Junio 14, 2013 por mariochavez

How versus why…

image alt

Bryan en su keynote menciona que los desarrolladores no nos enfocamos tanto en el porqué, nuestro enfoque siempre está en el como. No nos damos cuenta que lo importante es el porqué, por ejemplo: el porqué usas un editor en particular, porqué no haces pruebas o porque haces pruebas; usamos y tomamos muchas decisiones en el como, pero es raro que lo hagamos en el porqué.

La gente que escribe pruebas, entiende que el código de producción existe para que pase las pruebas, aunque pensemos que no nos pagan por escribir pruebas, pero si realmente ese es el motivo del porqué escribimos las pruebas primero.

Escribimos pruebas, porque probablemente no vamos a ser la última persona que vea nuestro código o quizás nosotros mismo tengamos que regresar a nuestro código en 6 meses; escribimos pruebas porque nos interesamos por nosotros, por nuestros nuestros compañeros de trabajo, por nuestros clientes y porque queremos que las cosas funcionen.

En Ruby todos conocemos el concepto de convención sobre configuración, la cual nos invita a poner los archivos de cierta forma y simplemente funciona, ese es el como. La pregunta es porqué esto funciona tan bien en el mundo de Ruby y no tanto en otros ambientes. El porqué se debe a que si conocemos la estructura de un proyecto de Rails, cualquier persona puede llegar más tarde y entender como funciona y se organiza nuestro proyecto.

Los desarrolladores de Rails sabemos el como hacer que el ruteador de Rails funcione, pero el porqué existe, se debe a que como desarrolladores nos llevaría mucho tiempo a nosotros el hablar directamente con el protocolo de http, el ruteador de Rails nos proporciona una buena abstracción que nos permite olvidarnos un poco de los detalles de http.

El patrón de la arquitectura orientada a servicios (SOA) hoy en día es un patrón bien conocido en donde casi todos los desarrolladores entienden el como realizar una implementación, pero una vez más el porqué es lo importante, y en este caso proviene de que entendemos que no todos vamos a trabajar con el mismo código fuente; además de que abstraemos y segregamos funcionalidad en sistemas complejos.

En la misma línea de ejemplos del como contra el porqué, Bryan comenta que muchos desarrolladores usan Macs, en este caso el porqué tiene que ver en que obtenemos el poder de Linux sin la estupidez de Windows.

Todos los que estamos aquí somos desarrolladores web - menciona Bryan - y todos sabemos como trabajar con el protocolo HTTP y como funciona, pero lo que no todos sabemos es el porqué, el estándar RFC 2616 documenta el porqué.

Un tema reciente es el como atraer más mujeres en el desarrollo de software, pero la pregunta correcta aquí es ¿porqué no hay mas mujeres en área de desarrollo?, quizás a ellas no les guste trabajar en ésta área, o no les guste ir a las conferencias/reuniones.

Entonces como hemos visto entre el como y el porqué, el más importante para nosotros en el desarrollo de software debería ser el porqué.

¿Quieres ser un mejor desarrollador? - que no es lo mismo a ser un buen programador - entonces tienes que programar como Indiana Jones, te tiene que gustar buscar y encontrar cosas nuevas, enfrentar retos en los que parece que no podrás salir victorioso.

No hay algo como código malo, pero si hay proyectos que pueden ir mal, en estos casos lo importante es encontrar el porqué el proyecto esta mal. No existe código malo, es solo código que alguien escribió y nunca entendió el porqué.

El saber el como no es más importante que saber el porqué. El entender el porqué te ayuda a construir confianza en lo haces, confianza en tus compañeros de trabajo, confianza en el código que produces y confianza en tu trabajo de parte de tus clientes.

Este es un buen momento para ser desarrollador de Rails, es un buen momento para ser un desarrollador web; entender el como es bueno, pero es mucho mejor entender el porqué.

MagmaConf 2013: Keynote día 2: Santiago Pastorino

Junio 10, 2013 por mariochavez

Del trabajo a la pasión

image alt

Santiago habló de como logró convertir sus pasiones profesionales en su trabajo diario. Eso lo logró trabajando en su empresa de desarrollo Wyeworks y contribuyendo a Ruby on Rails. Hizo una analogía entre la pasión que siente por el fútbol y sus pasiones profesionales para luego enumerarlas: alta calidad, alto impacto, desafíos y aprender. Definió el éxito en función de vivir de sus pasiones, explicando que se suele relacionar el éxito con los negocios y/o directamente con el dinero. Para él no es así, sino que es vivir de sus pasiones y fue ese el factor determinante que lo llevó a abrir su empresa.

El encontró 7 actitudes o factores que lo ayudaron a vivir de sus pasiones.

Animarse

Animarse es imprescindible, hay que buscar oportunidades hasta encontrarlas. Mencionó a Steve Jobs y su discurso de Standford dónde este usaba la muerte como una herramienta muy útil para la toma de decisiones. El miedo al ridículo o al fracaso se desvanecen frente a la muerte. Al pensar en la muerte cualquier otro miedo pierde el sentido.

Alianzas estratégicas

A él le fue importante encontrar aliados para lanzarse a vivir de sus pasiones. Mencionó que es importante tener confianza ciega en tus aliados y habla del caso en particular de él y José Costa, su socio en WyeWorks. La mayoría del tiempo están de acuerdo y cuando no lo están tienen una excelente comunicación que los lleva a entenderse a la perfección. En la alianza estratégica también entran otros colaboradores, donde es importante encontrar excelentes técnicos, pero también es importante buscar gente que comparte los mismos valores como personas y que nos llevemos bien para que el trabajo sea algo que se pueda disfrutar.

Esfuerzo y dedicación

Sin esfuerzo y dedicación es imposible llevar adelante nuestras pasiones. Existen los genios pero son muy pocos. Lo que existe es gente que se esfuerza muchísimo y le dedica mucho a lo que lo apasiona. Santiago encontró un factor común con toda la gente que trabaja en el OSS que es que la gente se esfuerza y le dedica mucho tiempo. Santiago comenzó trabajando en Rails en un evento de fin de semana, buscando contribuir con cosas pequeñas. Inicio solucionando una prueba en Rails que fallaba, envío su commit, y José Valim se lo rechazó y le recomendó una solución, envío un segundo parche y se lo volvió a rechazar José, pero en esta ocasión José le envío la solución, ese fue el primer commit de Santiago. Su dedicación en el proyecto con un gran volumen de commits, le llevo a que lo invitaran a ser miembro del core Rails. Pero nada de esto hubiera sido posible si no le hubiera dedicado y no se hubiera esforzado mucho por conseguirlo.

Perseverancia aún en su inconstancia

Parte importante de la pasión es la perseverancia para no detenerse ante los primeros problemas o limitaciones. No limitarse uno mismo es muy importante, cuando tenemos que resolver un problema difícil solemos pensar que no podemos. Es mejor no limitarse y enfrentar el problema con una actitud positiva. Con una actitud perseverante del estilo "Voy a resolver el problema cueste lo que cueste"

Foco

Es importante ser fiel a uno mismo. Hay muchos factores que nos pueden desviar de nuestras pasiones. Cuando uno arranca su empresa mientras esta no es rentable, se puede ver tentado a desviarse de su foco con el fin de subsistir. No hay que traicionarse a uno mismo porque nuestra pasión es lo que definimos como nuestro foco y es lo que queremos lograr.

Caerse y Levantarse

En el camino van a haber problemas, pero hay que saber levantarse cuando hay caídas. En casos así uno se pregunta si lo que se esta haciendo esta bien o no y empiezan a salir muchas dudas, se pierde la confianza en lo que se hace y en muchas ocasiones se ve como la salida mas fácil simplemente dejarse derrotar, pero siempre en necesario aprender de esas caídas y saber como recuperarse. WyeWorks estuvo a punto de fundirse y sus socios renunciaron por un momento pero en el fondo seguían creyendo en el proyecto y fue eso lo que los ayudó a seguir adelante y recuperarse de ese tropiezo.

image alt

Suerte

La suerte es un factor que también juega a la hora de vivir de lo que nos apasiona. La suerte llega más cuanto más trabajamos y es importante que esta nos agarre activos y no en una actitud pasiva.

Luego de tanto esfuerzo y dedicación, vivir de nuestras pasiones nos da mucho por lo que celebrar. Esa es la recompensa a tanto esfuerzo. Puedes vivir de tus pasiones. Puedes vivir de tus sueños. Tu trabajo puede ser apasionante, sí se puede!. Anímate!.

MagmaConf 2013: Keynote día 1 Gregg Pollack

Junio 7, 2013 por mariochavez

MagmaConf 2013 se esta llevando a cabo en la ciudad de Manzanillo, Colima. Es la conferencia de desarrollo web de México, donde se tratan temas de Ruby, Ruby on Rails, Sinatra, Frameworks de Javascript y HTML en general.

image alt

12 pasos para ser un mejor desarrollador

"La tecnología no es suficiente", es la forma en como Gregg Pollack inicia su keynote, parafraseando a Steve Jobs y su filosofía, Greeg la usa como referencia para comentarnos de que ser un programador no significa ser un buen desarrollador, ya que se necesitan de una serie de características más que complementan al trabajo de codificar.

En Envy Labs, y en cualquier otro negocio de tecnología, - comenta Gregg - se requiere de gente que además de ser un buen programador, pueda comunicarse de manera efectiva, pueda cumplir con las expectativas de los clientes, que sea capaz de trabajar de manera efectiva con los miembros del equipo.

Además se requiere la persona sea abierta a escuchar nuevas ideas, pero escuchar una nueva idea no significa hacer un diagnóstico de la idea y decir porque es mala, ya que el diagnóstico generalmente significa realizar una crítica, y la crítica esta asociada a matar la creatividad en el equipo.

Cuando la gente se acerque con una idea, en lugar de pensar en que falla esa idea, es mejor pensar de que forma se puede mejorar y hacerla funcionar, de esta forma la persona que originó la idea se siente apoyada y motivada a generar nuevas ideas, es decir ser creativo.

Un programador debe de ser capaz también de escuchar, pero escuchar de verdad, y no estar ahí, con la mente en algún otro lugar. Ya que es fácil leer la expresión corporal y la persona que esta hablando se dará cuenta que no eres auténtico y eso provoca se pierda la confianza para comunicarse, ya que la persona que quiere transmitir un mensaje se siente ignorada.

Es necesario tratar el desarrollo de software como un trabajo Artesanal - Craft -, es nuestro trabajo tomar una idea o un código fuente y hacer de él algo de lo cual te sientas orgulloso, aún y si el código original es feo o no tiene pruebas. No lo construyas para tí, constrúyelo para que lo disfrute alguien más.

También hay que aprender a delegar, hay que identificar como funciona tu empresa, hay que buscar a la gente que disfrute hacer las diferentes actividades de la empresa y delegar a ellos, ya que es posible que ellos disfruten hacer esa parte del trabajo, como tú no la podrías disfrutar. Para saber que delegar, no hay que caer en la falacia de porque tu puedes hacer algo, tu debes de hacerlo. Una forma sencilla, es hacer lo que disfrutas y lo que no, buscar quien si disfrutaría hacerlo y delegarlo.

Continua aprendiendo en tu empresa, hay formas muy sencillas de aprender, una puede ser haciendo pair programming, un club de libro, hacer code reviews, realizar retrospectivas al final de un proyecto o cuando se cumple un objetivo importante, otra herramienta es Show and tell, reunir al equipo y mostrar algo interesante, puede ser una librería o una herramienta.

Hay que salir de tu zona de confort, las tareas sencillas no dejan ganancia personal, las tareas mas difíciles con las que representan un reto, y te sacan de tu zona de confort. Las cosas en la cuales no es posible realizar suposiciones y no hay nada claro, generalmente son estas tareas de las cuales puedes aprender algo nuevo o te ayudan a mantener tu interés en las actividades que realizas.

Hay que hacer amigos en el área donde trabajas, para ser desarrollador es necesario contar con muchos amigos, mientras más amigos tengas, mas feliz serás. Cuando vayas a las reuniones y conferencias, no te apartes, platica con la gente, pregúntales de donde vienen, en donde trabajan, esta es una forma muy sencilla de relacionarte con gente que tiene los mismos intereses que tú.

Que no te de miedo pedir ayuda, no dejes que tu ego no te permita pedir ayuda cuando la necesites, es muy difícil aceptar que tienes un problema, pero es más difícil pedir ayuda. Por ejemplo si hay una tarea que ya se paso del tiempo de entrega, es muy probable que hay algo que se esta haciendo mal y buscar ayuda puede ser una solución.

Elimina distracciones, no es natural ser productivo el 100% del tiempo, quizás somos 80% productivos y el otro 20% no. Elimina las distracciones en el tiempo productivo, como la comunicación, por ejemplo no revises el email cada 2 minutos. Cambia tu lugar de trabajo, si es posible muévete a otra área en tu oficina, esta puede ser una forma de mejorar tu productividad.

Comunícate mejor que todos, las personas que mejor se comunican son los mejores desarrolladores. ¿Pero como nos comunicamos mas efectivamente? Esforzándote a comunicarte constantemente, una opción puede ser el stand up diario.

image alt

Hay que recordar lo que está en juego, no tendrás otra oportunidad de hacer el trabajo que estas haciendo en este momento, es tu única manera de expresarte con tus compañeros de trabajo y con tu cliente, esto es a través la forma en como haces tu trabajo. Debido a esto en cada proyecto, tienes que asegurarte de pones toda tu creatividad y tu empeño en el trabajo que haces en este momento, recuerda que es el más importante que haz hecho hasta ese momento.

Busca lo que te haga feliz, no es el dinero, no es la fama, pero tampoco es solo el código que haces, la tecnología por si sola no es suficiente, la clave es hacer trabajo que sea importante que tenga un sentido, a final de cuentas todos queremos ser parte de algo importante.

Rails Best practices

Junio 5, 2013 por joseluistorres

Mientras leia mi timeline in twitter vi que @bantik hizo una pregunta acerca de como encontrar métodos no utilizados en tu base de código y pensé si, yo creo que eso me ayudaría en buena manera en este caótico océano de viejo y nuevo código.

Así que le pregunté que cuando encontrara con que, pues que lo pasara para tenerlo, y efectivamente minutos después @bantik retuiteó acerca de rails best practices gem. Y dije mmm tal vez si me puede ayudar de alguna manera, digo yo se que se ya un buen de rails y pues definitivo no afectará saber un poco más.

Sorpresa que me llevé al revisar la salida del análisis de la gema. He trabajado al menos 4 años con proyecto de ruby on rails, algunos grandes, otros chicos, resolviendo cosas, armando cosas, conectando cosas y arreglando bugs. Al considerarme a mi mismo un desarrollador fluido en ruby y saber ya tantas cosas, pensé mmm creo que realmente no se tanto y aún me falta mucho por aprender. Instalé la gema, corrí el comando que imprime la salida en HTML y realmente quedé boquiabierto. Inmediatamente me dije necesito leer más, aprender más y corregir todo esto. Código sin usar, vulnerabilidades de seguridad, malas prácticas, hasta indices faltantes en la base de datos.

Como sea, la herramienta me sorprendió y me abrió los ojos para aprender más y mejores técnicas, más allá de la prisa de arreglar un bug o escribir una prueba.

¿Siguen interesados?

Chequen rails best practices gem e instalen:

$ gem install rails_best_practices

Corran este comando desde su directorio raiz de su aplicación:

$ rails_best_practices -f html .

Y voilá, tendrán un archivo gigante (al menos yo si) con todos los findings relacionados a buenas prácticas que este envoltorio de gemas provee, por supuesto no lo hagas si ya eres un desarrollador con graaan experiencia, no sea que te lleves una sorpresa como yo.

Eso es todo, esta herramienta es excelente y vale la pena incluirla en tus proyectos actuales y futuros.

Gracias por sus comentarios.

José Luis Torres, trabaja como Ingeniero Ruby on Rails en Powhow

Versión en Inglés: rails best practices gem

Encuesta de Rails Mx

Mayo 30, 2013 por mariochavez

Estamos publicando una encuesta para conocer un poco mejor a la gente que desarrolla con Ruby on Rails en México. La encuesta no pide correo electrónico, ni nombre, ni id de red social.

En la encuesta también los invitamos a que nos comenten que les gustaría ver en Rails Mx y como pueden ustedes ayudar.

La encuesta va a estar abierta durante el resto de ésta semana y durante la siguiente.

Una vez cerrada la encuesta se agruparán los datos recolectados y publicaremos información de forma general acerca del uso de Ruby on Rails en México.

Les agradecemos de antemano la ayuda contestando la encuesta. El objetivo de la misma es simplemente conocer un poco más cual es el entorno de Ruby on Rails en el país.

Encuesta de Rails Mx

La práctica hace al maestro

Mayo 27, 2013 por solojavier

Me gusta decir que soy un aprendiz de artesano de software.

A algunos les gustaría tener un nombre más llamativo, pero para mí es un recordatorio de que para ser mejor en mi oficio, requiero disciplina y mucha práctica.

He empezado a tomar lecciones de Kung-Fu y he estado descubriendo lo importante que es la práctica cada movimiento, cada pequeño detalle cambia el resultado final.

Como desarrolladores de software que necesitamos para estar siempre aprendiendo cosas nuevas, no contamos con técnicas antiguas de civilizaciones milenarias, pero existen algunas personas inteligentes con muchos años de experiencia y debemos escuchar atentamente lo que nos dicen.

Por lo tanto, dejar de leer este artículo ahora mismo y empezar a practicar, incluso en tus tareas diarias, trata a cada movimiento y esfuerzos como un pequeño pero irreversible paso para convertirte en un mejor artesano del software.

English version

Subir imágenes a nuestra aplicación Rails, usando la gema 'Paperclip'

Mayo 21, 2013 por iszandro

A continuación explicaré de manera sencilla como poder subir imágenes a nuestra aplicación en rails, usando paperclip.

Paperclip.

Primero, veamos que es paperclip.

Paperclip es una gema que permite mediante el comando has_attached_file poder adjuntar imágenes de manera fácil.

Además, paperclip permite poder agregar validaciones, tales como tamaño, formato, presencia, etc.

Cabe mencionar que paperclip hace uso de las utilidades 'ImageMagick', las cuales permiten mostrar, manipular y convertir imágenes.

Requisitos.

Antes de comenzar, es necesario tener los siguientes requisitos para poder desarrollar esta aplicación.

  • Tener la versión 2.0 de Ruby.
  • Tener la versión de Ruby on Rails 4.0 rc1
  • Tener instalado ImageMagick.

Nota: En este tutorial no se explica como instalar y/o configurar ruby, rails ni imagemagick.

Manos a la obra.

Si ya estas en este punto, quiere decir que cuentas con los requisitos antes mencionados. Entonces manos a la obra.

Creando nuestra aplicación.

Lo primero que necesitamos es crear nuestra aplicación, la cual llamaremos TestPaperclip. Así que abrimos nuestra terminal/consola y escribimos lo siguiente:

rails new TestPaperclip

Con el comando anterior, rails nos crea una carpeta llamada TestPaperclip (carpeta de aplicación), y dentro de esta carpeta se encuentran todos nuestros archivos necesarios para que nuestra aplicación rails funcione.

Nuevamente desde nuestra terminal/consola escribimos el siguiente comando, para poder entrar a la carpeta de nuestra aplicación.

cd TestPaperclip

Agregamos Paperclip.

Ahora es necesario agregar la gema de paperclip a nuestro archivo Gemfile, el cual se encuentra dentro de nuestra carpeta.

gem 'paperclip', git: 'git://github.com/thoughtbot/paperclip.git'

Se utiliza desde el repositorio, ya que hasta este momento no esta disponible una gema para Rails 4.0

Para poder instalar la gema de paperclip y poder usarla en nuestra aplicación, escribimos bundle en nuestra terminal/consola.

Creamos nuestro scaffold.

Después de haber instalado la gema, procederemos a crear nuestro modelo, controlador y vistas.

Nota: En este tutorial crearemos un scaffold para que rails nos cree rápidamente el modelo, controlador y las vistas necesarias, pero tu puedes crear los modelos, controladores y vistas individualmente si así lo deseas.

rails g scaffold Product name:string description:text

Gracias al comando anterior, rails nos ha creado un controlador llamado ProductsController, nos ha creado las vistas necesarias y nos ha creado un modelo llamado Product. Dicho modelo tendrá como atributos un nombre de tipo cadena y una descripción de tipo texto.

Configuramos nuestra migración.

Para generar la tabla de la base de datos, rails también ha generado una migración con los campos nombre y descripción. Es en esta migración donde debemos indicarle que queremos guardar los datos de la imagen. Para esto, abrimos la migración ubicada en db/migrate/ y le agregamos la siguiente línea: add_attachment :products, :image. De tal manera que nuestra migración queda de la siguiente manera:

class CreateProducts < ActiveRecord::Migration
  def change
    create_table :products do |t|
      t.string :name
      t.text :description

      t.timestamps
    end

   add_attachment :products, :image
  end
end

El método add_attachment es un helper que proporciona Paperclip para almacenar la información necesaria de las imágenes en la base de datos.

Ahora necesitamos correr nuestra migración para que se cree nuestra tabla products. Eso lo hacemos con el comando rake db:migrate.

Configurando nuestro modelo.

Ahora que ya tenemos nuestra tabla productos en la base de datos, el siguiente paso es abrir nuestro modelo de productos y decirle a rails que queremos que cada producto tenga una imagen.

class Product < ActiveRecord::Base
  has_attached_file :image, styles: { medium: '200x200>', thumb: '48x48>' }
end

Al igual que con la migración, has_attached_file es un método helper que nos ofrece Paperclip. :image es el nombre del atributo de nuestro modelo. :styles permite decirle a Paperclip que tamaño de imagen queremos almacenar. De esta manera, se nos almacenará en disco la imagen en tamaño original, mediano y thumb.

Nota: Todas las imágenes guardadas se encontrarán en public/system/products/images/000/000/.

Configurando nuestras vistas.

Ya que hemos configurado nuestro modelo, debemos modificar un poco nuestras vistas para que nos permitan poder adjuntar una imagen cada vez que queremos crear/editar nuestro producto.

Abrimos nuestra parcial _form.html.erb ubicada en app/views/products y antes de la línea <div class="actions"> agregaremos el siguiente código:

<div class="field">
  <%= f.label :image %>
  <%= f.file_field :image %>
</div>

Este pequeño código nos permitirá el poder adjuntar una imagen cada vez que queramos crear o editar un producto.

Siguiendo con nuestras vistas, ahora abriremos nuestro index.html.erb y le agregaremos 2 líneas de código:

  • <th>Image</th> justo antes de <th>Name</th>. Con esto, nuestra tabla que muestra todos nuestros productos en el navegador, tendrá una columna llamada Image.

  • <td><%= image_tag product.image.url(:thumb) %></td> justo antes de <td><%= product.name %></td>. Con esto, obtendremos la imagen (de tamaño thumb de cada producto).

Para finalizar con nuestras vistas, abriremos la vista show.html.erb y mostraremos la imagen del producto, pero esta vez de tamaño mediano:

<%= image_tag @product.image.url(:medium) %>

Configurando nuestro controlador.

Ahora si, hemos terminado editando nuestras vistas. Pero nos queda editar nuestro controlador products_controller.rb, el cual se encuentra en app/controllers/.

Al haber creado el scaffold, rails nos ha generado el código necesario para poder realizar las diferentes acciones (new, create, edit, update y destroy). Pero necesitamos decirle que al adjuntar una imagen, sea guardada.

Al final de nuestro controlador, se encuentran 2 métodos privados:

  • set_product
  • product_params. Éste método lo editaremos, para que quede de la siguiente manera:

    def product_params
      params.require(:product).permit(:name, :description, :image)
    end
    

Y nuestra aplicación.

Con esto, hemos terminado nuestra aplicación básica para poder adjuntar imágenes. Ahora solo queda verla en nuestro navegador, con lo que debemos correr el servidor. Escribimos el siguiente comando en nuestra terminal/consola:

rails s

Abrimos el navegador y escribimos localhost:3000/products para poder listar nuestros productos, que por el momento estará vacía. Así que a partir de aquí, podemos crear un producto y adjuntarle una imagen.

Finalizanding

Espero que este pequeño tutorial les haya sido de utilidad. Hasta la próxima.

¿Quieres descargar la aplicación? Aquí el link desde Github.

Isay Sosa es Ingeniero Junior en Crowd Interactive.

Validación y testeo de Modelos con ActiveRecord y RSpec.

Mayo 16, 2013 por efigarolam

En este ejemplo muy sencillo vamos a aprender como cambiar el entorno de pruebas de Rails para usar RSpec, así como validar un modelo y escribir las pruebas necesarias para garantizar que dicho modelo funcione correctamente.

Para la elaboración de este ejemplo utilizaremos el enfoque TDD (Test Driven Development) donde primero se escriben las pruebas,luego debemos verificar que fallen, posteriormente escribir solo el código necesario para que pasen y finalmente refactorizar.

Llevaremos a cabo una simple y sencilla aplicación de agenda telefónica. Comencemos creando la aplicación con rails:

rails new PhonebookApp -T

Con el comando anterior, creamos una aplicación llamada PhonebookApp y la opción -T indica que no se debe de crear el directorio de test por default. Rails utiliza TestUnit para las pruebas por default. Nosotros usaremos RSpec por lo que dicho directorio no es necesario para este ejemplo.

Ahora es necesario entrar al directorio de nuestra aplicación desde la consola.

cd PhonebookApp

El siguiente paso es configurar el entorno de pruebas con RSpec para ello es necesario, abrir el archivo Gemfile, ubicado en la raiz de nuestra aplicación y añadir lo siguiente:

group :test, :development do
  gem "rspec-rails", "~> 2.0"
end

Hasta aquí ya tenemos configurado nuestro Gemfile para que incluya la gema "rspec-rails" y todas sus dependencias en los entornos de pruebas y desarrollos de nuestra aplicación. Ahora es necesario ejecutar el siguiente comando para instalar dichas gemas:

bundle

Continuando, debemos inicializar RSpec para ello ejecutamos el siguiente comando en la consola, lo que nos creará el directorio spec y algunos archivos necesarios para la ejecución de pruebas con RSpec:

rails generate rspec:install

Ahora crearemos el modelo, en una agenda teléfonica nos interesa tener los campos de nombre, apellido, teléfono, dirección y la edad. Los campos obligatorios serán nombre y teléfono. El modelo se genera utilizando el siguiente comando:

rails generate model phone_book name:string last_name:string phone:string address:string age:integer

A partir del modelo creado, necesitamos correr las migraciones, esto significa: crear las tablas en la base de datos. Para ello debemos ejecutar el siguiente comando:

rake db:migrate

Siguiendo el enfoque TDD debemos primero escribir las pruebas, para este ejemplo, las pruebas son: debe ser válido con todos los atributos, debe ser inválido sin nombre, debe ser inválido sin teléfono, debe ser válido solo con nombre y teléfono, edad debe ser válida con un número, edad debe ser inválida sin un número. Son 6 pruebas en total, para escribirlas, es necesario hacerlo en el archivo phonebookspec.rb generado automáticamente cuando generamos el modelo, en el directorio de spec/models.

require 'spec_helper'

describe PhoneBook do
  before {
    @valid_attrs = { name: "Juan", last_name: "Perez", phone: "999-99-9-99-99", address: "Col. Vista Hermosa", age: 10 }
  }

  context "model validations" do
    it "is valid with all the attributes" do
      phone_book = PhoneBook.new @valid_attrs

      phone_book.valid?.should be_true
    end

    it "is invalid without name" do
      @valid_attrs.delete(:name)
      phone_book = PhoneBook.new @valid_attrs

      phone_book.valid?.should be_false
      phone_book.errors[:name].size.should > 0
    end

    it "is invalid without phone" do
      @valid_attrs.delete(:phone)
      phone_book = PhoneBook.new @valid_attrs

      phone_book.valid?.should be_false
      phone_book.errors[:phone].size.should > 0
    end

    it "is valid only with name and phone" do 
      @valid_attrs.delete(:last_name)
      @valid_attrs.delete(:address)
      phone_book = PhoneBook.new @valid_attrs

      phone_book.valid?.should be_true
    end

    it "age is valid with a number" do
      phone_book = PhoneBook.new @valid_attrs
      phone_book.age = 10

      phone_book.valid?.should be_true
    end

    it "age is invalid without a number" do 
      phone_book = PhoneBook.new @valid_attrs
      phone_book.age = "Diez"

      phone_book.valid?.should be_false
      phone_book.errors[:age].size.should > 0
    end
  end
end

En el código anterior se muestran las pruebas ya escritas y funcionando, a continuación explicaré el código paso a paso:

  • En la primer línea cargamos RSpec y sus métodos.
  • describe PhoneBook do, indica que vamos a escribir pruebas para la clase del modelo PhoneBook.
  • En el bloque before creamos un hash que contiene los atributos válidos para crear una instancia del modelo PhoneBook.
  • Después definimos el contexto, que es lo que estamos probando, pues las validaciones del modelo.
  • Dentro del contexto están las pruebas individuales, cada bloque de it "description" do
  • En general, creamos instancias de PhoneBook pasandole el hash creado en el before, y para probar las diferentes validaciones, pues quitamos los elementos del hash pertinentes para probar cada escenario.
  • Por ejemplo, en la primer prueba "debe ser válido con todos los atributos" pasamos el hash @valid_attrs tal cual. Para la segunda prueba "debe ser inválido sin nombre" eliminamos el elemento name del hash e instanciamos el modelo, después preguntamos si es válido y esperamos que la respuesta sea falsa. Caso similar con las demás pruebas.

Para ejecutar las pruebas y ver los resultados es necesario ejecutar el siguiente comando desde la consola:

rake

En este punto, nos arrojará 3 pruebas fallidas y 3 falsos positivos (pruebas que indican ser correctas, cuando en realidad no lo son) que dejarán de serlo una vez que escribamos el código necesario para hacer pasar las pruebas.

¿Qué es necesario para hacer pasar las pruebas? Escribir las validaciones pertinentes en el modelo, para ello, haremos uso de los métodos que nos brinda la clase ActiveRecord, clase que todos los modelos heredan automáticamente, el siguiente código deberá ser añadido al archivo phone_book.rb dentro de app/models:

validates :name, presence: true
validates :phone, presence: true
validates :age, numericality: true, allow_blank: true
  • La primer línea, nos indica que el modelo, debe de validar la presencia del campo name esto quiere decir que es un campo requerido.
  • La segunda línea es un caso similar, pero con el campo phone
  • Finalmente, la tercer línea válida que la edad (campo age) sea un número, pero no es requerido, especificado mediante: allow_blank: true

Si en este punto ejecutamos el comando:

rake

Tendremos el resultado de 6 pruebas en verde, es decir 6 pruebas que han sido completadas satisfactoriamente, lo que garantiza que nuestro modelo esté bien validado, según los requerimientos dados.

Conclusiones.

Validar y probar modelos es muy sencillo gracias a los métodos ya definidos por la clase ActiveRecord de Rails. Para probarlos existen algunos frameworks de prueba, tales como RSpec, Minitest, TestUnit. En este ejemplo se utilizó RSpec pues hace que el escribir pruebas sea en un lenguaje muy natural y transparente para el usuario.

Si quieren descargarse el código fuente de este ejemplo, está disponible en el siguiente repositiorio de github:

https://github.com/efigarolam/phonebookexample.git

¡Saludos! Y hasta la próxima.

Eduardo Figarola.

Eduardo Figarola es Ingeniero Junior en Crowd Interactive.

Rails Mx reboot

Mayo 15, 2013 por mariochavez

El día de hoy hacemos un reboot de Rails Mx. Rails Mx se formó ya hace algunos años como una comunidad para ayudar y difundir el desarrollo en el marco de trabajo de Ruby on Rails.

Durante largo tiempo la comunidad se encargó de realizar diversas actividades como reuniones y conferencias virtuales en busca de lograr los objetivos de difusión trazados. Pero en algún momento Rails Mx perdió fuerza y se apagó.

El nuevo Rails Mx

El objetivo de este reboot no es simplemente cambiar el CSS y dejar que el resto continúe como hasta ahora.

El cambio debe de ir más allá, el objetivo es retomar donde Rails Mx se quedó y hacer de ésta una comunidad activa nuevamente. A partir de este momento buscamos reactivar:

  • Las reuniones de comunidad, saber de ustedes mismos en que se puede apoyar. Quizás cambiar las reuniones del modelo de Presentador/audiencia a algún tipo OpenHack en Rails/Ruby, trabajando sobre cosas nuevas o resolviendo bugs a librerías.
  • Volver a realizar Conferencias virtuales, tipo Google Hangout.
  • Generar contenido nuevo y fresco en el blog.

Lo que buscamos es que el peso de la comunidad no caiga sobre una o unas personas, así como obtener retroalimentación por parte de ustedes sobre actividades que son de mas beneficio y en las cuales la comunidad sea participativa.

Por lo tanto los invitamos a entrar en contacto o a través de @railsmx.

El blog

El blog formó parte de este reboot, donde los posts anteriores no se migraron al nuevo blog, principalmente la razón es que en su gran mayoría los posts eran referentes a ofertas de trabajo.

La invitación está abierta a quien guste participar creando contenido técnico y de uso general, nos pueden hacer llegar sus participaciones a través de contacto.

Lo técnico

¿Qué fue lo que se hizo con el nuevo sitio de Rails Mx?

Para el nuevo sitio se desarrolló una aplicación de Ruby on Rails, el repositorio para Community está bajo la licencia MIT, por lo cual son libres de hacerle fork o enviar pull request.

Community hace uso de Crowdblog para el motor del Blog.