REDES(ror) 21/9/07 14:26 Página 54 REDES Programación ágil con Ruby on Rails DABNE TECNOLOGÍAS DE LA INFORMACIÓN Conflicto de intereses en un escenario conocido: el cliente presiona para conseguir cambios en la aplicación que se está desarrollando: desde su punto de vista los cambios son aspectos obvios, poca cosa. El desarrollador se resiste a aceptar esos cambios: es tirar por la borda trabajo ya realizado. El comercial también se resiste: cambios significa más tiempo, y más tiempo significa menos dinero. Estas tensiones, inevitables, se atenúan si disponemos de metodologías de desarrollo menos rígidas y de estructuras de soporte para la programación más productivas. Ruby on Rails es una de esas estructuras de soporte. La escusa, los encuentros digitales La aplicación que vamos a desarrollar a lo largo del artículo sirve para gestionar encuentros digitales. Como es sabido, los encuentros digitales se organizan en sitios web, y permiten a los visitantes formular preguntas que, en un día y hora señalados, serán respondidas por un única persona, habitualmente famosa. Finalizado el encuentro, la lista de preguntas y respuestas queda disponible para su posterior lectura. Por regla general las preguntas son filtradas o moderadas, según su pertinencia, y solo pueden ser respondidas por el famoso/a en cuestión. Pero nuestra aplicación será mucho más sencilla: cualquier usuario podrá crear, modificar o elimar encuentros y preguntas (véanse las figuras 1, 2 y 3). Entonces ¿qué sentido tiene un artículo que ilustre el desarrollo de una aplicación que no tiene nada del otro mundo? SOLO PROGRAMADORES nº 153 54 Figura 1. La aplicación muestra la lista de encuentros, con la posibilidad de modificar o eliminar los encuentros creados, y de añadir uno nuevo. Figura 2. La vista de un encuentro muestra las preguntas y sus respuestas, si las hay, con la posiblidad de modificar o eliminar preguntas, y de añadir una nueva. Figura 3. El añadido de una pregunta no muestra una vista muy elaborada pero ¿cuántos minutos hemos empleado para conseguir que esta aplicación sea funcional? El objetivo, la programación ágil En la actualidad, conviven distintos paradigmas del desarrollo de aplicaciones. Algunas veces, la http://digital.revistasprofesionales.com REDES(ror) 21/9/07 14:26 Página 55 REDES Programación ágil con Ruby on Rails especificación es lo más importante, y ceñirse a los requisitos es lo que conducirá al éxito del proyecto. Si los planos son muy detallados, es posible seguirlos al milímetro. Pero otras veces el usuario o promotor de la aplicación solo tiene una idea vaga de lo que quiere. Parte de nuestro trabajo consistirá en mostrarle posibilidades, opciones. En este caso, estamos ante un proceso imprevisible, que exige empezar a construir sin tener los planos. ¿Qué hacer? Las métodologías ágiles vienen en nuestra ayuda. En lugar de hacer reuniones para hablar de cómo será la aplicación, dediquemos ese tiempo a construir un prototipo funcional que podamos mostrar en acción al cliente, y sobre el que plantear modificaciones y mejoras. En principio, como idea, esto está muy bien. Pero ¿cómo hacerlo sin que el presupuesto se dispare? Ciertamente, uno de los inconvenientes de las metodologías ágiles es que son caras. El otro es que el cliente debe implicarse como parte del equipo de desarrollo. Por eso, las métodologías ágiles no siempre son adecuadas pero, incluso cuando lo son, para que sean operativas es necesario disponer de estructuras de soporte a la programación. Ruby on Rails es una de esas estructuras de soporte. El instrumento, Ruby on Rails Ruby on Rails (RoR) es una estructura de soporte (framework) para la programación web desarrollada en lenguaje Ruby. Ruby es un lenguaje de script, multiplataforma, orientado a objetos. Y todo ello es software libre. Ruby fue diseñado para un desarrollo rápido y sencillo: sencillo por fuera pero complejo por dentro. El lector encontrará en la edición 149 de Sólo Programadores un artículo de introducción a Ruby on Rails, con indicaciones para instalarlo. El artículo puede consultarse en la web de Dabne: http://www.dabne.net/article104.html. Damos por supuesto que el lector ya tiene instalado Ruby on Rails, por lo que nos ponemos manos a la obra en la creación de la aplicación. No es preciso conocer Ruby para seguir el artículo. Más bien al revés, si Ruby on Rails convence, Ruby ya se aprenderá. Primer paso, crear la aplicación Para crear la aplicación, (véase figura 4) abrimos “InstantRails.exe”, menú principal (botón http://digital.revistasprofesionales.com LISTADO 1 Figura 4. Creación de la aplicación encuentrosdigitales. Figura 5. Cambio de directorio encuentrosdigitales. “I”), opción “Rails Applications...”. Pulsamos en el botón “Create New Rails App...” y en la consola que se abre ejecutamos: rails encuentrosdigitales El listado 1 muestra la lista de directorios que se generan al crear la aplicación. Entramos en el directorio recien creado (véase figura 5): cd encuentrosdigitales/ Ahí podemos ver la estructura de directorios y ficheros creada por Rails para nuestra nueva aplicación, tal como se muestra en el listado 2. Nota: Si por algún motivo hemos cerrado InstanRails y queremos retomar el trabajo, al volver a abrir InstantRails se debe pulsar en el menú principal (icono “I”), opción “Rails Applications” y luego “Open Ruby Console Window”. Con esto tenemos el esqueleto de nuestra aplicación Rails, en el que todo tiene un sitio predefinido. Al principio esta estructuración puede parecer un engorro, pero luego se agradece el no tener que pensar dónde hay que poner cada fichero. El código de nuestra aplicación irá dentro de “app”, los ficheros estáticos (css, imágenes, javascripts, etc.) irán en “public”, la configuración de la base de datos en “config”, etc. Rails viene con un servidor web incorporado que podemos usar mientras desarrollamos. Se trata de Mongrel. Para arrancar la aplicación con Mongrel, desde InstantRails, menú principal (botón “I”), “Rails Applications”, create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create create Creación de la aplicación app/controllers app/helpers app/models app/views/layouts config/environments components db doc lib lib/tasks log public/images public/javascripts public/stylesheets script/performance script/process test/fixtures test/functional test/integration test/mocks/development test/mocks/test test/unit vendor vendor/plugins tmp/sessions tmp/sockets tmp/cache tmp/pids Rakefile README app/controllers/application.rb app/helpers/application_helper.rb test/test_helper.rb config/database.yml config/routes.rb public/.htaccess config/boot.rb config/environment.rb config/environments/production.rb config/environments/development.rb config/environments/test.rb script/about script/breakpointer script/console script/destroy script/generate script/performance/benchmarker script/performance/profiler script/process/reaper script/process/spawner script/process/inspector script/runner script/server script/plugin public/dispatch.rb public/dispatch.cgi public/dispatch.fcgi public/404.html public/500.html public/index.html public/favicon.ico public/robots.txt public/images/rails.png public/javascripts/prototype.js public/javascripts/effects.js public/javascripts/dragdrop.js public/javascripts/controls.js public/javascripts/application.js doc/README_FOR_APP log/server.log log/production.log log/development.log log/test.log 55 SOLO PROGRAMADORES nº 153 REDES(ror) 21/9/07 14:26 Página 56 REDES LISTADO 2 . |— |— |— | | | | | | | |— |— | | | | | | | | |— |— | |— | |— | | | | |— | | | | | | | | | | | | | | | | | |— | | | | | | | | | | | | | | | |— | | | | | | | | |— | | | | `— Estructura de la aplicación README Rakefile app |— controllers | `— application.rb |— helpers | `— application_helper.rb |— models `— views `— layouts components config |— boot.rb |— database.yml |— environment.rb |— environments | |— development.rb | |— production.rb | `— test.rb `— routes.rb db doc `— README_FOR_APP lib `— tasks log |— development.log |— production.log |— server.log `— test.log public |— 404.html |— 500.html |— dispatch.cgi |— dispatch.fcgi |— dispatch.rb |— favicon.ico |— images | `— rails.png |— index.html |— javascripts | |— application.js | |— controls.js | |— dragdrop.js | |— effects.js | `— prototype.js |— robots.txt `— stylesheets script |— about |— breakpointer |— console |— destroy |— generate |— performance | |— benchmarker | `— profiler |— plugin |— process | |— inspector | |— reaper | `— spawner |— runner `— server test |— fixtures |— functional |— integration |— mocks | |— development | `— test |— test_helper.rb `— unit tmp |— cache |— pids |— sessions `— sockets vendor `— plugins SOLO PROGRAMADORES nº 153 56 Figura 6. Inicio de la aplicación con el servidor web Mongrel. Figura 7. La aplicación en acción. “Manage Rails Applications...”, seleccionar “encuentrosdigitales” y pulsar en “Start with Mongrel” (véase figura 6). Se abrirá una consola donde aparecerán los logs y el mensaje de que pulsando Ctrl+C se cierra Mongrel. Ahora, si vamos al navegador y tecleamos http://localhost:3000/ en la barra de direcciones veremos una página de bienvenida de Rails, donde nos explica cuáles son los siguientes pasos que deberemos dar (véase figura 7). ción va mucho más lenta pero es más cómoda para el programador. En el entorno de pruebas se cargan todos los datos de pruebas en la base de datos para cada prueba que se ejecuta, independizando así los resultados de unas y otras. Por este motivo hay que tener una base de datos aparte para pruebas, puesto que si no perderíamos todos los datos que tuviesemos en la base de datos cada vez que ejecutásemos las pruebas. Cuando Rails genera un nuevo proyecto crea un fichero llamado “database.yml” en el directorio “config” con secciones para configurar cada una de las tres bases de datos. Como Rails destruye todos los datos en la base de datos de pruebas cada vez que se ejecuta un test, hay que tener cuidado de no poner la misma configuración en la de pruebas que en las otras. Editamos el fichero “config/database.yml” (véase listado 4) y modificamos los datos de conexión a las bases de datos que acabamos de crear. (El directorio debe ser “InstantRails/rails_apps/encuentrosdigitales/config/”). Además, añadimos una línea para indicarle a Rails que el set de caracteres de la base de datos es UTF8. Dado que por defecto los ficheros que genera Rails están en utf8 esto es lo más recomendable. Nota sobre la edición de ficheros: Como es natural, los ficheros se pueden editar con cualquier editor, pero es muy recomendable que se guarden con la codificación UTF-8. El bloc de notas de Windows tiene una opción para ello. Segundo paso, crear la base de datos Creamos una base de datos para el desarrollo y un usuario asociado para acceder a ella. En este caso usaremos MySQL pero podemos usar otras como SQLite o Postgres. El set de caracteres de la base de datos será utf8. Para ello, en el directorio “InstantRails”, ejecutamos las sentencias que se muestran en el listado 3. (Es preciso estar en una consola de Ruby, que se pude abir desde el menú principal de InstantRails, opción “Rails Applications”, “Open Ruby Console Window”. Observemos que el directorio actual será “InstantRails/rails_app”, por lo que habrá que moverse al directorio superior). En realidad deberíamos crear tres bases de datos, una para cada entorno de ejecución de Rails: desarrollo (development), pruebas (test) y producción (production). Rails incorpora el concepto de “entornos” para representar las etapas del ciclo de vida de una aplicación. El entorno se especifica en la variable de entorno “RAILS_ENV”. Cada entorno tiene su propia base de datos para no interferir con los demás. En desarrollo se recargan todas las clases cada vez que se produce una llamada a una acción, con lo que siempre se tienen cargados los últimos cambios realizados, evitando tener que reiniciar la aplicación. En producción se cargan las clases una sola vez buscando la eficiencia. En desarrollo la aplica- Más convención y menos configuración Ruby on Rails está construido siguiendo el patrón Modelo-Vista-Controlador (MVC). MVC es un patrón de diseño usado para separar el modelo de datos de la aplicación, la interfaz de usuario y la lógica de control en tres capas diferentes con unas mínimas dependencias entre ellas: http://digital.revistasprofesionales.com REDES(ror) 21/9/07 14:26 Página 57 REDES Programación ágil con Ruby on Rails LISTADO 3 Creación de la bd de desarrollo cd mysql mysql -u root CREATE DATABASE encuentrosdigitales_development CHARACTER SET utf8; GRANT ALL PRIVILEGES ON encuentrosdigitales_development.* TO usuario@localhost IDENTIFIED BY ‘solop’; exit El controlador es el componente que recibe la petición del navegador y ejecuta la acción especificada por el usuario. El modelo es la capa de datos que se usa, generalmente desde el controlador, para leer, añadir, modificar o borrar datos almacenados, por ejemplo, en una base de datos relacional. La vista es la representación de los datos que el usuario ve en su pantalla. ActiveRecord es el sistema de mapeo objetorelacional en Rails, la M en el patrón MVC. La responsabilidad del modelo en el paradigma MVC es ocuparse de la gestión del almacenamiento de los datos de la aplicación. Sin embargo, ActiveRecord es más que una simple librería para ejecutar queries SQL: automáticamente mapea tablas de la base datos con clases en la aplicación Rails, crea métodos públicos para todos los campos de la base de datos y añade muchos otros métodos útiles para acceder los datos de la base de datos. Además, Rails hace suposiciones sobre los nombres de las cosas. Por ejemplo, el nombre del modelo es singular y la tabla que contiene los datos de ese modelo tiene el mismo nombre pero en plural. Si por alguna razón no nos gusta que sea así es fácil decirle a Rails que lo haga de otra forma. Si creamos un “scaffold” a partir de un modelo el controlador tendrá el nombre del modelo pero en plural. (Un “scaffold” es un “andamio” para nuestra aplicación. Es código generado para realizar las operaciones básicas, como crear, editar y borrar registros, con nuestros modelos. Podríamos crear las tablas de nuestra base de datos directamente con comandos SQL, pero Rails tiene un sistema muy bueno, e independiente del gestor de base de datos que usemos, para definir la estructura de nuestra base de datos, escribiéndola en Ruby en los ficheros de “migraciones” y haciendo que un adaptador la convierta a la sintaxis adecuada para nuestro gestor de base de datos. Además, de esta forma también podemos mantener un histórico de los cambios que vamos haciendo en la base de datos y podemos avanzar o retroceder a la versión http://digital.revistasprofesionales.com LISTADO 4 database.yml development: adapter: mysql database: encuentrosdigitales_development username: usuario password: solop host: localhost encoding: utf8 # Warning: The database defined as ‘test’ will be erased and # re-generated from your development database when you run ‘rake’. # Do not set this db to the same as development or production. test: adapter: mysql database: encuentrosdigitales_test username: usuario password: solop host: localhost encoding: utf8 production: adapter: mysql database: encuentrosdigitales_production username: usuario password: solop host: localhost encoding: utf8 LISTADO 5 exists exists exists create create create create create ruby script/generate model Encuentro app/models/ test/unit/ test/fixtures/ app/models/encuentro.rb test/unit/encuentro_test.rb test/fixtures/encuentros.yml db/migrate db/migrate/001_create_encuentros.rb que queramos fácilmente. También facilita el despliegue en múltiples servidores de bases de datos, si la aplicación llega a tener esas dimensiones. Las migraciones se pueden generar independientemente o al crear cada modelo. campos que va a tener nuestra tabla en la base de datos. El número 001 al principio del nombre del fichero indica que es la primera migración de esta aplicación. Tercer paso, crear el modelo Una migración tiene que implementar los métodos “self.up” y “self.down”, que son creados automáticamente al generar la migración. El código que hay en “self.up” es lo que se ejecuta al migrar la base de datos a una versión superior, y el código en “self.down” cuando migramos a una versión inferior. En nuestra primera migración creamos una tabla llamada “encuentros” y añadimos columnas/campos para el nombre y la descripción del encuentro. En “self.down” simplemente borramos la tabla creada, para el caso de que queramos volver a la versión 0, en la que la base de datos está vacía. Empezamos con nuestra aplicación generando un modelo llamado “Encuentro” (en una consola de Ruby, directorio “encuentrosdigitales”: ruby script/generate model Encuentro La salida será como se muestra en el listado 5. La salida del comando de generación del modelo será como se muestra en el listado 5. Como podemos ver en la última línea, Rails crea automáticamente una migración para este modelo. Ahí es donde definimos los Cuarto paso, crear la migración 57 SOLO PROGRAMADORES nº 153 REDES(ror) 21/9/07 14:26 Página 58 REDES LISTADO 6 Editamos el fichero “db/migrate/001_create_encuentros.rb” y añadimos los campos que se muestran en el listado 6. Si ponemos las columnas “created_at” y “updated_at” con tipo “datetime” o “timestamp” en nuestra tabla, Rails se encargará de rellenarlas automáticamente con los valores correctos. Todas la tablas usadas por ActiveRecord (excepto las tablas join) tienen que tener un campo de clave primaria llamado id. No lo especificamos en el código de la migración porque Rails lo crea automáticamente a menos que le indiquemos lo contrario (con la opción “:id => false” al crear la tabla). Para ver una descripción completa de las opciones disponibles en las migraciones, se puede recurrir a la documentación disponible en http://api.rubyonrails.org/classes/ActiveRecord/ Migration.html. Ahora ejecutamos la tarea de migración de la base de datos, para que Rails se encargue de crear los campos que hemos especificado (en el directorio “encuentros digitales”): 001_create_encuentros.rb class CreateEncuentros < ActiveRecord::Migration def self.up create_table :encuentros do |t| t.column :nombre, :string t.column :descripcion, :text t.column :created_at, :datetime t.column :updated_at, :datetime end end def self.down drop_table :encuentros end end rake db:migrate La salida del comando se muestra en la figura 8. Rails usa el entorno de desarrollo por defecto, con lo que los cambios se han hecho en nuestra base de datos de desarrollo. Si queremos que se produzcan en otra base de datos podemos especificar el entorno usando la variable RAILS_ENV en el comando rake: rake db:migrate RAILS_ENV=production Rails también ha creado automáticamente una tabla llamada “schema_info”. Esta tabla tiene sólo un campo, “version”, cuyo valor indica cuál es el número de migración actual. Los scripts de migración usan este dato para saber qué migraciones tienen que ejecutar para poner todo al día. Además, la migración crea un fichero llamado “db/schema.rb”, con el mismo formato Figura 9. Creación del scaffold para el modelo. que los ficheros de migración, que describe el estado actual de la base de datos en su totalidad. Los scripts de migración mantienen este fichero al día, con lo que no se debería editar a mano. Quinto paso, crear el scaffold Como se ha mencionado anteriormente, un “scaffold” es un “andamiaje” de nuestra aplicación. Es código generado para realizar las operaciones básicas (crear, editar y borrar registros) con nuestros modelos. Generamos un “scaffold” para este modelo: ruby script/generate scaffold Encuentro La salida del comando puede verse en la figura 9. Ahora editamos el fichero “config/routes.rb” (directorio “encuentrosdigitales”) para que por defecto vaya al controlador encuentros, añadiendo, como se muestra en la figura 10, la siguiente línea (alerta porque antes de la coma hay dos comillas simples y no una doble): map.connect ‘’, :controller => “encuentros” El fichero “routes.rb” contiene las reglas para el manejo de las URL, quitándole esta responsabilidad al servidor web. Aplicando el principio de más convención y menos configuración, Rails realiza una serie de mapeos convecionales entre la vista y el controlador. Lo que queremos conseguir con la línea anterior es que cualquier petición de nuestra aplicación se dirija al controlador “encuentros”. Sexto paso, personalizar la vista Figura 8. Migración del modelo. SOLO PROGRAMADORES nº 153 58 Eliminamos el fichero public/index.html, que es donde está la página de bienvenida a Rails que veíamos antes, y vamos a http://localhost:3000 http://digital.revistasprofesionales.com REDES(ror) 21/9/07 14:26 Página 59 REDES Programación ágil con Ruby on Rails Figura 11. La aplicación, tal como la ha creado el scaffold. LISTADO 8 Figura 10. Asignando el control encuentros con edit, desde una consola, a la antigua usanza. LISTADO 7 app/views/layout/encuentros.rhtml <!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN” “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”> <html xmlns=”http://www.w3.org/1999/xhtml” xml:lang=”es” lang=”es”> <head> <meta http-equiv=”content-type” content=”text/html;charset=UTF-8” /> <title>Encuentros digitales</title> <%= stylesheet_link_tag ‘scaffold’ %> </head> <body> <div id=”cabecera”> <h1><%= link_to “Encuentros digitales”, :controller => ‘encuentros’ %></h1> </div> <p style=”color: green”><%= flash[:notice] %></p> <!—Content start—> <%= yield %> <!—Content end—> <div id=”pie”> </div> </body> </html> en el navegador. Vemos la aplicación básica que ha creado el scaffold (véase figura 11). Lo que genera el scaffold es el punto de partida. Ahora habrá que irlo modificando hasta que nuestra aplicación tenga el aspecto deseado. Los “layouts” se usan en Rails para poner en un solo directorio el contenido común a todas las vistas, como son la cabecera de la página y el pie. Editamos “app/views/layout/encuentros.rhtml” para añadirle una cabecera y algunas otras LISTADO 9 public/stylesheets/ scaffold.css #cabecera { background-color: lightblue; padding: 20px; } cosillas (véase listado 7). Así tenemos una plantilla general para todas las acciones, en la que ponemos el código común a todas las vistas. Luego con la llamada a “yield” incluimos el contenido concreto de la vista correspondiente a la acción actual, que es lo que se encuentra en cada uno de los ficheros rhtml que hay en “app/views/encuentros/”. Como queremos que esta plantilla sea para toda la aplicación, no sólo para este controlador renombramos el fichero “encuentros.rhtml” como “application.rhtml”. Así se aplicará a todos los controladores que no tengan su “layout” específico (un fichero con nombre “modelo_en_plural.rhtml” en el directorio “app/views/layouts/”). Añadimos estilos para la cabecera en “public/stylesheets/scaffold.css”, como se muestra en el listado 8. Esta parte ya depende de las habilidades gráficas de cada uno y no entra dentro del objetivo de este artículo, así que lo dejaremos tirando a feote. Cambiamos la vista del listado de encuentros, que está en “app/views/encuentros/list.rhtml”, para que se vea más clara, como se muestra en el listado 9. app/views/encuentros/list.rhtml <% for encuentro in @encuentros %> <p> <%= link_to encuentro.nombre, :action => ‘show’, :id => encuentro %> <%= link_to image_tag(‘edit.gif’), :action => ‘edit’, :id => encuentro %> <%= link_to image_tag(‘delete.gif’), { :action => ‘destroy’, :id => encuentro }, :confirm => ‘¿Seguro?’, :method => :post %> <br> <%= encuentro.descripcion %> </p> <% end %> <%= link_to ‘Anterior’, { :page => @encuentro_pages.current.previous } if @encuentro_pages.current.previous %> <%= link_to ‘Siguiente’, { :page => @encuentro_pages.current.next } if @encuentro_pages.current.next %> <p> <%= link_to ‘Nuevo encuentro’, :action => ‘new’ %> </p> http://digital.revistasprofesionales.com 59 SOLO PROGRAMADORES nº 153 REDES(ror) 21/9/07 14:26 Página 60 REDES LISTADO 10 Como se puede ver hemos puesto imágenes en lugar de textos en los enlaces para las acciones de editar y borrar, con lo que ahora hay que poner en el directorio “public/images” los ficheros “edit.gif” y “delete.gif” correspondientes. En Internet hay multitud de iconos gratis e incluso libres que podemos usar, y además el lector puede encontrar estos ficheros en el cd-rom. Ahora vamos a editar la vista de creación de nuevo encuentro, para quitarle al formulario los campos correspondientes a las marcas de tiempo de creación y actualización, ya que Rails se encarga de rellenarlos automáticamente. Editamos “app/views/encuentros/new.rhtml” y “app/views/encuentros/edit.rhtml” para poner los textos en castellano, como se muestra en los listados 10 y 11. Como podemos observar, los campos del formulario no están en ninguno de estos ficheros y en su lugar tenemos una línea: <%= render :partial => ‘form’ %> Esto lo que hace es incluir el contenido del fichero “_form.rhtml” en ese punto, de forma que se pueden reutilizar trozos de código en las vistas. En este caso, ya que el LISTADO 12 <h1>Nuevo encuentro</h1> <% form_tag :action => ‘create’ do %> <%= render :partial => ‘form’ %> <%= submit_tag “Crear” %> <% end %> <%= link_to ‘Volver’, :action => ‘list’ %> LISTADO 11 app/views/encuentros/edit.rhtml <h1>Edición del encuentro</h1> <% form_tag :action => ‘update’, :id => @encuentro do %> <%= render :partial => ‘form’ %> <%= submit_tag ‘Editar’ %> <% end %> <%= link_to ‘Mostrat’, :action => ‘show’, :id => @encuentro %> | <%= link_to ‘Volver’, :action => ‘list’ %> formulario es el mismo para las dos acciones, está contenido en un “partial” que se incluye desde sus vistas. Obsérvese que el nombre de fichero de un “partial” lleva un guión bajo delante, mientras que en la llamada desde la vista sólo se pone el nombre, sin extensión y sin guión. Del fichero “app/views/encuentros/ _form.rhtml” borramos las líneas que se muestran en el listado 12 y lo dejamos como se muestra en el listado 13. app/views/encuentros/_form.rhtml, borrar <p><label for=”encuentro_created_at”>Created at</label><br/> <%= datetime_select ‘encuentro’, ‘created_at’ %></p> <p><label for=”encuentro_updated_at”>Updated at</label><br/> <%= datetime_select ‘encuentro’, ‘updated_at’ %></p> LISTADO 13 app/views/encuentros/new.rhtml app/views/encuentros/_form.rhtml, final <%= error_messages_for ‘encuentro’ %> <!—[form:encuentro]—> <p><label for=”encuentro_nombre”>Nombre</label><br/> <%= text_field ‘encuentro’, ‘nombre’, :size => “70” %></p> <p><label for=”encuentro_descripcion”>Descripción</label><br/> <%= text_area ‘encuentro’, ‘descripcion’, :cols => “80”, :rows => “5” %></p> Séptimo paso, pulir Con esto ya podemos crear, modificar y borrar encuentros, pero quedan algunas cosas por pulir. Por ejemplo, podríamos crear un encuentro con todos los campos vacíos (menos el “id” que crea automáticamente Rails), pero eso es algo que no queremos. Para evitar eso tenemos algo muy potente en los modelos, que son las validaciones. Con un par de líneas podemos hacer que nuestro modelo no acepte campos vacíos, o que compruebe la longitud de los textos. En nuestro ejemplo queremos que todo encuentro tenga un nombre y que ese nombre no exceda de los 255 caracteres, que es el tamaño por defecto del string que hemos puesto antes al definir los campos de la tabla “encuentros” en la migración. Eso lo conseguimos añadiendo en “app/models/encuentro.rb” las dos líneas que, en el listado 14, se muestran en negrita. Ahora, si intentamos crear un encuentro con el nombre vacío nos saldrá un error que nos explica qué ha pasado y nos vuelve a pedir los datos. De vuelta al paso tres <!—[eoform:encuentro]—> LISTADO 14 app/models/encuentro.rb class Encuentro < ActiveRecord::Base validates_presence_of :nombre validates_length_of :nombre, :maximum => 255 end Nuestra aplicación tiene encuentros pero no tiene preguntas. Creamos el modelo “Pregunta” (véase figura 12): ruby script/generate model Pregunta La salida del comando se muestra en el listado 15. Y al paso cuatro Figura 12. ruby script/generate model Pregunta. SOLO PROGRAMADORES nº 153 60 Editamos la migración “db/migrate/002_create_preguntas.rb”, añadiendo las líneas que en el listado 16 se muestran en negrita. Cada pregunta pertenece a un encuentro, por lo http://digital.revistasprofesionales.com REDES(ror) 21/9/07 14:26 Página 61 REDES Programación ágil con Ruby on Rails LISTADO 15 exists exists exists create create create exists create ruby script/generate model Pregunta app/models/ test/unit/ test/fixtures/ app/models/pregunta.rb test/unit/pregunta_test.rb test/fixtures/preguntas.yml db/migrate db/migrate/002_create_preguntas.rb LISTADO 16 db/migrate/002_create_preguntas.rb class CreatePreguntas < ActiveRecord::Migration def self.up create_table :preguntas do |t| t.column :pregunta, :text t.column :respuesta, :text t.column :encuentro_id, :integer t.column :created_at, :datetime t.column :updated_at, :datetime end end def self.down drop_table :preguntas end end Figura 13. Ejecución de la migración. que ponemos una referencia al encuentro correspondiente. Ejecutamos la migración (véase figura 13): rake db:migrate Y al paso cinco Creamos el “scaffold”, respondiendo “n” a la pregunta “overwrite public/stylesheets/scaffold.css? [Ynaqd]” (véase figura 14): ruby script/generate scaffold Pregunta Cuando pregunte si sobreescribir el fichero “public/stylesheets/scaffold.css” decimos que no porque lo hemos modificado antes para incluir los estilos de la cabecera y si lo sobreescribimos perderíamos dichos cambios. “app/models/pregunta.rb”, como se muestra en las líneas en negrita de los listados 17 y 18. De esta manera, nos aseguramos de que se rellene el campo pregunta y el encuentro al que pertenece la pregunta. Después de todo esto, si vamos a http://localhost:3000/preguntas y creamos una nueva pregunta vemos que no hay forma de relacionarla con un encuentro. Para eso editamos el fichero “app/views/preguntas/_form.rhtml” y añadimos el campo al formulario, agregando las dos líneas que en el listado 19 se muestran en negrita. Como en las vistas no debe ir nada de la lógica de la aplicación, la función que genera la lista de encuentros para el desplegable la ponemos en “app/helpers/preguntas_helper.rb”, como se muestra en el listado 20. Al generar el “scaffold” de un modelo se crea un fichero llamado “modelo_helper.rb” en “app/helpers”, donde podemos poner las funciones auxiliares que necesitemos usar en nuestras vistas. Si hace falta que una de estas funciones o “helpers” sea accesible desde las vistas de varios modelos hay que ponerla en “application_helper.rb”. Los helpers son útiles para mantener las vistas limpias y fáciles de leer. Las vistas no deberían contener lógica compleja. Si la hay, ésta se debería refactorizar y moverla a un helper. Los métodos de la clase “helper” se pueden llamar desde la vista. Ahora vamos a modificar la vista “app/views/encuentros/show.rhtml” para que se vean todas las preguntas asociadas a ese encuentro, como se muestra en el listado 21. Después de cada pregunta ponemos un icono con un enlace para editar la pregunta y añadirle una respuesta y otro para borrar la pregunta. Para que al editar la respuesta y darle a guardar vuelva a la pantalla anterior, modificamos la redirección en el método “update” del controlador (“app/controllers/preguntas_controller.rb), sustituyendo la línea “redirect_to” por la que se muestra en negrita en el listado 22. En el mismo fichero, en el método “destroy”, también sustituimos la línea “redirect_to” por la que en el listado 23 se muestra en negrita. Para no perder el encuentro al que pertenecía la pregunta lo guardamos en una variable “e”. Siguiendo en el mismo fichero (app/controllers/preguntas_controller.rb), para que al pinchar en “Añadir pregunta” aparezca seleccio- Saltamos el paso seis También se ha generado un “layout” para este controlador, pero como queremos que use el general tenemos que borrar el fichero “app/views/layouts/preguntas.rhtml”. Y seguimos por el paso siete, pulir Añadimos las relaciones y las validaciones a los modelos en “app/models/encuentro.rb” y http://digital.revistasprofesionales.com Figura 14. Creación de scaffold para el modelo. 61 SOLO PROGRAMADORES nº 153 REDES(ror) 21/9/07 14:26 Página 62 REDES LISTADO 17 app/models/encuentro.rb class Encuentro < ActiveRecord::Base has_many :preguntas validates_presence_of :nombre validates_length_of :nombre, :maximum => 255 end LISTADO 18 app/models/pregunta.rb class Pregunta < ActiveRecord::Base belongs_to :encuentro validates_presence_of :pregunta , :encuentro_id end nado el encuentro desde el que hemos pinchado añadimos un parámetro “:encuentro_id => @encuentro” al enlace y en el método “new” lo asignamos al objeto pregunta, como se muestra en la línea en negrita del listado 23. De nuevo en el mismo fichero, para que al crear la pregunta vuelva a la pantalla del encuentro, no a la lista general de todas las preguntas modificamos la redirección en el método create, como como se muestra en la línea en negrita del listado 24. Ya que estamos editando el controlador podemos aprovechar y cambiar los textos en inglés por unos en castellano: flash[:notice] = ‘Pregunta creada tros digitales y, para cada encuentro, preguntas. Es cierto que en vista de los encuentros los mensajes se han puesto en LISTADO 19 español y se han eliminado los campos “Created at” y “Updated at”, cosa que no se ha hecho con las preguntas, así que esta sería una buena manera de seguir puliendo la aplicación. Por motivos de espacio y de simplicidad se ha omitido la validación de usuarios, pero el objetivo no es tanto desarrollar una aplicación como mostrar la manera de desarrollarla, al estilo Ruby on Rails. Con RoR, en cuestión de minutos disponemos de una aplicación operativa que empezamos a pulir, y que vamos mejorando mientras el tiempo app/views/preguntas/_form.rhtml <%= error_messages_for ‘pregunta’ %> <!—[form:pregunta]—> <p><label for=”pregunta_encuentro_id”>Encuentro</label><br/> <%= select ‘pregunta’, ‘encuentro_id’, encuentros %></p> <p><label for=”pregunta_pregunta”>Pregunta</label><br/> <%= text_area ‘pregunta’, ‘pregunta’, :cols => “80”, :rows => “5” %></p> <p><label for=”pregunta_respuesta”>Respuesta</label><br/> <%= text_area ‘pregunta’, ‘respuesta’, :cols => “80”, :rows => “5” %></p> <!—[eoform:pregunta]—> correctamente.’ flash[:notice] = ‘Pregunta actualizada LISTADO 20 correctamente.’ Conclusiones En este momento del desarrollo de la aplicación, el lector puede manejar encuen- LISTADO 21 app/helpers/preguntas_helper.rb module PreguntasHelper def encuentros Encuentro.find(:all).collect {|p| [ p.nombre, p.id ] } end end app/views/encuentros/show.rhtml <h2><%=h @encuentro.nombre %></h2> <p><%=h @encuentro.descripcion %></p> <h3>Preguntas: </h3> <ul> <% for p in @encuentro.preguntas %> <li> <strong><%= p.pregunta %></strong> <%= link_to image_tag(‘edit.gif’), :controller => ‘preguntas’, :action => ‘edit’, :id => p %> <%= link_to image_tag(‘delete.gif’), {:controller => ‘preguntas’, :action => ‘destroy’, :id => p }, :confirm => ‘¿Seguro?’, :method => :post %> <br> <%= p.respuesta %> </li> <% end %> </ul> <%= link_to “Añadir una pregunta”, :controller => ‘preguntas’, :action => ‘new’, :encuentro_id => @encuentro %> LISTADO 22 app/controllers/preguntas_controller.rb, update def update @pregunta = Pregunta.find(params[:id]) if @pregunta.update_attributes(params[:pregunta]) flash[:notice] = ‘Pregunta actualizada correctamente.’ redirect_to :controller => ‘encuentros’, :action => ‘show’, :id => @pregunta.encuentro else render :action => ‘edit’ end end SOLO PROGRAMADORES nº 153 62 http://digital.revistasprofesionales.com REDES(ror) 21/9/07 14:26 Página 63 REDES Programación ágil con Ruby on Rails LISTADO 22 y el presupuesto lo permiten. Desde la experiencia de dabne.net, Ruby on Rails aporta la posibilidad concreta y material de desarrollar según metodologías ágiles que integran los cambios en la especificación no como un escollo o un paso atrás sino como algo inherente al proceso de comunicación en el que comerciales, técnicos, clientes y usuarios estamos implicados. app/controllers/preguntas_controller.rb, destroy def destroy p = Pregunta.find(params[:id]) e = p.encuentro p.destroy redirect_to :controller => ‘encuentros’, :action => ‘show’, :id => e end LISTADO 23 app/controllers/preguntas_controller.rb, new def new @pregunta = Pregunta.new @pregunta.encuentro_id = params[:encuentro_id] end LISTADO 24 app/controllers/preguntas_controller.rb, create def create @pregunta = Pregunta.new(params[:pregunta]) if @pregunta.save flash[:notice] = ‘Pregunta was successfully created.’ redirect_to :controller => ‘encuentros’, :action => ‘show’, :id => @pregunta.encuentro else render :action => ‘new’ end end Conferencia Hispana Rails 2007 Durante el próximo mes de noviembre se celebrará en Madrid la II Conferencia Hispana Rails (www.conferenciarails.org/). De entre las propuestas de ponencia recibidas hasta el momento destacamos estas: APIs de Identidad y Rails Introducción en el mundo de las APIs más populares de la red como Flickr, Last.fm/Audiscrobbler o GoogleMaps. APIs Rest y proxificación Introducción a las APIs Rest y qué pueden aportar a nuestras aplicaciones. BDD y rSpec Además del desarrollo guiado por tests, existe otro paradigma que va un paso más allá de éste: el desarrollo orientado a comportamiento. Caché en Rails Mecanismos para introducir caché en las diferentes capas por las que transcurre la resolución de una petición. Camping, el microframework Para qué podríamos querer usar Camping y cómo hacerlo. Cómo programar una araña web con Rails Taller práctico en el que se explicará cómo construir desde cero una araña web. Flickr con Rails Taller práctico para construir una interfaz interactiva con el servicio Flickr. Integración de Rails en el escritorio con Slingshot Slingshot es la alternativa Open Source propuesta por Joyent a los recientes desarrollos de Microsoft y Adobe para integrar aplicaciones web en el escritorio usando Ruby on Rails. Inteligencia artificial aplicada a la publicidad con Ruby Cómo utilizar la inteligencia artificial en Ruby on Rails para optimizar automáticamente la publicidad de una aplicación. Introducción a RubyStack Presentación y demostración de RubyStack, una dis- http://digital.revistasprofesionales.com tribucion integrada Open Source de Ruby on Rails. JRuby on Rails: Agilidad en la empresa JRuby on Rails proporciona la agilidad de Rails sobre servidores y aplicaciones Java. La internacionalización sí es posible Aunque Rails no tiene soporte incorporado para la internacionalización, hay muchas opciones para abrir las puertas de una aplicación a audiencias más grandes. Más allá del testing Vale. Tests. Sabes que los necesitas, sabes que tienes que hacerlos. Pero, ¿por dónde empezar? ¿Qué tal especificar el comportamiento de tu aplicación en forma de tests? Phobos: scripting de servidor para plataforma Java Phobos es un en entorno de aplicaciones ligero con el rendimiento, escalabili- dad y fiabilidad de la plataforma Java. Programa en Rails como si jugases con Lego Cómo funcionan los plugins de Rails y cómo podemos crear nuestros propios plug-ins. Proyectos de bajo coste con limitaciones severas de tiempo y recursos Existen aplicaciones cuyo desarrollo se plantea con una limitación de tiempo y recursos severa. Rails nos ayuda. Rails against the machine Cómo crear entornos virtuales clonables para el despliegue de aplicaciones Rails con Capistrano 2 y XEN. Rails desde el código Bajaremos a las entrañas de Rails y echaremos un vistazo a la implementacion del framework. Rails para torpes como yo Es difícil transmitir qué es Ruby on Rails y qué signi63 fica que nos guste y que estemos felices por programar en este lenguaje. Rails y Globalize: un tren con destinos internacionales Cómo Globalize puede enseñar a tu aplicación Rails monolingüe a aprender idiomas. Seguridad web en aplicaciones Rails Un repaso a la seguridad web y a algunas de las vulnerabilidades más comunes. Tractis, un enfoque técnico Con un enfoque lo más técnico posible, comentaremos cómo trabajamos y qué herramientas usamos. Unión de MXML con Rails Se tratará la unión del lenguaje MXML usado por la plataforma Flex con el framework Rails para conseguir una aplicación de las que Adobe denomina RIA. SOLO PROGRAMADORES nº 153