1 Suposiciones de la guía
Esta guía está diseñada para principiantes que desean comenzar a crear una aplicación Rails desde cero. No asume que tienes experiencia previa con Rails.
Rails es un marco de aplicación web que se ejecuta en el lenguaje de programación Ruby. Si no tienes experiencia previa con Ruby, encontrarás una curva de aprendizaje muy pronunciada al sumergirte directamente en Rails. Hay varias listas seleccionadas de recursos en línea para aprender Ruby:
Ten en cuenta que algunos recursos, aunque siguen siendo excelentes, cubren versiones antiguas de Ruby y es posible que no incluyan algunas sintaxis que verás en el desarrollo diario con Rails.
2 ¿Qué es Rails?
Rails es un marco de desarrollo de aplicaciones web escrito en el lenguaje de programación Ruby. Está diseñado para facilitar la programación de aplicaciones web al hacer suposiciones sobre lo que cada desarrollador necesita para comenzar. Te permite escribir menos código mientras logras más que muchos otros lenguajes y marcos. Los desarrolladores experimentados de Rails también informan que hace que el desarrollo de aplicaciones web sea más divertido.
Rails es un software con opiniones. Parte de la premisa de que hay una "mejor" forma de hacer las cosas y está diseñado para fomentar esa forma, y en algunos casos, desalentar alternativas. Si aprendes "El camino de Rails", probablemente descubrirás un aumento tremendo en la productividad. Si persistes en traer viejos hábitos de otros lenguajes a tu desarrollo de Rails y tratas de usar patrones que aprendiste en otros lugares, es posible que tengas una experiencia menos satisfactoria.
La filosofía de Rails incluye dos principios rectores principales:
- No te repitas a ti mismo: DRY es un principio de desarrollo de software que establece que "Cada pieza de conocimiento debe tener una única representación inequívoca y autoritaria dentro de un sistema". Al no escribir la misma información una y otra vez, nuestro código es más mantenible, más extensible y menos propenso a errores.
- Convención sobre configuración: Rails tiene opiniones sobre la mejor manera de hacer muchas cosas en una aplicación web y se basa en este conjunto de convenciones en lugar de requerir que especifiques minucias a través de interminables archivos de configuración.
3 Creando un nuevo proyecto Rails
La mejor manera de leer esta guía es seguirla paso a paso. Todos los pasos son esenciales para ejecutar esta aplicación de ejemplo y no se necesita ningún código o paso adicional.
Siguiendo esta guía, crearás un proyecto Rails llamado blog
, un weblog (muy) simple. Antes de poder comenzar a construir la aplicación, debes asegurarte de tener Rails instalado.
NOTA: Los ejemplos a continuación usan $
para representar el indicador de comando en un sistema operativo similar a UNIX, aunque puede haber sido personalizado para aparecer de manera diferente. Si estás usando Windows, tu indicador se verá algo como C:\source_code>
.
3.1 Instalando Rails
Antes de instalar Rails, debes verificar que tu sistema tenga los requisitos previos adecuados instalados. Estos incluyen:
- Ruby
- SQLite3
3.1.1 Instalando Ruby
Abre una ventana de línea de comandos. En macOS, abre Terminal.app; en Windows, elige "Ejecutar" desde el menú Inicio y escribe cmd.exe
. Cualquier comando precedido por un signo de dólar $
debe ejecutarse en la línea de comandos. Verifica que tienes instalada una versión actual de Ruby:
$ ruby --version
ruby 2.7.0
Rails requiere la versión 2.7.0 o posterior de Ruby. Se prefiere usar la última versión de Ruby. Si el número de versión que se muestra es menor que ese número (como 2.3.7 o 1.8.7), deberás instalar una copia nueva de Ruby.
Para instalar Rails en Windows, primero deberás instalar Ruby Installer.
Para obtener más métodos de instalación para la mayoría de los sistemas operativos, consulta ruby-lang.org.
3.1.2 Instalando SQLite3
También necesitarás una instalación de la base de datos SQLite3. Muchos sistemas operativos similares a UNIX populares vienen con una versión aceptable de SQLite3. Otros pueden encontrar instrucciones de instalación en el sitio web de SQLite3.
Verifique que esté instalado correctamente y en su PATH
de carga:
$ sqlite3 --version
El programa debería mostrar su versión.
3.1.3 Instalación de Rails
Para instalar Rails, use el comando gem install
proporcionado por RubyGems:
$ gem install rails
Para verificar que todo esté instalado correctamente, debería poder ejecutar lo siguiente en una nueva terminal:
$ rails --version
Si muestra algo como "Rails 7.0.0", está listo para continuar.
3.2 Creando la aplicación de Blog
Rails viene con una serie de scripts llamados generadores que están diseñados para facilitar tu vida de desarrollo al crear todo lo necesario para comenzar a trabajar en una tarea en particular. Uno de estos es el generador de nuevas aplicaciones, que te proporcionará la base de una nueva aplicación de Rails para que no tengas que escribirla tú mismo.
Para usar este generador, abre una terminal, navega hasta un directorio donde tengas permisos para crear archivos y ejecuta:
$ rails new blog
Esto creará una aplicación de Rails llamada Blog en un directorio blog
e instalará las dependencias de gemas que ya se mencionan en Gemfile
usando bundle install
.
CONSEJO: Puedes ver todas las opciones de línea de comandos que acepta el generador de aplicaciones de Rails ejecutando rails new --help
.
Después de crear la aplicación de blog, cambia a su carpeta:
$ cd blog
El directorio blog
tendrá varios archivos y carpetas generados que conforman la estructura de una aplicación de Rails. La mayor parte del trabajo en este tutorial se realizará en la carpeta app
, pero aquí tienes una descripción básica de la función de cada uno de los archivos y carpetas que Rails crea por defecto:
Archivo/Carpeta | Propósito |
---|---|
app/ | Contiene los controladores, modelos, vistas, ayudantes, mailers, canales, trabajos y activos de tu aplicación. Te enfocarás en esta carpeta durante el resto de esta guía. |
bin/ | Contiene el script rails que inicia tu aplicación y puede contener otros scripts que uses para configurar, actualizar, implementar o ejecutar tu aplicación. |
config/ | Contiene la configuración de las rutas, la base de datos y más de tu aplicación. Esto se explica con más detalle en Configuración de aplicaciones de Rails. |
config.ru | Configuración de Rack para los servidores basados en Rack utilizados para iniciar la aplicación. Para obtener más información sobre Rack, consulta el sitio web de Rack. |
db/ | Contiene el esquema de la base de datos actual, así como las migraciones de la base de datos. |
Gemfile Gemfile.lock |
Estos archivos te permiten especificar las dependencias de gemas necesarias para tu aplicación de Rails. Estos archivos son utilizados por la gema Bundler. Para obtener más información sobre Bundler, consulta el sitio web de Bundler. |
lib/ | Módulos extendidos para tu aplicación. |
log/ | Archivos de registro de la aplicación. |
public/ | Contiene archivos estáticos y activos compilados. Cuando tu aplicación esté en funcionamiento, este directorio se expondrá tal cual. |
Rakefile | Este archivo localiza y carga tareas que se pueden ejecutar desde la línea de comandos. Las definiciones de tareas se definen en los componentes de Rails. En lugar de cambiar Rakefile , debes agregar tus propias tareas agregando archivos al directorio lib/tasks de tu aplicación. |
README.md | Este es un breve manual de instrucciones para tu aplicación. Debes editar este archivo para informar a otros sobre lo que hace tu aplicación, cómo configurarla, etc. |
storage/ | Archivos de Active Storage para el servicio de disco. Esto se explica en Descripción general de Active Storage. |
test/ | Pruebas unitarias, fixtures y otros aparatos de prueba. Estos se explican en Pruebas de aplicaciones de Rails. |
tmp/ | Archivos temporales (como archivos de caché y pid). |
vendor/ | Un lugar para todo el código de terceros. En una aplicación de Rails típica, esto incluye gemas vendidas. |
.gitattributes | Este archivo define metadatos para rutas específicas en un repositorio de git. Estos metadatos pueden ser utilizados por git y otras herramientas para mejorar su comportamiento. Consulta la documentación de gitattributes para obtener más información. |
.gitignore | Este archivo le indica a git qué archivos (o patrones) debe ignorar. Consulta GitHub - Ignorar archivos para obtener más información sobre cómo ignorar archivos. |
.ruby-version | Este archivo contiene la versión predeterminada de Ruby. |
4 ¡Hola, Rails!
Para empezar, pongamos algo de texto en la pantalla rápidamente. Para hacer esto, necesitas iniciar el servidor de aplicaciones de Rails.
4.1 Iniciando el servidor web
En realidad, ya tienes una aplicación de Rails funcional. Para verla, necesitas iniciar un servidor web en tu máquina de desarrollo. Puedes hacer esto ejecutando el siguiente comando en el directorio blog
:
$ bin/rails server
CONSEJO: Si estás utilizando Windows, debes pasar los scripts ubicados en la carpeta bin
directamente al intérprete de Ruby, por ejemplo ruby bin\rails server
.
CONSEJO: La compresión de activos de JavaScript requiere que tengas un tiempo de ejecución de JavaScript disponible en tu sistema. En ausencia de un tiempo de ejecución, verás un error de execjs
durante la compresión de activos. Por lo general, macOS y Windows vienen con un tiempo de ejecución de JavaScript instalado. therubyrhino
es el tiempo de ejecución recomendado para usuarios de JRuby y se agrega de forma predeterminada al Gemfile
en aplicaciones generadas con JRuby. Puedes investigar todos los tiempos de ejecución compatibles en ExecJS.
Esto iniciará Puma, un servidor web distribuido con Rails de forma predeterminada. Para ver tu aplicación en acción, abre una ventana del navegador y navega a http://localhost:3000. Deberías ver la página de información predeterminada de Rails:
Cuando quieras detener el servidor web, presiona Ctrl+C en la ventana de la terminal donde se está ejecutando. En el entorno de desarrollo, Rails generalmente no requiere reiniciar el servidor; los cambios que realices en los archivos serán recogidos automáticamente por el servidor.
La página de inicio de Rails es la "prueba de humo" para una nueva aplicación de Rails: se asegura de que tengas tu software configurado correctamente para servir una página.
4.2 Di "Hola", Rails
Para hacer que Rails diga "Hola", necesitas crear al menos una ruta, un controlador con una acción, y una vista. Una ruta mapea una solicitud a una acción del controlador. Una acción del controlador realiza el trabajo necesario para manejar la solicitud y prepara cualquier dato para la vista. Una vista muestra los datos en un formato deseado.
En términos de implementación: las rutas son reglas escritas en un DSL (Lenguaje Específico del Dominio) de Ruby. Los controladores son clases de Ruby y sus métodos públicos son acciones. Y las vistas son plantillas, generalmente escritas en una mezcla de HTML y Ruby.
Comencemos agregando una ruta a nuestro archivo de rutas, config/routes.rb
, en el bloque Rails.application.routes.draw
:
Rails.application.routes.draw do
get "/articles", to: "articles#index"
# Para obtener más detalles sobre el DSL disponible en este archivo, consulta https://guides.rubyonrails.org/routing.html
end
La ruta anterior declara que las solicitudes GET /articles
se mapean a la acción index
de ArticlesController
.
Para crear ArticlesController
y su acción index
, ejecutaremos el generador de controladores (con la opción --skip-routes
porque ya tenemos una ruta adecuada):
$ bin/rails generate controller Articles index --skip-routes
Rails creará varios archivos para ti:
create app/controllers/articles_controller.rb
invoke erb
create app/views/articles
create app/views/articles/index.html.erb
invoke test_unit
create test/controllers/articles_controller_test.rb
invoke helper
create app/helpers/articles_helper.rb
invoke test_unit
El más importante de estos es el archivo del controlador, app/controllers/articles_controller.rb
. Echemos un vistazo:
class ArticlesController < ApplicationController
def index
end
end
La acción index
está vacía. Cuando una acción no renderiza explícitamente una vista (o de otra manera desencadena una respuesta HTTP), Rails automáticamente renderizará una vista que coincida con el nombre del controlador y la acción. ¡Convención sobre configuración! Las vistas se encuentran en el directorio app/views
. Por lo tanto, la acción index
renderizará app/views/articles/index.html.erb
de forma predeterminada.
Abramos app/views/articles/index.html.erb
y reemplacemos su contenido con:
<h1>¡Hola, Rails!</h1>
Si anteriormente detuviste el servidor web para ejecutar el generador de controladores, reinícialo con bin/rails server
. Ahora visita http://localhost:3000/articles y ¡verás nuestro texto mostrado!
4.3 Configurando la página de inicio de la aplicación
En este momento, http://localhost:3000 todavía muestra una página con el logotipo de Ruby on Rails. También vamos a mostrar nuestro texto "¡Hola, Rails!" en http://localhost:3000. Para hacerlo, agregaremos una ruta que mapee la ruta raíz de nuestra aplicación a la acción y controlador correspondientes.
Abramos config/routes.rb
y agreguemos la siguiente ruta root
al principio del bloque Rails.application.routes.draw
:
Rails.application.routes.draw do
root "articles#index"
get "/articles", to: "articles#index"
end
Ahora podemos ver nuestro texto "¡Hola, Rails!" cuando visitamos http://localhost:3000, confirmando que la ruta root
también se mapea a la acción index
de ArticlesController
.
CONSEJO: Para obtener más información sobre enrutamiento, consulta Rails Routing from the Outside In.
5 Carga automática
Las aplicaciones de Rails no utilizan require
para cargar el código de la aplicación.
Puede que hayas notado que ArticlesController
hereda de ApplicationController
, pero app/controllers/articles_controller.rb
no tiene algo como
require "application_controller" # NO HAGAS ESTO.
Las clases y módulos de la aplicación están disponibles en todas partes, no necesitas y no debes cargar nada bajo app
con require
. Esta característica se llama carga automática, y puedes obtener más información al respecto en Autoloading and Reloading Constants.
Solo necesitas llamadas require
para dos casos de uso:
- Cargar archivos bajo el directorio
lib
. - Cargar dependencias de gemas que tienen
require: false
en elGemfile
.
6 MVC y Tú
Hasta ahora, hemos discutido rutas, controladores, acciones y vistas. Todos estos son componentes típicos de una aplicación web que sigue el patrón MVC (Modelo-Vista-Controlador). MVC es un patrón de diseño que divide las responsabilidades de una aplicación para facilitar el razonamiento. Rails sigue este patrón de diseño por convención.
Dado que tenemos un controlador y una vista con los que trabajar, generemos la siguiente pieza: un modelo.
6.1 Generando un Modelo
Un modelo es una clase de Ruby que se utiliza para representar datos. Además, los modelos pueden interactuar con la base de datos de la aplicación a través de una característica de Rails llamada Active Record.
Para definir un modelo, utilizaremos el generador de modelos:
$ bin/rails generate model Article title:string body:text
NOTA: Los nombres de los modelos son singulares, porque un modelo instanciado representa un
único registro de datos. Para ayudar a recordar esta convención, piensa en cómo llamarías al constructor del modelo: queremos escribir Article.new(...)
, no
Articles.new(...)
.
Esto creará varios archivos:
invoke active_record
create db/migrate/<timestamp>_create_articles.rb
create app/models/article.rb
invoke test_unit
create test/models/article_test.rb
create test/fixtures/articles.yml
Los dos archivos en los que nos enfocaremos son el archivo de migración
(db/migrate/<timestamp>_create_articles.rb
) y el archivo del modelo
(app/models/article.rb
).
6.2 Migraciones de la Base de Datos
Las migraciones se utilizan para alterar la estructura de la base de datos de una aplicación. En las aplicaciones de Rails, las migraciones se escriben en Ruby para que puedan ser independientes de la base de datos.
Echemos un vistazo al contenido de nuestro nuevo archivo de migración:
class CreateArticles < ActiveRecord::Migration[7.0]
def change
create_table :articles do |t|
t.string :title
t.text :body
t.timestamps
end
end
end
La llamada a create_table
especifica cómo se debe construir la tabla articles
.
Por defecto, el método create_table
agrega una columna id
como clave primaria autoincremental.
Entonces, el primer registro en la tabla tendrá un id
de 1, el siguiente registro tendrá un id
de 2, y así sucesivamente.
Dentro del bloque de create_table
, se definen dos columnas: title
y
body
. Estas se agregaron mediante el generador porque las incluimos en nuestro
comando de generación (bin/rails generate model Article title:string body:text
).
En la última línea del bloque se encuentra una llamada a t.timestamps
. Este método define
dos columnas adicionales llamadas created_at
y updated_at
. Como veremos,
Rails las administrará por nosotros, estableciendo los valores cuando creamos o actualizamos
un objeto del modelo.
Ejecutemos nuestra migración con el siguiente comando:
$ bin/rails db:migrate
El comando mostrará una salida que indica que se creó la tabla:
== CreateArticles: migrating ===================================
-- create_table(:articles)
-> 0.0018s
== CreateArticles: migrated (0.0018s) ==========================
CONSEJO: Para obtener más información sobre las migraciones, consulta Migraciones de Active Record.
Ahora podemos interactuar con la tabla utilizando nuestro modelo.
6.3 Usar un Modelo para Interactuar con la Base de Datos
Para jugar un poco con nuestro modelo, vamos a utilizar una característica de Rails llamada
consola. La consola es un entorno de codificación interactivo como irb
, pero
también carga automáticamente Rails y el código de nuestra aplicación.
Iniciemos la consola con este comando:
$ bin/rails console
Deberías ver un indicador irb
como este:
Loading development environment (Rails 7.0.0)
irb(main):001:0>
En este indicador, podemos inicializar un nuevo objeto Article
:
irb> article = Article.new(title: "Hola Rails", body: "¡Estoy en Rails!")
Es importante tener en cuenta que solo hemos inicializado este objeto. Este objeto
no se guarda en la base de datos en absoluto. Solo está disponible en la consola en este
momento. Para guardar el objeto en la base de datos, debemos llamar a save
:
irb> article.save
(0.1ms) begin transaction
Article Create (0.4ms) INSERT INTO "articles" ("title", "body", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["title", "Hola Rails"], ["body", "¡Estoy en Rails!"], ["created_at", "2020-01-18 23:47:30.734416"], ["updated_at", "2020-01-18 23:47:30.734416"]]
(0.9ms) commit transaction
=> true
La salida anterior muestra una consulta a la base de datos INSERT INTO "articles" ...
. Esto
indica que el artículo se ha insertado en nuestra tabla. Y si volvemos a
ver el objeto article
, veremos algo interesante:
irb> article
=> #<Article id: 1, title: "Hola Rails", body: "¡Estoy en Rails!", created_at: "2020-01-18 23:47:30", updated_at: "2020-01-18 23:47:30">
Los atributos id
, created_at
y updated_at
del objeto ahora están configurados. Rails lo hizo por nosotros cuando guardamos el objeto.
Cuando queremos buscar este artículo en la base de datos, podemos llamar a find
en el modelo y pasar el id
como argumento:
irb> Article.find(1)
=> #<Article id: 1, title: "Hello Rails", body: "I am on Rails!", created_at: "2020-01-18 23:47:30", updated_at: "2020-01-18 23:47:30">
Y cuando queremos buscar todos los artículos en la base de datos, podemos llamar a all
en el modelo:
irb> Article.all
=> #<ActiveRecord::Relation [#<Article id: 1, title: "Hello Rails", body: "I am on Rails!", created_at: "2020-01-18 23:47:30", updated_at: "2020-01-18 23:47:30">]>
Este método devuelve un objeto ActiveRecord::Relation
, que se puede pensar como un array con superpoderes.
CONSEJO: Para obtener más información sobre los modelos, consulte Active Record Basics y Active Record Query Interface.
Los modelos son la pieza final del rompecabezas MVC. A continuación, conectaremos todas las piezas juntas.
6.4 Mostrando una lista de artículos
Volviendo a nuestro controlador en app/controllers/articles_controller.rb
, y cambiamos la acción index
para buscar todos los artículos en la base de datos:
class ArticlesController < ApplicationController
def index
@articles = Article.all
end
end
Las variables de instancia del controlador se pueden acceder desde la vista. Eso significa que podemos hacer referencia a @articles
en app/views/articles/index.html.erb
. Abramos ese archivo y reemplacemos su contenido con:
<h1>Articles</h1>
<ul>
<% @articles.each do |article| %>
<li>
<%= article.title %>
</li>
<% end %>
</ul>
El código anterior es una mezcla de HTML y ERB. ERB es un sistema de plantillas que evalúa código Ruby incrustado en un documento. Aquí, podemos ver dos tipos de etiquetas ERB: <% %>
y <%= %>
. La etiqueta <% %>
significa "evaluar el código Ruby encerrado". La etiqueta <%= %>
significa "evaluar el código Ruby encerrado y mostrar el valor que devuelve". Cualquier cosa que se pueda escribir en un programa Ruby regular puede ir dentro de estas etiquetas ERB, aunque generalmente es mejor mantener el contenido de las etiquetas ERB corto, para facilitar la lectura.
Dado que no queremos mostrar el valor devuelto por @articles.each
, hemos encerrado ese código en <% %>
. Pero, dado que sí queremos mostrar el valor devuelto por article.title
(para cada artículo), hemos encerrado ese código en <%= %>
.
Podemos ver el resultado final visitando http://localhost:3000. (¡Recuerda que bin/rails server
debe estar en ejecución!) Esto es lo que sucede cuando lo hacemos:
- El navegador realiza una solicitud:
GET http://localhost:3000
. - Nuestra aplicación Rails recibe esta solicitud.
- El enrutador de Rails asigna la ruta raíz a la acción
index
deArticlesController
. - La acción
index
utiliza el modeloArticle
para buscar todos los artículos en la base de datos. - Rails renderiza automáticamente la vista
app/views/articles/index.html.erb
. - El código ERB en la vista se evalúa para mostrar HTML.
- El servidor envía una respuesta que contiene el HTML de vuelta al navegador.
Hemos conectado todas las piezas del MVC y tenemos nuestra primera acción del controlador. A continuación, pasaremos a la segunda acción.
7 CRUDit Donde CRUDit Es Debido
Casi todas las aplicaciones web involucran operaciones CRUD (Crear, Leer, Actualizar y Eliminar). Incluso puede encontrar que la mayoría del trabajo que realiza su aplicación es CRUD. Rails reconoce esto y proporciona muchas características para ayudar a simplificar el código que realiza CRUD.
Comencemos a explorar estas características agregando más funcionalidad a nuestra aplicación.
7.1 Mostrando un solo artículo
Actualmente tenemos una vista que muestra todos los artículos en nuestra base de datos. Agreguemos una nueva vista que muestre el título y el cuerpo de un solo artículo.
Comenzamos agregando una nueva ruta que se asigna a una nueva acción del controlador (que agregaremos a continuación). Abra config/routes.rb
e inserte la última ruta mostrada aquí:
Rails.application.routes.draw do
root "articles#index"
get "/articles", to: "articles#index"
get "/articles/:id", to: "articles#show"
end
La nueva ruta es otra ruta get
, pero tiene algo adicional en su ruta: :id
. Esto designa un parámetro de ruta. Un parámetro de ruta captura un segmento de la ruta de la solicitud y coloca ese valor en el Hash params
, al que se puede acceder desde la acción del controlador. Por ejemplo, al manejar una solicitud como GET http://localhost:3000/articles/1
, 1
se capturaría como el valor para :id
, que luego sería accesible como params[:id]
en la acción show
de ArticlesController
.
Agreguemos ahora esa acción show
, debajo de la acción index
en app/controllers/articles_controller.rb
:
class ArticlesController < ApplicationController
def index
@articles = Article.all
end
def show
@article = Article.find(params[:id])
end
end
La acción show
llama a Article.find
(mencionado anteriormente) con el ID capturado por el parámetro de la ruta. El artículo devuelto se guarda en la variable de instancia @article
, por lo que es accesible desde la vista. Por defecto, la acción show
renderizará app/views/articles/show.html.erb
.
Creemos app/views/articles/show.html.erb
, con el siguiente contenido:
<h1><%= @article.title %></h1>
<p><%= @article.body %></p>
¡Ahora podemos ver el artículo cuando visitamos http://localhost:3000/articles/1!
Para terminar, agreguemos una forma conveniente de acceder a la página de un artículo. Vamos a enlazar el título de cada artículo en app/views/articles/index.html.erb
a su página:
<h1>Artículos</h1>
<ul>
<% @articles.each do |article| %>
<li>
<a href="/articles/<%= article.id %>">
<%= article.title %>
</a>
</li>
<% end %>
</ul>
7.2 Enrutamiento de recursos
Hasta ahora, hemos cubierto la "Lectura" (Read) de CRUD. Eventualmente cubriremos las secciones "C" (Create), "U" (Update) y "D" (Delete). Como podrás haber adivinado, lo haremos agregando nuevas rutas, acciones de controlador y vistas. Cuando tenemos una combinación de rutas, acciones de controlador y vistas que trabajan juntas para realizar operaciones CRUD en una entidad, llamamos a esa entidad un recurso. Por ejemplo, en nuestra aplicación, podríamos decir que un artículo es un recurso.
Rails proporciona un método de rutas llamado resources
que mapea todas las rutas convencionales para una colección de recursos, como los artículos. Así que antes de pasar a las secciones "C", "U" y "D", reemplacemos las dos rutas get
en config/routes.rb
con resources
:
Rails.application.routes.draw do
root "articles#index"
resources :articles
end
Podemos inspeccionar qué rutas se mapean ejecutando el comando bin/rails routes
:
$ bin/rails routes
Prefix Verb URI Pattern Controller#Action
root GET / articles#index
articles GET /articles(.:format) articles#index
new_article GET /articles/new(.:format) articles#new
article GET /articles/:id(.:format) articles#show
POST /articles(.:format) articles#create
edit_article GET /articles/:id/edit(.:format) articles#edit
PATCH /articles/:id(.:format) articles#update
DELETE /articles/:id(.:format) articles#destroy
El método resources
también configura métodos auxiliares de URL y ruta que podemos usar para evitar que nuestro código dependa de una configuración de ruta específica. Los valores en la columna "Prefix" más un sufijo de _url
o _path
forman los nombres de estos ayudantes. Por ejemplo, el ayudante article_path
devuelve "/articles/#{article.id}"
cuando se le pasa un artículo. Podemos usarlo para mejorar nuestros enlaces en app/views/articles/index.html.erb
:
<h1>Artículos</h1>
<ul>
<% @articles.each do |article| %>
<li>
<a href="<%= article_path(article) %>">
<%= article.title %>
</a>
</li>
<% end %>
</ul>
Sin embargo, daremos un paso más utilizando el ayudante link_to
.
El ayudante link_to
renderiza un enlace con su primer argumento como el texto del enlace y su segundo argumento como el destino del enlace. Si pasamos un objeto de modelo como segundo argumento, link_to
llamará al ayudante de ruta correspondiente para convertir el objeto en una ruta. Por ejemplo, si pasamos un artículo, link_to
llamará a article_path
. Por lo tanto, app/views/articles/index.html.erb
se convierte en:
<h1>Artículos</h1>
<ul>
<% @articles.each do |article| %>
<li>
<%= link_to article.title, article %>
</li>
<% end %>
</ul>
¡Genial!
CONSEJO: Para obtener más información sobre el enrutamiento, consulta Rails Routing from the Outside In.
7.3 Crear un nuevo artículo
Ahora pasamos a la "C" (Create) de CRUD. Típicamente, en aplicaciones web, crear un nuevo recurso es un proceso de varios pasos. Primero, el usuario solicita un formulario para completar. Luego, el usuario envía el formulario. Si no hay errores, entonces se crea el recurso y se muestra alguna confirmación. De lo contrario, el formulario se muestra nuevamente con mensajes de error y se repite el proceso.
En una aplicación Rails, estos pasos se manejan convencionalmente mediante las acciones new
y create
de un controlador. Agreguemos una implementación típica de estas acciones a app/controllers/articles_controller.rb
, debajo de la acción show
:
class ArticlesController < ApplicationController
def index
@articles = Article.all
end
def show
@article = Article.find(params[:id])
end
def new
@article = Article.new
end
def create
@article = Article.new(title: "...", body: "...")
if @article.save
redirect_to @article
else
render :new, status: :unprocessable_entity
end
end
end
La acción new
instancia un nuevo artículo, pero no lo guarda. Este artículo se utilizará en la vista al construir el formulario. Por defecto, la acción new
renderizará app/views/articles/new.html.erb
, que crearemos a continuación.
La acción create
instancia un nuevo artículo con valores para el título y el cuerpo, e intenta guardarlo. Si el artículo se guarda correctamente, la acción redirige el navegador a la página del artículo en "http://localhost:3000/articles/#{@article.id}"
.
De lo contrario, la acción vuelve a mostrar el formulario al renderizar app/views/articles/new.html.erb
con el código de estado 422 Entidad no procesable.
Aquí, el título y el cuerpo son valores ficticios. Después de crear el formulario, volveremos y cambiaremos estos valores.
NOTA: redirect_to
hará que el navegador realice una nueva solicitud,
mientras que render
renderiza la vista especificada para la solicitud actual.
Es importante usar redirect_to
después de modificar la base de datos o el estado de la aplicación.
De lo contrario, si el usuario actualiza la página, el navegador realizará la misma solicitud y la modificación se repetirá.
7.3.1 Usando un constructor de formularios
Utilizaremos una característica de Rails llamada constructor de formularios para crear nuestro formulario. Usando un constructor de formularios, podemos escribir una cantidad mínima de código para generar un formulario completamente configurado y que siga las convenciones de Rails.
Creemos app/views/articles/new.html.erb
con el siguiente contenido:
<h1>Nuevo Artículo</h1>
<%= form_with model: @article do |form| %>
<div>
<%= form.label :title %><br>
<%= form.text_field :title %>
</div>
<div>
<%= form.label :body %><br>
<%= form.text_area :body %>
</div>
<div>
<%= form.submit %>
</div>
<% end %>
El método auxiliar form_with
instancia un constructor de formularios. En el bloque form_with
llamamos a
métodos como label
y text_field
en el constructor de formularios para generar los elementos de formulario correspondientes.
La salida resultante de nuestra llamada a form_with
se verá así:
<form action="/articles" accept-charset="UTF-8" method="post">
<input type="hidden" name="authenticity_token" value="...">
<div>
<label for="article_title">Título</label><br>
<input type="text" name="article[title]" id="article_title">
</div>
<div>
<label for="article_body">Cuerpo</label><br>
<textarea name="article[body]" id="article_body"></textarea>
</div>
<div>
<input type="submit" name="commit" value="Crear Artículo" data-disable-with="Crear Artículo">
</div>
</form>
CONSEJO: Para obtener más información sobre los constructores de formularios, consulta Action View Form Helpers.
7.3.2 Usando Strong Parameters
Los datos del formulario enviado se colocan en el Hash params
, junto con los parámetros de ruta capturados. Por lo tanto, la acción create
puede acceder al título enviado a través de params[:article][:title]
y al cuerpo enviado a través de params[:article][:body]
. Podríamos pasar estos valores individualmente a Article.new
, pero eso sería verboso y posiblemente propenso a errores. Y empeoraría a medida que agregamos más campos.
En su lugar, pasaremos un solo Hash que contenga los valores. Sin embargo, aún debemos especificar qué valores están permitidos en ese Hash. De lo contrario, un usuario malintencionado podría enviar campos de formulario adicionales y sobrescribir datos privados. De hecho, si pasamos directamente el Hash params[:article]
sin filtrar a Article.new
, Rails generará un error ForbiddenAttributesError
para alertarnos sobre el problema. Por lo tanto, utilizaremos una característica de Rails llamada Strong Parameters para filtrar params
. Piensa en ello como una tipificación fuerte para params
.
Agreguemos un método privado al final de app/controllers/articles_controller.rb
llamado article_params
que filtre params
. Y cambiemos create
para usarlo:
class ArticlesController < ApplicationController
def index
@articles = Article.all
end
def show
@article = Article.find(params[:id])
end
def new
@article = Article.new
end
def create
@article = Article.new(article_params)
if @article.save
redirect_to @article
else
render :new, status: :unprocessable_entity
end
end
private
def article_params
params.require(:article).permit(:title, :body)
end
end
CONSEJO: Para obtener más información sobre Strong Parameters, consulta Resumen de Action Controller § Strong Parameters.
7.3.3 Validaciones y Mostrar Mensajes de Error
Como hemos visto, crear un recurso es un proceso de varios pasos. Manejar una entrada de usuario no válida es otro paso de ese proceso. Rails proporciona una característica llamada validaciones para ayudarnos a lidiar con una entrada de usuario no válida. Las validaciones son reglas que se verifican antes de que se guarde un objeto de modelo. Si alguna de las verificaciones falla, el guardado se abortará y se agregarán mensajes de error apropiados al atributo errors
del objeto de modelo.
Agreguemos algunas validaciones a nuestro modelo en app/models/article.rb
:
class Article < ApplicationRecord
validates :title, presence: true
validates :body, presence: true, length: { minimum: 10 }
end
La primera validación declara que un valor de title
debe estar presente. Dado que title
es una cadena, esto significa que el valor de title
debe contener al menos un carácter que no sea un espacio en blanco.
La segunda validación declara que también debe estar presente un valor de body
. Además, declara que el valor de body
debe tener al menos 10 caracteres de longitud.
NOTA: Es posible que te preguntes dónde se definen los atributos title
y body
. Active Record define automáticamente los atributos del modelo para cada columna de la tabla, por lo que no es necesario declarar esos atributos en el archivo del modelo.
Con nuestras validaciones en su lugar, modifiquemos app/views/articles/new.html.erb
para mostrar cualquier mensaje de error para title
y body
:
<h1>Nuevo Artículo</h1>
<%= form_with model: @article do |form| %>
<div>
<%= form.label :title %><br>
<%= form.text_field :title %>
<% @article.errors.full_messages_for(:title).each do |message| %>
<div><%= message %></div>
<% end %>
</div>
<div>
<%= form.label :body %><br>
<%= form.text_area :body %><br>
<% @article.errors.full_messages_for(:body).each do |message| %>
<div><%= message %></div>
<% end %>
</div>
<div>
<%= form.submit %>
</div>
<% end %>
El método full_messages_for
devuelve una matriz de mensajes de error amigables para el usuario para un atributo especificado. Si no hay errores para ese atributo, la matriz estará vacía.
Para entender cómo funciona todo esto en conjunto, echemos otro vistazo a las acciones del controlador new
y create
:
def new
@article = Article.new
end
def create
@article = Article.new(article_params)
if @article.save
redirect_to @article
else
render :new, status: :unprocessable_entity
end
end
Cuando visitamos http://localhost:3000/articles/new, la solicitud GET /articles/new
se asigna a la acción new
. La acción new
no intenta guardar @article
. Por lo tanto, las validaciones no se verifican y no habrá mensajes de error.
Cuando enviamos el formulario, la solicitud POST /articles
se asigna a la acción create
. La acción create
sí intenta guardar @article
. Por lo tanto, las validaciones se verifican. Si alguna validación falla, @article
no se guardará y se renderizará app/views/articles/new.html.erb
con mensajes de error.
CONSEJO: Para obtener más información sobre las validaciones, consulta Validaciones de Active Record. Para obtener más información sobre los mensajes de error de validación, consulta Validaciones de Active Record § Trabajar con mensajes de error de validación.
7.3.4 Finalizando
Ahora podemos crear un artículo visitando http://localhost:3000/articles/new. Para finalizar, agreguemos un enlace a esa página en la parte inferior de app/views/articles/index.html.erb
:
<h1>Artículos</h1>
<ul>
<% @articles.each do |article| %>
<li>
<%= link_to article.title, article %>
</li>
<% end %>
</ul>
<%= link_to "Nuevo Artículo", new_article_path %>
7.4 Actualizando un Artículo
Hemos cubierto el "CR" de CRUD. Ahora pasemos al "U" (Actualizar). Actualizar un recurso es muy similar a crear un recurso. Ambos son procesos de múltiples pasos. Primero, el usuario solicita un formulario para editar los datos. Luego, el usuario envía el formulario. Si no hay errores, entonces el recurso se actualiza. De lo contrario, el formulario se muestra nuevamente con mensajes de error y el proceso se repite.
Estos pasos se manejan convencionalmente mediante las acciones edit
y update
de un controlador. Agreguemos una implementación típica de estas acciones a app/controllers/articles_controller.rb
, debajo de la acción create
:
class ArticlesController < ApplicationController
def index
@articles = Article.all
end
def show
@article = Article.find(params[:id])
end
def new
@article = Article.new
end
def create
@article = Article.new(article_params)
if @article.save
redirect_to @article
else
render :new, status: :unprocessable_entity
end
end
def edit
@article = Article.find(params[:id])
end
def update
@article = Article.find(params[:id])
if @article.update(article_params)
redirect_to @article
else
render :edit, status: :unprocessable_entity
end
end
private
def article_params
params.require(:article).permit(:title, :body)
end
end
Observa cómo las acciones edit
y update
se asemejan a las acciones new
y create
.
La acción edit
obtiene el artículo de la base de datos y lo almacena en @article
para que se pueda usar al construir el formulario. Por defecto, la acción edit
renderizará app/views/articles/edit.html.erb
.
La acción update
vuelve a obtener el artículo de la base de datos e intenta actualizarlo con los datos del formulario enviados filtrados por article_params
. Si no hay validaciones que fallen y la actualización tiene éxito, la acción redirige el navegador a la página del artículo. De lo contrario, la acción vuelve a mostrar el formulario, con mensajes de error, al renderizar app/views/articles/edit.html.erb
.
7.4.1 Usar Partials para Compartir Código de Vista
Nuestro formulario edit
se verá igual que nuestro formulario new
. Incluso el código será el mismo, gracias al constructor de formularios de Rails y el enrutamiento basado en recursos. El constructor de formularios configura automáticamente el formulario para realizar el tipo de solicitud adecuado, según si el objeto del modelo se ha guardado previamente.
Dado que el código será el mismo, lo vamos a extraer en una vista compartida llamada partial. Creemos app/views/articles/_form.html.erb
con el siguiente contenido:
<%= form_with model: article do |form| %>
<div>
<%= form.label :title %><br>
<%= form.text_field :title %>
<% article.errors.full_messages_for(:title).each do |message| %>
<div><%= message %></div>
<% end %>
</div>
<div>
<%= form.label :body %><br>
<%= form.text_area :body %><br>
<% article.errors.full_messages_for(:body).each do |message| %>
<div><%= message %></div>
<% end %>
</div>
<div>
<%= form.submit %>
</div>
<% end %>
El código anterior es el mismo que nuestro formulario en app/views/articles/new.html.erb
,
excepto que todas las apariciones de @article
han sido reemplazadas por article
.
Dado que los parciales son código compartido, es una buena práctica que no dependan de
variables de instancia específicas establecidas por una acción del controlador. En su lugar, pasaremos
el artículo al parcial como una variable local.
Actualicemos app/views/articles/new.html.erb
para usar el parcial a través de render
:
<h1>Nuevo Artículo</h1>
<%= render "form", article: @article %>
NOTA: El nombre de archivo de un parcial debe tener un guión bajo como prefijo, por ejemplo,
_form.html.erb
. Pero al renderizar, se hace referencia sin el guión bajo, por ejemplo, render "form"
.
Y ahora, creemos un app/views/articles/edit.html.erb
muy similar:
<h1>Editar Artículo</h1>
<%= render "form", article: @article %>
CONSEJO: Para obtener más información sobre parciales, consulta Layouts and Rendering in Rails § Using Partials.
7.4.2 Finalizando
Ahora podemos actualizar un artículo visitando su página de edición, por ejemplo,
http://localhost:3000/articles/1/edit. Para finalizar, agreguemos un enlace a la página de edición desde el final de app/views/articles/show.html.erb
:
<h1><%= @article.title %></h1>
<p><%= @article.body %></p>
<ul>
<li><%= link_to "Editar", edit_article_path(@article) %></li>
</ul>
7.5 Eliminando un Artículo
Finalmente, llegamos a la "D" (Delete) de CRUD. Eliminar un recurso es un proceso más simple
que crear o actualizar. Solo requiere una ruta y una acción del controlador. Y nuestras rutas de recursos (resources :articles
) ya proporcionan la
ruta, que mapea las solicitudes DELETE /articles/:id
a la acción destroy
del
controlador ArticlesController
.
Entonces, agreguemos una acción destroy
típica a app/controllers/articles_controller.rb
,
debajo de la acción update
:
class ArticlesController < ApplicationController
def index
@articles = Article.all
end
def show
@article = Article.find(params[:id])
end
def new
@article = Article.new
end
def create
@article = Article.new(article_params)
if @article.save
redirect_to @article
else
render :new, status: :unprocessable_entity
end
end
def edit
@article = Article.find(params[:id])
end
def update
@article = Article.find(params[:id])
if @article.update(article_params)
redirect_to @article
else
render :edit, status: :unprocessable_entity
end
end
def destroy
@article = Article.find(params[:id])
@article.destroy
redirect_to root_path, status: :see_other
end
private
def article_params
params.require(:article).permit(:title, :body)
end
end
La acción destroy
obtiene el artículo de la base de datos y llama a destroy
en él. Luego, redirige el navegador a la ruta raíz con el código de estado
303 See Other.
Hemos elegido redirigir a la ruta raíz porque ese es nuestro punto principal de acceso
para los artículos. Pero, en otras circunstancias, podrías elegir redirigir a
por ejemplo, articles_path
.
Ahora agreguemos un enlace al final de app/views/articles/show.html.erb
para que
podamos eliminar un artículo desde su propia página:
<h1><%= @article.title %></h1>
<p><%= @article.body %></p>
<ul>
<li><%= link_to "Editar", edit_article_path(@article) %></li>
<li><%= link_to "Eliminar", article_path(@article), data: {
turbo_method: :delete,
turbo_confirm: "¿Estás seguro?"
} %></li>
</ul>
En el código anterior, usamos la opción data
para establecer los atributos HTML data-turbo-method
y
data-turbo-confirm
del enlace "Eliminar". Ambos atributos se conectan a Turbo, que está incluido de forma predeterminada en las aplicaciones Rails nuevas. data-turbo-method="delete"
hará que el
enlace realice una solicitud DELETE
en lugar de una solicitud GET
.
data-turbo-confirm="¿Estás seguro?"
hará que aparezca un cuadro de diálogo de confirmación
cuando se hace clic en el enlace. Si el usuario cancela el cuadro de diálogo, la solicitud se
cancelará.
¡Y eso es todo! ¡Ahora podemos listar, mostrar, crear, actualizar y eliminar artículos! ¡InCRUDable!
8 Agregando un Segundo Modelo
Es hora de agregar un segundo modelo a la aplicación. El segundo modelo se encargará de los comentarios en los artículos.
8.1 Generando un Modelo
Vamos a utilizar el mismo generador que usamos anteriormente para crear
el modelo Article
. Esta vez crearemos un modelo Comment
para almacenar una
referencia a un artículo. Ejecuta este comando en tu terminal:
$ bin/rails generate model Comment commenter:string body:text article:references
Este comando generará cuatro archivos:
Archivo | Propósito |
---|---|
db/migrate/20140120201010_create_comments.rb | Migración para crear la tabla de comentarios en tu base de datos (tu nombre incluirá una marca de tiempo diferente) |
app/models/comment.rb | El modelo Comment |
test/models/comment_test.rb | Conjunto de pruebas para el modelo Comment |
test/fixtures/comments.yml | Comentarios de muestra para usar en las pruebas |
Primero, echa un vistazo a app/models/comment.rb
:
class Comment < ApplicationRecord
belongs_to :article
end
Esto es muy similar al modelo Article
que viste anteriormente. La diferencia
es la línea belongs_to :article
, que configura una asociación de Active Record.
Aprenderás un poco sobre asociaciones en la siguiente sección de esta guía.
La palabra clave (:references
) utilizada en el comando de shell es un tipo de dato especial para modelos.
Crea una nueva columna en la tabla de tu base de datos con el nombre del modelo proporcionado seguido de _id
que puede contener valores enteros. Para comprender mejor, analiza el archivo db/schema.rb
después de ejecutar la migración.
Además del modelo, Rails también ha creado una migración para crear la tabla correspondiente en la base de datos:
class CreateComments < ActiveRecord::Migration[7.0]
def change
create_table :comments do |t|
t.string :commenter
t.text :body
t.references :article, null: false, foreign_key: true
t.timestamps
end
end
end
La línea t.references
crea una columna entera llamada article_id
, un índice
para ella y una restricción de clave externa que apunta a la columna id
de la tabla articles
.
Ve y ejecuta la migración:
$ bin/rails db:migrate
Rails es lo suficientemente inteligente como para ejecutar solo las migraciones que aún no se hayan ejecutado en la base de datos actual, por lo que en este caso solo verás:
== CreateComments: migrating =================================================
-- create_table(:comments)
-> 0.0115s
== CreateComments: migrated (0.0119s) ========================================
8.2 Asociación de modelos
Las asociaciones de Active Record te permiten declarar fácilmente la relación entre dos modelos. En el caso de los comentarios y los artículos, podrías escribir las relaciones de esta manera:
- Cada comentario pertenece a un artículo.
- Un artículo puede tener muchos comentarios.
De hecho, esto es muy similar a la sintaxis que Rails utiliza para declarar esta
asociación. Ya has visto la línea de código dentro del modelo Comment
(app/models/comment.rb) que hace que cada comentario pertenezca a un Artículo:
class Comment < ApplicationRecord
belongs_to :article
end
Necesitarás editar app/models/article.rb
para agregar el otro lado de la
asociación:
class Article < ApplicationRecord
has_many :comments
validates :title, presence: true
validates :body, presence: true, length: { minimum: 10 }
end
Estas dos declaraciones permiten un buen comportamiento automático. Por ejemplo, si
tienes una variable de instancia @article
que contiene un artículo, puedes recuperar
todos los comentarios que pertenecen a ese artículo como un array usando
@article.comments
.
CONSEJO: Para obtener más información sobre las asociaciones de Active Record, consulta la Guía de asociaciones de Active Record.
8.3 Agregar una ruta para los comentarios
Al igual que con el controlador articles
, deberemos agregar una ruta para que Rails
sepa a dónde queremos navegar para ver los comments
. Abre el archivo
config/routes.rb
nuevamente y edítalo de la siguiente manera:
Rails.application.routes.draw do
root "articles#index"
resources :articles do
resources :comments
end
end
Esto crea comments
como un recurso anidado dentro de articles
. Esto es
otra parte de capturar la relación jerárquica que existe entre
artículos y comentarios.
CONSEJO: Para obtener más información sobre el enrutamiento, consulta la Guía de enrutamiento de Rails.
8.4 Generar un controlador
Con el modelo en mano, puedes centrarte en crear un controlador correspondiente. Nuevamente, utilizaremos el mismo generador que usamos antes:
$ bin/rails generate controller Comments
Esto crea tres archivos y un directorio vacío:
Archivo/Directorio | Propósito |
---|---|
app/controllers/comments_controller.rb | El controlador Comments |
app/views/comments/ | Aquí se almacenan las vistas del controlador |
test/controllers/comments_controller_test.rb | La prueba para el controlador |
app/helpers/comments_helper.rb | Un archivo de ayuda para las vistas |
Al igual que con cualquier blog, nuestros lectores crearán sus comentarios directamente después
de leer el artículo, y una vez que hayan agregado su comentario, se les enviará de vuelta
a la página de visualización del artículo para ver su comentario en la lista. Debido a esto, nuestro
CommentsController
está allí para proporcionar un método para crear comentarios y eliminar
comentarios de spam cuando lleguen.
Entonces, primero conectaremos la plantilla de visualización del artículo
(app/views/articles/show.html.erb
) para permitirnos crear un nuevo comentario:
<h1><%= @article.title %></h1>
<p><%= @article.body %></p>
<ul>
<li><%= link_to "Edit", edit_article_path(@article) %></li>
<li><%= link_to "Destroy", article_path(@article), data: {
turbo_method: :delete,
turbo_confirm: "Are you sure?"
} %></li>
</ul>
<h2>Add a comment:</h2>
<%= form_with model: [ @article, @article.comments.build ] do |form| %>
<p>
<%= form.label :commenter %><br>
<%= form.text_field :commenter %>
</p>
<p>
<%= form.label :body %><br>
<%= form.text_area :body %>
</p>
<p>
<%= form.submit %>
</p>
<% end %>
Esto agrega un formulario en la página de visualización del Article
que crea un nuevo comentario
llamando a la acción create
del CommentsController
. La llamada form_with
aquí utiliza
un array, que construirá una ruta anidada, como /articles/1/comments
.
Vamos a conectar el método create
en 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
Aquí verás un poco más de complejidad que en el controlador de artículos. Esto es un efecto secundario del anidamiento que has configurado. Cada solicitud de un comentario debe hacer un seguimiento del artículo al que está adjunto el comentario, por lo que se realiza una llamada inicial al método find
del modelo Article
para obtener el artículo en cuestión.
Además, el código aprovecha algunos de los métodos disponibles para una asociación. Utilizamos el método create
en @article.comments
para crear y guardar el comentario. Esto vinculará automáticamente el comentario para que pertenezca a ese artículo en particular.
Una vez que hemos creado el nuevo comentario, redirigimos al usuario de vuelta al artículo original utilizando el ayudante article_path(@article)
. Como ya hemos visto, esto llama a la acción show
del controlador ArticlesController
, que a su vez renderiza la plantilla show.html.erb
. Aquí es donde queremos que se muestre el comentario, así que vamos a agregar eso a app/views/articles/show.html.erb
.
<h1><%= @article.title %></h1>
<p><%= @article.body %></p>
<ul>
<li><%= link_to "Editar", edit_article_path(@article) %></li>
<li><%= link_to "Eliminar", article_path(@article), data: {
turbo_method: :delete,
turbo_confirm: "¿Estás seguro?"
} %></li>
</ul>
<h2>Comentarios</h2>
<% @article.comments.each do |comment| %>
<p>
<strong>Comentarista:</strong>
<%= comment.commenter %>
</p>
<p>
<strong>Comentario:</strong>
<%= comment.body %>
</p>
<% end %>
<h2>Agregar un comentario:</h2>
<%= form_with model: [ @article, @article.comments.build ] do |form| %>
<p>
<%= form.label :commenter %><br>
<%= form.text_field :commenter %>
</p>
<p>
<%= form.label :body %><br>
<%= form.text_area :body %>
</p>
<p>
<%= form.submit %>
</p>
<% end %>
Ahora puedes agregar artículos y comentarios a tu blog y hacer que aparezcan en los lugares correctos.
9 Refactorización
Ahora que tenemos los artículos y comentarios funcionando, echemos un vistazo a la plantilla app/views/articles/show.html.erb
. Se está volviendo larga y complicada. Podemos usar parciales para limpiarla.
9.1 Renderizando colecciones de parciales
Primero, vamos a crear un parcial para los comentarios y extraer la lógica de mostrar todos los comentarios del artículo. Crea el archivo app/views/comments/_comment.html.erb
y coloca lo siguiente en él:
<p>
<strong>Comentarista:</strong>
<%= comment.commenter %>
</p>
<p>
<strong>Comentario:</strong>
<%= comment.body %>
</p>
Luego, puedes cambiar app/views/articles/show.html.erb
para que se vea así:
<h1><%= @article.title %></h1>
<p><%= @article.body %></p>
<ul>
<li><%= link_to "Editar", edit_article_path(@article) %></li>
<li><%= link_to "Eliminar", article_path(@article), data: {
turbo_method: :delete,
turbo_confirm: "¿Estás seguro?"
} %></li>
</ul>
<h2>Comentarios</h2>
<%= render @article.comments %>
<h2>Agregar un comentario:</h2>
<%= form_with model: [ @article, @article.comments.build ] do |form| %>
<p>
<%= form.label :commenter %><br>
<%= form.text_field :commenter %>
</p>
<p>
<%= form.label :body %><br>
<%= form.text_area :body %>
</p>
<p>
<%= form.submit %>
</p>
<% end %>
Esto ahora renderizará el parcial en app/views/comments/_comment.html.erb
una vez por cada comentario que esté en la colección @article.comments
. A medida que el método render
itera sobre la colección @article.comments
, asigna cada comentario a una variable local con el mismo nombre que el parcial, en este caso comment
, que luego está disponible en el parcial para mostrarlo.
9.2 Renderizando un formulario parcial
También vamos a mover esa sección de agregar un nuevo comentario a su propio parcial. Nuevamente, crea un archivo app/views/comments/_form.html.erb
que contenga:
<%= form_with model: [ @article, @article.comments.build ] do |form| %>
<p>
<%= form.label :commenter %><br>
<%= form.text_field :commenter %>
</p>
<p>
<%= form.label :body %><br>
<%= form.text_area :body %>
</p>
<p>
<%= form.submit %>
</p>
<% end %>
Luego, haz que app/views/articles/show.html.erb
se vea así:
<h1><%= @article.title %></h1>
<p><%= @article.body %></p>
<ul>
<li><%= link_to "Editar", edit_article_path(@article) %></li>
<li><%= link_to "Eliminar", article_path(@article), data: {
turbo_method: :delete,
turbo_confirm: "¿Estás seguro?"
} %></li>
</ul>
<h2>Comentarios</h2>
<%= render @article.comments %>
<h2>Agregar un comentario:</h2>
<%= render 'comments/form' %>
El segundo render
simplemente define la plantilla parcial que queremos renderizar, comments/form
. Rails es lo suficientemente inteligente como para detectar la barra diagonal en esa cadena y darse cuenta de que quieres renderizar el archivo _form.html.erb
en el directorio app/views/comments
.
El objeto @article
está disponible para cualquier parcial que se renderice en la vista porque lo hemos definido como una variable de instancia.
9.3 Uso de Concerns
Los Concerns son una forma de hacer que los controladores o modelos grandes sean más fáciles de entender y gestionar. Esto también tiene la ventaja de ser reutilizable cuando varios modelos (o controladores) comparten los mismos Concerns. Los Concerns se implementan utilizando módulos que contienen métodos que representan una porción bien definida de la funcionalidad de un modelo o controlador. En otros lenguajes, los módulos a menudo se conocen como mixins.
Puedes usar concerns en tu controlador o modelo de la misma manera que usarías cualquier módulo. Cuando creaste tu aplicación con rails new blog
, se crearon dos carpetas dentro de app/
junto con el resto:
app/controllers/concerns
app/models/concerns
En el siguiente ejemplo, implementaremos una nueva funcionalidad para nuestro blog que se beneficiaría de usar un concern. Luego, crearemos un concern y refactorizaremos el código para usarlo, haciendo que el código sea más DRY y mantenible.
Un artículo de blog puede tener varios estados, por ejemplo, puede ser visible para todos (es decir, public
), o solo visible para el autor (es decir, private
). También puede estar oculto para todos pero aún recuperable (es decir, archived
). Los comentarios también pueden estar ocultos o visibles. Esto se podría representar utilizando una columna status
en cada modelo.
Primero, ejecutemos las siguientes migraciones para agregar status
a Articles
y Comments
:
$ bin/rails generate migration AddStatusToArticles status:string
$ bin/rails generate migration AddStatusToComments status:string
Y luego, actualicemos la base de datos con las migraciones generadas:
$ bin/rails db:migrate
Para elegir el estado de los artículos y comentarios existentes, puedes agregar un valor predeterminado a los archivos de migración generados agregando la opción default: "public"
y luego ejecutar las migraciones nuevamente. También puedes llamar en la consola de Rails Article.update_all(status: "public")
y Comment.update_all(status: "public")
.
CONSEJO: Para obtener más información sobre las migraciones, consulta Migraciones de Active Record.
También debemos permitir la clave :status
como parte de los strong parameters en app/controllers/articles_controller.rb
:
private
def article_params
params.require(:article).permit(:title, :body, :status)
end
y en app/controllers/comments_controller.rb
:
private
def comment_params
params.require(:comment).permit(:commenter, :body, :status)
end
Dentro del modelo article
, después de ejecutar una migración para agregar una columna status
usando el comando bin/rails db:migrate
, agregaríamos:
class Article < ApplicationRecord
has_many :comments
validates :title, presence: true
validates :body, presence: true, length: { minimum: 10 }
VALID_STATUSES = ['public', 'private', 'archived']
validates :status, inclusion: { in: VALID_STATUSES }
def archived?
status == 'archived'
end
end
y en el modelo Comment
:
class Comment < ApplicationRecord
belongs_to :article
VALID_STATUSES = ['public', 'private', 'archived']
validates :status, inclusion: { in: VALID_STATUSES }
def archived?
status == 'archived'
end
end
Luego, en nuestra plantilla de la acción index
(app/views/articles/index.html.erb
), usaríamos el método archived?
para evitar mostrar cualquier artículo que esté archivado:
<h1>Artículos</h1>
<ul>
<% @articles.each do |article| %>
<% unless article.archived? %>
<li>
<%= link_to article.title, article %>
</li>
<% end %>
<% end %>
</ul>
<%= link_to "Nuevo Artículo", new_article_path %>
De manera similar, en nuestra vista parcial de comentarios (app/views/comments/_comment.html.erb
), usaríamos el método archived?
para evitar mostrar cualquier comentario que esté archivado:
<% unless comment.archived? %>
<p>
<strong>Comentarista:</strong>
<%= comment.commenter %>
</p>
<p>
<strong>Comentario:</strong>
<%= comment.body %>
</p>
<% end %>
Sin embargo, si observas nuevamente nuestros modelos ahora, verás que la lógica está duplicada. Si en el futuro aumentamos la funcionalidad de nuestro blog, por ejemplo, para incluir mensajes privados, podríamos encontrarnos duplicando la lógica nuevamente. Aquí es donde los concerns son útiles.
Un concern es responsable solo de un subconjunto enfocado de la responsabilidad del modelo; los métodos en nuestro concern estarán relacionados con la visibilidad de un modelo. Llamemos a nuestro nuevo concern (módulo) Visible
. Podemos crear un nuevo archivo dentro de app/models/concerns
llamado visible.rb
y almacenar todos los métodos de estado que se duplicaron en los modelos.
app/models/concerns/visible.rb
module Visible
def archived?
status == 'archived'
end
end
Podemos agregar nuestra validación de estado al concern, pero esto es un poco más complejo ya que las validaciones son métodos llamados a nivel de clase. El ActiveSupport::Concern
(API Guide) nos brinda una forma más sencilla de incluirlas:
module Visible
extend ActiveSupport::Concern
VALID_STATUSES = ['public', 'private', 'archived']
included do
validates :status, inclusion: { in: VALID_STATUSES }
end
def archived?
status == 'archived'
end
end
Ahora, podemos eliminar la lógica duplicada de cada modelo e incluir nuestro nuevo módulo Visible
:
En app/models/article.rb
:
class Article < ApplicationRecord
include Visible
has_many :comments
validates :title, presence: true
validates :body, presence: true, length: { minimum: 10 }
end
y en app/models/comment.rb
:
class Comment < ApplicationRecord
include Visible
belongs_to :article
end
Los métodos de clase también se pueden agregar a los concerns. Si queremos mostrar un recuento de artículos públicos o comentarios en nuestra página principal, podríamos agregar un método de clase a Visible de la siguiente manera:
module Visible
extend ActiveSupport::Concern
VALID_STATUSES = ['public', 'private', 'archived']
included do
validates :status, inclusion: { in: VALID_STATUSES }
end
class_methods do
def public_count
where(status: 'public').count
end
end
def archived?
status == 'archived'
end
end
Luego, en la vista, puedes llamarlo como cualquier método de clase:
<h1>Artículos</h1>
¡Nuestro blog tiene <%= Article.public_count %> artículos y contando!
<ul>
<% @articles.each do |article| %>
<% unless article.archived? %>
<li>
<%= link_to article.title, article %>
</li>
<% end %>
<% end %>
</ul>
<%= link_to "Nuevo Artículo", new_article_path %>
Para terminar, agregaremos un cuadro de selección a los formularios y permitiremos al usuario seleccionar el estado al crear un nuevo artículo o publicar un nuevo comentario. También podemos especificar el estado predeterminado como public
. En app/views/articles/_form.html.erb
, podemos agregar:
<div>
<%= form.label :status %><br>
<%= form.select :status, ['public', 'private', 'archived'], selected: 'public' %>
</div>
y en app/views/comments/_form.html.erb
:
<p>
<%= form.label :status %><br>
<%= form.select :status, ['public', 'private', 'archived'], selected: 'public' %>
</p>
10 Eliminación de comentarios
Otra característica importante de un blog es poder eliminar comentarios no deseados. Para hacer esto, necesitamos implementar un enlace de algún tipo en la vista y una acción destroy
en el CommentsController
.
Entonces, primero, agreguemos el enlace de eliminación en el parcial app/views/comments/_comment.html.erb
:
<% unless comment.archived? %>
<p>
<strong>Comentarista:</strong>
<%= comment.commenter %>
</p>
<p>
<strong>Comentario:</strong>
<%= comment.body %>
</p>
<p>
<%= link_to "Eliminar Comentario", [comment.article, comment], data: {
turbo_method: :delete,
turbo_confirm: "¿Estás seguro?"
} %>
</p>
<% end %>
Al hacer clic en este nuevo enlace "Eliminar Comentario", se enviará un DELETE /articles/:article_id/comments/:id
a nuestro CommentsController
, que luego puede usar esto para encontrar el comentario que queremos eliminar, así que agreguemos una acción destroy
a nuestro controlador (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
def destroy
@article = Article.find(params[:article_id])
@comment = @article.comments.find(params[:id])
@comment.destroy
redirect_to article_path(@article), status: :see_other
end
private
def comment_params
params.require(:comment).permit(:commenter, :body, :status)
end
end
La acción destroy
encontrará el artículo que estamos viendo, localizará el comentario dentro de la colección @article.comments
y luego lo eliminará de la base de datos y nos enviará de vuelta a la acción show
del artículo.
10.1 Eliminación de objetos asociados
Si eliminas un artículo, sus comentarios asociados también deben eliminarse, de lo contrario, simplemente ocuparían espacio en la base de datos. Rails te permite usar la opción dependent
de una asociación para lograr esto. Modifica el modelo Article, app/models/article.rb
, de la siguiente manera:
class Article < ApplicationRecord
include Visible
has_many :comments, dependent: :destroy
validates :title, presence: true
validates :body, presence: true, length: { minimum: 10 }
end
11 Seguridad
11.1 Autenticación básica
Si publicaras tu blog en línea, cualquier persona podría agregar, editar y eliminar artículos o eliminar comentarios.
Rails proporciona un sistema de autenticación HTTP que funcionará bien en esta situación.
En el ArticlesController
necesitamos tener una forma de bloquear el acceso a las diversas acciones si la persona no está autenticada. Aquí podemos usar el método http_basic_authenticate_with
de Rails, que permite el acceso a la acción solicitada si ese método lo permite.
Para usar el sistema de autenticación, lo especificamos en la parte superior de nuestro ArticlesController
en app/controllers/articles_controller.rb
. En nuestro caso, queremos que el usuario esté autenticado en cada acción excepto index
y show
, así que escribimos eso:
class ArticlesController < ApplicationController
http_basic_authenticate_with name: "dhh", password: "secret", except: [:index, :show]
def index
@articles = Article.all
end
# fragmento por brevedad
También queremos permitir solo a usuarios autenticados eliminar comentarios, así que en el CommentsController
(app/controllers/comments_controller.rb
) escribimos:
class CommentsController < ApplicationController
http_basic_authenticate_with name: "dhh", password: "secret", only: :destroy
def create
@article = Article.find(params[:article_id])
# ...
end
# fragmento por brevedad
Ahora, si intentas crear un nuevo artículo, te encontrarás con un desafío básico de autenticación HTTP:
Después de ingresar el nombre de usuario y la contraseña correctos, permanecerás autenticado hasta que se requiera un nombre de usuario y contraseña diferentes o se cierre el navegador. Hay otros métodos de autenticación disponibles para las aplicaciones de Rails. Dos complementos populares de autenticación para Rails son el motor de Rails Devise y la gema Authlogic, junto con otros más.
11.2 Otras consideraciones de seguridad
La seguridad, especialmente en aplicaciones web, es un área amplia y detallada. La seguridad en tu aplicación de Rails se cubre con más detalle en la Guía de seguridad de Ruby on Rails.
12 ¿Qué sigue?
Ahora que has visto tu primera aplicación de Rails, siéntete libre de actualizarla y experimentar por tu cuenta.
Recuerda, no tienes que hacer todo sin ayuda. Si necesitas asistencia para comenzar y trabajar con Rails, no dudes en consultar estos recursos de soporte:
13 Problemas de configuración a tener en cuenta
La forma más fácil de trabajar con Rails es almacenar todos los datos externos como UTF-8. Si no lo haces, las bibliotecas de Ruby y Rails a menudo podrán convertir tus datos nativos en UTF-8, pero esto no siempre funciona de manera confiable, por lo que es mejor asegurarse de que todos los datos externos sean UTF-8.
Si has cometido un error en esta área, el síntoma más común es un diamante negro con un signo de interrogación en el interior que aparece en el navegador. Otro síntoma común es que aparezcan caracteres como "ü" en lugar de "ü". Rails toma una serie de medidas internas para mitigar las causas comunes de estos problemas que se pueden detectar y corregir automáticamente. Sin embargo, si tienes datos externos que no se almacenan como UTF-8, ocasionalmente puede resultar en este tipo de problemas que Rails no puede detectar y corregir automáticamente.
Dos fuentes muy comunes de datos que no son UTF-8:
- Tu editor de texto: la mayoría de los editores de texto (como TextMate) se guardan de forma predeterminada como UTF-8. Si tu editor de texto no lo hace, esto puede hacer que los caracteres especiales que ingreses en tus plantillas (como é) aparezcan como un diamante con un signo de interrogación en el navegador. Esto también se aplica a tus archivos de traducción i18n. La mayoría de los editores que no tienen UTF-8 como predeterminado (como algunas versiones de Dreamweaver) ofrecen una forma de cambiar la configuración predeterminada a UTF-8. Hazlo.
- Tu base de datos: Rails convierte de forma predeterminada los datos de tu base de datos a UTF-8 en el límite. Sin embargo, si tu base de datos no utiliza UTF-8 internamente, es posible que no pueda almacenar todos los caracteres que ingresan tus usuarios. Por ejemplo, si tu base de datos utiliza Latin-1 internamente y un usuario ingresa un carácter ruso, hebreo o japonés, los datos se perderán para siempre una vez que ingresen a la base de datos. Si es posible, utiliza UTF-8 como almacenamiento interno de tu base de datos.
Comentarios
Se te anima a ayudar a mejorar la calidad de esta guía.
Por favor, contribuye si encuentras algún error tipográfico o factual. Para empezar, puedes leer nuestra contribución a la documentación sección.
También puedes encontrar contenido incompleto o desactualizado. Por favor, añade cualquier documentación faltante para main. Asegúrate de revisar Edge Guides primero para verificar si los problemas ya están resueltos o no en la rama principal. Consulta las Directrices de las Guías de Ruby on Rails para el estilo y las convenciones.
Si por alguna razón encuentras algo que corregir pero no puedes solucionarlo tú mismo, por favor abre un problema.
Y por último, cualquier tipo de discusión sobre la documentación de Ruby on Rails es muy bienvenida en el Foro oficial de Ruby on Rails.