¿Se pueden usar los ayudantes de enrutamiento de Rails (es decir, mymodel_path (modelo)) en los modelos?

368

Digamos que tengo un modelo de rieles llamado Thing. La cosa tiene un atributo de URL que opcionalmente se puede establecer en una URL en algún lugar de Internet. En el código de vista, necesito una lógica que haga lo siguiente:

<% if thing.url.blank? %>
<%= link_to('Text', thing_path(thing)) %>
<% else %>
<%= link_to('Text', thing.url) %>
<% end %>

Esta lógica condicional en la vista es fea. Por supuesto, podría construir una función auxiliar, que cambiaría la vista a esto:

<%= thing_link('Text', thing) %>

Eso resuelve el problema de verbosidad, pero realmente preferiría tener la funcionalidad en el modelo en sí. En cuyo caso, el código de vista sería:

<%= link_to('Text', thing.link) %>

Obviamente, esto requeriría un método de enlace en el modelo. Esto es lo que debería contener:

def link
  (self.url.blank?) ? thing_path(self) : self.url
end

Hasta el punto de la pregunta, thing_path () es un método indefinido dentro del código del Modelo. Supongo que es posible "incorporar" algunos métodos auxiliares al modelo, pero ¿cómo? ¿Y hay una razón real por la que el enrutamiento solo funciona en el controlador y ve las capas de la aplicación? Puedo pensar en muchos casos en los que el código del modelo puede necesitar tratar con URL (integración con sistemas externos, etc.).

Aaron Longwell
fuente
Un caso de uso sería: generar una URL acortada de goo.gl en un aftersave,
lulalala
2
Probablemente debería envolver su modelo en un presentador si desea agregar lógica de vista, esto mantendrá las capas MVC separadas. Ver Draper ( github.com/jcasimir/draper ).
Kris
2
Consulte también la sección "Generación de URL para rutas con nombre" en la documentación en api.rubyonrails.org/classes/ActionDispatch/Routing/UrlFor.html
Backo

Respuestas:

698

En los rieles 3, 4 y 5 puede usar:

Rails.application.routes.url_helpers

p.ej

Rails.application.routes.url_helpers.posts_path
Rails.application.routes.url_helpers.posts_url(:host => "example.com")
Paul Horsfall
fuente
14
Ypu puede incluir esto en cualquier módulo y luego puede acceder a los ayudantes de URL allí. Thx
Viacheslav Molokov
41
Ahórrese de copiar su :hostopción en todas partes y configúrela una vez en los archivos de configuración de su entorno:Rails.application.routes.default_url_options[:host] = 'localhost:3000'
Andrew
55
include Rails.application.routes.url_helpersfunciona para mí en Rails 4.1
Jan
1
Si solo necesita la ruta, puede usar: only_path => true como una opción sin pasar el parámetro del host.
trueinViso
1
esta parece ser una buena opción: hawkins.io/2012/03/…
Renars Sirotins
182

He encontrado la respuesta sobre cómo hacer esto yo mismo. Dentro del código del modelo, solo pon:

Para rieles <= 2:

include ActionController::UrlWriter

Para rieles 3:

include Rails.application.routes.url_helpers

Esto mágicamente hace que thing_path(self)devuelva la URL de la cosa actual, o other_model_path(self.association_to_other_model)devuelva alguna otra URL.

Aaron Longwell
fuente
27
Solo una actualización, ya que lo anterior está en desuso. Esta es la corriente incluyen:include Rails.application.routes.url_helpers
yalestar
2
Obtengo NoMethodError (método indefinido `optimizar_rutas_generación? 'Para # <ActionView :: Base: 0x007fe8c0eecbd0>) cuando intento esto
moger777
118

También puede encontrar el siguiente enfoque más limpio que incluir todos los métodos:

class Thing
  delegate :url_helpers, to: 'Rails.application.routes' 

  def url
    url_helpers.thing_path(self)
  end
end
matthuhiggins
fuente
77
La documentación sobre esto parecía difícil de encontrar, así que aquí hay un enlace decente con respecto al uso de delegado en ActiveSupport. simonecarletti.com/blog/2009/12/inside-ruby-on-rails-delegate
tlbrack
2
Me sale un undefined local variable or method 'url_helpers' for Event:Classerror ... :(
Augustin Riedinger
Consigo undefined method url_helpers. Que voy a hacer
asiniy
@asiniy, ¿estás seguro de que has utilizado la opción delegar a url_helpers que se escribe después de la declaración de clase?
Krzysztof Witczak
No funciona, pero puede ser que esto solo funcione si creas el tuyo class, como se muestra en la respuesta. Si su clase de modelo ya se extiende, ¿ < ApplicationRecordesto no funcionará?
skplunkerin
13

Cualquier lógica que tenga que ver con lo que se muestra en la vista debe delegarse en un método auxiliar, ya que los métodos en el modelo son estrictamente para el manejo de datos.

Esto es lo que puedes hacer:

# In the helper...

def link_to_thing(text, thing)
  (thing.url?) ? link_to(text, thing_path(thing)) : link_to(text, thing.url)
end

# In the view...

<%= link_to_thing("text", @thing) %>
Josh Delsman
fuente
1
Lo que no me gusta de los métodos de ayuda en este caso: cuando miro link_to_thing (), tengo que decidir si se trata de un ayudante específico de una cosa o de toda la aplicación (fácilmente podría serlo). Necesito considerar revisar 2 archivos para la fuente. thing.link no deja dudas sobre el archivo fuente.
Aaron Longwell
Además, ¿qué pasa si necesito usar esta funcionalidad en una tarea Rake (para exportar un archivo CSV de URL de cosas tal vez) ... ir directamente al modelo también sería mucho mejor en ese caso.
Aaron Longwell
Pero la diferencia es que link_to no estaría disponible en el modelo, ya que es un ayudante de ActionView. Entonces, esto no funcionaría. Podría hackear algunos valores predeterminados de atributos en el modelo, por lo que si no está configurado, el valor predeterminado es algo, pero eso depende de usted.
Josh Delsman
12
Con el crecimiento de las API de servicios web, a menudo los modelos necesitan contactar recursos externos con sus propios datos y proporcionar URL de devolución de llamada. Por ejemplo, un objeto fotográfico debe publicarse en Socialmod, que volverá a llamar a la URL de esa foto cuando se realice la moderación. Un enlace after_create () tendría sentido para publicar en Socialmod, pero ¿cómo sabe la URL de devolución de llamada? Creo que la propia respuesta del autor tiene sentido.
JasonSmith
3
Si bien tienes razón en un sentido estrictamente técnico, hay momentos en que las personas solo necesitan cosas para trabajar sin tener que afeitarse todos los yaks para evitar violar algún patrón de diseño sagrado o lo que sea que tengas, tal vez necesiten obtener la URL y realmente almacenar en la base de datos, sería útil que un modelo supiera cómo hacerlo en lugar de pasar cosas de un lado a otro, a veces las reglas pueden romperse.
nitecoder
1

Si bien puede haber una forma, tendería a mantener ese tipo de lógica fuera del Modelo. Estoy de acuerdo en que no debe poner eso en la vista ( mantenerlo delgado ), pero a menos que el modelo devuelva una url como un dato al controlador, el material de enrutamiento debe estar en el controlador.

Ryan Montgomery
fuente
44
Re: "A menos que el modelo devuelva una URL como un dato". Eso es exactamente lo que está sucediendo aquí ... el Modelo "posee" este dato, que es un enlace a una URL dentro o fuera del sitio. En algunos casos, la URL se genera a partir del enrutamiento Rails. En otros casos, la URL son datos proporcionados por el usuario.
Aaron Longwell
0

(Editar: olvidar mi balbuceo anterior ...)

Ok, podría haber situaciones en las que irías al modelo o a alguna otra URL ... Pero realmente no creo que esto pertenezca al modelo, la vista (o tal vez el modelo) suena más apropiado.

Sobre las rutas, hasta donde yo sé, las rutas son para las acciones en los controladores (que usualmente usan "mágicamente" una vista), no directamente a las vistas. El controlador debe manejar todas las solicitudes, la vista debe presentar los resultados y el modelo debe manejar los datos y entregarlos a la vista o al controlador. He escuchado a mucha gente aquí hablando de rutas a modelos (hasta el punto en que estoy empezando a creerlo), pero como lo entiendo: las rutas van a los controladores. Por supuesto, muchos controladores son controladores para un modelo y a menudo se le llama <modelname>sController(por ejemplo, "UsersController" es el controlador del modelo "Usuario").

Si se encuentra escribiendo cantidades desagradables de lógica en una vista, intente mover la lógica a un lugar más apropiado; la lógica de solicitud y comunicación interna probablemente pertenece al controlador, la lógica relacionada con los datos se puede colocar en el modelo (pero no la lógica de visualización, que incluye etiquetas de enlace, etc.) y la lógica que está relacionada únicamente con la pantalla se colocaría en un asistente.

Stein G. Strindhaug
fuente
Digamos que tiene un modelo de imagen. Si la imagen tiene una URL externa asociada, apunte a esa URL. De lo contrario, ¿apunta a la página de presentación de imágenes para cargar una imagen? Solo una idea.
Josh Delsman
¿Qué tal este ejemplo menos genérico: link_to "Full Size Image", image.link El método de enlace en el modelo enlazaría a la URL en el sitio (image_path) o, por ejemplo, a una URL de Flickr si el usuario proporcionó una y .url se estableció en la imagen.
Aaron Longwell