Relaciones entre entidades y validación en Rails
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???