Omitir navegación.
Principal

Relaciones entre entidades y validación en Rails


Experiencias e informes

Lo prometido es deuda y tal y como comenté en el post anterior hoy toca explicar el resto de pasos que me han llevado a implementar la funcionalidad de la primera versión de SprintTracker funcional.

Estos han consistido en la creación de las herramientas de gestión de Proyectos, Sprints, Tareas y (re)Estimaciones con las oportunas relaciones entre estas entidades. Después he añadido un poco de validación (muy fácil, ya lo verás).

Proyectos, Sprints, Tareas y estimaciones

En el último post concluía que parece preferible usar la generación de código por script cuando vas a tener que modificar el resultado así que en esta ocasión lo uso directamente. Eso sí, lo primero es crear el modelo de datos. Lo hice en dos pasos en los ficheros 003_add_projects_and_sprints.rb y 004_add_tasks_and_estimates.rb, creados ejecutando:

$ ruby script/generate migration add_projects_and_sprints
$ ruby script/generate migration add_tasks_and_estimates

Muestro la parte más interesante de su contenido (el código de self.up)en ese orden:

    create_table "projects" do |table|
      table.column "name",:string,:limit=>128
      table.column "description",:string
    end
    create_table "sprints" do |table|
      table.column "target",:string
      table.column "start_date",:date
      table.column "duration", :int
      table.column "project_id", :int
    end
    create_table "tasks" do |table|
      table.column "name",:string,:limit=>256
      table.column "description",:string
      table.column "user_id",:int
      table.column "sprint_id",:int
      table.column "initial_estimate", :int
      table.column "real_effort", :int
    end
    create_table "estimates" do |table|
      table.column "reestimation_date",:date
      table.column "reestimation", :int
      table.column "task_id", :int
      table.column "is_going_well", :int
      table.column "explanation",:string
    end

Es importante fijarse que las claves extranjeras deben seguir un convenio de nombres. Por ejemplo en estimates la columna task_id apunta a tasks y por ello debe llamarse como la tabla a la que apunta en singular seguido de _id.
Para cada uno de ellos apliqué los cambios a la base de datos con:

$ rake migrate

Y ya estamos listos para generar el código:

$ ruby script/generate scaffold Project
$ ruby script/generate scaffold Sprint
$ ruby script/generate scaffold Task
$ ruby script/generate scaffold Estimate

Tras ejecutar estos comandos ya tengo el 80% de las herramientas de gestión. Lo primero que queda es establecer las relaciones.

Relaciones entre entidades persistidas

Tomemos como ejemplo la relación entre Proyectos y Sprints (un proyecto contiene varios sprints, un sprint pertenece a un proyecto). Para declarar esto en Rails basta con escribir lo siguiente:

[project.rb]
class Project < ActiveRecord::Base
  has_many :sprints
end
[sprint.rb]
class Sprint < ActiveRecord::Base
  belongs_to :project
end

Esto permite navegar de uno a otro usando la notación habitual: project.sprints o sprint.project y Rails se encarga del acceso a base de datos. También proporciona facilidades adicionales opcionales como la obtención de datos a priori (eager).

En el formulario de los sprints debemos poder elegir uno de los proyectos existentes. Para eso uso el siguiente código:

<p><label for="sprint_project_id">Project</label><br>
  <%= select("sprint", "project_id", Project.find_all.collect {|p| [ p.name, p.id ] }) %>

Y el resultado es:

Para el resto de relaciones el código es muy similar. Podeis verlo en el directorio models.

Validación

Ya en el primer post comenté que el sistema de validación de Rails apuntaba en la dirección buena pero había que confirmarlo: absolutamente confirmado. Es de los aspectos de Rails que más me han gustado dado que es increíblemente sencillo y me ha permitido hacer cosas que con Struts Validation resultan mucho más difíciles.

Por seguir el ejemplo del sprint, quiero asegurarme de que está asociado a un proyecto (validates_presence_of) y que el proyecto es válido (validates_associated). También quiero asegurarme de que se establece un objetivo para el sprint y no mayor de 300 caracteres (validates_length_of). La fecha de comienzo y la duración también son obligatorias y esta última debe ser un número. La cantidad de código para conseguir esto es menor que la explicación:

class Sprint < ActiveRecord::Base
  belongs_to :project
  has_many :tasks

  
  validates_presence_of :project
  validates_associated :project

  validates_presence_of :target
  validates_length_of :target, :maximum=>300

  validates_presence_of :start_date
  
  validates_presence_of :duration
  validates_numericality_of :duration
  
end

Y el resultado si trato de salvar el formulario sin introducir ningún dato es:

¡Tachan! eso es casi todo lo que tienes que saber del sistema de validación. Más fácil imposible. Por supuesto los mensajes de error son personalizables y hay más métodos de validación.

Siguientes pasos

Los últimos pasos para lanzar la versión 0.1 han sido un lavado de cara y empaquetar la aplicación. Mañana os lo cuento junto con mis conclusiones de Rails hasta ahora.

Una pequeña errata

La primera línea debería ser


ruby script/generate migration add_projects_and_sprints

gracias

Muchas gracias, ya lo he corregido.

Pepe

No deberias mezclar en la Vista acciones del controlador:

%= select("sprint", "project_id", Project.find_all.collect {|p| [ p.name, p.id ] }) %>

Mejor que en el controlador cargues un array y luego en la vista simplemente lo recorres...cada cosa en su sitio.

Curiosidad

Existe alguna forma de ordenar la data del select alfabeticamente antes de mostrarla???