Skip to main content

4 Libertades

Tutorial de Rails

Autor y contacto

Ernest Henry Shackleton

shackleton@riseup.net

OBJETIVOS

  • Instalar Rails, crear una nueva aplicación Rails y conectar tu aplicación a una base de datos.

  • La estructura general de una aplicación Rails.

  • Los principios básicos del patrón MVC (Modelo, Vista, Controlador) y Diseño RESTful.

  • Cómo generar rápidamente las primeras piezas de una aplicación Rails.

¿Qué es Rails?

Rails es un framework de desarrollo de aplicaciones web escrito en el lenguaje de programación Ruby. Está diseñado para hacer que la programación de aplicaciones web sea más fácil, haciendo supuestos sobre lo que cada desarrollador necesita para comenzar. Te permite escribir menos código realizando más que muchos otros lenguajes y frameworks. Además, expertos desarrolladores en Rails reportan que hace que el desarrollo de aplicaciones web sea más divertido.

Rails es un software dogmático. Éste asume que existe una forma "mejor" de hacer las cosas, y está diseñado para fomentar esa forma - y en algunos casos para desalentar alternativas. Si aprendes "El Modo Rails" probablemente descubrirás un tremendo incremento en tu productividad. Si persistes trayendo viejos hábitos de otros lenguajes a tu desarrollo en Rails, e intentas usar patrones aprendidos en otros lugares, podrías tener una experiencia menos agradable.

La filosofía de Rails incluye dos mayores principios:

  • DRY - "Don't Repeat Yourself" - sugiere que escribir el mismo código una y otra vez es una mala práctica.

  • Convención sobre Configuración - significa que Rails hace algunas suposiciones sobre lo que tú quieres hacer y cómo vas a hacerlo, en lugar de requerir que especifiques cada pequeña cosa a través de un sin fin de archivos de configuración.

Entorno de desarrollo

Instalar Ruby desde los repositorios de Debian

Este es el método más sencillo para instalar Ruby en Debian. En el momento de escribir este artículo, la versión en los repositorios estándar de Debian es 2.5.5.

Ejecute los siguientes comandos como root o usuario con privilegios sudo para actualizar la lista de paquetes e instalar Ruby:

Ejecute los siguientes comandos como root o usuario con privilegios sudo para actualizar la lista de paquetes e instalar Ruby:

sudo apt update
sudo apt install ruby-full

Una vez completada la instalación, verifíquela imprimiendo la versión Ruby:

ruby --version

La salida se verá así:

ruby 2.5.5p157 (2019-03-15 revision 67260) [x86_64-linux-gnu]

Instalación de Ruby usando Rbenv

Rbenv es una utilidad de administración de versiones de Ruby liviana que le permite cambiar fácilmente las versiones de Ruby.

Usaremos el ruby-build complemento que amplía la Funcionalidad principal de Rbenv y le permite instalar cualquier versión de Ruby desde la fuente.

Comience instalando git y otras dependencias necesarias para construir Ruby desde la fuente:

sudo apt update

sudo apt install git curl libssl-dev libreadline-dev zlib1g-dev \
        autoconf bison build-essential libyaml-dev \
        libreadline-dev libncurses5-dev libffi-dev libgdbm-dev

Ejecute el siguiente comando para instalar los scripts rbenv y ruby-build:

curl -sL https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-installer | bash -

El script clonará los repositorios rbenv y ruby-build desde GitHub al ~/.rbenvdirectorio.

Para comenzar a usar rbenv, debe agregar $HOME/.rbenv/bin a su PATH.

Si está utilizando Bash:


echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(rbenv init -)"' >> ~/.bashrc
source ~/.bashrc

Si está utilizando Zsh:

echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.zshrc
echo 'eval "$(rbenv init -)"' >> ~/.zshrc
source ~/.zshrc

Ejecute el rbenv -v comando para asegurarse de que la instalación se haya realizado correctamente:

rbenv -v

Para obtener una lista de todas las versiones de Ruby que se pueden instalar con rbenv:

rbenv install -l

Por ejemplo, para instalar Ruby versión 2.7.0 y configurarla como versión predeterminada, debe escribir:

rbenv install 2.7.0
rbenv global 2.7.0

Verifique que Ruby esté instalado correctamente:

ruby -v

Instalar Rails utilizando Gem

Para instalar Rails, usa el comando proporcionado por RubyGems:

gem install rails

Para verificar que tu instalación esté correcta, deberías poder correr lo siguiente:

rails --version

Creando el primer proyecto

Rails viene con un número de generadores que están diseñados para hacer tu ciclo de desarrollo más fácil. Uno de esos es el generador de nuevas aplicaciones, el cual te proveerá con la estructura base de una aplicación Rails, así ya no tienes que escribirla por ti mismo.

Para usar este generador, abre la terminal, navega hacia el directorio en donde tienes permiso para crear archivos, y escribe:

rails new blog

Esto creará una aplicación Rails llamada Blog en un directorio llamado blog e instalará las dependencias (gemas) que están mencionadas en el Gemfile usando el comando bundle install.

TIP: Puedes ver todas las opciones que el generador de nuevas aplicaciones provee, ejecutando rails new -h.

Después de crear la aplicación, ingresa a su directorio para continuar trabajando directamente en la aplicación:

cd blog

El comando rails new blog que acabamos de ejecutar, creó una carpeta en tu directorio de trabajo llamado blog. El directorio blog tiene un número de archivos auto generados y carpetas que conforman la estructura de una aplicación Rails. La mayoría del trabajo en este tutorial se llevará a cabo en la carpeta app/, pero acá hay una explicación básica de las funciones de cada archivo y carpetas que Rails creó por defecto:

Estructura de Directorios de un proyecto en Rails

  • app/

Contiene los controllers, models, views, helpers, mailers y assets para tu aplicación. Te centrarás en esta carpeta por el resto de esta guía.

  • config/

Configura las reglas de ejecución de la aplicación, rutas, base de datos y más. Este tema es cubierto en mayor detalle en Configuring Rails Applications.

  • config.ru

Configuración Rack para servidores basados en Rack usados para iniciar la aplicación.

  • db/

Contiene el esquema actual de tu base de datos, así como las migraciones de la base de datos.

  • doc/

Documentación detallada de tu aplicación.

  • Gemfile

  • Gemfile.lock

Estos arhivos te permiten especificar qué dependencias de gemas son necesitadas para tu aplicación Rails. Estos archivos son usados por la gema Bundler, ver Sitio web de Bundler.

  • lib/

Módulos extendidos para tu aplicación.

  • log/

Archivos de Log de tu aplicación.

  • public/

La única carpeta vista por el mundo tal como es. Contiene los archivos estáticos y assets compilados.

  • Rakefile

Este archivo localiza y carga tareas que pueden ser ejecutadas desde la línea de comandos. La lista de tareas son definidas a través de los componentes de Rails. En vez de cambiar el Rakefile, deberías agregar tus propias tareas, añadiendo archivos al directorio lib/tasks de tu aplicación.

  • README.rdoc

Este es un breve manual de instrucciones para tu aplicación. Deberías editar este archivo para comunicar a otros lo que tu aplicación hace, cómo configurala y demás.

  • script/

Contiene el script de Rails que inicia tu aplicación y contiene otros scripts usados para deployar o correr tu aplicación.

  • test/

Pruebas unitarias, fixtures y otras pruebas. Éstos son cubiertos en Testing Rails Applications.

  • tmp/

Archivos temporales (como archivos de caché, pid y archivos de sesiones).

  • vendor/

Lugar para código de terceros. En una típica aplicación Rails, ésta incluye librerías y plugins.

Iniciando WEBrick

En realidad ya tienes una aplicación Rails funcional, Para verla, necesitas iniciar un servidor web en tu máquina de desarrollo. Puedes hacerlo ejecutando:

rails server

Esto lanzará WEBrick, un servidor web incorporado en Ruby por defecto. Para ver tu aplicación en acción, abre tu navegador preferido e ingresa a http://localhost:3000. Deberías ver la página de información por defecto de Rails.

Welcome Aboard screenshot

./img/RubyonRails.png

La página "Welcome Aboard" es la primera prueba para una nueva aplicación Rails: Ésta asegura que tienes el software configurado correctamente para servir una página. También puedes hacer click en el link About your application's enviroment para ver un resumen del entorno de tu aplicación.

TIP: Para detener el servidor web, presiona Ctrl+C en la ventana de la línea de comandos donde se está ejecutando. En modo de desarrollo, Rails generalmente no requiere reiniciar el servidor web; los cambios realizados serán tomados automáticamente por el servidor.

Hello World!

Para conseguir que Rails diga "Hola", necesitas crear como mínimo un controlador y una vista.

El propósito de un controlador es recibir peticiones específicas (requests) de la aplicación. El enrutamiento (Routing) decide qué controlador recibe qué petición. A menudo, hay más de una ruta para cada controlador, y diferentes rutas pueden ser servidas por diferentes acciones (actions). El propósito de cada acción es recolectar información para posteriormente brindarla a la vista.

El propósito de una vista es mostrar la información en un formato legible para los humanos. Una distinción importante que hacer es que es el controlador, y no la vista, donde la información es recolectada. La vista sólo debería mostrar la información. Por defecto, las plantillas de las vistas están escritas en un lenguaje llamado ERB (Embedded Ruby), el cual es convertido por cada ciclo de un request en Rails antes de ser enviado al usuario.

Para crear un nuevo controlador, necesitas correr el generador de controladores y decirle que quieres un controlador llamado "welcome" con una acción llamada "index", casi como lo siguiente:

rails generate controller welcome index

Rails creará una serie de archivos y añadirá una ruta por ti.

create app/controllers/welcome_controller.rb route get "welcome/index" invoke erb create app/views/welcome create app/views/welcome/index.html.erb invoke test_unit create test/functional/welcome_controller_test.rb invoke helper create app/helpers/welcome_helper.rb invoke test_unit create test/unit/helpers/welcome_helper_test.rb invoke assets invoke coffee create app/assets/javascripts/welcome.js.coffee invoke scss create app/assets/stylesheets/welcome.css.scss

Los más importantes de éstos son por supuesto el controlador, localizado en app/controllers/welcome_controller.rb y la vista, localizada en app/views/welcome/index.html.erb.

Abre el archivo app/views/welcome/index.html.erb en tu editor de texto y edítalo para que contenga sólo está línea de código:

<h1>Hello, Rails!</h1>

Estableciendo la base

La primera cosa que necesitaremos para crear un nuevo post, es crear un espacio para hacer eso en nuestra aplicación. Un buen lugar podría ser /posts/new. Si tratas de navegar a esa dirección ahora – visitando http://localhost:3000/posts/new – Rails te dará un error de ruteo:

A routing error, no route matches /posts/new

./img/norute.png

Esto es porque no en ningún lugar dentro de las rutas para la aplicación – definidas dentro de config/routes.rb – que defina esta ruta. Por defecto, Rails no tiene rutas configuradas, excepto por la ruta root que definimos anteriormente, es por esto que tú tendrás que definir tus rutas cuando las necesites.

Para hacer esto, vas a necesitar crear una ruta dentro de el archivo config/routes.rb, en una nueva línea entre el do y el end del método draw:

get "posts/new"

Esta ruta es super simple: define una nueva ruta que sólo responderá a peticiones GET, y que la ruta se encuentra en posts/new. Pero cómo sabe a donde ir sin haber usado la opción :to? Bueno, Rails usa una convención sensible aquí: Rails asumirá que lo que tú quieres es que esta ruta vaya a la acción new dentro de el controlador posts.

Con esta ruta definida, tus peticiones ahora se pueden hacer a través de /posts/new en la aplicación. Navega hacia: http://localhost:3000/posts/new. Verás otro error de ruteo:

Another routing error, uninitialized constant PostsController

./img/postcontroller.png

Este error está pasando porque esta ruta necesita un controlador que esté definido. La ruta está tratando de encontrar el controlador para servir la petición, pero con el controlador no definido, simplemente no lo puede hacer. La solución para este caso en particular es simple: crea un controlador llamado PostsController. Puedes hacerlo ejecutando el siguiente comando:

rails g controller posts

Si abres el nuevo archivo generado app/controllers/posts_controller.rb, verás un controlador vacío:

class PostsController < ApplicationController
end

Un controlador es simplemente una clase que es definida para heredar de ApplicationController. Dentro de ésta, vas a definir los métodos que se convertirán en acciones para este controlador. Estas acciones van a ejecutar operaciones CRUD sobre los posts dentro de nuestro sistema.

Si refrescas la dirección http://localhost:3000/posts/new ahora, vas a tener un nuevo error:

Unknown action new for PostsController!

./img/new.png

Este error indica que Rails no puede encontrar la acción new dentro de PostsController que recién has generado. Esto es porque cuando los controladores son generados en Rails están vacíos por defecto, a menos que le digas que acciones quieres durante el proceso de generación.

Para definir manualmente una acción dentro de un controlador, todo lo que necesitas es definir un nuevo método dentro del controlador. Abre app/controllers/posts_controller.rb y dentro de la clase PostsController, define el método new así:

def new
end

Con el método new definido en PostsController, si tú refrescas http://localhost:3000/posts/new verás otro error:

Template is missing for posts/new

./img/template.png

Estás obteniendo este error ahora porque Rails espera que las acciones planas como ésta tengan vistas asociadas a ellas para mostrar la información. Con ninguna vista disponible, Rails mostrará un error.

En la imagen de arriba, la línea de abajo fue eliminada. Vamos a ver como se ve el error completo:

Missing template posts/new, application/new with {:locale=>[:en], :formats=>[:html], :handlers=>[:erb, :builder, :coffee]}. Searched in: * "/path/to/blog/app/views"

Eso es casi un montón de texto! Revisemos rápidamente para entender cuál es la función de cada parte.

La primera parte identifica que plantilla está faltando. En este caso, es la plantila posts/new. Rails primero buscará esta plantilla, si no la encuentra, luego tratará de cargar la plantilla llamada application/new. Busca una aquí porque el PostsController hereda de ApplicationController.

La siguiente parte del mensaje contiene un map (hash en inglés). La clave :locale en este map simplemente indica el lenguaje que debe contener la plantilla solicitada. Por defecto, está configurado en Inglés – o "en" –. La siguiente clave, :formats especifica el formato de la plantilla que será servido en la respuesta. El formato por defecto es :html, es por eso que Rails busca una plantilla HTML. La clave final, :handlers, nos dice que manejador de plantilla (o template handler en inglés) puede ser usada para producir nuestra plantilla. :erb es el manejador más usado para plantillas HTML, :builder es usado para plantillas XML, y :coffee usa CoffeeScript para construir plantillas JavaScript.

La más simple plantilla que funcionaría en nuestro caso sería una localizada en app/views/posts/new.html.erb. La extensión de este nombre de archivo es importante: la primera extensión define el formato de la plantilla, y la segunda extensión el manejador de plantilla que será usado. Rails está tratando de encontrar una plantilla llamada posts/new en app/views para la aplicación. El formato para esta plantilla puede sólo ser html y el manejador debería ser erb, builder o coffee. Ya que tú quieres crear un nuevo formulario HTML, tendrás que usar el lenguaje ERB. Por lo tanto el archivo debería ser llamado posts/new.html.erb y necesita ser localizado dentro de la carpeta app/views de la aplicación.

Ahora ve y crea un nuevo archivo en app/views/posts/new.html.erb y escribe el siguiente contenido en él:

<h1>New Post</h1>

Cuando refresques http://localhost:3000/posts/new, verás que la página tiene un título. La ruta, el controlador, la acción y la vista ahora están funcionando de manera armoniosa! Es hora de crear el formulario para un nuevo post.

./img/newpost.png

El primer formulario

Para crear una formulario con esta plantilla usarás un constructor de formularios (o form builder en inglés). El form builder primario de Rails está provisto por un método de ayuda llamado form_for. Para usar este método agrega este código a app/views/posts/new.html.erb:

<%= form_for :post do |f| %>
  <p>
    <%= f.label :title %><br>
    <%= f.text_field :title %>
  </p>

  <p>
    <%= f.label :text %><br>
    <%= f.text_area :text %>
  </p>

  <p>
    <%= f.submit %>
  </p>
<% end  %>

Si refrescas la página, ahora verás el mismo formulario que en el ejemplo. Construir un formulario en Rails es así de sencillo!

Cuando llamas a form_for, le pasas un objeto identificador para este formulario, que en este caso, es el símbolo :post. Esto le indica al método form_for para quién es este formulario. Dentro del bloque para este método, el objeto FormBuilder - representado por f - es usado para construir dos etiquetas y dos cajas de texto, una de cada una para el título y el texto del post. Finalmente, la llamada al método submit en el objeto f creará un botón de submit en el formulario.

Sin embargo, existe un problema con este formulario. Si inspeccionas el código HTML generado, al ver la fuente de la página, verás que el atributo action de el formulario apunta a /posts/new. Ésto es un problema debido a que esta ruta va a la misma página donde te encuentras en este momento cuando en realidad la ruta debería sólo ser usada para mostrar el formulario para un nuevo post.

El formulario necesita usar un URL distinto para poder dirigirse hacia algún otro lugar. Ésto puede hacerse, de manera bastante sencilla, con la opción :url de form_for. En Rails la acción que se usa típicamente para nuevas entradas es llamada "create" (crear en español) por lo que el formulario debería ser apuntado hacia esa acción.

Edita la linea de form_for siguiente: app/views/posts/new.html.erb para que se vea así:

<%= form_for :post, :url => { :action => :create } do |f| %>

En este ejemplo un objeto Hash es pasado a la opción :url. Lo que Rails hará con esto es apuntar el formulario a la acción create del controlador actual, el PostsController. y enviará una petición de POST hacia esa ruta. Para que esto funcione necesitarás añadir una ruta a config/routes.rb, justo debajo de la de "post/new":

post "posts" => "posts#create"

Al usar el método post en vez de get, Rails definirá una ruta que solo responderá a metodos POST. El método POST es el método típico utilizado por todos los formularios a lo largo de la red.

./img/form.png

Con el formulario y su ruta asociada definidas, serás capaz de llenar el formulario y dar click en el botón de submit para empezar el proceso de creación de un nuevo post, así que ve y haz eso. Cuando des click al botón y envíes el formulario, te encontrarás con un error familiar.

Unknown action create for PostsController

./img/create.png

Ahora necesitas crear la acción create en PostsController para que funcione.

Creando artículos

Para hacer que "Unknown action" desaparezca, puedes definir una acción create dentro de la clase PostsController en app/controllers/posts_controller.rb, debajo de la acción new:

class PostsController < ApplicationController
  def new
  end

  def create
  end
end

Si reenvías el formulario ahora, verás otro error común: falta una plantilla. Está bien, vamos a ignorar eso por ahora. Lo que la acción create debe hacer es salvar el nuevo artículo en la base de datos.

Cuando un formulario es enviado, los campos del formulario son enviados a Rails como parámetros. Estos parámetros pueden ser referenciados dentro de las acciones del controlador, generalmente para realizar una tarea determinada. Para ver qué hacen estos parámetros, cambiar la acción create a esto:

def create
  render :text => params[:post].inspect
end

El método render toma un simple hash con la clave text y el valor de params[:post].inspect. El método params es el objeto que representa a los parámetros (o campos) que vienen desde el formulario. El método params retorna un objeto HashWithIndifferentAccess, que te permite acceder a las claves del hash usando cadenas o símbolos. En esta situación los únicos parámetros que importan son los que vienen del formulario.

Si reenvias el formulario una vez más, ya no obtendrás el error de plantilla faltante, en su lugar verás algo como lo que sigue:

{"title"=>"First post!", "text"=>"This is my first post."}

Esta acción muestra ahora los parámetros para el artículo que están llegando desde el formulario. Sin embargo, esto no es realmente útil. Sí, puedes ver los parámetros, pero no hay nada en particular que se está haciendo con ellos.

Creando el modelo post

Los modelos en Rails usan un nombre en singular, y sus correspondientes tablas de base de datos usan un nombre en plural. Rails provee un generador para crear modelos, el cual la mayoría de desarrolladores en Rails tienden a usar para crear nuevos modelos:

rails generate model Post title:string text:text

Con ese comando le decimos a Rails que nosotros queremos un modelo Post, junto con un atributo title de tipo string, y un atributo texto de tipo text. Esos atributos son automáticamente añadidos a la tabla posts en la base de datos y mapeados al modelo Post.

Rails respondió con la creación de un montón de archivos. Por ahora, nosotros sólo estamos interesados en app/models/post.rb y db/migrate/20120419084633_create_posts.rb (en tu caso puede ser un poco diferente). Este último es responsable de crear la estructura de la base de datos, que es lo que revisaremos luego.

TIP: Active Record es lo suficientemente inteligente para asignar automáticamente el nombre de las columnas a atributos del modelo, lo que significa que tú no tienes que declarar los atributos dentro de los modelos de Rails, ya que será realizado automáticamente por Active Record.

Salvando datos en el controlador

En posts_controller necesitamos cambiar la acción create para usar el nuevo modelo Post y guardar los datos en la base de datos. Abre el archivo y cambia la acción create que debe verse de la siguiente manera:

def create
  @post = Post.new(params[:post])

  @post.save
  redirect_to :action => :show, :id => @post.id
end

Esto es lo que sucede: cada modelo Rails puede ser inicializado con sus respectivos atributos, los cuales son automáticamente asignados a sus respectivas columnas de base de datos. En la primera línea hacemos justamente eso (recuerda que params[:post] contiene los atributos en los cuales estamos interesados). Luego, @post.save es responsable de guardar el modelo en la base de datos. Finalmente, direccionamos al usuario a la acción show que definiremos más tarde.

TIP: Como veremos más tarde, @post.save devuelve un indicador que indica si el modelo fue guardado o no.

Mostrando artículos

Si envías el formulario de nuevo, Rails se quejará de que no has definido la acción show. Esto no es muy útil así que vamos a agregar la acción show antes de continuar. Abre el archivo config/routes.rb y agrega la siguiente ruta:

get "posts/:id" => "posts#show"

La sintaxis especial :id le dice a Rails que la ruta espera un parámetro :id, el cual en nuestro caso será el id del artículo. Nota que en esta ocasión debemos indicar la asignación real posts#show porque de otra manera Rails no sabrá qué acción debe realizar.

Como dijimos anteriormente, necesitamos agregar la acción show en el posts_controller y su respectiva vista.

def show
  @post = Post.find(params[:id])
end

Un par de cosas a tener en cuenta. Usamos Post.find para encontrar el artículo en el cual estamos interesados. También usamos una variable de instancia (con el prefijo @) para contener una referencia al objeto post. Hacemos eso porque Rails pasará todas las variables de instancia a la vista.

Ahora, crea un nuevo archivo app/view/posts/show.html.erb con el siguiente contenido:


<p>
  <strong>Title:</strong>
  <%= @post.title %>
</p>

<p>
  <strong>Text:</strong>
  <%= @post.text %>
</p>

Finalmente, si vas a http://localhost:3000/posts/new serás capaz de crear un artículo. ¡Inténtalo!

Show action for posts

Listando todos los artículos

Aún necesitamos una forma de listar todos los artículos, de manera que vamos a hacerlo. Como de costumbre, vamos a necesitar una ruta ubicada dentro de config/routes.rb:

get "posts" => "posts#index"

Y una acción para esa ruta dentro de PostsController en el archivo app/controllers/posts_controller.rb:

def index
  @posts = Post.all
end

Y finalmente una vista para esta acción, ubicada en app/views/posts/index.html.erb:

<h1>Listing posts</h1>

<table>
  <tr>
    <th>Title</th>
    <th>Text</th>
  </tr>

  <% @posts.each  do |post| %>
    <tr>
      <td><%= post.title %></td>
      <td><%= post.text %></td>
    </tr>
  <% end %>
</table>

Ahora si vamos a http://localhost:3000/posts veremos una lista con todos los artículos que has creado.

Agregando enlaces

Hasta el momento puedes crear, mostrar y listar artículos. Ahora vamos a agregar algunos enlaces para navegar a través de las páginas.

Abre app/views/welcome/index.html.erb y modifîcalo como se indica a continuación:

<h1>Hello, Rails!</h1>
<%= link_to "My Blog", :controller => "posts" %>

El método link_to es uno de los asistentes de la vista incorporados en Rails. Este método crea el hipervínculo mostrando un texto e indicando a dónde irá, en este caso a la ubicación posts.

Vamos a agregar enlaces a las otras vistas, empezando por agregar a "New Post" el enlace a app/views/posts/index.html.erb, ubicándola encima de la etiqueta <table>:

<%= link_to 'New post', :action => :new %>

Este enlace te permitirá abrir el formulario para crear un nuevo artículo. También deberías agregar un enlace a esta plantilla – app/views/posts/new.html.erb – para regresar a la acción index. Para hacer eso agrega el enlace debajo del formulario en esta plantilla:

<%= form_for :post do |f| %>
  ...
<% end %>

<%= link_to 'Back', :action => :index %>

Finalmente, agrega otro enlace a la plantilla app/views/posts/show.html.erb para regresar a la acción index, de manera que la persona que está viendo un artículo pueda regresar y ver la lista completa nuevamente.

<p>
  <strong>Title:</strong>
  <%= @post.title %>
</p>

<p>
  <strong>Text:</strong>
  <%= @post.text %>
</p>

<%= link_to 'Back', :action => :index %>

TIP: Si deseas crear un enlace a una acción dentro del mismo controlador no necesitas especificar la opción :controller, Rails usará el actual controlador por omisión.

TIP: En modo de desarrollo (en la cual estás trabajando por omisión), Rails recarga los archivos de tu aplicación en cada petición del navegador, por lo que no hay necesidad de reiniciar el servidor web cuando un cambio es realizado.

Permitiendo la actualización de campos

El archivo del modelo app/models/post.rb es tan simple como:

class Post < ActiveRecord::Base
end

No hay mucho en ese archivo - pero nota que la clase Post hereda de ActiveRecord::Base. Active Record provee una gran cantidad de funcionalidad a los modelos de Rails de forma sencilla, incluyendo las operaciones básicas CRUD (del inglés Create, Read, Update, Destroy) de base de datos, validación de datos así como el soporte a búsquedas sofisticadas y la habilidad de relacionar múltiples modelos entre sí.

Rails incluye métodos que ayudaran a asegurar los campos de tu modelo. Abre el archivo app/models/post.rb y complétalo de esta manera:

class Post < ActiveRecord::Base
  attr_accessible :text, :title
end

Este cambio asegura que todos los cambios realizados a través de formularios HTML puedan editar los campos text y title. No será posible definir la edición de otro campo diferente a través de formularios. Por supuesto aún será posible definirlo usando el método field=. La accesibilidad de los atributos y el problema de la asignación masiva es cubierto en detalle en Security guide.

Agregando validaciones

Rails incluye métodos para ayudarte a validar los datos que son enviados a los modelos. Abre el archivo app/models/post.rb y edítalo de la siguiente manera:

class Post < ActiveRecord::Base
  attr_accessible :text, :title

  validates :title, :presence => true,
                    :length => { :minimum => 5 }
end

Estos cambios asegurarán que todos los artículos tengan un título que sea al menos de cinco caracteres de longitud. Rails puede validar una variedad de condiciones en un modelo, incluyendo la presencia o la unicidad de columnas, sus formatos y la existencia de objetos asociados. Las validaciones están cubiertas en detalle en Active Record Validations and Callbacks.

Con las validaciones agregadas, cuando llames a @post.save en un artículo inválido retornará false. Si abres el archivo app/controllers/posts_controller.rb otra vez, notarás que no verificamos el resultado de la llamada @post.save dentro de la acción create. Si @post.save falla en esta situación, necesitamos mostrar el formulario de regreso al usuario. Para hacer esto, cambiar las acciones new y create dentro de app/controllers/posts_controller.rb a esto:

def new
  @post = Post.new
end

def create
  @post = Post.new(params[:post])

  if @post.save
    redirect_to :action => :show, :id => @post.id
  else
    render 'new'
  end
end

La acción new está ahora creando una nueva instancia llamada @post, y verás el motivo de ello en un momento.

Notas que dentro de la acción create usamos render en lugar de redirect_to cuando acción save retorna false. El método render es usado de manera que el objeto @post es regresado a la plantilla new cuando es interpretado. Esta interpretación se hace dentro de la misma solicitud que hace el envío del formulario, mientras que redirect_to le dirá al navegador para emitir una nueva solicitud.

Si recargas http://localhost:3000/posts/new y tratas de guardar un artículo sin título, Rails enviará de regreso un formulario pero que no es muy útil. Necesitas decirle al usuario que algo está mal. Para hacer esto hay que modificar el archivo app/views/posts/new.html.erb para verificar los mensajes de error:

<%= form_for :post, :url => { :action => :create } do |f| %>
  <% if @post.errors.any? %>
  <div id="errorExplanation">
    <h2><%= pluralize(@post.errors.count, "error") %> prohibited
      this post from being saved:</h2>
    <ul>
    <% @post.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end  %>
    </ul>
  </div>
  <% end %>
  <p>
    <%= f.label :title %><br>
    <%= f.text_field :title %>
  </p>

  <p>
    <%= f.label :text %><br>
    <%= f.text_area :text %>
  </p>

  <p>
    <%= f.submit %>
  </p>
<% end %>

<%= link_to 'Back', :action => :index %>

Veamos qué está pasando. Verificamos si hay errores con @post.errors.any? y en caso que los haya mostramos una lista de todos los errores con @post.errors.full_messages.

pluralize es un asistente de Rails que toma un número, un texto y sus argumentos. Si el número es mayor de 1, el texto será automáticamente pluralizado

La razón de por qué agregamos @post = Post.new en posts_controller es que de otra manera @post será nil en tu vista y llamar a @post.errors.any? mostrará un error.

CONSEJO: Rails automaticamente ajustará los campos que contienen error en un div dentro de la clase field_with_errors. Puedes definir una regla css para destacarlos.

Ahora verás un bonito mensaje de error cuando intentes guardar un artículo sin título en new post form (http://localhost:3000/posts/new).

Actualizando artículos

Hemos cubierto la parte "CR" del acrónimo CRUD. Ahora nos enfocaremos en la parte "U", actualización de artículos.

El primer paso será agregar la acción edit al posts_controller.

Empecemos agregando una ruta a config/routes.rb:

get "posts/:id/edit" => "posts#edit"

Y luego agregar la acción al controlador:

def edit
  @post = Post.find(params[:id])
end

La vista contendrá un formulario similar al que usamos cuando creamos nuevos artículos. Crea un archivo llamado app/views/posts/edit.html.erb que contenga lo siguiente:

<h1>Editing post</h1>

<%= form_for :post, :url => { :action => :update, :id => @post.id },
:method => :put do |f| %>
  <% if @post.errors.any? %>
  <div id="errorExplanation">
    <h2><%= pluralize(@post.errors.count, "error") %> prohibited
      this post from being saved:</h2>
    <ul>
    <% @post.errors.full_messages.each  do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
  <% end %>
  <p>
    <%= f.label :title %><br>
    <%= f.text_field :title %>
  </p>

  <p>
    <%= f.label :text %><br>
    <%= f.text_area :text %>
  </p>

  <p>
    <%= f.submit %>
  </p>
<% end  %>

<%= link_to 'Back', :action => :index %>

Esta vez indicamos al formulario la acción update, la cual no está definido aún pero pronto lo estará.

La opción :method => :put le dice a Rails que queremos que este formulario sea enviado a través del método HTTP PUT, el cual es el método que tú esperas que se use para actualizar recursos de acuerdo al protocolo REST.

CONSEJO: Por omisión los formularios construidos con el asistente +form_for_ son enviados a través de POST.

A continuación, necesitamos agregar la acción update. El archivo config/routes.rb necesitará una línea más:

put "posts/:id" => "posts#update"

Y luego crear la acción update en app/controllers/posts_controller.rb:

def update
  @post = Post.find(params[:id])

  if @post.update_attributes(params[:post])
    redirect_to :action => :show, :id => @post.id
  else
    render 'edit'
  end
end

El nuevo método update_attributes, es usado cuando deseas actualizar un registro que ya existe, y acepta un hash conteniendo los atributos que deseas actualizar. Como hicimos anteriormente, si hay un error actualizando el artículo queremos mostrar el formulario de regreso al usuario.

CONSEJO: no necesitas enviar todos los atributos a update_attributes. Por ejemplo, si llamas a @post.update_attributes(:title => 'A new title') Rails solo actualizará el atributo title sin tocar los otros atributos.

Finalmente, queremos mostrar un enlace a la acción edit en la lista de todos los artículos, de esta manera hacemos que ahora en app/views/posts/index.html.erb aparezca un nuevo enlace adicional a la acción show:

<table>
  <tr>
    <th>Title</th>
    <th>Text</th>
    <th></th>
    <th></th>
  </tr>

<% @posts.each do |post| %>
  <tr>
    <td><%= post.title %></td>
    <td><%= post.text %></td>
    <td><%= link_to 'Show', :action => :show, :id => post.id %></td>
    <td><%= link_to 'Edit', :action => :edit, :id => post.id %></td>
  </tr>
<% end %>
</table>

Y también la agregaremos en la plantilla app/views/posts/show.html.erb de manera que haya un enlace "Edit" en la página del artículo. Agregar esto al final de tu plantilla:

...

<%= link_to 'Back', :action => :index %>
| <%= link_to 'Edit', :action => :edit, :id => @post.id %>

Y así es cómo nuestra aplicación se ve hasta el momento

Index action with edit link

Usando parciales para eliminar la duplicidad en las vistas

partials son lo que Rails usa para remover la duplicidad en las vistas. Aquí hay un ejemplo:

# app/views/user/show.html.erb

<h1><%= @user.name %></h1>

<%= render 'user_details' %>

# app/views/user/_user_details.html.erb

<%= @user.location %>

<%= @user.about_me %>

La plantilla users/show automáticamente incluirá el contenido de la plantilla users/_user_details. Nota que los parciales tienen como prefijo un subrayado, de manera de no confundirlas con vistas regulares. Sin embargo, no incluyas el subrayado cuando las incluyas dentro del método helper.

CONSEJO: Puedes leer más acerca de parciales en la guía Layouts and Rendering in Rails.

Nuestra acción edit parece muy similar a la acción new, en efecto ellos comparten el mismo código para mostrar el formulario. Vamos a limpiarlo usando un parcial.

Crea un nuevo archivo app/views/posts/_form.html.erb con el siguiente contenido:

<%= form_for @post do |f| %>
  <% if @post.errors.any? %>
  <div id="errorExplanation">
    <h2><%= pluralize(@post.errors.count, "error") %> prohibited
      this post from being saved:</h2>
    <ul>
    <% @post.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end  %>
    </ul>
  </div>
  <% end %>
  <p>
    <%= f.label :title %><br>
    <%= f.text_field :title %>
  </p>

  <p>
    <%= f.label :text %><br>
    <%= f.text_area :text %>
  </p>

  <p>
    <%= f.submit %>
  </p>
<% end  %>

Todo, excepto la declaración form_for permanece igual. Como form_for puede averiguar la action y los atributos del method correcto cuando construye el formulario será explicado en unos momentos. Por ahora, vamos a actualizar la vista app/views/posts/new.html.erb para usar el nuevo parcial, reescribiendo completamente:

<h1>New post</h1>

<%= render 'form' %>

<%= link_to 'Back', :action => :index %>

Luego hacer lo mismo a la vista app/views/posts/edit.html.erb:

<h1>Edit post</h1>

<%= render 'form' %>

<%= link_to 'Back', :action => :index %>

Ve con tu navegador a http://localhost:3000/posts/new y trata de crear un nuevo artículo. Todo funciona todavía, ahora tratemos de editar el artículo y recibiremos el siguiente mensaje de error:

Undefined method post_path

Para entender este error necesitamos conocer cómo funciona form_for. Cuando pasas un objeto a form_for y no especificas la opción de :url, Rails trata de adivinar las opciones de action y method verificando si el objeto pasado es un nuevo registro o no. Rails sigue la convención REST, de esta manera si se está creando un nuevo objeto Post buscará por una ruta llamada post_path y para actualizar un objeto Post buscará por una ruta llamada post_path y le pasará el objeto actual. Similarmente, Rails conoce que debe crear nuevos objetos a través de POST y actualizarlos a través de PUT.

Si ejecutas rake routes desde la consola verás que ya tenemos una ruta posts_path, la cual fue creada automáticamente por Rails cuando se definió la ruta por la acción index. Sin embargo, no tenemos aún un post_path, la cual es la razón por la que recibimos el error anterior.

 rake routes

    posts GET  /posts(.:format)            posts#index
posts_new GET  /posts/new(.:format)        posts#new
          POST /posts(.:format)            posts#create
          GET  /posts/:id(.:format)        posts#show
          GET  /posts/:id/edit(.:format)   posts#edit
          PUT  /posts/:id(.:format)        posts#update
     root      /                           welcome#index

Para arreglar eso, abrimos el archivo config/routes.rb y modificamos la línea get "posts/:id" a esto:

get "posts/:id" => "posts#show", :as => :post

La opción :as le dice al método get que queremos hacer que los asistentes de ruteo llamados post_url y post_path estén disponibles para nuestra aplicación. Estos son los métodos que form_for necesita cuando estamos editando un artículo y que ahora están disponibles para actualizar los artículos.

NOTA: La opción :as esta disponible también en los métodos de ruteo post, put, delete y match.

Eliminando artículos

Ahora estamos listos para cubrir la parte "D" del acrónimo CRUD: eliminar artículos de la base de datos. Siguiendo la convención REST, vamos a agregar una ruta para la eliminación de artículos a config/routes.rb:

delete "posts/:id" => "posts#destroy"

El método de enrutamiento delete debe ser usado para métodos que destruyen recursos. Si se deja como un típico comando de ruteo get, es posible que se puedan enviar URLs malintencionadas como estas:

<a href='http://yoursite.com/posts/1/destroy'>look at this cat!</a>

Nosotros usamos el método delete para destruir recursos y este ruteo está enlazado con la acción destroy dentro de app/controllers/posts_controller.rb, la cual no existe aún pero que se muestra a continuación:

def destroy
  @post = Post.find(params[:id])
  @post.destroy

  redirect_to :action => :index
end

Puedes llamar a destroy en objetos Active Record cuando desees eliminarlos de la base de datos. Hay que tener en cuenta que no necesitas agregar una vista para esta acción ya que estamos siendo redireccionados a la acción index.

Finalmente, agregamos un enlace a la plantilla de la acción index (app/views/posts/index.html.erb) para completar todo.

<h1>Listing Posts</h1>
<table>
  <tr>
    <th>Title</th>
    <th>Text</th>
    <th></th>
    <th></th>
    <th></th>
  </tr>

<% @posts.each do |post| %>
  <tr>
    <td><%= post.title %></td>
    <td><%= post.text %></td>
    <td><%= link_to 'Show', :action => :show, :id => post.id %></td>
    <td><%= link_to 'Edit', :action => :edit, :id => post.id %></td>
    <td><%= link_to 'Destroy', { :action => :destroy, :id => post.id }, :method => :delete, :data => { :confirm => 'Are you sure?' } %></td>
  </tr>
<% end %>
</table>

Usaremos aquí link_to de diferente manera. Empaquetaremos los atributos :action y :id en un hash de manera que podamos pasar las dos claves como uno en un solo argumento y finalmente las otras dos claves como otro argumento. Las opciones :method y :'data-confirm' son usadas como atributos HTML de manera de que cuando se hace clic en el enlace Rails mostrará un diálogo de confirmación al usuario y luego direccionará al enlace con el método delete. Esto es realizado a través de un archivo JavaScript jquery_ujs el cual fue automáticamente incluido dentro del diseño de tu aplicación (app/views/layouts/application.html.erb) cuando la aplicación fue generada. Sin este archivo el diálogo de confirmación no aparecerá.

Confirm Dialog

Felicitaciones, ahora puedes crear, mostrar, enumerar, actualizar y eliminar artículos. En la siguiente sección veremos cómo Rails puede ayudarnos en la creación de aplicaciones REST, y cómo podemos refactorizar nuestro Blog para tomar ventaja de ello.

Profundizando en REST

Ya hemos cubierto todas las acciones CRUD de una aplicación REST. Lo hicimos declarando rutas separadas con los verbos adecuados en config/routes.rb. Así es como se ve el archivo hasta ahora:

get "posts" => "posts#index"
get "posts/new"
post "posts" => "posts#create"
get "posts/:id" => "posts#show", :as => :post
get "posts/:id/edit" => "posts#edit"
put "posts/:id" => "posts#update"
delete "posts/:id" => "posts#destroy"

Eso es un montón de código para cubrir un único recurso. Afortunadamente, Rails proporciona un método llamado resources el cual puede ser usado para declarar un recurso REST estándar. Así es como el archivo config/routes.rb se ve luego de utilizar resources:

Blog::Application.routes.draw do

  resources :posts

  root :to => "welcome#index"
end

Si ejecutas rake routes, podrás ver que todas las rutas que declaramos anteriormente aún están disponibles:

rake routes
    posts GET    /posts(.:format)          posts#index
          POST   /posts(.:format)          posts#create
 new_post GET    /posts/new(.:format)      posts#new
edit_post GET    /posts/:id/edit(.:format) posts#edit
     post GET    /posts/:id(.:format)      posts#show
          PUT    /posts/:id(.:format)      posts#update
          DELETE /posts/:id(.:format)      posts#destroy
     root        /                         welcome#index

Además, si vas a través de las acciones de creación, actualización y eliminación de posts, la aplicación sigue funcionando como antes.

TIP: En general, Rails fomenta el uso de recursos (resources) en lugar de declarar las rutas manualmente. Se hizo solamente en esta guía como un ejercicio de aprendizaje. Para más información acerca de routing, mira Rails Routing from the Outside In.

Generando un Modelo

Vamos a usar el mismo generador que usamos antes al crear el modelo Post. Esta vez crearemos el modelo Comment para los comentarios del artículo. Ejecuta esto en tu terminal:

rails generate model Comment commenter:string body:text post:references

Archivos y Proposito

db/migrate/20100207235629_create_comments.rb

Migración para crear la tabla de comentarios en tu base de datos (en tu caso con un timestamp diferente).

app/models/comment.rb

El modelo Comment.

test/unit/comment_test.rb

Pruebas unitarias para el modelo de comentarios.

test/fixtures/comments.yml

Muestras de comentarios para usar de pruebas.

Primero, miremos comment.rb:

class Comment < ActiveRecord::Base
  belongs_to :post
  attr_accessible :body, :commenter
end

Ésto es muy similar al modelo post.rb que vimos antes. La diferencia es la línea belongs_to :post, que establece una asociación de Active Record. Aprenderás un poco más sobre asociaciones en la siguiente sección de esta guía.

Además del modelo, Rails hizo la migración para crear la tabla correspondiente en la base de datos:

class CreateComments < ActiveRecord::Migration
  def change
    create_table :comments do |t|
      t.string :commenter
      t.text :body
      t.references :post

      t.timestamps
    end

    add_index :comments, :post_id
  end
end

La línea t.references establece una columna foreign key para la asociación entre los dos modelos. La línea add_index establece un index para esta columna de la asociación. Corre la migración:

rake db:migrate

Rails es suficientemente listo para sólo ejecutar las migraciones que no se han ejecutado todavía en la base de datos actual, así que en este caso sólo verás:

==  CreateComments: migrating =================================================
-- create_table(:comments)
   -> 0.0008s
-- add_index(:comments, :post_id)
   -> 0.0003s
==  CreateComments: migrated (0.0012s) ========================================

Asociando Modelos

Las asociaciones de Active Record te permiten declarar fácilmente la relación entre dos modelos. En el caso de los comentarios y artículos, puedes escribir la relación de esta manera:

  • Cada comentario pertence a un artículo.

  • Un artículo puede tener muchos comentarios.

De hecho, ésto es muy cercano a la sintáxis que usa Rails para declarar esta asociación. Ya has visto la línea de código en el modelo Comment que hace que cada comentario pertenezca a un artículo:

class Comment < ActiveRecord::Base
  belongs_to :post
end

Vas a tener que editar el archivo post.rb para agregar el otro lado de la asociación:

class Post < ActiveRecord::Base
  validates :title, :presence => true,
                    :length => { :minimum => 5 }

  has_many :comments
end

Estas dos declaraciones permiten bastante comportamiento automatizado. Por ejemplo, si tienes una variable de instancia @post conteniendo un artículo, puedes obtener todos los comentarios que pertenecen a ese artículo como un arreglo usando @post.comments.

SUGERENCIA: Para más información sobre las asociaciones de Active Record, revisa la guía Active Record Associations (en inglés).

Agregando una Ruta para Comentarios

Así como con el controlador welcome, vamos a necesitar agregar una ruta para que Rails sepa donde queremos navegar para ver comments. Abre el archivo config/routes.rb nuevamente, y edítalo de la siguiente manera:

resources :posts do
  resources :comments
end

Ésto crea comments como un recurso anidado dentro de posts. Esta es otra parte de capturar la relación jerárquica que existe entre artículos y comentarios.

SUGERENCIA: Para más información sobre el ruteo, mira la guía Rails Routing from the Outside In (en inglés).

Generando un Controlador

Con el modelo terminado, necesitamos crearle un controlador. Nuevamente, usaremos el mismo generador que antes:

rails generate controller Comments

Esto crea seis diferentes archivos y una carpeta vacía:

Archivo/Carpeta Propósito
app/controllers/comments_controller.rb El controlador de Comments.
app/views/comments/ Donde se guardan las vistas del controlador.
test/functional/comments_controller_test.rb Las pruebas funcionales del controlador.
app/helpers/comments_helper.rb El helper de la vista.
test/unit/helpers/comments_helper_test.rb Las pruebas unitarias para el helper.
app/assets/javascripts/comment.js.coffee CoffeeScript para el controlador.
app/assets/stylesheets/comment.css.scss Hojas de estilo para el controlador.

Como con cualquier blog, nuestros lectores crearán sus comentarios directamente después de leer el artículo, y una vez que agregaron su comentario, serán enviados de vuelta al artículo para ver su comentario ahora listado. Debido a esto, nuestro CommentsController está ahí para proveer un método para crear comentarios y eliminar spam cuando aparezca.

Así que primero, vamos a armar la plantilla show del artículo (/app/views/posts/show.html.erb) para que nos permita hacer comentarios:

<p>
  <strong>Title:</strong>
  <%= @post.title %>
</p>

<p>
  <strong>Text:</strong>
  <%= @post.text %>
</p>

<h2>Add a comment:</h2>
<%= form_for([@post, @post.comments.build]) do |f| %>
  <p>
    <%= f.label :commenter %><br />
    <%= f.text_field :commenter %>
  </p>
  <p>
    <%= f.label :body %><br />
    <%= f.text_area :body %>
  </p>
  <p>
    <%= f.submit %>
  </p>
<% end  %>

<%= link_to 'Edit Post', edit_post_path(@post) %> |
<%= link_to 'Back to Posts', posts_path %>

Ésto agrega un formulario en la vista show del artículo que crea un comentario al llamar a la acción create en el CommentsController. Al llamar form_for se necesita pasar un arreglo, que construirá una ruta anidada, siguiendo el esquema /posts/1/comments.

Armemos la acción create:

class CommentsController < ApplicationController
  def create
    @post = Post.find(params[:post_id])
    @comment = @post.comments.create(params[:comment])
    redirect_to post_path(@post)
  end
end

Verás un poco más de complejidad aquí comparado a lo visto en el controlador de artículos. Eso es un efecto secundario del anidado que estás usando. Cada vez que se crea un comentario es necesario saber a que artículo pertenece. Por eso la primera llamada al método find del modelo Post, para ubicar el artículo en particular.

Además, el código trata de tomar ventaja de algunos de los métodos disponibles a las asociaciones. Usamos el método create en @post.comments para crear y guardar el comentario. Esto asociará automaticamente el comentario el artículo en particular.

Una vez que hemos hecho el comentario nuevo, enviamos al usuario de vuelta al artículo original usando el helper post_path(@post). Como hemos podido ver, ésto luego llama a la acción show en el PostsController que hace render con la plantilla show.html.erb. Aquí es donde queremos mostrar los comentarios, así que agreguemos app/views/posts/show.html.erb.

<p>
  <strong>Title:</strong>
  <%= @post.title %>
</p>

<p>
  <strong>Text:</strong>
  <%= @post.text %>
</p>

<h2>Comments</h2>
<% @post.comments.each do |comment| %>
  <p>
    <strong>Commenter:</strong>
    <%= comment.commenter %>
  </p>

  <p>
    <strong>Comment:</strong>
    <%= comment.body %>
  </p>
<% end  %>

<h2>Add a comment:</h2>
<%= form_for([@post, @post.comments.build]) do |f| %>
  <p>
    <%= f.label :commenter %><br />
    <%= f.text_field :commenter %>
  </p>
  <p>
    <%= f.label :body %><br />
    <%= f.text_area :body %>
  </p>
  <p>
    <%= f.submit %>
  </p>
<% end  %>

<%= link_to 'Edit Post', edit_post_path(@post) %> |
<%= link_to 'Back to Posts', posts_path %>

Ahora puedes agregar artículos y comentarios a tu blog, y hacer que se muestren en los lugares correctos.

Artículo con Comentarios

Haciendo Refactoring

Ahora que tenemos los artículos y comentarios funcionando, dale una mirada a la plantilla app/views/posts/show.html.erb. Se está volviendo larga y complicada. Podemos usar parciales (partials) para simplificarla.

Usando parciales

Primero, creemos el parcial de comentario para extraer el mostrar de todos los comentarios del artículo. Crea el archivo app/views/comments/_comment.html.erb y coloca lo siguiente:

<p>
  <strong>Commenter:</strong>
  <%= comment.commenter %>
</p>

<p>
  <strong>Comment:</strong>
  <%= comment.body %>
</p>

Luego puedes cambiar app/views/posts/show.html.erb para que se vea de esta manera:

<p>
  <strong>Title:</strong>
  <%= @post.title %>
</p>

<p>
  <strong>Text:</strong>
  <%= @post.text %>
</p>

<h2>Comments</h2>
<%= render @post.comments %>

<h2>Add a comment:</h2>
<%= form_for([@post, @post.comments.build]) do |f| %>
  <p>
    <%= f.label :commenter %><br />
    <%= f.text_field :commenter %>
  </p>
  <p>
    <%= f.label :body %><br />
    <%= f.text_area :body %>
  </p>
  <p>
    <%= f.submit %>
  </p>
<% end  %>

<%= link_to 'Edit Post', edit_post_path(@post) %> |
<%= link_to 'Back to Posts', posts_path %>

Ésto hará que el parcial en app/views/comments/_comment.html.erb se haga render una vez por cada comentario en la colección @post.comments. A medida que el método render itera sobre la colección @post.comments, asigna cada comentario a la variable local llamada igual que el parcial, en este caso comment que luego está disponible en el parcial para que la usemos.

Creando un formulario

Movamos también la sección de comentarios nuevos a su propio parcial. De nuevo, crea un archivo app/views/comments/_form.html.erb que contenga:

<%= form_for([@post, @post.comments.build]) do |f| %>
  <p>
    <%= f.label :commenter %><br />
    <%= f.text_field :commenter %>
  </p>
  <p>
    <%= f.label :body %><br />
    <%= f.text_area :body %>
  </p>
  <p>
    <%= f.submit %>
  </p>
<% end  %>

Luego haz que app/views/posts/show.html.erb se vea de la siguiente manera:

<p>
  <strong>Title:</strong>
  <%= @post.title %>
</p>

<p>
  <strong>Text:</strong>
  <%= @post.text %>
</p>

<h2>Add a comment:</h2>
<%= render "comments/form" %>

<%= link_to 'Edit Post', edit_post_path(@post) %> |
<%= link_to 'Back to Posts', posts_path %>

El segundo render sólo define la plantilla parcial que queremos usar, comments/form. Rails es suficientemente inteligente para detectar el slash en el texto y darse cuenta que lo que quieres es hacer render del archivo _form.html.erb en la carpeta app/views/comments.

El objeto @post está disponible a cualquier parcial al que se le haga render en la vista porque lo hemos definido como una variable de instancia.

Eliminando Comentarios

Otra funcionalidad importante para un blog es poder eliminar comentarios spam. Para hacer esto, necesitamos implementar un enlace de algún tipo en la vista, y una acción DELETE en el CommentsController.

Primero, agreguemos el enlace para eliminar en el parcial app/views/comments/_comment.html.erb:

<p>
  <strong>Commenter:</strong>
  <%= comment.commenter %>
</p>

<p>
  <strong>Comment:</strong>
  <%= comment.body %>
</p>

<p>
  <%= link_to 'Eliminar Comentario', [comment.post, comment],
               :method => :delete,
               :data => { :confirm => '¿Estás seguro?' } %>
</p>

Al hacer click en este nuevo enlace "Eliminar Comentario" se enviará un DELETE posts:id/comments/:id a nuestro CommentsController, que puede luego usar para ubicar el comentario que queremos eliminar, así que agreguemos una acción de eliminar a nuestro controlador:

class CommentsController < ApplicationController

  def create
    @post = Post.find(params[:post_id])
    @comment = @post.comments.create(params[:comment])
    redirect_to post_path(@post)
  end

  def destroy
    @post = Post.find(params[:post_id])
    @comment = @post.comments.find(params[:id])
    @comment.destroy
    redirect_to post_path(@post)
  end

end

La acción destroy encontrará el artículo que estamos viendo, luego el comentario en la colección @post.comments, para eliminarlo de la base de datos y enviarnos de vuelta a la acción show para el artículo.

Eliminando Objetos Asociados

Si eliminas un artículo entonces sus comentarios asociados necesitas ser eliminados también. Si no lo haces simplemente van a ocupar espacio en la base de datos. Rails te permite usar la opción dependent en las asociaciones para lograrlo. Modifica el modelo Post, en app/models/post.rb, de esta manera:

class Post < ActiveRecord::Base
  validates :title, :presence => true,
                    :length => { :minimum => 5 }
  has_many :comments, :dependent => :destroy
end

Seguridad

Si fueras a publicar tu blog en línea, cualquier persona podría agregar, editar y eliminar artículos o eliminar comentarios.

Rails provee un sistema muy simple de autenticación por HTTP que funcionaría muy bien en esta situación.

Necesitamos una forma de bloquear el acceso a ciertas acciones en el controlador PostsController si la persona no está autenticada, aquí podemos usar el método de Rails http_basic_authenticate_with, que permite acceso a la acción solicitada si el método retorna true.

Para usar el sistema de autenticación, lo especificamos en la parte inicial de PostsController, en este caso, queremos autenticar al usuario en cada una de las acciones, excepto index y show, así lo escribimos:

class PostsController < ApplicationController

  http_basic_authenticate_with :name => "dhh", :password => "secret", :except => [:index, :show]

  def index
    @posts = Post.all
# cortado por brevedad

También queremos limitar la eliminación de comentarios a los usuarios autenticados, así que en CommentsController escribimos:

class CommentsController < ApplicationController

  http_basic_authenticate_with :name => "dhh", :password => "secret", :only => :destroy

  def create
    @post = Post.find(params[:post_id])
# cortado por brevedad

Ahora si intentas crear un nuevo artículo, vas a ser recibido con una ventana de diálogo de autenticación HTTP básica.

Ventana de diálogo de autenticación HTTP básica

Errores comunes de configuración

La forma más fácil de trabajar con Rails es guardando todos los datos externos como UTF-8. Si no lo haces, por lo general las librerías de Ruby y Rails van a ser capaces de convertir tus datos nativos a UTF-8, pero esto no funciona en todos los casos, así que lo ideal es asegurarte que todos tus datos externos usen UTF-8.

Si has cometido un error de este tipo, el síntoma más común es un símbolo de un diamante negro con un signo de interrogación en vez de algunos caracteres en tu navegador. Otro síntoma común es ver caracteres como "ü" apareciendo en vez de la "ü" por ejemplo. Rails realiza una serie de pasos internos para resolver casos comunes. Sin embargo, si tienes datos externos que no están guardados en UTF-8, puede resultar en este tipo de problemas al no ser automáticamente detectados y resueltos por Rails.

Dos fuentes comunes de datos que no están en UTF-8:

  • Tu editor de texto: La mayoría de editores de texto (como TextMate, o Sublime Text), guardan los archivos en UTF-8 por defecto. Si tu editor no lo hace, esto puede resultar en los problemas mencionados arriba. Esto también aplica a los archivos de traducción para I18n. La mayoría de editores que no usan UTF-8 por defecto (como algunas versionesde Dreamweaver) ofrecen una manera para cambiar la opción por defecto a UTF-8. Házlo!

  • Tu base de datos. Rails convierte los datos que intercambia con tu base de datos a UTF-8 por defecto. Sin embargo, si tu base de datos no está usando UTF-8 internamente, puede no ser capaz de guardar todos los datos que ingresen tus usuarios. Por ejemplo, si tu base de datos está usando Latin-1 internamente, y tus usuarios ingresan caracteres rusos, hebreos o japoneses, esos datos serán perdidos para siempre una vez que entren a la base de datos. Si es posible, usa UTF-8 internamente para el almacenamiento de datos.