Rails Routing from the Outsise In: Definir nuestras propias rutas en una aplicación de Rails


Rails Creo que merece la pena dedicarle un post al tema de las Rutas de Rails «Rails Routing from the Outside In« y cómo se definen, por la importancia y relevancia que puede llegar a tener, por eso y que además he oído decir a compañeros de trabajo que si no defines las rutas y las dejas por defecto, puede ser un autentico infierno.

Ahora bien, si tengo que responder a la pregunta ¿qué son las rutas en Rails?, yo diría que routes hace básicamente dos cosas. Una es, que es capaz de reconocer lo que le llega y ver en la URL, lo que contiene, para enviar a la acción correspondiente del controlador. Otra cosa que hace routes es, que nos facilita mediante una serie de helpers en las vistas la navegación, sin poner URL con path del estilo ‘/user/compra/artículo’, sino del estilo de user_path, es decir, permite la generación dinámica de direcciones URL para que podamos utilizarlo como argumentos a métodos como link_to y redirect_to Esto nos aporta un gran ventaja como puedes ya imaginarte.

Pero para acercarnos un poco más a las rutas en Rails, pensemos por un momento que cuando estamos solicitando desde nuestro navegador una URL, lo que estamos solicitando es un recurso y ese recurso puede ser una página estática, dinámica, un vídeo, un audio, una imagen, una rss, un xml, un json…etc. Son recursos y veremos que el tipo de arquitectura que Rails utiliza, es la arquitectura tipo RESTful, para ver cómo poder acceder a dichos recursos dentro de la aplicación y la manipulación mediante verbos HTTP.

Veamos primeramente a nuestro protagonista y responsable, el fichero de routes.rb que lo tenemos en el directorio config de nuestro proyecto. Pero veamos algunos conceptos necesarios para entender Routes de Rails. Primero, necesitamos saber en REST que HTTP verb necesitamos, y para ello lo que necesitamos saber son los principales verbos, GET, POST, PUT y DELETE, pero también es necesario conocer las posibles acciones que van implicadas en REST, que son las siguientes:

  • index que devuelve una lista de recursos y es una solicitud GET del estilo /users

  • create que crea un nuevo recurso de los datos de solicitud POST añadiendo a la colección del estilo /users

  • new crea un nuevo recurso y lo pasa al cliente, como un formulario que se crea para que lo rellene el usuario y es una solicitud solicitud GET del estilo /users/new

  • show devuelve los contenidos de los recursos que se han identificado en params[:id] y es una  solicitud GET del estilo /users/:id

  • update actualiza los contenidos del recurso identificado params[:id] con los datos asociados a la solicitud y es una solicitud  PUT del estilo /users/:id

  • edit devuelve los contenidos del recurso identificado params[:id] en un formato adecuado para la edición y es una solicitud  GET del estilo /users/:id/edit

  • destroy que destruye un recurso identificado params[:id] y es una solicitud DELETE del estilo /users/:id

Estas son las operaciones denominadas CRUD, Create, Read, Update y Destroy, pero seguro que ahora mismo estás pensando que en alguna que otra ocasión podemos necesitar algunas de ellas y no todas, y te estarás preguntando ¿cómo le decimos cuales son las que necesitamos? pues para eso tenemos “: only” o “: except”. Estos conceptos hay que tenerlos muy claros, ¿por qué digo esto? pues fíjate bien el lo siguiente, si en una petición hacemos por ejemplo la llamada a  /products con el verbo GET, toma la acción de index, pero si ahora hacemos que la misma petición a /products pero cambiando el verbo a POST, toma la acción de create. Date cuenta que con este cambios de de un verbo HTTP a la misma petición, no es lo mismo. Por eso es muy portante aclararse en este concepto y cómo serán nuestra rutas.

Ahora ya podemos decir que en Rails, una ruta dispone de una correspondencia entre los verbos HTTP y las URLs a acciones de los controladores.

Pero antes entendamos una parte muy sencilla de las rutas. Normalmente tenemos un recurso principal como entrada en nuestra aplicación y está relacionada con nuestro dominio, es decir, a lo que denominamos nuestra ruta principal root, para que cuando hagamos http://www.midominio.com nos lleve al recurso que está definido como root, para lo que tendremos en routes lo siguiente:

root :to => «products#index» products es el controlador y el index es el método al que le llevamos

Pero si nos fijamos en el fichero route.rb, tenemos al principio, que todo está contenido en un bloque do end  y tendremos algo así como el nombre de nuestra aplicación MiApplicationWeb::Application.routes.draw do…….end, pero no es más que código Ruby en la que tenemos una clase dónde le estamos pasando un método draw y dentro tenemos definidas nuestras rutas como métodos, en nuestro caso el método root. Pero ¿cómo podemos ver la relación de lo que estamos poniendo en nuestro fichero de rutas y lo que nos crea?, para ver la creación de la ruta de acceso en modo comando le decimos: bundle exec rake routes y nos dice que root / products#index, ¿qué significa esto? pues que tenemos el path / de acceso por URL y en la que tenemos una combinación de controlador y método products#index y que se encarga de resolver la ruta. Finalmente un helper method generado dinámicamente por Rails para ser utilizado dentro del código Ruby.

Resumiendo tenemos helper_method path controller#actionroot / product#index

Veamos más cosas de Routes, si ponemos una entrada de resources en routes nos proporciona las 7 entradas que hemos comentado anteriormente de index, create, new, show, update, edit y destroy. Por ejemplo:

resources :products o si son varias podemos poner en una única fila todas ellas o un resource por cada una, es decir:

resources :products, :reviews, :videos 

o

resources :products

resources :reviews

resources videos

En los casos en los que tengamos una relación establecida entre por ejemplo dos tablas de products y reviews, es decir, que en nuestro modelo de product tenemos un has_many :reviews, :dependent => :destroy y en el modelo de review tenemos un belongs_to :product,  podemos decirle a routes que existe esa relación mediante:

resources :products do
          resources :reviews, :except => [:show, :destroy]
end

Y ¿para qué necesitamos que nos cree unas rutas de este estilo? pues para que cuando necesitemos crear un comentario, dicho comentario esté relacionado con el producto que estemos seleccionando, de esta forma cuando estemos en la vista show de un producto podemos disponer de un helper que nos lleve a la vista de un comentario tal como este <%= link_to ‘Add review’, new_product_review_path(@product) %> proporcionando una URL http:…../products/1/reviews/new desde la que creamos nuestro comentario al producto.

Para los casos en los que necesitamos únicamente una serie de recursos lo que tenemos que proporcionar a routes sería:

resources :sessions, : only => [:new, :create]
resources :users, :except => [:new]

Esto quiere decir que nos proporcione únicamente los recursos para new y create con : only y con :execpt todos menos new. Si ahora queremos crear los recursos únicamente sin ID y sin el index tendremos que utilizar el singular de resources:

resource :product

También podemos utilizar los nombres de namespace para poder encuadrar bajo una denominación común:

namespace :admin do
       resources :posts, :comments
end

Esto nos ayuda a disponer de un entorno de URL /admin/posts, /admin/posts/new etc, es decir, los mismos recursos pero con admin.

Por otro lado, no estamos limitados a las siete rutas anteriormente mencionadas que RESTful crea de forma predeterminada, sino que es posible añadir rutas adicionales que se aplican a una colección o los miembros individuales de la colección. Para ello tenemos member, collection y new. Vemos el siguiente ejemplo para entender bien member y collection:

resources :posts do
       member do
             get :status
             put :update_status
       end
      collection do
          get :search
          post :new_search
          get :show_search
      end
end

Como podemos observar estamos utilizando resources para obtener las rutas que nos proporciona resources propiamente dichas y que hemos hablado anteriormente. Por otro lado tenemos un member de bloque con un método get y otro put que actúa sobre un elemento (por eso vemos el :id), arrojando los helpers y las rutas:

status_post GET                  /posts/:id/status(.:format)                    {:action=>»status», :controller=>»posts»}
update_status_post PUT   /posts/:id/update_status(.:format)      {:action=>»update_status», :controller=>»posts»}

Los colletion actúan sobre las colecciones de elementos proporcionando igualmente los helpers y rutas de acceso:

search_posts GET               /posts/search(.:format)                  {:action=>»search», :controller=>»posts»}

new_search_posts POST   /posts/new_search(.:format)         {:action=>»new_search», :controller=>»posts»}

show_search_posts GET    /posts/show_search(.:format)       {:action=>»show_search», :controller=>»posts»}

También es posible nombrar nosotros los nombre de las rutas por ejemplo:

get «login»     => «sessions#new»,       :as => «login»
get «logout»   => «sessions#destroy», :as => «logout»

get  «signup»  => «users#new»,            :as => «new_user«

En este caso estamos llamando el método get para decirle que cuando haga login en nuestra aplicación, la URL que muestre al usuario sea /login, es decir, el método get con «login» => «sessions#new» lo pones como :as => «login» y lo mismo hace para los casos de logout y el registro de signup.

En los casos que tengamos que pasar, por ejemplo un parámetro opcional y dejar una página estática típica de about, podemos crear las rutas de la siguiente forma:

match «/about» => «info#about», :as => :about

match «/:year(/:month(/:day))» => «info#about», :constraints => { :year => /\d{4}/, :month => /\d{2}/, :day => /\d{2}/ }

Podíamos tener la necesidad de utilizar un método definido por notros mismo mediante las contrains, como por ejemplo;

get ‘sample/url’ => ‘my#wants_photo‘, constraints: WantsPhotoConstraint

y el desarrollo del método sería;

class WantsPhotoConstraint
       def self.matches?(request)
             request.query_parameters[‘wants_photo’].present?
       end
end

Todo dentro del fichero de routes,rb y en el controlador deberíamos tener su correspondiente acción:

def wants_photo

# my code to execute

end

Hay muchas más cosas que ver sobre las rutas, pero con esto que hemos visto, ya serás capaz de defenderte con lo mínimo necesario, aunque ya digo, hay muchas más cosas que ver. Echa un vistazo a Rails Routing from the Outside In .

2 comentarios en “Rails Routing from the Outsise In: Definir nuestras propias rutas en una aplicación de Rails

  1. Antonio dijo:

    Hola Carlos.
    Gracias por compartir la información, se que el post es antiguo, pero quería preguntarte una duda referente a las rutas.
    ¿Es posible en Rails si has creado un modelo con su controlador ‘users’ por ejemplo, convertir las rutas a ‘players’? Es decir, si prefieres que las urls de los resources sean por ejemplo ‘players’ en vez de ‘users’ una vez tengas avanzado un proyecto, sin tocar nada de lo que tienes hecho hasta ahora.

    Muchas gracias.

Deja un comentario