Gli helper di routing Rails (ovvero mymodel_path (modello)) possono essere utilizzati nei modelli?


368

Supponiamo che io abbia un modello Rails chiamato Thing. La cosa ha un attributo url che può essere facoltativamente impostato su un URL da qualche parte su Internet. Nel codice vista, ho bisogno di una logica che procede come segue:

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

Questa logica condizionale nella vista è brutta. Naturalmente, potrei creare una funzione di supporto, che cambierebbe la vista in questo:

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

Questo risolve il problema della verbosità, ma preferirei davvero avere la funzionalità nel modello stesso. In tal caso, il codice vista sarebbe:

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

Ciò, ovviamente, richiederebbe un metodo di collegamento sul modello. Ecco cosa dovrebbe contenere:

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

Al punto della domanda, thing_path () è un metodo indefinito all'interno del codice modello. Suppongo che sia possibile "inserire" alcuni metodi di supporto nel modello, ma come? E c'è una vera ragione per cui il routing funziona solo sul controller e visualizza i livelli dell'app? Mi vengono in mente molti casi in cui potrebbe essere necessario che il codice modello gestisca gli URL (integrazione con sistemi esterni, ecc.).


Un caso d'uso potrebbe essere: generare un URL abbreviato da goo.gl in un salvataggio,
lulalala

2
Probabilmente dovresti avvolgere il tuo modello in un presentatore se desideri aggiungere la logica di visualizzazione, in questo modo i livelli MVC verranno separati. Vedi Draper ( github.com/jcasimir/draper ).
Kris,

2
Vedi anche la sezione "Generazione URL per percorsi denominati" nella documentazione su api.rubyonrails.org/classes/ActionDispatch/Routing/UrlFor.html
Backo

Risposte:


698

In Rails 3, 4 e 5 puoi usare:

Rails.application.routes.url_helpers

per esempio

Rails.application.routes.url_helpers.posts_path
Rails.application.routes.url_helpers.posts_url(:host => "example.com")

14
Ypu può includere questo in qualsiasi modulo e successivamente puoi accedere a url-helpers lì. Thx
Viacheslav Molokov

41
Salva te stesso dal copiare la tua :hostopzione ovunque e impostala una volta nel tuo ambiente file di configurazione:Rails.application.routes.default_url_options[:host] = 'localhost:3000'
Andrew

5
include Rails.application.routes.url_helpersfunziona per me in Rails 4.1
Jan

1
Se hai solo bisogno del percorso puoi usare: only_path => true come opzione senza passare il parametro host.
trueinViso,

1
questa sembra essere una buona opzione: hawkins.io/2012/03/…
Renars Sirotins

182

Ho trovato la risposta su come farlo da solo. All'interno del codice modello, basta inserire:

Per binari <= 2:

include ActionController::UrlWriter

Per Rails 3:

include Rails.application.routes.url_helpers

In questo modo magicamente viene thing_path(self)restituito l'URL per la cosa corrente o other_model_path(self.association_to_other_model)restituito un altro URL.


27
Solo un aggiornamento, poiché quanto sopra è obsoleto. Questa è la corrente include:include Rails.application.routes.url_helpers
yalestar

2
Ottengo NoMethodError (metodo non definito `optim_routes_generation? 'Per # <ActionView :: Base: 0x007fe8c0eecbd0>) quando provo questo
moger777,

118

Potresti anche trovare il seguente approccio più pulito di includere ogni metodo:

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

  def url
    url_helpers.thing_path(self)
  end
end

7
La documentazione su questo sembra difficile da trovare, quindi ecco un link decente per quanto riguarda l'uso del delegato in ActiveSupport. simonecarletti.com/blog/2009/12/inside-ruby-on-rails-delegate
tlbrack

2
Ottengo un undefined local variable or method 'url_helpers' for Event:Classerrore ... :(
Augustin Riedinger,

Ho capito undefined method url_helpers. Cosa farò?
asiniy

@asiniy, sei sicuro di aver usato l'opzione delegato per url_helpers che è scritto dopo la dichiarazione di classe?
Krzysztof Witczak,

Non funziona, ma potrebbe essere che questo funzioni solo se ne crei uno tuo class, come mostrato nella risposta. Se la tua classe Model si estende già, < ApplicationRecordquesto non funzionerà?
skplunkerin,

13

Qualsiasi logica relativa a ciò che viene visualizzato nella vista deve essere delegata a un metodo di supporto, poiché i metodi nel modello sono strettamente per la gestione dei dati.

Ecco cosa potresti fare:

# 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) %>

1
Cosa non mi piace dei metodi helper in questo caso: quando guardo link_to_thing (), devo decidere se si tratta di un helper specifico per l'applicazione o dell'intera applicazione (potrebbe facilmente essere uno dei due). Devo considerare di controllare 2 file per l'origine. thing.link non lascia dubbi sul file sorgente.
Aaron Longwell,

Inoltre, se avessi bisogno di usare questa funzionalità in un'attività Rake (forse per esportare un file CSV di URL delle cose) ... andare direttamente al modello sarebbe molto meglio anche in quel caso.
Aaron Longwell

Ma la differenza è che il link_to non sarebbe disponibile nel modello, poiché è un aiuto di ActionView. Quindi, questo non funzionerebbe. Potresti hackerare alcune impostazioni predefinite degli attributi nel modello, quindi se non è impostato, il valore predefinito è qualcosa, ma dipende da te.
Josh Delsman,

12
Con la crescita delle API dei servizi Web, spesso i modelli devono contattare risorse esterne con i propri dati e fornire URL di callback. Ad esempio, un oggetto fotografico deve essere pubblicato su Socialmod, che richiamerà l'URL di quella foto quando viene eseguita la moderazione. Un hook after_create () avrebbe senso pubblicare su Socialmod, ma come si fa a conoscere l'URL di callback? Penso che la risposta dell'autore abbia un buon senso.
JasonSmith,

3
Mentre hai ragione in senso strettamente tecnico, ci sono momenti in cui le persone hanno solo bisogno di cose per funzionare senza dover radere ogni yak che c'è per evitare di violare un modello di design sacro o quello che hai, forse hanno bisogno di ottenere l'URL e effettivamente archiviare nel database, sarebbe utile quindi per un modello sapere come farlo invece di passare le cose avanti e indietro, a volte le regole possono essere infrante.
nitecoder il


1

Mentre ci potrebbe essere un modo in cui tenderei a tenere quel tipo di logica fuori dal Modello. Sono d'accordo che non dovresti metterlo in vista ( mantienilo magro ) ma a meno che il modello non restituisca un url come pezzo di dati al controller, le cose di routing dovrebbero essere nel controller.


4
Ri: "A meno che il modello non restituisca un URL come parte di dati". Questo è esattamente ciò che sta accadendo qui ... il Modello "possiede" questo dato, che è un collegamento a un URL in loco o fuori sito. In alcuni casi, l'URL viene generato da Rails Routing. In altri casi, l'URL è costituito da dati forniti dall'utente.
Aaron Longwell,

0

(Modifica: dimentica la mia precedente chiacchiera ...)

Ok, ci potrebbero essere situazioni in cui potresti andare sul modello o su qualche altro URL ... Ma non penso davvero che questo appartenga al modello, la vista (o forse il modello) sembra più appropriata.

Per quanto riguarda le rotte, per quanto ne so le rotte sono per le azioni nei controller (che di solito "magicamente" usa una vista), non direttamente alle viste. Il controller dovrebbe gestire tutte le richieste, la vista dovrebbe presentare i risultati e il modello dovrebbe gestire i dati e servirli alla vista o al controller. Ho sentito molte persone qui parlare di rotte verso modelli (al punto che sto quasi iniziando a crederci), ma a quanto ho capito: le rotte vanno ai controller. Naturalmente molti controller sono controller per un modello e vengono spesso chiamati<modelname>sController (ad es. "UsersController" è il controller del modello "User").

Se ti ritrovi a scrivere cattive quantità di logica in una vista, prova a spostare la logica in un posto più appropriato; la logica di richiesta e di comunicazione interna appartiene probabilmente al controller, la logica relativa ai dati potrebbe essere inserita nel modello (ma non la logica di visualizzazione, che include tag di collegamento ecc.) e la logica che è puramente correlata alla visualizzazione verrebbe inserita in un helper.


Supponi di avere un modello di immagine. Se all'immagine è associato un URL esterno, allora punta a quell'URL. Altrimenti, punta alla pagina dello spettacolo dell'immagine per caricare un'immagine? Solo un'idea
Josh Delsman,

Che ne dici di questo esempio meno generico: link_to "Immagine a dimensione intera", image.link Il metodo di collegamento nel modello collegherebbe all'URL sul sito (percorso_immagine), o, per esempio, a un URL Flickr se l'utente ne ha fornito uno e .url è stato impostato sull'immagine.
Aaron Longwell,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.