Les aides au routage Rails (par exemple mymodel_path (modèle)) peuvent-elles être utilisées dans les modèles?

368

Disons que j'ai un modèle Rails appelé Thing. La chose a un attribut url qui peut éventuellement être défini sur une URL quelque part sur Internet. Dans le code d'affichage, j'ai besoin d'une logique qui effectue les opérations suivantes:

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

Cette logique conditionnelle dans la vue est moche. Bien sûr, je pourrais créer une fonction d'aide, qui changerait la vue en ceci:

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

Cela résout le problème de verbosité, mais je préférerais vraiment avoir la fonctionnalité dans le modèle lui-même. Dans ce cas, le code d'affichage serait:

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

Cela nécessiterait évidemment une méthode de liaison sur le modèle. Voici ce qu'il devrait contenir:

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

Au point de la question, thing_path () est une méthode non définie dans le code du modèle. Je suppose qu'il est possible de "tirer" certaines méthodes d'aide dans le modèle, mais comment? Et y a-t-il une vraie raison pour laquelle le routage ne fonctionne qu'au niveau du contrôleur et des couches d'affichage de l'application? Je peux penser à de nombreux cas où le code du modèle peut avoir besoin de gérer les URL (intégration avec des systèmes externes, etc.).

Aaron Longwell
la source
Un cas d'utilisation serait: pour générer une URL raccourcie à partir de goo.gl dans une aftersave,
lulalala
2
Vous devriez probablement envelopper votre modèle dans un présentateur si vous souhaitez ajouter une logique de vue, cela gardera les couches MVC séparées. Voir Draper ( github.com/jcasimir/draper ).
Kris
2
Voir également la section "Génération d'URL pour les itinéraires nommés" dans la documentation à api.rubyonrails.org/classes/ActionDispatch/Routing/UrlFor.html
Backo

Réponses:

698

Dans Rails 3, 4 et 5, vous pouvez utiliser:

Rails.application.routes.url_helpers

par exemple

Rails.application.routes.url_helpers.posts_path
Rails.application.routes.url_helpers.posts_url(:host => "example.com")
Paul Horsfall
la source
14
Ypu peut l'inclure dans n'importe quel module et après cela, vous pouvez y accéder à l'aide d'url. Thx
Viacheslav Molokov
41
Évitez de copier votre :hostoption partout et définissez-la une fois dans vos fichiers de configuration d'environnement:Rails.application.routes.default_url_options[:host] = 'localhost:3000'
Andrew
5
include Rails.application.routes.url_helpersfonctionne pour moi dans Rails 4.1
Jan
1
Si vous avez juste besoin du chemin, vous pouvez utiliser: only_path => true comme option sans passer le paramètre hôte.
trueinViso
1
cela semble être une bonne option: hawkins.io/2012/03/…
Renars Sirotins
182

J'ai trouvé la réponse concernant la façon de procéder moi-même. Dans le code du modèle, mettez simplement:

Pour les rails <= 2:

include ActionController::UrlWriter

Pour Rails 3:

include Rails.application.routes.url_helpers

Cela fait comme par magie thing_path(self)renvoyer l'URL de la chose actuelle, ou other_model_path(self.association_to_other_model)renvoyer une autre URL.

Aaron Longwell
la source
27
Juste une mise à jour, car ce qui précède est déconseillé. Il s'agit de l'actuel comprennent:include Rails.application.routes.url_helpers
yalestar
2
J'obtiens NoMethodError (méthode non définie `optimise_routes_generation? 'Pour # <ActionView :: Base: 0x007fe8c0eecbd0>) quand j'essaye ceci
moger777
118

Vous pouvez également trouver l'approche suivante plus propre que d'inclure toutes les méthodes:

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

  def url
    url_helpers.thing_path(self)
  end
end
matthuhiggins
la source
7
La documentation à ce sujet semblait difficile à trouver, voici donc un lien décent concernant l'utilisation de délégué dans ActiveSupport. simonecarletti.com/blog/2009/12/inside-ruby-on-rails-delegate
tlbrack
2
Je reçois une undefined local variable or method 'url_helpers' for Event:Classerreur ... :(
Augustin Riedinger
Je comprends undefined method url_helpers. Qu'est-ce que je vais faire?
asiniy
@asiniy, êtes-vous sûr que vous avez utilisé l'option déléguée à url_helpers qui est écrite après la déclaration de classe?
Krzysztof Witczak
Ne fonctionne pas, mais il se peut que cela ne fonctionne que si vous créez le vôtre class, comme indiqué dans la réponse. Si votre classe Model s'étend déjà, < ApplicationRecordcela ne fonctionnera pas?
skplunkerin
13

Toute logique liée à ce qui est affiché dans la vue doit être déléguée à une méthode d'assistance, car les méthodes du modèle sont strictement destinées à la gestion des données.

Voici ce que vous pourriez faire:

# 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
la source
1
Ce que je n'aime pas dans les méthodes d'assistance dans ce cas: quand je regarde link_to_thing (), je dois décider si c'est une aide spécifique à une chose ou à l'échelle de l'application (cela pourrait facilement être l'un ou l'autre). Je dois envisager de vérifier 2 fichiers pour la source. thing.link ne laisse aucune question sur le fichier source.
Aaron Longwell
De plus, que se passe-t-il si j'ai besoin d'utiliser cette fonctionnalité dans une tâche Rake (pour exporter un fichier CSV d'URL de chose peut-être) ... aller directement au modèle serait bien mieux dans ce cas également.
Aaron Longwell
Mais la différence est que le link_to ne serait pas disponible dans le modèle, car il s'agit d'un assistant ActionView. Donc, cela ne fonctionnerait pas. Vous pouvez pirater certains attributs par défaut dans le modèle, donc s'il n'est pas défini, il prend par défaut quelque chose, mais c'est à vous de décider.
Josh Delsman
12
Avec la croissance des API de services Web, les modèles doivent souvent contacter des ressources externes avec leurs propres données et fournir des URL de rappel. Par exemple, un objet photo doit se publier sur Socialmod, qui rappellera l'URL de cette photo lors de la modération. Un hook after_create () aurait du sens à publier sur Socialmod, mais comment connaissez-vous l'URL de rappel? Je pense que la propre réponse de l'auteur est logique.
JasonSmith
3
Bien que vous ayez raison au sens strictement technique, il y a des moments où les gens ont juste besoin que les choses fonctionnent sans avoir à raser chaque yak pour éviter de violer un modèle de conception sacré ou ce que vous avez, peut-être qu'ils ont besoin d'obtenir l'url et de stocker réellement dans la base de données, serait alors pratique pour un modèle de savoir comment faire cela au lieu de faire des va-et-vient, parfois les règles peuvent être enfreintes.
nitecoder
1

Bien qu'il puisse y avoir un moyen, j'aurais tendance à garder ce type de logique hors du modèle. Je suis d'accord que vous ne devriez pas mettre cela dans la vue ( gardez-le maigre ) mais à moins que le modèle ne renvoie une URL en tant que donnée au contrôleur, les éléments de routage devraient être dans le contrôleur.

Ryan Montgomery
la source
4
Re: "A moins que le modèle ne renvoie une URL sous forme de donnée". C'est exactement ce qui se passe ici ... le modèle "possède" cet élément de données, qui est soit un lien vers une URL sur site ou hors site. Dans certains cas, l'URL est générée à partir du routage Rails. Dans d'autres cas, l'URL correspond aux données fournies par l'utilisateur.
Aaron Longwell
0

(Edit: Oubliez mon babillage précédent ...)

Ok, il pourrait y avoir des situations où vous iriez soit au modèle, soit à une autre URL ... Mais je ne pense pas vraiment que cela appartienne au modèle, la vue (ou peut-être le modèle) semble plus appropriée.

À propos des itinéraires, pour autant que je sache, les itinéraires sont destinés aux actions des contrôleurs (qui utilisent généralement "par magie" une vue), pas directement aux vues. Le contrôleur doit gérer toutes les demandes, la vue doit présenter les résultats et le modèle doit gérer les données et les servir à la vue ou au contrôleur. J'ai entendu beaucoup de gens ici parler de routes vers des modèles (au point que je commence presque à le croire), mais si je comprends bien: les routes vont aux contrôleurs. Bien sûr, de nombreux contrôleurs sont des contrôleurs pour un modèle et sont souvent appelés <modelname>sController(par exemple, "UsersController" est le contrôleur du modèle "User").

Si vous vous retrouvez en train d'écrire des quantités désagréables de logique dans une vue, essayez de déplacer la logique dans un endroit plus approprié; la logique de demande et de communication interne appartient probablement au contrôleur, la logique liée aux données peut être placée dans le modèle (mais pas la logique d'affichage, qui inclut les étiquettes de lien, etc.) et la logique qui est purement liée à l'affichage serait placée dans une aide.

Stein G. Strindhaug
la source
Disons que vous avez un modèle d'image. Si l'image a une URL externe associée, pointez sur cette URL. Sinon, pointez sur la page d'affichage de l'image pour télécharger une image? Juste une idée.
Josh Delsman
Que diriez-vous de cet exemple moins générique: link_to "Full Size Image", image.link La méthode de lien dans le modèle serait soit un lien vers l'URL sur site (image_path), soit vers, disons, une URL Flickr si l'utilisateur en a fourni une et .url a été défini sur l'image.
Aaron Longwell