Cómo paginar, ordenar y hacer búsquedas en una tabla con Ajax y Rails
Introducción y observaciones
(Nota: este documento es una traducción del tutorial How to paginate, sort and search a table with Ajax and Rails, escrito por Julien Barnier, y publicado en su web http://nozav.org)
En este tutorial intentaremos utilizar Ajax con Rails para mostrar una tabla de elementos con diversas funcionalidades:
- paginación: separar la tabla en páginas distintas, si el contenido no puede mostrarse de una sola vez
- ordenación: ordenar la tabla por alguna de las columnas, en orden ascendente o descendente
- búsqueda: escoger qué elementos van a ser mostrados con una consulta
Esta es una tarea muy común en el desarrollo de aplicaciones web. El interés de usar Ajax para esto es proporcionar una interfaz dinámica que no hace necesario recargar toda la página cuando la tabla cambia. El interés de usar Rails es, bueno, si estás leyendo esto ya deberías saberlo, pero Ajax y Rails se llevan bastante bien, de forma que es muy fácil implementar funcionalidades Ajax con nuestro framework favorito. Además, el código de este tutorial debería funcionar también de la manera tradicional (recargando la página entera) si JAvascript no está disponible en la parte del cliente. Esto es muy importante para la accesibilidad.
El código que sigue a continuación esta muy inspirado en diferentes páginas del wiki de Rails, en especial “How to make a real-time search box with the Ajax helpers” y “How to paginate with Ajax”. Simplemente puse las cosas juntas y adapté el código ligeramente.
Ahora, las observaciones. No soy un gurú de Rails, de hecho estoy bien lejos de ser un experto en Ajax. Soy un principiante en ambas áreas, así que tómense este documento como una introducción para principiantes escrita por un principiante: el código podría ser más claro, las explicaciones más acertadas y algunas cosas podrían haberse diseñado de manera más eficaz y elegante. Además, como el inglés no es mi lengua nativa, es posible que se encuentren muchos errores e inexactitudes durante el texto. Les pido excusas por anticipado (N. del T: ¡esto va también por mí!)
En todo caso no duden en enviarme sus comentarios a la siguiente dirección:
julien (at) nozav (dot) (org)
Instalación y configuración de la aplicación
Lo primero que hemos de hacer es instalar y configurar la aplicación. Si ya lo has hecho, te puedes saltar lo que viene a continuación.
Requisitos
En este tutorial se supone que tienes una versión reciente e Rails instalado (al menos la versión 1.0) y un sistema de bases de datos funcional. Aquí usaremos SQLite, pero se puede reemplazar por cualquier otro sin el menor problema.
Archivos
Primero, tenemos que crear el esqueleto de la aplicación en un directorio de nuestra elección
$ rails ajaxtable
$ cd ajaxtable
Como se trata de una aplicación muy básica solamente utilizaremos un modelo que representará a los elementos de nuestra tabla, así como un controlador para estos elementos. Generamos todo el código correspondiente usando Rails:
$ ruby script/generate model Item
$ ruby script/generate controller Item
La base de datos
Una vez que tengamos el código, debemos configurar una fuente de datos. En aras de la simplicidad utilizaremos SQLite junto a un esquema Rails. Esto significa que tendremos que describir nuestra base de datos dentro de Rails y dejar que sea el framework quien gestione la creación de la base de datos.
Tenemos que modificar la fuente de datos para el entorno de desarrollo en
config/database.yml:
1 2 3 |
development:
adapter: sqlite3
database: db/development.db |
A continuación, crearemos la base de datos utilizando las herramientas de Rails
$ rake db_schema_dump
Esto debería crear un archivo llamado db/schema.rb en el que definiremos nuestro esquema
de base de datos de la siguiente manera:
1 2 3 4 5 6 7 8 |
ActiveRecord::Schema.define() do create_table "items" do |t| t.column "name", :string, :limit => 30 t.column "quantity", :integer, :null => false, :default => 0 t.column "price", :integer, :null => false, :default => 0 end end |
Esto crea una tabla llamada items con tres columnas: un campo de cadena llamado nombre, y dos
columnas numéricas llamadas cantidad y precio (no es nada demasiado original, ciertamente)
Luego tan sólo hemos de hacer
$ rake db_schema_import
Y ya deberíamos tener un fichero development.db en nuestro directorio db que es la base
de datos creada por Rails con la tabla items en su interior.
Después podemos introducir algunos ítems en la tabla para tener algo que mirar mientras
desarrollamos el código. Se puede hacer manualmente o escribiendo las siguientes instrucciones SQL
en un fichero db/dump.sql:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
BEGIN TRANSACTION; INSERT INTO "items" VALUES(1, 'caja', 3, 10); INSERT INTO "items" VALUES(2, 'carretilla', 2, 60); INSERT INTO "items" VALUES(3, 'tenazas', 15, 3); INSERT INTO "items" VALUES(4, 'batman', 1, 3000); INSERT INTO "items" VALUES(5, 'morcilla de pescado', 2, 8); INSERT INTO "items" VALUES(6, 'sauerkraut', 9, 9); INSERT INTO "items" VALUES(7, 'regadera', 4, 13); INSERT INTO "items" VALUES(8, 'dandiliones', 78, 1); INSERT INTO "items" VALUES(9, 'nevera', 12, 250); INSERT INTO "items" VALUES(10, 'cerillas voladoras', 8, 145); INSERT INTO "items" VALUES(11, 'acordeón roto', 1, 18); INSERT INTO "items" VALUES(12, 'silbido salvaje', 5, 7); INSERT INTO "items" VALUES(13, 'caracol histérico', 8, 13); COMMIT; |
Podremos insertar todas estas tablas en la base de datos ejecutando
$ sqlite3 db/development.db < db/dump.sql
(Nota, si se utiliza una base de datos MySQL, la sintaxis adecuada es @INSERT INTO items VALUES (1, ‘hoe’, 3, 10);@, etc)
Creación del modelo
Como seguramente sabrán, una aplicación Rails se divide en tres tipos de componentes: modelos, vistas y controladores. Los iremos creando uno por uno.
El modelo de nuestra aplicación será muy sencillo. De hecho, como no tenemos ninguna consulta
compleja que hacer a la base de datos, será exactamente el mismo que ha generado Rails para
nostros en nuestra instalación de la aplicación. Vamos, que irá vacío. Así que no tocaremos el
fichero app/models/item.rb
¡Espero que este paso no haya sido demasiado difícil!
Creación de la vista
La vista de nuestra aplicación estará dividida en dos componentes: un layout, una vista y un parcial.
Layout
El layout es una plantilla de página que se utilizará para interpretar diferentes vistas. En él se pondrán los elementos que no varían, tales como los encabezamientos y pies de página en HTML, menús, elementos de diseño, etc. La utilidad de un layout en nuestro ejemplo es muy limitada, porque sólo tendremos una página. Pero a fin de cuentas esto no es más que un tutorial…
En nuestro caso guardaremos el layout en app/views/layouts/item.rhtml, que contendrá algo
parecido a
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<html> <head> <title>Ajax table manipulation attempt</title> <%= stylesheet_link_tag "style" %> <%= javascript_include_tag :defaults %> </head> <body> <div id="content"> <%= @content_for_layout %> </div> </body> </html> |
Es muy importante no olvidar la sentencia javascript_include_tag, que será reemplazada por las
librerías que utiliza Rails para proporcionar toda la funcionalidad Ajax: sin esto no funcionará.
También es de señalar la instrucción content_for_layout, que será reemplazada por el contenido
generado convenientemente por…
La vista
El componente de la vista se utiliza para mostrar acciones particulares de nuestros controladores.
Como es una lista de nuestro controlador de items, lógicamente se hallará en
app/views/item/list.rhtml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
<h1>Welcome to my wonderful items list</h1> <p>This list is updated real-time from the world largest database of items by using bleeding-edge technology associated to full-featured Web 2.0 eye candy goodies.</p> <p>But be careful, there are plenty of bugs.</p> <h2>And here is the list...</h2> <p> <form name="sform" action="" style="display:inline;"> <label for="item_name">Filter on item name : </label> <%= text_field_tag("query", @params['query'], :size => 10 ) %> </form> <%= image_tag("spinner.gif", :align => 'absmiddle', :border=> 0, :id => "spinner", :style=>"display: none;" ) %> </p> <%= observe_field 'query', :frequency => 2, :update => 'table', :before => "Element.show('spinner')", :success => "Element.hide('spinner')", :url => {:action => 'list'}, :with => 'query' %> <div id="table"> <%= render :partial => "items_list" %> </div> |
En principo no hay nada muy complicado. Un texto estúpido de presentación y un formulario de búsqueda que utilizaremos para filtrar los items por su nombre.
Después, tendremos una imagen cuyo id es spinner y que está oculto por defecto. Esta imagen se
mostrará brevemente durante las operaciones Ajax en la página, y luego se ocultará otra vez cuando
todo haya acabado. Se pueden conseguir algunas imágenes de domino público de este estilo en
http://mentalized.net/activity-indicators/
Hay que poner la imagen escogdia en el directorio public/images de nuestra aplicación.
La instrucción observe_field es algo más especial. Lo que hace esta instrucción es añadir un
nuevo observer Ajax al campo de consulta. Este observador periódicamente comprobará (aquí es
cada dos segundos, pero hay un parámero que permite establecer la frecuencia) los contenidos de
este campo y se activará si cambian sus contenidos. La acción a tomar viene descrita por los
parámetros,
updatedetermina eliddel elemento de la página que será actualizado. En este caso es la tabla que rodea a nuestra llamada al parcial (la tabla, por supuesto, tendrá elid‘table’).
urldetermina la acción que mostrará el nuevo contenido HTML a insertar. Aquí cada petición será tratada por la acciónlistde nuestro controlador
withindica la manera en al que el campo del contenido será pasadao a la URL de la acción. Aquí añadiremos un parámetroquerya nuestra petición Ajax con un valor igual a lo que se haya introducido en el campo observado.
beforeysuccessindican, respectivamente, una acción a realizar durante el tiempo que se trata la acción Ajax, y una vez que haya sido completada satisfactoriamente.
El efecto concreto de todo esto es muy sencillo. Cuando el usuario escribe algo en el campo de
consulta, el observador detectará los nuevos contenidos del campo y a continuación generará una
petición Ajax al servidor con la URL y los parámetros especificados. En cuanto que se envíe la
petición, la acción :before cambia la visibilidad del elmento spinner, y el usuario puede ver
la imagen animada. Cuando se recibe la respuesta, el elemento de la tabla XHTML se actualiza y la
acción :success oculta la imagen otra vez.
Por curiosidad, aquí está el código que devuelve la función observe_field con los parámetros
mostrados:
1 2 3 4 5 6 7 8 9 |
<script type="text/javascript"> //<![CDATA[ new Form.Element.Observer('query', 2, function(element, value) {Element.show('spinner'); new Ajax.Updater('table', '/item/list', { asynchronous:true, evalScripts:true, onSuccess:function(request){Element.hide('spinner')}, parameters:'query=' + value})}) //]]> </script> |
Hemos detallado las diferentes opciones de los métodos Ajax porque volveremos a verlas en casi todos los métodos Ajax que veamos en este tutorial.
Para concluir con esta descripción de la vista, tenemos una llamada a un parcial llamado
items_list. Describiremos este concepto en detalle después del controlador.
Creando el controlador
El controlador gestionará diferentes tipos de peticiones para actualizar la vista, llamando al
modelo según convenga dependiendo de la petición y sus parámetros. Nuestro controlador Item será
muy sencillo, y tan sólo incluirá una acción llamada list. No implementaremos ninguna otra
acción CRUD (Create, Read, Update, Delete) en el tutorial.
Aquí están los contenidos de app/controllers/item_controller.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
class ItemController < ApplicationController def list items_per_page = 10 sort = case @params['sort'] when "name" then "name" when "qty" then "quantity" when "price" then "price" when "name_reverse" then "name DESC" when "qty_reverse" then "quantity DESC" when "price_reverse" then "price DESC" end conditions = ["name LIKE ?", "%#{@params[:query]}%"] unless @params[:query].nil? @total = Item.count(:conditions => conditions) @items_pages, @items = paginate :items, :order => sort, :conditions => conditions, :per_page => items_per_page if request.xml_http_request? render :partial => "items_list", :layout => false end end end |
Nuestro controlador sólo define una acción, llamada list, que gestionará todas las peticiones.
Primero, definimos una variable llamada items_per_page que representará el número de líneas que
nuestra tabla mostrará en cada página.
A continuación, definiremos otra variable llamada sort, que depende del parámetro de la petición
que tiene el mismo nombre. Con la sentencia case construimos la sentencia a enviar a la base de
datos para ordenar los elementos de la tabla. La cadena reverse es el parámetro que indica que
la ordenación debe hacerse por orden inverso (en el helper veremos cómo se envía este parámetro
al controlador).
Luego se construye una variable conditions si viene un parámetro de consulta, armando una
sentencia SQL que filtrará los resultados.
Tras esto, asignamos el número total de elementos en nuestra base de datos que se corresponden con
las condiciones en la variable total
Y, por fin, encontramos una llamada a la función paginate de Rails. Le damos a
esta instrucción el modelo al que deberia asociarse (:items), un campo de ordenación, las
condiciones para aplicar a la consulta y el número delementos que queremos en cada página.
Y automágicamente devuelve un objeto paginador llamado items_pages y los items para
la página actual (este número de pagina se pasa como un parámetro en la petición de página,
que veremos más adelante) El objeto paginador se usará después para mostrar enlaces de
paginación.
Todo lo que hemos visto para el controlador hasta ahora era aplicable a cada petición que se pasa
a la aplicación, sea cual sea su tipo. Los tipos de peticiones HTTP más frecuentes son los
tradicionales GET y POST, pero Ajax y Rails pueden hacer uso de un tercer tipo, cuyo nombre es
*XmlHttpRequest* (en realidad, la peticón XmlHttpRequest no es otro tipo de petición HTTP, es
tan sólo una petición GET o POST tradicional que es enviada y tratada por el intérprete Javascript
de manera asíncrona)
Como hemos dicho, este tipo de petición es invocada desde Javascript, que la enviará en segundo plano por HTTP hacia el sevidor. Un uso posible (y que es el que usaremos) de este tipo de petición es el de obtener un fragmento de una página XHTML para actualizar parte del contenido que muestra el navegador sin volver a cargar la página entera, lo cual proporciona una experiencia de usuario más rápida y agil.
De esto es de lo que va la parte final de nuestro controlador: comprueba si la petición que tiene
es de tipo xml_http_request (también podría haberse escrito abreviadamente xhr). En este
caso no mostrará la lista completa sino tan sólo un fragmento de ella el parcial cuyo nombre es
items_list
Así que, como puede verse, esta comprobación xml_http_request es el único código “realmente
Ajax” con que nos topamos en el controlador. Esto es así porque en Rails los asuntos de Ajax
están enlazados al interfaz de nuestra aplicación (es decir, a la vista) y más específicamente
a la parte de ella que será gestionada por Ajax (es decir, el parcial).
Así que, ¿adivinan qué es lo que vamos a hacer ahora?
Creación del parcial
Un componente parcial de la vista se utiliza para mostrar una parte de la página. Es muy útil separar diferentes partes de la vista para poder reutilizarlas y seguir el principio Rails de _No te Repitas_ (DRY, Don’t Repeat Yourself) Pero además también es muy útil con Ajax.
De hecho el efecto de nuestras acciones Ajax en este tutorial siempre consistirá en volver a
dibujar la tabla de elementos, ya cambie la página, el filtro por nombre o el orden de búsqueda.
Así que tendremos que refrescar la tabla pero no toda la página: por eso separaremos todos los
elementos que tendrán que ser actualizados en un parcial. (Esto podríamos haberlo hecho de alguna
otra manera. Por ejemplo, creando otra acción de nombre ajax_list que gestionaría las peticiones
xml_http_request, con una vista asociada pero sin ningún parcial. El problema que tiene este
enfoque es que habríamos duplicado buena parte del código en las acciones list y ajax_list,
así que.. ¡DRY!)
Los nombres de fichero de los parciales siempre empiezan con un subrayado. Así que nuestro
parcial será app/views/item/_items_list.rhtml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
<% if @total == 0 %> <p>No items found...</p> <% else %> <p>Number of items found : <b><%= @total %></b></p> <p> <% if @items_pages.page_count > 1 %> Page : <%= pagination_links_remote @items_pages %> <% end %> </p> <table> <thead> <tr> <td <%= sort_td_class_helper "name" %>> <%= sort_link_helper "Name", "name" %> </td> <td <%= sort_td_class_helper "qty" %>> <%= sort_link_helper "Quantity", "qty" %> </td> <td <%= sort_td_class_helper "price" %>> <%= sort_link_helper "Price", "price" %> </td> </tr> </thead> <tbody> <% @items.each do |i| %> <tr class="<%= cycle("even","odd") %>"> <td><%= i.name %></td> <td><%= i.quantity %></td> <td><%= i.price %></td> </tr> <% end %> </tbody> </table> <% end %> |
El parcial contiene la paginación de la tabla y la gestión de la ordenación. Vamos a verlo con más detalle.
Helpers de paginación
Al principio tenemos una comprobación para ver si el número total de elementos que se ha encontrado es mayor que cero. En este caso, mostramos este número y después un párrafo que estará vacío si sólo hay una página en nuestro objeto de paginación.
Si tenemos más de una página de resultados está claro que tendremos que mostrar los enlaces de paginación. Rails por supuesto ya tiene métodos de ayuda pero tendremos que personalizarlos un poco. Para hacer esto, crearemos un helper. Un helper es una función Ruby que se utiliza para ayudar a generar la vista. La idea es separar el código en estas funciones de la propia vista, favoreciendo la reusabilidad.
Nuestros helpers estarán todos en app/helpers/item_helper.rb. Cada uno de los métodos de este
archivo estarán disponibles para usarse en el código de nuestra vista. Opcionalmente, y si
quisiéramos que estuviera disponible en todas las vistas de la aplicación, lo añadiríamos a
application_helper.rb
Bien, ya basta de charla, he aquí el código de nuestro helper,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
def pagination_links_remote(paginator) page_options = {:window_size => 1} pagination_links_each(paginator, page_options) do |n| options = { :url => {:action => 'list', :params => @params.merge({:page => n})}, :update => 'table', :before => "Element.show('spinner')", :success => "Element.hide('spinner')" } html_options = {:href => url_for(:action => 'list', :params => @params.merge({:page => n}))} link_to_remote(n.to_s, options, html_options) end end |
A este método le pasaremos un objeto paginador como argumento. El paginador, como vimos en el controlador, es un objeto de Rails que guarda información sobre el estado de paginación (número de páginas, la página que estamos viendo, etc.)
Después definiremos un hash llamado page_options que contiene tan sólo un item llamado
window_size Este será un parámetro que le dice a Rails cuántas páginas tiene que mostrar
alrededor de la actual en la línea a los enlaces de paginación. Por ejemplo, si window_size vale
uno, tendremos algo como
1 ... 5 6 7 ... 13
Mientras que si window_size es 2:
Podríamos entonces hacer una llamada a la función pagination_links, que generaría el código
XHTML necesario para dibujar nuestros enlaces. El problema es que esto generaría enlaces XHTML
“clásicos”, que no realizarían llamadas Ajax. Así que tendremos que generar por nuestra cuenta el
código de los enlaces. Esto lo haremos con el método
pagination_links_each
Este método de Rails se recorre las páginas a mostrar y después aplica el bloque que se le pasa
como argumento.
Nuestro bloque en primer lugar define dos tipos de hashes de opciones
optionsse define para la generación del enlace Ajax. Son muy similares a las que se definieron en el elementoobserve_field. La única novedad es la invocación deparams.merge, que añadirá los parámetros de la petición al enlace reemplazando cualquier parámetro que ya hubiera en la página por el número del bloque .
html_optionssimplemente se definen para generar el enlace “clásico” XHTML, para que funcione la paginación si Javascript no está disponible o el navegador no lo soporta.
Después hay una llamada a la función link_to_remote que generará el código XHTML para nuestro
enlace, incluyendo el Javascript que hará la invocación Ajax y la parte “clásica” con su href.
Por ejemplo, esto es lo que devuelve el helper si hay dos páginas, y estamos ya mostrando la primera:
1 2 3 4 5 6 7 8 |
1 <a href="/item/list?page=2"
onclick="Element.show('spinner');
new Ajax.Updater('table', '/item/list?page=2', {
asynchronous:true,
evalScripts:true,
onSuccess:function(request){Element.hide('spinner')}
});
return false;">2</a> |
Helpers de ordenación
Volvamos a nuestro parcial. Después de los enlaces de paginación empezamos a definir la tabla propiamente dicha. La definición de la cabecera es algo complicada porque ahí es donde vamos a definir los enlaces que ordenarán los datos por una u otra columna. Cada celda del encabezamiento de la tabla hace uso de dos helpers.
El primer helper no es del todo necesario. Se llama sort_td_class_helper, y su único objetivo es
el de añadir una clase “sortup” si la columna que va a dibujar es la que se está usando para
ordenar la tabla, o la clase “sortdown” si se utiliza para ordenar en orden inverso. La única
utilidad de esta función es que, usando CSS, podremos indicar al usuario que función se está
empleando como campo de ordenación
El código no es precisamente interesante:
1 2 3 4 5 |
def sort_td_class_helper(param) result = 'class="sortup"' if @params[:sort] == param result = 'class="sortdown"' if @params[:sort] == param + "_reverse" return result end |
Luego tenemos el segundo helper, llamado sort_link_helper.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
def sort_link_helper(text, param) key = param key += "_reverse" if @params[:sort] == param options = { :url => {:action => 'list', :params => @params.merge({:sort => key, :page => nil})}, :update => 'table', :before => "Element.show('spinner')", :success => "Element.hide('spinner')" } html_options = { :title => "Sort by this field", :href => url_for( :action => 'list', :params => @params.merge({:sort => key, :page => nil})) } link_to_remote(text, options, html_options) end |
Este helper, de hecho, es muy similar al que hemos visto más arriba, pagination_links_remote, y
recoge dos argumentos:
textes tan sólo el texto a mostrar como cabecera de la columna y el enlace para ordenar *parames el nombre del parámetro de la petición asociado con la columna que luego el controlador usará para generar la consulta SQL
Las primeras dos líneas definen una nueva variable, llamada key, que recibe el valor del
argumento param, es decir, la clave de búsqueda. Se le añadirá el sufijo _reverse si el
parámetro ya está siendo para ordenar. Esto se utiliza para implementar la búsqueda tanto con
criterio ascendente como descendente: si el usuario pulsa un enlace de ordenación, se ordenará por
orden ascendente y si escoge otra vez el mismo enlace ordenará por criterio descendiente y así
sucesivamente. Viendo el código del controlador probablemente todo esto quedará más claro.
El resto del helper define las opciones para la llamada final a la función link_to_remote, que
son muy similares a los de pagination_links_remote
optionscomo ya sabemos se utiliza para el enlace Ajax y contiene la URL a invocar para generar la nueva tabla, eliddel elemento a actualizar y las accionesbeforeysuccesspara mostrar y ocultar el spinner.
html_optionsse utiliza para el enlace HTML. Genera el contenido del atributohrefcon la funciónurl_forde Rails.
Esto es lo que devuelve sort_link_helper para una llamada con las cadenas Quantity y qty como
texto y parámetro, respectivamente:
1 2 3 4 5 6 7 8 |
<a href="/item/list?sort=qty"
onclick="Element.show('spinner');
new Ajax.Updater('table', '/item/list?sort=qty', {
asynchronous:true,
evalScripts:true,
onSuccess:function(request){Element.hide('spinner')}});
return false;"
title="Sort by this field">Quantity</a> |
Cuerpo de la tabla
Por último, el cuerpo de la tabla simplemente muestra el contenido de la misma: un elemento en
cada fila. Lo único relevante aquí es el uso de la función cycle de Rails, que automática y
alternativamente añadirá la clase odd o even al estilo de nuestras filas de la tabla, lo que
puede resultar útil a la hora de mostrarlas de forma más vistosa.
¡Y eso es todo amigos!
Ya hemos visto todos los trozos de nuestra aplicación con más menos detalle. En teoría, uno podría ver los resultados arrancando del servidor WEBrick y entrando a la dirección
http://localhost:3000/item/list
Espero que este documento les haya sido útil. Repito que pueden enviarme cualquier comentario, etc. a la direccón de correo dada en la introducción.
Acerca de este documento.
Este documento se publica bajo licencia Creative Commons Attribution.
Agradecimientos a Nicolas St-Laurent y Rachel McConnel por sus observaciones.


Sorry, comments are closed for this article.