Tips para ordenar tus rutas en Ruby on Rails

Nicolás Galdámez
Unagi
Published in
6 min readJun 4, 2024

El archivo de rutas (routes.rb) es una parte muy importante de cualquier proyecto de desarrollo. Es en cierto modo el mapa de nuestra aplicación y es clave que se mantenga ordenado. Usualmente comenzamos con todo prolijito, ordenando las rutas por módulos o alfabéticamente, pero con el correr del tiempo se termina convirtiendo en una selva difícil de atravesar.

Es como ese cuarto/depósito que tenés en tu casa donde guardás esas cosas que no sabés dónde poner. Inicialmente sabés qué hay adentro, pero un día entraste a buscar algo y te encontrás con un campo de batalla, donde hay un acolchado viejo encima de una caja de madera llena de humedad de tu abuela y encima de eso un cuadro con una foto tuya de bebé junto a el primer collar que usó Tony, tu primera mascota.

Como no soy muy amante de ese caos, en este artículo voy a contarte algunas prácticas que hemos adquirido con los años que nos han sido muy útiles para organizar ese cuarto/depósito llamado routes.rb.

Aquí te dejo la lista de los tips que menciono en el artículo ordenados según el impacto que han tenido en mis proyectos. Se pueden aplicar todos de forma aislada así que no dudes en saltar directamente al que más te interese si no querés leer todos.

  1. Rutas ordenadas alfabéticamente
  2. Resource y resources
  3. Only, no except
  4. Namespaces
  5. Constraints
  6. Concerns

1. Rutas ordenadas alfabéticamente

Este tip no es el primero por mera casualidad, sino que lo considero el más importante de todos. Es el que nos ha traído los mejores resultados a la hora de ordenar las rutas. No solo por el hecho de organizarlas alfabéticamente, sino por la importancia de establecer acuerdos con el equipo.

Personalmente, considero que una de las premisas más importantes de Ruby on Rails es Convention over Configuration. Seguir las convenciones que propone el framework suele traer buenos resultados, porque hace que cualquier dev del equipo no tenga dudas a la hora de encontrar alguna pieza de código o saber dónde tirar alguna instrucción nueva. Esto es muy importante, y no solo porque nos ahorra tiempo, sino que es una decisión menos que tomar. No por nada Steve Jobs vestía lo mismo todos los días.

Con las rutas deberíamos hacer lo mismo, establecer reglas en el equipo para que el mantenimiento de las mismas sea lo más sencillo y prolijo posible.

En distintos proyectos hemos seguido distintas convenciones: ordenar por módulos, separar en distintos archivos, ordenar alfabéticamente, entre otras. Sin dudas, la más sencilla y práctica de todas ha sido la de ordenar alfabéticamente el archivo de rutas. Quizás si se tienen muchas rutas sirve el separar en distintos archivos, pero trataría de evitarlo a no ser que lo veamos muy necesario.

Con orden alfabético me refiero a ordenar TODAS las rutas alfabéticamente. Esto incluye resources y namespaces a nivel general y cada anidamiento.

2. Resource y resources

Así como la capa de negocio en cualquier proyecto está representada por objetos que se comunican e interactúan entre sí, me gusta ver las rutas como acciones sobre recursos. Es por esto, que casi todas las rutas que definimos en nuestro proyectos suelen estar asociadas a un recurso en particular.

Rails, provee distintos mecanismos para manejar esto, aunque lo básico sería usar las instrucciones resources y resource, con todas las variantes que estos incluyen.

A su vez, si lo vemos necesario, las rutas resource/s se pueden anidar y tener algo como lo siguiente:

resources :articles do
resources :comments
end

⚠️ Los anidamientos suelen ser muy útiles aunque debemos tener cuidado porque pueden traer cierta complejidad. Lo que sí recomiendo es definitivamente no usar más de un nivel de anidamiento.

Shallow nesting

Un problema con el anidamiento, es que nos quedan las rutas member del recurso anidado dentro del padre.

En el ejemplo anterior, las rutas para comments quedarían dentro de un artículo:

GET /articles/:article_id/comments
GET /articles/:article_id/comments/new
POST /articles/:article_id/comments

GET /articles/:article_id/comments/:id <===== ⚠️
GET /articles/:article_id/comments/:id/edit <===== ⚠️
DELETE /articles/:article_id/comments/:id <===== ⚠️
PUT/PATCH /articles/:article_id/comments/:id <===== ⚠️

Las primeras tres rutas las veo bien, aunque las rutas que involucran un comentario en particular quedan raras dentro de un artículo.

Para solucionar esto, podríamos definir las rutas de este modo:

resources :articles do
resources :comments, only: [:index, :new, :create]
end
resources :comments, only: [:show, :edit, :update, :destroy]

Esto nos generaría las siguientes rutas, que a mi gusto tiene mucho más sentido:

GET /articles/:article_id/comments
GET /articles/:article_id/comments/new
POST /articles/:article_id/comments

GET /comments/:id <===== ✅
GET /comments/:id/edit <===== ✅
DELETE /comments/:id <===== ✅
PUT/PATCH /comments/:id <===== ✅

Esto mismo podría realizarse a través del parámetro shallow y tendríamos el mismo resultado:

resources :articles do
resources :comments, shallow: true
end

¿Qué hago con las rutas que no están asociadas a un resource?

Lo tomaría como un bad smell. Es bastante probable que detrás de esa ruta haya un resource/s escondido.

De todas formas, hay algunas rutas sueltas en nuestros routes.rb. Los ejemplos que se me ocurren son login, logout y algún healthcheck.

post 'login' => 'sessions#login', as: :login
delete 'logout' => 'sessions#logout', as: :logout
get 'up' => 'rails/health#show', as: :rails_health_check

3. Only, no except

Como dev soy de la regla “siempre es mejor ser explícito que implícito”. Muchas veces por ahorrarnos una par de palabras, una línea de código, un comentario (sí, banco los comentarios), terminamos generando problemas en nuestra aplicación o le complicamos la existencia a un futuro dev que tome nuestro trabajo.

Para las rutas, sigo la misma regla, y prefiero más el only que el except. El uso de except puede ser cómodo, pero podríamos terminar generando rutas que luego no se terminan usando.

Es por esto que lo primero que hago al generar un resource en mis rutas es añadirle el parámetro only.

resources :products, only: %i[index new create show]
resources :users, only: %i[index new create destroy]

En el único caso donde no incluyo el only — y rompiendo mi propia regla — es cuando el recurso contiene todas las rutas. Es decir, en lugar de tener un only con todas las rutas, no escribo el only directamente.

4. Namespaces

Los namespaces nos permiten separar nuestras rutas en grupos de controllers, algo muy útil para separar lógica por módulos.

Son muy valiosos en las siguientes situaciones:

1) Cuando se tienen accesos claramente separados uno de otro. Por ejemplo, un backoffice de un front.

2) Cuando el sistema crece y empezamos a generar demasiada complejidad y queremos separar los controllers en grupos / módulos.

namespace :admin do
resources :payments
resources :users
end

namespace :ads do
resource :report, only: %i[show]
end

namespace :finance do
resource :report, only: %i[show]
end

De esta forma tendríamos los siguientes controllers:

Admin::PaymentsController # app/controllers/admin/payments_controller.rb
Admin::UsersController # app/controllers/admin/users_controller.rb
Ads::ReportController # app/controllers/ads/report_controller.rb
Finance::ReportController # app/controllers/finance/report_controller.rb

5. Constraints

Los constraints son restricciones que se pueden asignar a las rutas. Al definir un constraint se invoca el método indicado sobre el request object y se compara el valor retornado con el valor parametrizado.

Un ejemplo clásico es la restricción de subdominios. Por ejemplo, podríamos definir un constraint para las rutas que son de admin.

namespace :admin do
constraints subdomain: 'admin' do
resources :users
end
end

6. Concerns

Otra herramienta útil son los concerns. Estos permiten definir rutas comunes que luego pueden ser utilizadas para distintos resources.

Para dar un ejemplo, te comparto un concern que utilizamos en un desarrollo reciente. El sistema tenía distintos recursos y esos recursos contaban con la posibilidad de reordenarse dentro de un listado, es decir cambiar su posición. Como la ruta para cambiar dicha posición nos quedaba repetida por todos lados, la extrajimos en un concern:

concern :positionable do
patch :update_position, on: :member
end

resources :categories, only: %i[index new create], concerns: %i[positionable]
resources :category_groups, concerns: %i[positionable]
resources :sections, only: %i[index new create destroy], concerns: %i[positionable]

⚠️ Los concerns generan una indirección en el código. Es importante preguntarse si vale la pena extraer las rutas en común. Hay veces que es preferible repetir código que generar la indirección.

Estos son algunos de las prácticas que seguimos para construir nuestro archivos de rutas. No quiere decir que debas seguir las mismas reglas con tu equipo, son las que nos funcionaron a nosotros.

Lo más importante es acordar las convenciones con tu equipo para que el archivo de rutas esté lo más prolijo y mantenible posible.

Unagi brinda servicios de desarrollo de software en Ruby y React. Podés conocer más de nosotros en cualquiera de nuestros canales.

--

--

Nicolás Galdámez
Unagi
Editor for

Co-fundador de @unagi. Me gusta el cine, la lectura, y la ensalada de frutas.