Blog de Jordi Romero

Las últimas cinco entradas.

Creando una aplicación web con Ruby on Rails: Capítulo 2

24 de Marzo de 2009 2 comentarios

Seguimos donde lo dejamos, rápidamente nos dedicaremos a preparar el layout general de la página y empezaremos a poner Ajax por todas partes.

Se suele comentar que Rails y Ajax se llevan bien. Vamos a demostrarlo creando una pequeña aplicación pseudo GTD para mantener una lista de TODO’s.

En Rails un layout es una vista especial: es la parte común en un grupo de controladores/acciones. En muchas aplicaciones, con uno o dos layouts nos bastará, por ejemplo: uno para usuarios sin identificar y otro para los miembros, etc. En nuestra aplicación microtodo sólo necesitaremos uno. El generador scaffold nos generó el único fichero que podemos encontrar en app/views/layouts: todo_items.html.erb.

Muy típico de Rails: Convention over Configuration. Si no decimos nada, el controlador TodoItemsController buscará si existe un layout llamado todo_items, y si no lo encuentra después buscará uno llamado application. Podemos sobreescribir estas convenciones poniendo layout :nuevo_layout en el controlador, pero ahora no nos interesa.

Lo primero que haremos con este layout es cambiarle el nombre precisamente a application.html.erb, para que sirva para posibles nuevos controladores. Después le haremos un pequeño lavado de cara dejándolo así:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!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="en" lang="en">
<head>
  <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
  <title>Micro Todo</title>
  <%= stylesheet_link_tag 'scaffold' %>
  <%= javascript_include_tag :defaults %>
</head>
<body>
 
<% flash.each do |key, msg| %>
  <%= content_tag :div, msg, :id => key %>
<% end %>
 
<%= yield %>
 
</body>
</html>

Es bastante sencillo: stylesheet_link_tag 'scaffold' usa un helper que nos ofrece Rails para insertar la etiqueta <link> y el fichero CSS en cuestión (scaffold.css). Usar este helper tiene algunas ventajas que ahora no estudiaremos, como por ejemplo cachear los ficheros de estilo. Lo mismo sirve para javascript_include_tag, aunque este usa la palabra defaults que se traduce en los ficheros necesarios para usar Prototype y Scriptaculous (Los usaremos para el Ajax).

Finalmente hay este bloque Ruby tan chulo y que puede parecer difícil de entender, aunque hace una cosa bien sencilla: Para cada flash[:algo] que exista, ponemos un <div id="algo">Con su contenido.</div>. Esto nos servirá tanto para flash[:error] como para flash[:notice], o cualquier otro que nos inventemos.

La última palabra Ruby del layout es la palabra mágica yield. Se sustituirá por el contenido de la vista de la acción que se esté ejecutando (index, edit, add, …). Espero no ir demasiado lento profundizando en los detalles, pero al ser un tutorial para principiantes… Pido paciencia a los iniciados que lo estén siguiendo.

Queremos tener el formulario para añadir tareas en la página principal, y que al pulsar el botón Add o la tecla Enter se añada a la lista la tarea mediante Ajax. Volvemos a modificar app/views/todo_items/index.html.erb para usar partials y añadir el formulario:

1
2
3
4
5
6
7
8
9
10
11
12
<!-- index.html.erb -->
<h1>Things To Do</h1>
 
<div id="todos">
<%= render @todo_items %>
</div>
<br />
 
<% form_remote_for TodoItem.new do |f| -%>
  <%= f.text_field :task %>
  <%= f.submit "Add" %>
<% end -%>

Como podéis observar, hemos simplificado mucho el código que lista las tareas usando render @todo_items. Rails es muy listo, y observa que @todo_items es un array de todo_item, y piensa: Si me piden hacer un render de un todo_item, a lo mejor es que hay un partial para este… Y (oh! qué casualidad) justo acabo de crear un fichero llamado _todo_item.html.erb en app/views/todo_items/. Para que os funcione teneis que crearlo y dejarlo así:

1
2
3
4
5
6
7
<!-- _todo_item.html.erb -->
<% div_for todo_item do %>
  <%= h todo_item.task %>
  (<%= link_to 'Edit', edit_todo_item_path(todo_item) %>)
  <small>(<%= link_to 'Destroy', todo_item, :confirm => 'Are you sure?', :method => :delete %>)</small>
  <span><%= todo_item.completed_at %></span>
<% end %>

Seguramente habréis observado que he puesto form_remote_for en lugar de form_for, y aquí empieza el Ajax: Esto mandará el contenido del formulario en una llamada Ajax, y en lugar de refrescar el navegador ejecutará lo que el controlador le diga. Así que vamos al controlador y cambiamos un poco el método create.

42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
def create
  @todo_item = TodoItem.new(params[:todo_item])
 
  respond_to do |format|
    if @todo_item.save
      flash[:notice] = 'Todo Item added!.'
      format.html { redirect_to(todo_items_url) }
      format.xml  { render :xml => @todo_item, :status => :created, :location => @todo_item }
      format.js
    else
      format.html { render :action => "new" }
      format.xml  { render :xml => @todo_item.errors, :status => :unprocessable_entity }
    end
  end
end

Hemos añadido la línia format.js en el bloque respond_to do |format|. Esto lo que hará es responder tamién al método create si se pide con Content-Type: text/javascript, que es lo que hará la llamada Ajax, pero responderá usando el view create.js.rjs. Hasta ahora todos los views tenian la doble extensión .html.erb, que significa que se trata de ficheros HTML procesados con ERB (Embedded Ruby). Ahora ofrecemos un fichero JavaScript usando RJS, un lenguaje muy simple para generar código JavaScript escribiendo Ruby. Este fichero estará obviamente en app/views/todo_items/ y será así:

1
2
3
4
5
# create.js.rjs
page.insert_html :bottom, :todos, :partial => 'todo_item', :object => @todo_item
page.visual_effect :highlight, "todo_item_#{@todo_item.id}"
page[:new_todo_item].reset
flash.discard

Resumiendo: La primera línea generará el partial con el objeto recién creado en el controlador y lo añadirá al final de <div id="todos">. La segunda hará un efecto de sobresalto en la tarea recién añadida, la tercera vaciará el formulario y con la última descartaremos el mensaje flash que se añade en el controlador. La magia de Ajax y Rails es que este código Ruby se convertirá en JavaScript, y gracias a form_remote_for se mandará de vuelta al cliente, donde se ejecutará dejándonos asombrados a nosotros mismos de lo que sómos capaces de hacer.

Ahora (sólo porque me conviene que se vean las cosas un poco mejor) añadiremos un poco de CSS al final de public/stylesheets/scaffold.css

55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
.todo_item
{
  display: block;
  padding: 5px 0;
  background: #DADADA;
  margin: 10px;
}
 
.todo_item span
{
  font-size: 0.7em;
  color: #747474;
}
 
body
{
  margin: 0 auto;
  width: 60%;
}

Lo último que haremos en este capítulo es completar y descompletar las tareas mediante un checkbox que usando Ajax actualizará el estado de la tarea. Dado que estamos usando un controlador REST, tenemos que modificar el fichero config/routes.rb para permitir una nueva acción: toggle, que cambiará de completado a pendiente una tarea. Modificamos el fichero:

1
2
3
4
5
6
ActionController::Routing::Routes.draw do |map|
 
  map.resources :todo_items, :member => { :toggle => :put }
  map.root :todo_items
 
end

Ahora añadimos el método toggle en TodoItemsController:

87
88
89
90
def toggle
  @todo_item = TodoItem.find(params[:id])
  @todo_item.update_attribute :completed_at, (@todo_item.completed_at.nil? ? Time.now : nil)
end

Añadimos el checkbox al parcial _todo_item.html.erb

1
2
3
4
5
6
7
8
9
<!-- _todo_item.html.erb -->
<% div_for todo_item do %>
<%= check_box_tag :completed_at, "1", !todo_item.completed_at.nil?,
                  :onclick => remote_function(:url => toggle_todo_item_path(todo_item), :method => :put) %>
  <%= h todo_item.task %>
  (<%= link_to 'Edit', edit_todo_item_path(todo_item) %>)
  <small>(<%= link_to 'Destroy', todo_item, :confirm => 'Are you sure?', :method => :delete %>)</small>
  <span><%= todo_item.completed_at %></span>
<% end %>

Y sólo nos falta crear otro fichero RJS: toggle.js.rjs junto a las otras vistas de views/todo_items:

1
2
3
# toggle.js.rjs
page.replace "todo_item_#{@todo_item.id}", :partial => 'todo_item', :object => @todo_item
page.visual_effect :highlight, "todo_item_#{@todo_item.id}"

Esto es todo por hoy. Os podéis bajar el código de Github usando Git o directamente un fichero comprimido. Y para los más perezosos que tienen ganas de ver cómo funciona esto del Ajax pero no quieren probar el código o instalar Ruby on Rails, podéis ver este pequeño screencast de 30 segundos (sin audio):
Imagen del Screencast de Microtodo

Dejo para el próximo capítulo separar las tareas pendientes de las completadas, eliminarlas usando ajax, cambiarles el texto in-situ y reordenarlas! (Recordáis el campo position:integer de nuestra migración?).

Creando una aplicación web con Ruby on Rails: Capítulo 1

22 de Marzo de 2009 6 comentarios

Se suele comentar que Rails y Ajax se llevan bien. Vamos a demostrarlo creando una pequeña aplicación pseudo GTD para mantener una lista de TODO’s. Aunque en el primer capítulo aún no se verá Ajax, aparecerá muy pronto. Fácilmente se puede extender para gestionar varias listas, pero interesa mantener el código simple.

Comprovamos las versiones de Ruby y Rails, y creamos la aplicación con el comando rails. El comando mate . abre el editor TextMate (sólo para Mac OS X). Aquí usad el equivalente para vuestro editor o simplemente abrid el directorio con vuestro navegador de ficheros.

~% ruby -v
ruby 1.8.6 (2008-03-03 patchlevel 114) [universal-darwin9.0]
~% rails -v
Rails 2.3.2
~% rails microtodo
      create
      create  app/controllers
      [...]
      create  log/test.log
~% cd microtodo
~/microtodo% mate .

Ahora toca respirar un poco y curiosear por el directorio que acabamos de crear y llenar. Podríamos ejecutar script/server y abrir con nuestro navegador http://0.0.0.0:3000/ para ver la página inicial de Rails, pero si habéis seguido la entrada anterior, esto no es necesario.

A continuación nos cargaremos esta página inicial (el objetivo es ver la lista de tareas por hacer cuando vayamos a la página principal) y usaremos el generador scaffold para las tareas. Crearemos el modelo TodoItem y la migración a la base de datos con tres campos: task: string (descripción de la tarea), position: integer (para poder reorganizar las tareas como nos parezca) y completed_at: timestamp (para saber si una tarea se ha completado y en qué instante se ha hecho). Una migración es un pequeño código ruby que se convertirá en sentencias SQL al ejecutar el comando rake db:migrate. Recomiendo echar un vistazo a la guía sobre migrations. El generador scaffold además crea el controlador TodoItemsController, las vistas para las acciones CRUD, un layout para el controlador y un fichero css básico.

~/microtodo% rm public/index.html
~/microtodo% script/generate scaffold TodoItem task:string position:integer completed_at:timestamp
      [...]
~/microtodo% rake db:migrate
(in /Users/jordi/microtodo)
==  CreateTodoItems: migrating ================================================
-- create_table(:todo_items)
   -> 0.0037s
==  CreateTodoItems: migrated (0.0041s) =======================================
 
~/microtodo%

Ahora es un buen momento para ejecutar script/server y jugar un poco con las tareas generadas por el scaffold. A continuación vamos a hacer que el controlador TodoItemsController responda a la página inicial modificando el fichero config/routes.rb y dejándolo así:

1
2
3
4
5
6
ActionController::Routing::Routes.draw do |map|
 
  map.resources :todo_items
  map.root :todo_items
 
end

Hoy acabaremos el capítulo uno limpiando un poco los views modificando los ficheros de app/views/todo_items. Primero borraremos el fichero show.html.erb puesto que no nos interesa una página para cada To Do Item, luego editaremos index.html.erb, edit.html.erb y new.html.erb y crearemos _form.html.erb con este contenido:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- index.html.erb -->
<h1>Things To Do</h1>
 
<% @todo_items.each do |todo_item| %>
  <% div_for todo_item do %>
    <%= h todo_item.task %>
    (<%= link_to 'Edit', edit_todo_item_path(todo_item) %>)
    <small>(<%= link_to 'Destroy', todo_item, :confirm => 'Are you sure?', :method => :delete %>)</small>
  <% end %>
<% end %>
 
<br />
 
<%= link_to 'Add To Do Item', new_todo_item_path %>

Aquí observamos el uso del iterador each sobre la variable @todo_items que se crea en el controlador. Se usa el clásico bloque de Ruby, y para cada todo_item usamos los helpers que Rails nos ofrece para definir un <div> que contiene la descripción de la tarea y enlaces para editarla y eliminarla. Sustituiremos estos enlaces por edición y eliminación in-situ usando Ajax, pero será en otro capítulo.

Vamos las vistas de las acciones edit y new, que son muy parecidas:

1
2
3
4
5
6
<!-- edit.html.erb -->
<h1>Editing To Do Item</h1>
 
<%= render :partial => "form", :locals => { :todo_item => @todo_item, :button_label => "Update Item" } %>
 
<%= link_to 'Back', todo_items_path %>
1
2
3
4
5
6
<!-- new.html.erb -->
<h1>New To Do Item</h1>
 
<%= render :partial => "form", :locals => { :todo_item => @todo_item, :button_label => "Add Item" } %>
 
<%= link_to 'Back', todo_items_path %>

Aquí, además de usar el helper link_to que ya hemos visto en la anterior vista, utilizamos render :partial, que es una herramienta muy potente que permite mantener el código mas limpio siguiendo fielmente el principio DRY. Más información en la guía Layout and Rendering.

Finalmente, la nueva vista que se usará en las acciones edit y new para mostrar el formulario. Tener este formulario duplicado era muy poco DRY, y puesto que usamos los helpers de Rails para crear los campos del formulario, si el objeto todo_item contiene información se mostrará en el campo creado por el helper.

1
2
3
4
5
6
7
8
9
10
11
<!-- _form.html.erb -->
<% form_for(todo_item) do |f| %>
  <%= f.error_messages %>
  <p>
    <%= f.label :task %><br />
    <%= f.text_field :task %>
  </p>
  <p>
    <%= f.submit button_label %>
  </p>
<% end %>

Casi estamos por hoy, pero si nos cargamos la vista para la acción show, tenemos que modificar el controlador para que nunca intente llevarnos a ver un To Do Item. Concretamente, el controlador creado por el scaffolding hace un redirect a la página /todo_items/ID después de crear o editar una tarea. Cambiaremos este comportamiento modificando las acciones create y update.

Modificamos las líneas 48 y 65 del fichero app/controllers/todo_items_controller.rb:

48
        format.html { redirect_to(@todo_item) }
65
        format.html { redirect_to(@todo_item) }

por

48
        format.html { redirect_to(todo_items_url) }
65
        format.html { redirect_to(todo_items_url) }

Hasta aquí en este primer capítulo. En el próximo haremos que toda la acción suceda en una página, insertando tareas desde la página principal y haciendo que una tarea se pueda completar con un checkbox, ambas acciones usando Ajax.

He colgado el proyecto en Github, por si en lugar de seguir los pasos queréis consultar directamente el resultado. Si no usáis Git, os podéis bajar un tarball también desde Github.

Responderé encantado cualquier duda que aparezca siguiendo este tutorial.

La verdadera definición de Ruby on Rails

19 de Marzo de 2009 6 comentarios

La definición de Ruby on Rails dice:

“Ruby on Rails es un Framework para desarrollar aplicaciones web escrito en Ruby.”

Bueno, esto es casi tan inútil como cierto. Qué es un framework? Nunca he visto dos veces la misma definición para esta palabra: herramienta, conjunto de herramientas, entorno de trabajo, estructura de soporte, … Y “uno de esos” escrito en ruby? Más confuso aún.

Creo que se entiende mejor así:

“Ruby on Rails es una aplicación web vacía, esperando a ser completada con código Ruby.”

“Ruby on Rails es una aplicación que usa el patrón MVC.”

“Ruby on Rails es Active Record [M]: La persistencia de datos como problema se desvanece.”

“Ruby on Rails es Action View [V]: Crear documentos HTML (y otros formatos) dinámicos con cuatro palabras en Ruby.”

“Ruby on Rails es Action Controller [C]: Ejecutar acciones dependiendo de la URL, gestionar las redirecciones, sesiones, mandar trabajo a Action View o Active Record en muy poco código.”

“Ruby on Rails es una aplicación web que se puede testear, que se debe testear, que te hará querer testear!”

“Ruby on Rails es una comunidad que no para de escribir plugins, gems, tutoriales, trucos, screencasts, …”

Por supuesto es muchas cosas más, pero lo fundamental está dicho. Recomiendo leer las guias oficiales de Ruby on Rails, empezando por Getting Started with Rails para más detalles sobre qué es Ruby on Rails.

Y para acabar, una demo:

rails blog
cd blog
script/generate scaffold Post titulo:string contenido:text
rake db:migrate
script/server

Si habías instalado correctamente Ruby y Rails, ahora simplemente abre un navegador y ve a http://0.0.0.0:3000/posts. Una aplicación web (muy sencilla) sin escribir ni una línea de código. A partir de aquí se puede ir haciendo retoques a los ficheros generados por el comando rails hasta tener un Wordpress-killer en Rails. Cuando lo tengas, avísame.

Miedo al FTP

16 de Marzo de 2009 0 comentarios

Inexplicablemente el FTP no desapareció junto a TELNET en calidad de protocolo absolutamente inseguro.

Sin entrar a explicar cómo funcionan este tipo de protocolos, FTP lo que hace es establecer una conexión TCP con el servidor y mandarle mensajes del tipo:

USER pepito
331 Please identify yourself in a password.
 
PASS juanito
230 Thanks.
 
...
 
RETR /pub/report
150 I see that file.

Lo que la mayoría de usuarios de FTP no saben es que estos mensajes viajan tal cual por la red! Cualquier persona que esté monitoreando la red en la que está conectado el usuario, o cualquier router administrado por alguien con mala baba puede implicar que estés gritando a los cuatro vientos: PASS juanito

Supongo que es muy evidente que es una limitación absolutamente inaceptable teniendo en cuenta que muchas veces necesitamos acceder para subir/bajar ficheros privados en servidores remotos.

La solución al problema, desgraciadamente, depende de lo simpático (o bueno) que sea el proveedor que administra el servidor al que queremos acceder y los protocolos que nos proporcione. Podemos usar FTPS, o FTP sobre SSL. Su nombre lo dice todo, funciona normalmente igual que FTP pero cifrando los datos de la comunicación. Alternativamente podemos enviar y recibir ficheros a un servidor mediante SFTP, siglas de Secure File Transfer Protocol, y que en definitiva significa: Transferencia de ficheros vía SSH. Éste es mi método favorito, ya que se usa únicamente el puerto SSH para acceder a la máquina y para subir y bajar ficheros, y evidentemente toda la comunicación viaja encriptada con pares de claves pública/privada.

Mi consejo: evitad el uso de FTP siempre que sea posible, y absolutamente dejad de usarlo cuando se trate de acceder a sistemas donde la seguridad sea mínimamente importante. La mayoría de clientes FTP decentes también soportan FTPS y SFTP. Para el que tenga curiosidad en las tecnologías comentadas, recomiendo visitar los enlaces del texto (todos van a la Wikipedia) y en el caso de FTP, añado esta página.

No más bash scripts

15 de Marzo de 2009 0 comentarios

El otro día estuve perdiendo casi una hora con un bash script para hacer copias de seguridad. De repente, vi que podía hacer un script Ruby en la mitad del líneas. Así lo hice, de hecho pasé de 60 a 13 líneas de código. Y nunca abuso de la notación de Ruby para compactar el código, en aras de la legibilidad. A pesar del pequeño coste en rendimiento, el desgaste se divide casi por infinito al escribir un pequeño programa en ruby.

Sólo voy a usar bash script para algún alias o para ejecutar 3 o 4 instrucciones en un comando. Se ha acabado programar en bash script.