Recursos REST

REST es una forma particular de definir las rutas HTTP para hacer el CRUD de los modelos en nuestra aplicación Web.
A los modelos también se les conoce como recursos.
Por ejemplo, las rutas para hacer el CRUD de un modelo Product serían las siguientes:
Verbo
Path
Controlador#Acción
Descripción
GET
/products
products#index
muestra la lista de productos
GET
/products/new
products#new
muestra el formulario para crear un producto
POST
/products
products#create
crea un nuevo producto
GET
/products/:id
products#show
muestra los detalles de un producto
GET
/products/:id/edit
products#edit
muestra el formulario para editar un producto
PATCH/PUT
/products/:id
products#update
actualiza un producto
DELETE
/products/:id
products#destroy
eliminar un producto

Definiendo las rutas de un recurso

Para definir las rutas de un recurso en config/routes.rb se utiliza la palabra resources:
resources :products
La línea anterior es equivalente a las siguientes 8 rutas:
get "/products", to: "products#index"
get "/products/new", to: "products#new"
post "/products", to: "products#create"
get "/products/:id", to: "products#show"
get "/products/:id/edit", to: "products#edit"
put "/products/:id", to: "products#update"
patch "/products/:id", to: "products#update"
delete "/products/:id", to: "products#destroy"
Para actualizar un recurso podemos utilizar el verbo PUT o PATCH.

Utilizando el generador de recursos

Así como desde la consola podemos generar un modelo y un controlador, existe un generador que nos permite crear un recurso, que genera el modelo y el controlador.
Por ejemplo, para generar el recurso de productos, es decir, el modelo Product y el controlador ProductsController utilizaríamos el siguiente comando:
$ rails g resources products name description:text price:decimal
Ese comando genera, entre otros, lo siguiente:
  • El modelo Product en app/models/product.rb
  • La migración que se va a utilizar para crear la tabla.
  • El controlador en app/controllers/products_controller.rb
  • El archivo products.scss en app/assets/stylesheets/.
  • El archivo products.coffee en app/assets/javascripts/.
  • La línea resources :products en config/routes.rb.
No olvides correr la migración:
$ rails db:migrate

El controlador y las vistas de un recurso

El controlador de un recurso está compuesto de las siguientes acciones (métodos):
  • index para mostrar la lista de registros.
  • show para mostrar los detalles de un registro.
  • new para mostrar el formulario de creación.
  • create para crear un registro.
  • edit para mostrar el formulario de edición.
  • update para actualizar un registro.
  • destroy para eliminar un registro.
Veamos cómo se implementaría cada una de estas acciones para un modelo Product.

Listando registros

El método index en el controlador ProductsController quedaría de la siguiente forma:
def index
@products = Product.all
end
Y la vista en app/views/products/index.html.erb:
<h1>Productos</h1>
<table>
<thead>
<tr>
<th>Nombre</th>
<th>Descripción</th>
<th>Precio</th>
<th></th>
</tr>
</thead>
<tbody>
<% @products.each do |product| %>
<tr>
<td><%= product.name %></td>
<td><%= product.description %></td>
<td><%= product.price %></td>
<td>
<%= link_to "Mostrar", product %>
<%= link_to "Editar", edit_product_path(product) %>
<%= link_to "Eliminar", product, method: :delete, data: { confirm: "¿Estás seguro de eliminar este producto?" } %>
</td>
</tr>
<% end %>
</tbody>
</table>

Mostrando los detalles de un registro

El método show quedaría de la siguiente forma:
def show
@product = Product.find(params[:id])
end
Y la vista en app/views/products/show.html.erb quedaría de la siguiente forma:
<h1>Detalles del Producto</h1>
<div>Nombre: <%= @product.name %></div>
<div>Descripción: <%= @product.description %></div>
<div>Precio: <%= @product.price %></div>

Mostrando el formulario de creación

La creación de un registro se divide en dos: la ruta que muestra el formulario y la ruta que inserta el registro en la base de datos cuando alguien llena el formulario.
Para mostrar el formulario se utiliza el método new que se implementaría de la siguiente forma:
def new
@product = Product.new
end
Y la vista en app/views/products/new.html.erb sería la siguiente:
<%= form_for @product do |f| %>
<div>
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<div>
<%= f.label :description %><br>
<%= f.text_area :description %>
</div>
<div>
<%= f.label :price %><br>
<%= f.number_field :price %>
</div>
<div>
<%= f.submit %>
</div>
<% end %>

Creando un registro

Para crear el registro necesitamos implementar el método create y un método de ayuda product_params:
def create
@product = Product.new(product_params)
if @product.save
redirect_to products_path
else
render :new
end
end
private
def product_params
params.require(:product).permit(:name, :description, :price)
end
La razón por la que tuvimos que crear ese método de ayuda es por un tema de seguridad. Ese método está filtrando la información que queremos que se pueda cambiar directamente. A esto se le conoce como strong parameters.
No necesitamos una vista para esta acción.

Mostrando el formulario de edición

Para mostrar el formulario de editar se utiliza el método edit que se implementaría de la siguiente forma:
def edit
@product = Product.find(params[:id])
end
Y la vista en app/views/products/edit.html.erb sería la siguiente:
<%= form_for @product do |f| %>
<div>
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<div>
<%= f.label :description %><br>
<%= f.text_area :description %>
</div>
<div>
<%= f.label :price %><br>
<%= f.number_field :price %>
</div>
<div>
<%= f.submit %>
</div>
<% end %>
El formulario de edición es igual al de creación, así que una buena práctica es moverlo a un partial _form.html.erb.

Actualizando un registro

Para actualizar el registro necesitamos implementar el método update y el método de ayuda product_params que ya creamos previamente:
def update
@product = Product.find(params[:id])
if @product.update(product_params)
redirect_to products_path
else
render :edit
end
end
No necesitamos una vista para esta acción.

Eliminando un registro

Para eliminar un registro necesitamos implementar el método destroy:
def destroy
product = Product.find(params[:id])
product.destroy
redirect_to products_path
end

Limitando las acciones

Para limitar las rutas y acciones de un recurso utiliza la opción only o except de resources en config/routes.rb:
resources products, only: [:index, :new, :create]
resources articles, except: [:destroy]
En este caso, para products sólo se generarían 3 rutas: la de listar, la de mostrar el formulario y la de crear.
Para resources se generarían todas excepto la de eliminar.

Notificaciones

Para mostrar notificaciones de éxito al crear, editar y eliminar registros utilizando el flash.
Por ejemplo, para mostrar una notificación de éxito al eliminar un producto:
def destroy
product = Product.find(params[:id])
product.destroy
flash[:notice] = "El producto ha sido eliminado exitosamente"
redirect_to products_path
end
El notice también se puede agregar directamente sobre el redirect_to:
redirect_to products_path, notice: "El producto ha sido eliminado exitosamente"

Mostrando las notificaciones

Puedes agregar el siguiente código donde quieres que aparezcan los mensajes de flash en el layout app/views/layouts/application.html.erb:
<% flash.each do |name, msg| -%>
<%= content_tag :div, msg, class: name %>
<% end -%>

Filtros

Puedes ejecutar métodos que se ejecutan antes o después de una acción de un controlador utilizando before_action y after_action.
Por ejemplo, podemos verificar si los usuarios ya están autenticados con un before_action:
class ApplicationController < ActionController::Base
before_action :require_login
private
def require_login
unless logged_in?
flash[:error] = "You must be logged in to access this section"
redirect_to new_login_url # halts request cycle
end
end
end
Si haces un render o redirect_to en un before_action la acción no se ejecuta.
Los filtros son heredados, así que si defines el filtro en ApplicationController se va a ejecutar en todos los controladores de la aplicación.

Limitando el alcance de los filtros

Puedes limitar las acciones a las que aplica un filtro utilizando las opciones only o except.
Por ejemplo:
before_action :require_login, only: [:new, :create]
before_action :require_login, except: [:index]