Introducción a Ruby on Rails

Capítulo 6. Añadiendo otro modelo

A continuación vamos a añadir un segundo modelo a la aplicación. Este nuevo modelo se encargará de gestionar los comentarios de los artículos.

6.1. Generando el modelo

Para generar el nuevo modelo usaremos el mismo generador que se explicó anteriormente para el modelo Article. Esta vez se creará un modelo llamado Comment que gestionará los comentarios de los artículos. Para crearlo, ejecuta el siguiente comando:

$ bin/rails generate model Comment commenter:string body:text article:references

Como resultado de este comando se generarán cuatro archivos:

File Purpose
db/migrate/20140120201010_create_comments.rb Archivo de migración para crear la tabla comments en la base de datos (en tu caso el nombre del archivo será ligeramente diferente)
app/models/comment.rb El modelo Comment
test/models/comment_test.rb Los tests del modelo
test/fixtures/comments.yml Comentarios de prueba para los tests

Primero echa un vistazo al archivo app/models/comment.rb:

class Comment < ActiveRecord::Base
  belongs_to :article
end

Su contenido es muy similar al del modelo Article generado anteriormente. La única diferencia es la línea belongs_to :article, que configura una relación para Active Record. En la próxima sencción se explican estas relaciones entre modelos.

Además del modelo, Rails genera un archivo de 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
 
      # this line adds an integer column called `article_id`.
      t.references :article, index: true
 
      t.timestamps
    end
  end
end

La línea t.references crea una columna de tipo clave foránea para establecer la relación entre los dos modelos. Además se crea un índice para esta columna. Como ya tenemos todo preparado, ejecuta el siguiente comando:

$ bin/rake db:migrate

Rails es lo bastante inteligente como para ejecutar solamente las migraciones que todavía no se han ejecutado en la base de datos que se está utilizando. Así que el resultado de ejecutar el comando será:

==  CreateComments: migrating =================================================
-- create_table(:comments)
   -> 0.0115s
==  CreateComments: migrated (0.0119s) ========================================

6.2. Asociando modelos

Las asociaciones de Active Record permiten declarar las relaciones que existen entre dos modelos. En el caso de los comentarios y los artículos, las relaciones se podrían escribir de esta manera:

  • Cada comentario pertenece (en inglés, "belongs to") a un artículo.
  • Un artículo puede tener muchos (en inglés, "have many") comentarios.

Si te fijas un poco, la sintaxis que utiliza Rails es prácticamente la misma que como se describen las relaciones en inglés. Recuerda la línea del modelo Comment (archivo app/models/comment.rb) que establece la relación con Article:

class Comment < ActiveRecord::Base
  belongs_to :article
end

Ahora edita el archivo app/models/article.rb para definir el otro extremo de la relación:

class Article < ActiveRecord::Base
  has_many :comments
  validates :title, presence: true,
                    length: { minimum: 5 }
end

Gracias a estas dos declaraciones (belongs_to y has_many), Rails puede hacer casi todo el trabajo automáticamente. Si por ejemplo tienes una variable de instancia llamada @article que contiene un artículo, puedes obtener todos sus comentarios mediante la isntrucción @article.comments.

Nota Consulta el artículo Active Record Associations para obtener más información sobre las asociaciones.

6.3. Añadiendo una ruta para los comentarios

En primer lugar debemos añadir una nueva ruta para que Rails sepa dónde queremos navegar para ver los comentarios. Para ello, abre el archivo config/routes.rb y haz que tenga el siguiente contenido:

resources :articles do
  resources :comments
end

Esta configuración crea la ruta comments dentro de la ruta articles que definimos anteriormente. Esta es otra forma de establecer la relación entre los dos modelos.

Nota Para obtener más información sobre el enrutamiento, consulta la guía Routing Guide.

6.4. Generando un controlador

El modelo ya está creado así que ahora podemos dedicarnos a su controlador asociado. De nuevo utilizaremos el comando que genera controladores:

$ bin/rails generate controller Comments

Como resultado de este comando se generan seis archivos y un directorio vacío:

Archivo/Directorio Propósito
app/controllers/comments_controller.rb El controlador Comments
app/views/comments/ Direcotrio donde guardar las vistas del controlador
test/controllers/comments_controller_test.rb El test funcional del controlador
app/helpers/comments_helper.rb El helper para las vistas relacionadas con los comentarios
test/helpers/comments_helper_test.rb Test unitario para el helper
app/assets/javascripts/comment.js.coffee Archivo CoffeeScript para las vistas del controlador
app/assets/stylesheets/comment.css.scss Hoja de estilos CSS para las vistas del controlador

Como sucede en cualquier blog de Internet, los usuarios podrán añadir comentarios en los artículos y después de hacerlo, se les redirigirá a la página de ese mismo artículo para que puedan ver su comentario publicado. Por eso el controlador CommentsController deberá incluir un método para crear comentarios y para borrar todos los comentarios de spam que lleguen.

Así que en primer lugar vamos a modificar la plantilla que muestra los artículos (archivo app/views/articles/show.html.erb) para que deje crear nuevos comentarios:

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>
 
<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>
 
<h2>Add a comment:</h2>
<%= form_for([@article, @article.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 'Back', articles_path %>
| <%= link_to 'Edit', edit_article_path(@article) %>

Este código añade un formulario en la página show de los artículos para poder crear nuevos comentarios llamando a la acción create del controlador CommentsController. En este caso el método form_for utiliza un array que crea rutas anidadas de tipo /articles/1/comments.

El siguiente paso consiste en crear la acción create en el archivo app/controllers/comments_controller.rb:

class CommentsController < ApplicationController
  def create
    @article = Article.find(params[:article_id])
    @comment = @article.comments.create(comment_params)
    redirect_to article_path(@article)
  end
 
  private
    def comment_params
      params.require(:comment).permit(:commenter, :body)
    end
end

Este controlador es un poco más complejo del que creamos para los artículos. Esta es una de las consecuencias de anidar relaciones. Cada petición relacionada con un comentario debe contener una referencia al artículo con el que está relacionado. Por eso tenemos que buscar primero el modelo Article que está relacionado con el comentario.

Además, la acción utiliza algunos de los métodos disponibles para las relaciones. Así por ejemplo se utiliza el método create sobre @article.comments para crear y guardar un comentario. Esto hace que el comentario esté automáticamente relacionado con este artículo específico.

Después de crear el nuevo comentario, se redirige al usuario de nuevo a la página que meustra el artículo original mediante el helper article_path(@article). Como acabamos de ver, este helper llama a la acción show del controlador ArticlesController, que a su vez renderiza la plantilla show.html.erb. Como esta es la plantilla en la que se debe mostrar el nuevo comentario, añade lo siguiente en el archivo app/views/articles/show.html.erb:

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>
 
<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>
 
<h2>Comments</h2>
<% @article.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([@article, @article.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 Article', edit_article_path(@article) %> |
<%= link_to 'Back to Articles', articles_path %>

Ahora ya puedes añadir artículos y comentarios en el blog y cada contenido se muestra en el lugar adecuado.

Artículo con comentarios

Figura 6.1 Artículo con comentarios