Tenemos que tener en cuenta que Rails es un framework de opinión y que uno de sus principios subyacentes es la convención sobre la configuración. Esto significa que, a no ser que tengas una buena razón, no deberías romper ninguno de los patrones y convenciones de nomenclatura anteriores.
Por ejemplo, si decidiera codificar la acción de indexación como get 'cars/index'
o la acción de creación como post 'car/create'
, esto rompería el comportamiento de Rails para métodos de ayuda como form_for, link_to y redirect_to.
La consecuencia de romper las convenciones sin intención es el caos y produce un enredo que te tendrá luchando contra el framework durante horas y horas.
Rutas de recursos singulares
También es posible tener rutas para recursos singulares, es decir, recursos que se pueden buscar sin especificar un :id
, como los que se aplican a un current_user
o current_account
. Esto se puede conseguir utilizando los recursos singulares incorporados en Rails.
resource :profile
Rutas anidadas
A veces necesitamos anidar recursos dentro de otros recursos. Por ejemplo, si quiero crear una reserva para un coche específico en mi garaje, podría ser más conveniente coger el :id
de ese coche de la URL en lugar de un campo oculto del formulario, que podría ser manipulado por un usuario malicioso. La forma de crear recursos anidados en rails es la siguiente:
resources :cars do
resources :bookings
end
Esto crea las siete acciones CRUD para las reservas anidadas en los coches. Sin embargo, normalmente no se necesitan las siete rutas. E incluso cuando las necesitas, ciertamente no todas tienen que estar anidadas. Digamos que lo que necesitamos es la acción para crear una reserva (supondremos que el formulario para crear una reserva vive en la acción mostrar del coche) y para editar y actualizar una reserva existente. ¿Necesito conocer el :id
de un coche para editar/actualizar una reserva? La respuesta es obviamente no. Por lo tanto, quiero que mi aplicación responda a las siguientes URLs:
# to CREATE a booking
POST request to http://my-ruby-garage.com/cars/:id/bookings/create# to EDIT a booking
GET request to http://my-ruby-garage.com/bookings/:id/edit# to UPDATE a booking
PATCH request to http://my-ruby-garage.com/bookings/:id/
Que son generadas por el siguiente código:
resources :cars do
resources :bookings, only:
end
resources :bookings, only:
Rails nos permite modificar cuál de las rutas CRUD estándar debe ser generada suministrando un array de símbolos o un solo símbolo a la opción :only
o su opuesto :except
.
Rutas no CRUD
No estamos limitados a las siete rutas de recursos CRUD. También podemos especificar rutas que se apliquen a un solo recurso (rutas de miembros) o a varios recursos (rutas de colección).
Rutas de miembros
Continuando con nuestro ejemplo de RubyGarage, los coches pueden ser aparcados o retirados del garaje. Imaginemos que tenemos acciones de controlador que son capaces de realizar estas acciones que modifican algún atributo de un coche específico.
resources :cars do
member do
patch :park
patch :remove
end
end
Lo anterior nos permite enviar peticiones de parche a http://my-ruby-garage.com/cars/:id/park
y http://my-ruby-garage.com/cars/:id/remove
y encontrar el coche específico en el controlador antes de modificar el recurso en consecuencia.
Rutas de colección
Del mismo modo, a veces queremos realizar alguna acción en varios recursos al mismo tiempo. Por ejemplo, quizá necesitemos aparcar y retirar una colección de coches a la vez. Podemos utilizar el siguiente código:
resources :cars do
collection do
post :park, as: :bulk_park
post :remove, as: :bulk_remove
end
end
Aquí, configuramos nuestra aplicación para que responda a http://my-ruby-garage.com/cars/park
y http://my-ruby-garage.com/cars/remove
y, respectivamente, nombramos estas acciones bulk_park y bulk_remove. Recuerda que podemos utilizar rutas con nombre para generar las rutas URL dentro de nuestra aplicación. Para construir un enlace de ruta para aparcar una colección de coches, podríamos utilizar:
<%= link_to "Park Cars", bulk_park_path, method: :post, class: "button" %>
Rutas con nombre
Las rutas con nombre prefijan la ruta URL de los recursos dentro del bloque del espacio de nombres y tratarán de localizar los controladores relevantes bajo un módulo con el mismo nombre que el espacio de nombres. Los usos típicos de este patrón son los espacios de nombres admin y los espacios de nombres api.
namespace :factory do
resources :cars
end
Este ejemplo construye las siguientes rutas:
Prefix Verb URI Pattern Controller#Action factory_cars GET /factory/cars(.:format) factory/cars#index
POST /factory/cars(.:format) factory/cars#create factory_car GET /factory/cars/:id(.:format) factory/cars#show
PATCH /factory/cars/:id(.:format) factory/cars#update
PUT /factory/cars/:id(.:format) factory/cars#update
DELETE /factory/cars/:id(.:format) factory/cars#destroy
Y el controlador también tendría que tener un espacio de nombres :
class Factory::CarsController < ApplicationController
# ...
end
Rutas Específicas
El método scope
nos permite permanecer DRY y agrupar reglas de enrutamiento relacionadas. Cuando se utiliza sin opciones, es similar a namespace
, pero los controladores pertinentes no tienen que ser namespaced con un módulo.
scope :factory do
resources :cars
end
Genera las siguientes rutas:
Prefix Verb URI Pattern Controller#Action factory_cars GET /factory/cars(.:format) cars#index
POST /factory/cars(.:format) cars#create factory_car GET /factory/cars/:id(.:format) cars#show
PATCH /factory/cars/:id(.:format) cars#update
PUT /factory/cars/:id(.:format) cars#update
DELETE /factory/cars/:id(.:format) cars#destroy
Scope admite tres opciones: módulo, ruta y como.
Supongamos que tenemos un asistente de varios pasos para crear un nuevo coche en la fábrica que es manejado por un controlador que vive en un módulo de asistentes. Queremos que la ruta aparezca en el navegador como http://my-ruby-garage.com/create-a-car
y poder referenciar esta ruta dentro de nuestra aplicación como create_car
. Hay un módulo Wizard::Car
que conoce cada paso del asistente.
scope module: 'wizards', path: 'create-a-car', as: 'create_car' do
Wizard::Car::STEPS.each do |step|
get step, to: "cars##{step}"
end
post :validate-step, to: 'cars#validate_step'
end
El código anterior crea el mismo patrón para cada paso. Por ejemplo, el paso1 es accesible en el navegador a través de la URL http://my-ruby-garage.com/create-a-car/step1
, su correspondiente formulario envía una petición de post a http://my-ruby-garage.com/create-a-car/validate-step
, y la ruta puede ser invocada llamando a create_car_step1_path
.
Redirecciones de ruta
Rails también nos permite hacer redirecciones directamente en routes.rb
. En el ejemplo anterior, quizás quiero que cualquiera que aterrice en http://my-ruby-garage.com/create-a-car
sea redirigido automáticamente al primer paso. Esto se puede conseguir con el siguiente código:
get 'create-a-car',
to: redirect("/create-a-car/#{Wizard::SpotAccount::STEPS.first}")
Route Defaults
Podemos definir parámetros por defecto en una ruta pasando un hash para la opción :defaults
.
resources :cars do
collection do
get :export, defaults: { format: 'csv' }
end
end
Usando esto, al visitar http://my-ruby-garage.com/cars/export
se llamará a la acción de exportación en el controlador de coches y la acción del controlador correspondiente responderá con un csv por defecto.
Route Globbing
Usando segmentos comodín (fragmentos prefijados con una estrella), podemos especificar que las partes restantes de una ruta deben coincidir con un parámetro concreto. Esto se llama «route globbing».
get '/rent/*slugs', to: 'cars#index', as: :rent_cars
Esta ruta coincidiría con http://my-ruby-garage.com/rent/lisbon/suv-sedan
y establecería los parámetros como «lisbon/suv-sedan». Esto podría usarse en un sistema de búsqueda o filtrado contra nuestra base de datos para encontrar coches en Lisboa del tipo suv o sedán.
Estoy escribiendo una gema Slugcatcher con esta funcionalidad en mente para etiquetar modelos de Rails que puedan ser buscados como route slugs.