02: ROUTES : Ruteo - LPC-Ltda/Ruby-on-Rails GitHub Wiki
"Soñé mil nuevos caminos... Desperté y camine por el de siempre." (Proverbio Chino)
El sistema de ruteo en Rails es el sistema que examina el URL de un requerimiento entrante y determina que acción se debe ser tomada por la aplicación. Y hace un poco más que esto. El ruteo de Rails puede ser un hueso duro de roer. Pero resulta que la mayor duresa residen en un pequeño número de conceptos. Después de que se tiene un puñado de ellos, el resto cae amablemente.
Este capítulo le introducirá en las principales técnicas para definir y manipular rutas. El siguiente capítulo construirá sobre este conocimiento para explorar las facilidades que Rails ofrece para soportar la escritura de aplicaciones que cumplen con los principios de Representational State Transfer (REST). Como usted verá, estas facilidades pueden ser de tremenda utilidad para usted incluso si usted no planea escalar las alturas de la teoría REST. Ambos capítulos asumen un conocimiento básico del patrón MVC y de controladores Rails.
Algunos de los ejemplos de estos dos capítulos están basados en una pequeña aplicación de subastas. Los ejemplos son simplificados para que ellos sean comprensibles por si mismos. La idea básica es que hay subastas y que cada subasta comprende la subasta de un ítem. Hay usuarios y ellos envían ofertas.
Gatillar una acción del controlador es el evento principal en el ciclo de vida de una conección a una aplicación Rails. Por lo tanto, hace sentido que el proceso por el cual Rails determina que controlador y cual acción ejecutar deba ser muy importante. Este proceso se encarna en el sistema de ruteo.
El sistema de ruteo mapea URLs en acciones. Esto se hace aplicando reglas que usted especifica usando una sintaxis especial en el archivo config/routes.rb
. En realidad, esto es sólo código Ruby plano, pero que usa métodos y parámetros especiales, una técnica a veces referida como un lenguaje interno de dominio especifico (DSL). Si usted está usando los generadores de Rails, se agrega código al archivo de rutas automáticamente, y usted tiene un comportamiento razonable. Pero no cuesta mucho trabajo escribir reglas personalizadas y cosechar los beneficios de la flexibilidad del sistema de ruteo.
2.1 Los dos propósitos del ruteo
El sistema de ruteo hace dos cosas: mapea requests a métodos de acciones del controlador, y habilita la generación dinámica de URLs para su uso como argumentos de métodos como link_to
y redirec_to
.
Cada regla -o usando el término común, ruta- especifica un pattern, el cual puede ser usado tanto como un template que coincide con URLs y como un modelo para su creación. El pattern puede ser generado automáticamente basado en las convenciones, como en el caso de los recursos REST. Los patterns también pueden contener una mezcla de sub-strings estáticos, slashes (imitanto la sintaxis de una URL), y parámetros segment key posicionales que sirven como "receptores" para los valores correspondientes en las URLs.
Una ruta también puede incluir uno o más llaves de segmentos modificables, en forma de par llave/valor accequibles a la acción del controlador en un hash vía el parámetro params
. Una pareja de llaves (:controler
y :action
) determinan que controlador y acción serán invocadas. Otras llaves presentes en la definición de la ruta simplemente quedan escondidas para propósitos de referencia.
Poniendo algo de carne en los huesos de esta definición, aquí hay un ejemplo de ruta:
get 'recipes/:ingredient' => "recipes#index"
En este ejemplo, usted encuentra lo siguiente:
- Verbo HTTP de método restrictivo (
get
) - String estático (
recipes
) - Slash (
/
) - Llave de segmento (
:ingredient
) - Mapeo a la acción del controlador (
"recipes#index"
)
Las rutas tienen una sintaxis muy rica -esta no es la más compleja (ni la más simple)- porque tienen que hacer mucho. Una ruta simple, como la del ejemplo, tiene que proveer suficiente información para hacerla coincidir con una URL existente y para construir una nueva. la sintaxis de la ruta se las ingenia para dirigir ambos procesos.
routes.rb
2.2 El archivo Las rutas están definidas en el archivos config/routes.rb como se muestra (con algunos comentarios explicativos) en en listado 2.1. Este archivo es creado cuando usted crea su aplicación Rails y contiene instrucciones acerca de cómo usarlo.
Listado 2.1 El archivo routes.rb que se crea por defecto
Rails.application.routes.draw do
# The priority is based on order of creation
# first created -> highest priority.
# See how all your routes lay out with "rake routes".
#
# You can have the root of your site routed with "root":
# root 'welcome#index'
#
# Example of regular route:
# get 'products/:id' => 'catalog#view'
#
# Example of named route that can be invoked with
# purchase_url(id: product.id)
# get 'products/:id/purchase' => 'catalog#purchase', as: :purchase
#
# Example resource route (maps HTTP verbs to controller
# actions automatically):
# resources :products
#
# Example resource route with options:
# resources :products do
# member do
# get 'short'
# post 'toggle'
# end
#
# collection do
# get 'sold'
# end
# end
#
# Example resource route with subresources:
# resources :products do
# resources :comments, :sales
# resource :seller
# end
#
# Example resource route with more complex subresources:
# resources :products do
# resources :comments
# resources :sales do
# get 'recent', on: :collection
# end
# end
#
# Example resource route with concerns:
# concern :toggleable do
# post 'toggle'
# end
# resources :posts, concerns: :toggleable
# resources :photos, concerns: :toggleable
#
# Example resource route within a namespace:
# namespace :admin do
# # Directs /admin/products/* to Admin::ProductsController
# # (app(controllers/admin/products_controller.rb)
# resources :products
# end
end
El archivo completo consiste en una llamada única al método draw de Rails.application.routes. Ese método tiene un block, y todo desde la segunda línea del archivo hasta la penúltima línea del cuerpo es ese block.
En tiempo de ejecución, el block es evaluado dentro de una instancia de la clase ActionDispatch::Routing::Mapper. A través de ella, usted configura el sistema de ruteo de Rails.
El sistema de ruteo tiene que encontrar el patrón que coincida con la URL que está tratando de reconocer o un parámetro que coincida para la URL que está tratando de generar. Realiza esto yendo a través de las rutas en el orden en el que fueron definidas -esto es en elorden en que aparecen el el archivo routes.rb. Si una ruta dada no coincide, la rutina cae a la proxima. Tan rápido como se encuentre una ruta que coincida la búsqueda se detiene.
2.2.1 Rutas regulares
La forma básica de definir una ruta es proporcionar un patrón de URL más una (clase de controlador) / (método acción) mapeados con el parámetro especial :to.
get 'products/:id', to 'products#show'
Ya que esto es tan común, se provee una versión corta:
get 'products/:id' => 'products#show'
David comentó públicamente sobre la decisión de diseño detrás de la forma abreviada cuando dijo que se inspiró en dos fuentes:
- el patrón que hemos usado en Rails desde que comenzamos a referenciar controladores en minúsculas sin la parte "Controller" en la declaraciones controller: "main".
- El patrón de Rubí que señala que tu estás hablando de un método de instancia al usar #. Las influencias son compartidas. Main#index era un poco confuso para mí porque indicaría que un objeto llamado Main realmente existe, lo cual no es así. MainController#index puede ser un poco molesto de tipear cada vez. Exáctamente la misma razón aplica con
controller: "main"
vscontroller: "MainController"
. Dadas estas restricciones, Pienso que"main#index"
es lejos la mejor alternativa (http://yehudakatz.com/2009/12/26/the-rails-3-router-rack-it-up).
Restringiendo Métodos de requerimientos
A partir de Rails 4, se recomienda limitar el método HTTP usado para acceder una ruta. Si usted está usando la instrucción match para definir una ruta, usted realice esto usando la opción :via:
match 'products/:id' => 'products#show', via: :get
Rails provee una forma abreviada de expresar esta restricción particular reemplazando match con el método HTTP deseado (get, post, patch, etc.)
get 'products/:id' => 'products#show'
post 'products' => 'products#create"
Si por alguna razón usted desea restringir una ruta a más de un método HTTP, usted puede pasar a :via un arreglo de nombres de verbos.
match 'products/:id' => 'products#show', via: [:get, :post]
Definir una ruta sin especificar un método HTTP puede resultar en que Rails levante un excepción RuntimeError. Aunque es fuertemente no recomendado, una ruta puede coincidir con cualquier método HTTP al pasar :any a la opción :via.
match 'products' => 'products#index', via: :any
2.2.3 Patrones de URL
Tenga en mente que no hay necesaria correspondencia para entre el número de campos en un string pattern, el número de llaves de segmentos, y el hecho de que cada conexión necesita un controlador y una acción. Por ejemplo usted puede escribir una ruta como la siguiente:
get ":id" => "products#show"
Esto podría reconocer una URL como la siguiente:
http://localhost:3000/8
El sistema de ruteo setea params[:id] a 8 (basado en la posición que indica la llave de segmento :id, la cual coincide con la posición 8 en la URL), y esto ejecutaría la acción show del controlador products. Por supuesto, esta es una ruta tacaña en términos de información. Por otro lado, la siguiente ruta ejemplo contiene un string estático, products/, dentro del patrón URL:
match 'products/:id' => 'products#show'
Este string ancla el proceso de reconocimiento. Cualquier URL que no contenga el string estático products/ en su espacio de más a la izquierda no coincidirá con esta ruta.
En cuanto a la generación de URL, los strings estáticos en la ruta simplemente son colocados dentro de la URL que el sistema de ruteo genera. El generador de URL usa el string patrón de la ruta como modelo para la URL que generó. El string patrón estipula el substring products.
Así como vamos, usted debe tener en mente el propósito dual de reconocimiento/generación, el cual es porque lo hemos mencionado muchas veces hasta ahora. Hay dos principios útiles de recordar:
- La misma regla gobierna tanto el reconocimiento como la generación. El sistema completo está hecho para que no tenga que escribir reglas dos veces. Usted escribe las reglas sólo una vez y la lógica fluye en ambas direcciones.
- El URL que es generado por el sistema de ruteo (via link_to y sus amigos) sólo tiene sentido para el sistema de ruteo. La URL resultante (http://example.com/products/19201) no contiene ni una pizca de una pista en cuanto a lo que se supone que sucede cuando un usuario la sigue -excepto en la medida que mapea a una regla de ruteo. La regla de ruteo provee entonces la información necesaria para gatillar una acción del controlador. Alguien que mira la URL sin saber las reglas de ruteo no sabrá a que controlador y acción mapeará la URL.
Llaves de segmentos
El string patrón de URL puede contener parámetros (denotados con dos puntos), a los que nos referimos como llaves de segmentos (segment keys). En la siguiente declaración de ruta, :id es una llave de segmento:
get 'products/:id' => 'products#show'
Cuando esta ruta coincide con una URL requerida, la porción :id del patrón actúa como un tipo de buscador (matcher) y toma el valor del segmento. Por ejemplo, usando el mismo ejemplo, el valor de id para el URL http://example.com/products/4
es 4.
Esta ruta cuando coincide, siempre llevará al visitante a la acción show del controlador de productos. Usted verá técnicas para hacer coincidir controlador y acción basado en segmentos de la URL en breve. El símbolo :id dentro del patrón entre comillas en la ruta es una llave de segmento (usted la puede pensar como un tipo de variable). Su tarea es ser enganchada a un valor.
Esto significa en el ejemplo que el valor de params[:id] será seteado al string "4". Usted puede acceder a este valor dentro dentro de su acción products/show.
Cuando usted genera una URL, tiene que proporcionar valores que se adjuntarán a la llave de segmento dentro del string patrón de la URL. La forma simple de entender de usar esto es usando un hash como este:
link_to "products",
controller: "products",
action: "show",
id: 1
Como usted probablemente sabe, es más común en estos días generar URLs usando lo que llamamos rutas nombradas (named routes) versus proporcionar los parámetros del controlador y acción explícitamente en un hash. Sin embargo, ahora estamos revisando lo básico del ruteo.
En la llamada a link_to
, hemos provisto valores para los tres parámetros de la ruta. Dos de ellos coicidirán con los segmentos claves en la ruta, el tercero, :id será asignado al segemento llave correspondiente en el patrón URL.
Es vital comprender que la llamada a link_to
no sabe si le proporcionan valores modificables o valores de segmentos. Sólo sabe (o tiene la esperanza) que esos tres valores, atados a estas tres llaves, serán suficiente para determinar con precisión una ruta, un string patrón, y de esta forma un modelo para generar una URL dinámicamente.
** Parámetros hard-coded **
Es siempre posible insertar parámetros modificables adicionales dentro de la definición de ruta que no tienen un efecto dentro en la coincidencia de URL sino que son pasados con los params esperados normalmente.
get 'products/special' => 'products#show', special: 'true'
Esi sí, no estamos sugiriendo que esto es una buena práctica, Me hace más sentido (como una cuestión de estilo) apuntar a una acción distinta más que agregar una clausula.
get 'products/special' => 'products#special'
2.2.5 Poniendo el foco en el campo :id
Note que el tratamiento del campo :id en la URL no es magia, sólo es tratado como un valor con un nombre. Si usted lo lo desea, puede cambiar la regla para que :id sea :blah, entonces usted tendrá que hacer lo siguiente en su acción del controlador:
@product = Product.find(params[:blah])
El nombre :id es sólo una convención, refleja lo común del caso en que una acción dada necesita acceso a un registro particular de una base de datos. El negocio principal de el ruteador es determinar el controlador y acción que serán ejecutados.
El campo :id termina en el hash params ya mencionado. En el caso común clásico usted usará el valor provisto para llevar un registro fuera de la base de datos:
class ProductsController < ApplicationController
def show
@product = Product.find(params[:id])
end
end
2.2.6 Segmentos llave opcionales
Rails 3 introdujo una sintaxis para definir partes opcionales del patrón de URL. La forma más fácil de ilustrar esta sintaxis es mirando el legacy default controller route, encontrada en la versión previa de Rails en la parte baja del archivo config/routes.rb:
match ':controller(/:action(/:id(.:format)))', via: :any
Note que el parentesis es usado para definir segmentos llave opcionales, del tipo que usted esperaría ver cuando define grupos opcionales para espresiones regulares.
2.2.7 Definiendo defaults
Puede definir los parámetros default en una ruta al proporcionar un hash para la opción :default
. Esto incluso se aplica a los parámetros que no especifique como segmentos dinámicos en la ruta misma.
get 'photos/:id', to: 'photos#show', defaults: {format: 'jpg'}
En el ejemplo precedente, Rails hace coincidir http://example.com/photos/12 con la acción show
de PhotosController
, y setea params[:format]
como "jpg"
.
Por razones de seguridad usted no puede sobrescribir defaults para cambiar los valores de objetos params.
2.2.8 Redireccionando Rutas
Es posible escribir un redireccionamiento directamente dentro de la definición de ruta usando el método redirect:
get "/foo", to: redirect('/bar')
El argumento de redirect puede contener una URL relativa o una URI completa.
get "/google", to: redirect('https://google.com/')
El método redirect puede también tomar un block, el cual recibe los parámetros requeridos como sus argumentos. Esto le permite, por ejemplo, hacer un versionado rápido de sevicios web API en su punto final. (http://yehudakatz.com/2009/12/20/generic-actions-in-rails-3). Solo recuerde que la sintaxis do end
para el block de redirección no funcionara, porque Ruby pasará el block para el match
en lugar que al redirect
. Use llaves a cambio.
match "api/v1/:api",
to: redirect { |params| "/api/v2/#{params[:api].pluralize}"},
via: :any
Si usted lo necesita, el método redirect
también acepta parámetros, como :status
. (Rails usa un código de status 301 para redirecciones por defecto)
match "/api/v1/:api", to:
redirect(status: 302) { |params| "/api/v2/#{params[:api].pluralize}" },
via: :any
Todas las otras opciones que funcionarían con un llamada a url_for
funcionarán con redirect (:host, :port, etc.).
Por ejemplo, es posible pasar un parámetro path
, el cual en conjunción con un wildcard e interpolación, lo habilita a entregar sólo las partes de la URL que necesitan cambiar.
get 'stores/:name', to: redirect(status: 302, path: '%{name}')
get 'stores/:name(*all)', to: redirect(status: 302, path: '%{name}%{all}')
Finalmente, un objeto que responde a call
puede ser entregado como el último (o único) parámetro de redirect
, habilitandolo para encapsular el código comunmente usado en redirección en objetos. El método call
debe aceptar dos argumentos, params
y request
, y retorna un string.
get 'accounts/:name' => redirect(SubdomainRedirector.new('api'))
Si usted devuelve una ruta sin una barra diagonal inicial, la URL usa el prefijo de la variable de entorno SCRIPT_NAME actual. Esto suele ser una barra inclinada, pero puede ser diferente en un motor montado o donde la aplicación se implementa en un subdirectorio de un sitio web.
2.2.9 El segmento Format
Revisitemos la ruta legado por defecto nuevamente:
get ':controller(/:action(/:id(.:format)))'
El .:format al final coincide con un punto más un segmento llave de "formato" después del campo id. Esto quiere decir que coincidirá con una URL como la siguiente:
http://localhost:3000/products/show/3.json
Aquí params[:format] es seteado a json. El campo :format es especial, tiene un efecto dentro de la acción del controlador. Este efecto está relacionado con un método llamado respond_to.
El método respond_to le permite escribir su acción para que retorne diferentes resultados, dependiendo del formato requerido. Aquí una acción show para el controlador de productos que ofrece HTML o JSON:
def show
@product = Product.find(params[:id])
respond_to do |format|
format.html
format.json { render json: @product.to_json }
end
end
El block respond_to en este ejemplo tiene dos clausulas. La clausula HTML sólo consiste de format.html. Un requerimiento para HTML será manejado por el rendereo usual de un template de vistas. La clausula JSON incluye un block de código; si JSON es requerido, el block será ejecutado y el resultado de su ejecución será retornado al cliente.
Aqui está una ilustración de una línea de comandos, usando curl (con pequeñas modificaciones para reducir el ruido de línea):
$ curl http://localhost:3000/products/show/1.json -i
HTTP/1.1 200 OK
Content-Type: application/json; charset-utf-8
Content-Length: 81
Connection: Keep_alive
{"created_at":"2013-02-09T18:25:03.513Z",
"descrption":"Keyboard",
"id":"1",
"maker":"Apple",
"updated_at":"2013-02-09T18:25:03.513Z"}
El .json al final de la URL resulta en que respond_to
elige la rama json, y el documento retornado es una representación JSON del producto.
Al requerir un formato que no está incluido como una opción en el block respond_to
no generará una excepción. Rails retornará un estado 406 Not Acceptable para indicar que no puede manejar el requerimiento.
Si usted desea setear una condición else para su bloque respond_to, usted puede usar el método any, el cual le cuenta a Rails que puede alcanzar cualquier otro formato no definido explicitamente.
def show
@product = Product.find(params[:id])
respond_to do |format|
format.html
format.json { render json: @product.to_json }
format.any
end
end
Sólo asegúrese de contarle a any que hacer con el requerimiento o tener templates de las vistas correspondientes al formato que usted espera. De otra forma obtendrá una excepción :MissingTemplate_.
ActionView::MissingTemplate (Missing template products/show,
application/show with {:locale=>[:en], :formats=>[:xml],
:handlers=>[:erb, :builder, :raw, :ruby, :jbuilder, :coffee]}.)
2.2.10 Rutas como Rack Endpoints (Rack endpoints)
Usted verá el uso de la opción :to
en rutas a través de este capítulo. Lo más interesante de :to
es que su valor es aquello a lo que nos referimos como un Rack endpoints. Para ilustrar considere el siguiente ejemplo simple:
get "/hello", to: proc { |env| [200, {}, ["Hello world"]] }
El router está muy poco vinculado a los controladores! La sintaxis abreviada ("items#show"
) confía en el método action
de las clases del controlador para retornar un Rack endpoint que ejecuta la acción requerida.
>> ItemsController.action(:show)
=> #Proc:0x01e96cd0@...
La habilidad de despachar una aplicación basada en estantes (Rack-based), como las creadas con Sinatra (http://sinatrarb.com), pueden ser realizados usando el método mount
. El método mount
acepta una opción :at
, que especifica la ruta a la que la aplicación Rack-based mapeará.
class HelloApp < Sinatra::Base
get "/" do
"Hello World!"
end
end
Rails.application.routes.draw do
mount HelloApp, at: '/hello'
end
Alternativamente una forma abreviada está también disponible:
mount HelloApp => '/hello'
2.2.10 Encabezado Accept
Usted puede también gatillar una ramificación de respond_to
seteando un encabezado Accept
en el requerimiento. Cuando usted hace esto, no hay necesidad de agregar las parte .:format
de la URL. (Sin embargo, note que afuera en el mundo real es difícil hacer esta técnica para trabajar confiablemente debido a las inconsistencias cliente/browser de HTTP).
Aquí un ejemplo curl
que no especifica un formato .json
pero que setea un encabezado Accept
a application/json
:
$ curl -i -H "Accept: application/json" http://localhost:3000/products/show/1
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 81
Connection: Keep_alive
{"created_at":"2013-02-09T18:25:03.513Z",
"description":"Keyboard",
"id":"1",
"maker":"Apple",
"updated_at":"2013-02-09T18:25:03.513Z"}
El resultado es exactamente el mismo que en el ejemplo previo.
###2.2.11 Restricciones de segmentos llave
Algunas veces usted desea no sólo reconocer una ruta sino reconocerla en un nivel más fino que simplemente saber que componentes o campos tiene. Usted puede hacer esto a través del uso de la opción :constraint
(y expresiones regulares posiblemente).
Por ejemplo, usted puede rutear todos los requerimientos show
como errores de acción si su campo id
es no numérico. Usted hará esto creando dos rutas, una para manejar ids numéricos y una ruta para caer (fall-through) que maneje el resto:
get ':controller/show/:id' => :show, constraint: {:id => /\d+/}
get ':controller/show/:id' => :show_error
Anclaje Implicito: El ejemplo de restricción que hemos estado usando,
constraints: {:id => /\d+/}
Parece que pudiera coincidir con "foo32bar". No lo hace porque Rails ancla implicitamente a ambos finales. De hecho, a partir de este escrito, agregar anclajes explicitos \A y \z causa que una excepción sea levantada.
Aparentemente es muy común setear restricciones sobre el parámetro :id
que Rails acortó nuestros ejemplos previos para simplificar.
get ':controller/show/:id' => :show, id: /\d+/
get ':controller/show/:id' => :show_error
Las expresiones regulares en las rutas pueden ser útiles, especialmente cuando usted tiene rutas que difieren de otras sólo con respecto a los patrones de sus componentes. Pero ellas no son un sustituto completo para el chequeo de integridad de datos, Usted probablemente aún desea estar seguro que los valores con los que está tratando son usables y apropiados para el dominio de su aplicación.
De el ejemplo, usted puede concluir que el chequeo de :constraints
aplica a los elementos del hash params
. Sin embargo, usted puede chequear también una caja de sorpresas de otros atributos del requerimiento que retornan un string, como :sobdomain
y :referred
. Métodos de coincidencia de requests
que retornan valores numéricos o booleanos no estan soportados y pueden levantar excepciones cripticas durante la resolución de la ruta.
# only allow users admin subdomain to do old-school routing
get ':controller/:action/:id' => :show, constraints: {subdomain: 'admin'}
Si por alguna razón usted necesita un chequeo más poderoso de restricciones, usted tiene completo acceso al objeto :request
pasando un bloque o cualquier otro objeto que responda a call
como el valor de :constraints
como el siguiente:
# protect records with id under 100
get 'records/:id' => "records#protected",
constraints: proc { |req| req.params[:id].to_i < 100 }
2.2.12 La ruta Root
Cerca de la línea 8 del archivo config/routes.rb
(Listado 2.1), usted verá lo siguiente:
# You can have the root of your site routed with "root":
# root 'welcome#index'
Lo que estamos viendo aquí es la ruta raíz (root) -esto es, una regla que especifica que debe pasar cuando alguien se conecta.
http://example.com # Note the lack of "/anything" at the end!
La ruta root dice, "no deseo valores, no necesito nada, ya sé que controlador y acción voy a gatillar!"
En un archivo routes.rb
recien generado, la ruta root está comentada, ya que no hay una valor universal o por defecto para ella. Usted debe decidir que hace esta "sin URL" para cada aplicación que escribe.
Aqui hay algunos ejemplo de reglas de rutas vacías muy comúnes:
root to: "welcome#index"
root to: "pages#home"
# Shorthand syntax
root "user_sessions#new"
Definir la ruta vacía le da a las persons algo que ver cuando se conectan a su sitio sólo con el nombre del dominio. Usted puede estar maravillado porque ve algo cuando una aplicación Rails recien creada tiene su ruta root comentada.
La respuesta es que si la ruta root no está definida, por defecto, Rails ruteará al controlador interno Rails::WelcomeController
y rendereará una página de bienvenida a cambio.
En la versión previa de Rails, esto se realizaba al incluir un archivo index.html
en el directorio public de una aplicación recién generada. Cualquier contenido estático en la jerarquía del directorio public que coincida con el esquema de URL que ocurrió en la aplicación resulta en el contenido estático servido en lugar de gatillar una regla de rutas. En realidad el servidor web servira el contenido sin considerar a Rails para nada.
Nota acerca del orden de rutas. Las rutas son cosultadas, para reconocer y generar, en el orden en que son definidas en
routes.rb
. La busqueda termina cuando se encuentra la primera coincidencia, lo que significa que debe preocuparse por los falsos positivos.
2.3 Globbing rutas
En algunas situaciones, usted desearía agarrar uno o más componentes de una ruta sin tener que reconocer cada uno en parámetros posicionales específicos. Por ejemplo, su URL puede reflejar una estructura de directorios. Si alguien se conecta a:
/items/list/base/books/fiction/dickens
entonces usted necesita que la acción items/list
tenga acceso a los otros cuatro términos. Pero a veces puede haber sólo tres campos:
/items/list/base/books/fiction
O puede haber cinco:
/items/list/base/books/fiction/dickens/little_dorrit
Así usted necesita una ruta que coincida (en este caso particular) con todo después de los componentes de la sección URI. Usted la define haciendo globbing
de la ruta con un asterisco.
get 'items/list/*specs', controller: 'items', action: 'list'
Ahora la acción items/list
tendrá acceso a un número variable de campos URL delimitados por un slash, accequibles via params[:specs]
:
def list
specs = params[:specs] # e.g., "base/books/fiction/dickens"
end
Globbing pares llave-valor: El globbing de rutas puede proveer las bases para un mecanismo general para pasar campos en consutas. Digamos que usted diseña un esquema que tiene la siguiente forma:
http://localhost:3000/items/q/field1/value1/field2/value2/...
Al hacer requerimientos en esta forma retornará una lista de todos los productos cuyos campos coincidan con los valores, basado en un conjunto de pares ilimitado en la URL.
En otras palabras, http://localhost:3000/items/q/year/1939/material/wood
puede generar una lista de todos los ítem de madera construidos en 1939. La ruta que puede realizar esto es la siguiente:
get 'items/q/*specs', controller: "item", action: "query"
Por supuesto, usted tiene que escribir una acción query
como esta para soportar la ruta:
def query
@items = Item.where(Hash[*params[:scpec].split("/")])
if @items.empty?
flash[:error] = "Can't find items with those properties"
end
render :index
end
Que es método dentro de los parentesis cuadrados en Hash? Convierte un arreglo unidimensional de pares llave/valor en un hash! Prueba adicional de que un conocimiento profundo de Ruby es un prerequisito para ser un experto desarrollador Rails.
2.4 Rutas nombradas
El tema de nombrar rutas casi merece un capítulo completo. De hecho, lo que usted aprenda aquí alimentará directamente la revisión del ruteo relacionado con REST en el capítulo 3, "REST, recursos, y Rails."
La idea de nombrar un ruta es básicamente para hacerle la vida más fácil al programador. No hay efectos externos visibles en lo que a la aplicación se refiere. Cuando usted nombra una ruta, un nuevo método es definido para ser usado en sus controladores y vistas; este método es llamado name_url
(donde name es el nombre que usted le dio a la ruta), y llamar al método, con los argumentos apropiados, resulta que la URL es generada para la ruta. Adicionalmente, un método llamado name_path
también es creado; este método genera sólo la parte de la ruta del URL, sin el protocolo y componentes del host.
2.4.1 Creando una ruta nombrada
La forma en que usted nombra una ruta es usando el parámetro opcional :as
:
get 'help' => 'help#index', as: 'help'
En este ejemplo, Rails generará métodos llamados help_url
y help_path
en los contextos del controlador y vistas, los cuales usted puede usar dondequiera que Rails espere un URL o componentes de un URL.
link_to "help", help_path
Y por supuesto, las reglas usuales de reconocimiento y generación harán efecto. El string patrón consiste sólo en un componente string estático "help"
. De esta forma la ruta que usted verá en será la siguiente:
/help
Cuando alguien vaya a este link, la acción index
del controlador help
será invocada.
Usted puede probar las rutas nombradas directamente en la consola usando el objeto especial app
.
>> app.clients_path
=> "/clients"
>> app.clients_url
=> "http://www.example.com/clients"
Las rutas nombradas le ahorran algo de esfuerzo cuando usted desea una URL generada. Una ruta nombrada se va directamente a la ruta que usted necesita, saltándose el proceso de buscar la coincidencia que necesitaría otra. Esto significa que usted no tiene que proveer tanta información como deberia de otra forma, pero usted aún debe proveer valores para cualquier segmento llave en el string patrón de la ruta que no pueda ser inferido.
name_path
versus name_url
2.4.2 Cuando usted crea una ruta nombrada, usted está creando al menos dos métodos helpers de rutas. En el ejemplo anterior estos dos métodos son help_url
and help_path
. La diferencia es que el método _url
genera un URL completo, incluyendo el protocolo y el dominio, mientras que el método _path
genera sólo la parte de la ruta (algunas veces referidos como ruta absoluta y URL relativa).
De acuerdo a las especificaciones de HTTP, los re-direccionamientos deben especificar una URI, la cual puede ser interpretada (por algunos) como una URL completamente calificada (http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html). Por lo tanto, si usted desea ser pedante al respecto, usted siempre usará la versión _url
cuando use una ruta nombrada como argumento para redirect_to
en el código de su controlador.
El método redirect_to
trabaja perfectamente con URLs relativas generadaspor el helper _path
, argumentar acerca del tema es un poco inutil. De hecho, aparte de redirecciones, enlaces permanentes, y un puñado de otros casos extremos, es la forma de Rails usar _path
en lugar de _url
. Esto produce un string más corto y el agente del usuario (browser u otro) estará habilitado para inferir la URL completamente calificada cuando necesite hacer eso, basado en el encabezado HTTP del requerimiento, un elemento base en el documento, o la URL del requerimiento.
En la medida que lea este libro o examine otros códigos ejemplos, la principal cosa a recordar es que help_url
y help_path
hacen básicamente lo mismo. Tendemos a usar el estilo _url
en discusiones generales acerca de técnicas de rutas nombradas y el estilo _path
en ejemplos que ocurren dentro de los templates de vistas (por ejemplo, con link_to
y form_for
). Es más un estilo de escritura, basado en la teoría que la versión URL es más general y la versión path más especializada. En cualquier caso, es bueno acostumbrarse a ver ambas y a considerarlas íntimamente conectadas.
Usando URLs literales: Usted puede, si desea, codificar sus rutas y URLs como argumentos string para
link_to
,redirect_to
y sus amigos. Por ejemplo, en lugar de
link_to "Help", controller: "main", action: "help"
Usted puede escribir
link_to "Help", "/main/help"
Sin embargo, usar una ruta o URL literal se salta el sistema de ruteo. Si usted escribe URLs literales, tendrá que darle mantenimiento en el tiempo. Y en ese caso, espero que sea un experto en encontrarlas y reemplazarlas! Usted puede por supuesto usar las técnicas de interpolación de strings de Ruby para insertar valores, si esto es apropiado para lo que está haciendo, pero piense si no está reinventando la funcionalidad de Rails haciendo esto.
2.4.3 Qué nombran sus rutas
Como aprenderemos en el Capítulo 3, "REST, recursos, y Rails" la mejor forma de averiguar que nombres debemos usar para sus rutas es seguir la convención REST, la cual es puesta en Rails y simplifica mucho las cosas. De otra forma, usted necesita pensar de arriba a abajo; esto es, pensar acerca de que que desea escribir en el código de su aplicación y entonces crear las rutas que lo harán posible.
Tomo por ejemplo, esta llamada a link_to
:
link_to "Auction of #{item.name}",
controller: "items",
action: "show",
id: item_id
El la regla de ruteo para coincidir con esta ruta es (una ruta genérica)
get "item/:id" => "items#show"
Seguro sería bueno acortar este código link_to
. Después de todo, la regla de ruteo ya especifica el controlador y la acción. Esta es un buen candidato de una ruta nombrada para items:
get "item/:id" => "items#show", as: "item"
Mejoremos la situación al introducir item_path
en la llamada a link_to
:
link_to "Auction of #{item.name} ", item_path(id: item.id)
Dada la ruta el nombre es sólo un atajo; nos lleva directamente a la ruta, sin una larga búsqueda y sin tener que proveer una larga descripción de todo los parámetros codificados en la ruta.
2.4.4 Azúcar de argumento
De hecho, podemos hacer los argumentos de item_path
aún más cortos. Si usted necesita proveer un número id como argumento de una ruta nombrada, puede proveer sólo el número, sin la llave :id
:
link_to "Auction of #{item.name} ", item_path(item.id)
Y la azúcar sintáctica va mucho más allá: Usted puede entregar objetos y Rails obtendrá el id automáticamente.
link_to "Auction of #{item.name} ", item_path(item)
Este principio se extiende a otros segmentos llave en el string patron de una ruta nombrada. Por ejemplo, si usted tiene una ruta como la siguiente:
get "auction/:action_id/item/:item_id" => "items#show", as: "item"
Usted está habilitado para para llamarla:
link_to "Auction of #{item.name}", item_path(auction, item)
y usted obtendrá algo como esto como su ruta (dependiendo de la cantidad exacta de números id):
/auction/5/item/11
Aquí, dejamos que Rails infiera los ids tanto del objeto auction como del objeto item, lo cual hace al llamar to_params
sobre cualquier argumento no-hash que usted pase dentro del helper de ruta nombrada. En la medida que usted provea los argumentos en el orden en el cual sus ids ocurren en el string patrón de ruta, el valor correcto caerá dentro del lugar en la ruta generada.
2.4.5 Un poco más de azúcar con su azúcar?
Además, no tiene que ser el valor de id que el generador de rutas inserta dentro de la URL. Como aludimos hace un momento, usted puede sobre-escribir ese valor al definir un método to_param
en su modelo.
Digamos que usted desea que la descripción de un ítem aparezca en la URL para la subasta de ese ítem. En el archivo del modelo item.rb
, usted debe sobre-escribir to_params
; aquí, lo sobre-escribiremos para proveer una versión "unitaria" (sin puntuación y unida con guiones) de la descripción, cortesía del método parametrize
agregado a los strings en Active Support.
def to_param
description.parametrize
end
Subsecuentemente, la llamada al método item_path(auction, item) producirá algo como:
/auction/3/item/cello-bow
Por supuesto, si usted pone cosas como "cello-bow" en el campo de la ruta llamado :id
, necesitará hacer provisiones para obtener el objeto nuevamente. Las aplicaciones de blog usan esta técnica para crear fichas (slugs) para usar en links permanentes más que tener una columna de la base de datos separada para almacenar la versión unitaria del título que sirve como parte de la ruta. De esta forma es posible hacer algo como esto:
Item.where(munged_description: params[:id].first!
para desenterrar el ítem correcto. (Y sí, usted puede llamar algo diferente a :id
en la ruta para hacerlo más claro!)
Por qué usted no usa un id numérico en sus URLs? Primero, sus competidores pueden ver cuantas subastas ha creado. Los ids numéricos consecutivos también le permiten a las personas escribir arañas automatizadas para robar su contenido. Esta es una ventana a su base de datos. Y finalmente, las palabras en las URLs se ven mejor. (Google "German Tank problem" para aprender cómo el número serial en los tanques alemanes ayudó a los aliados a ganar la Segunda Guerra Mundial.)
2.5 El alcance de las reglas de ruteo
Rails le da una variedad de formas para agrupar concísamente rutas de ruteo relacionadas. Ellas están todas basadas en el uso del método scope
y sus abreviaciones. Por ejemplo, digamos que usted desea definir las siguientes rutas para subastas:
get 'auctions/new' => 'auctions#new'
get 'auctions/edit/:id' => 'auctions#edit'
post 'auctions/pause/:id' => 'auctions#pause'
Usted puede aplicar DRY a su archivo routes.rb
usando el método scope
:
scope controller: :auctions do
get 'auctions/new' => :new
get 'auctions/edit/:id' => :edit
post 'auctions/pause/:id' => :pause
end
Luego usted puede aplicar DRY nuevamente agregando el argumento :path
a scope
:
scope path: '/auctions', controller: :auctions do
get 'new' => :new
get 'edit/:id' => :edit
post 'pause/:id' => :pause
end
2.5.1 Controller
El método scope
acepta una opción :controller
(o puede interpretar un símbolo como su primer argumento que asume un controlador). De esta forma, las siguientes dos definiciones de scope son idénticas:
scope controller: :auctions do
scope :actions do
Para hacer lo que pasa aún más obvio, usted puede usar el método controller
en lugar de scope
, en lo que escencialmente azúcar sintáctica:
controller :auctions do
2.5.2 Prefijo de Ruta
El método scope
acepta una opción :path
( o se puede interpretar un string como primer parámetro representando un prefijo de ruta). De esta forma, las siguientes dos definiciones scope son idénticas:
scope path: '/auctions' do
scope '/auctions' do
Nuevo en Rails 4 es la habilidad de pasar la opción símbolos a la opción :path
en lugar de strings, la declaración de scope
scope :auctions, :arvchived do
alcanzará todas las rutas anidadas bajo la ruta "/auctions/archived".
2.5.3 Prefijo de nombre
El método scope
también acepta una opción :as
que afecta la forma en que el método URL de la ruta nombrada es generado. La ruta
scope :auctions, as: 'admin' do
get 'new' => :new as: 'new_auction'
end
Generará un helper URL de ruta nombrada llamado admin_new_auction_url
.
2.5.4 Namespaces
Las URLs pueden ser agrupadas usando el método namespace
el cual enrolla modulo, prefijo de nombre, y seteo de prefijo de ruta dentro de una declaración. La implementación del método namespace
convierte su primer argumento en un string, lo cual es la razón en algunos códigos de ejemplos usted verá que toma un símbolo.
namespace :auctions, :controller => :auctions do
get 'new' => :new
get 'edit/:id' => :edit
post 'pause/:id' => :pause
end
2.5.5 Agrupación de restricciones
Si usted se encuentra a si mismo repitiendo restricciones de segmentos llave similares en rutas relacionadas, usted agruparlas usando la opción :constraints
del método scope
:
scope controller: :auctions, constraints: {:id => /\d+/} do
get 'edit/;id' => :edit
post 'pause/:id' => :pause
end
Es probable que sólo un subconjunto de reglas en un scope dado necesite restricciones aplicadas a ellos. De hecho, el ruteo se romperá si usted aplica una restricción a una regla que no toma el segmento llave especificado. Ya que usted está anidando, usted probablemente deseará usar el método constraints
:
scope path: '/auctions', controller: :auctions do
get 'new' => :new
constraints id: /\d+/ do
get 'edit/:id' => :edit
post 'pause/:id' => :pause
end
end
Para permitir la reutilización modular, usted puede proveer el método con un objeto que tenga el método matches?
.
class DateFormatConstraints
def self.matches?(request)
request.params[:date] =~ /\A\d{4}-\d\d\z/ # YYYY-MM-DD
end
end
# in routes.rb
constraints(DateFormatConstraints) do
get 'since/:date' => :since
end
En este ejemplo en particular (DateFormatConstraints
), si un usuario errante o malicioso ingresa un parámetro fecha maliciosamente formateado vía la URL, Rails puede responder con un 404 status en lugar de levantar una excepción.
2.6 Listando rutas
Un utilitario para listar rutas es incluido en todo proyecto Rails como una tarea rake estándar. Invoquelo tipeando rake routes
en el directorio de su aplicación. Por ejemplo, aquí esta la salida para un archivo routes que contiene sólo una regla resources :products
única:
$ rake routes
products GET /products(.:format) products#index
POST /products(.:format) products#create
new_product GET /products/new(.:format) products#new
edit_product GET /products/:id/edit(.:format) products#edit
product GET /products/:id(.:format) products#show
PATCH /products/:id(.:format) products#update
PUT /products/:id(.:format) products#update
DELETE /products/:id(.:format) products#destroy
La salida es una tabla con cuatro columnas. Las dos primeras columnas son opcionales y contienen el nombre de la ruta y la restricción del método HTTP, si es que es provista. La tercera columna contiene el string de mapeo de URL. Finalmente, la cuarta columna indica el controlador y el método de acción que la ruta mapea más las restricciones que han sido definidas sobre esos segmentos llave de la ruta (si los hay).
Note que la tarea routes chequea una variable de ambiente CONTROLLER
opcional
rake routes CONTROLLER=products
listará sólo las rutas relacionadas a ProductsController
.
Mientras tenga un servidor arriba y corriendo sobre el ambiente desarrollo, usted puede visitar
rails/info/routes
para obtener una completa lista de las rutas de su aplicación Rails.