Os auxiliares de roteamento do Rails (por exemplo, mymodel_path (model)) podem ser usados ​​nos modelos?

368

Digamos que eu tenho um modelo Rails chamado Thing. O item tem um atributo de URL que pode opcionalmente ser definido como um URL em algum lugar da Internet. No código de exibição, preciso de uma lógica que faça o seguinte:

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

Essa lógica condicional na exibição é feia. Claro, eu poderia criar uma função auxiliar, que mudaria a visão para isso:

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

Isso resolve o problema da verbosidade, mas eu realmente preferiria ter a funcionalidade no próprio modelo. Nesse caso, o código de exibição seria:

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

Obviamente, isso exigiria um método de link no modelo. Aqui está o que ele precisa conter:

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

Ao ponto da pergunta, thing_path () é um método indefinido dentro do código Model. Estou assumindo que é possível "puxar" alguns métodos auxiliares para o modelo, mas como? E existe um motivo real pelo qual o roteamento opera apenas no controlador e exibe as camadas do aplicativo? Posso pensar em muitos casos em que o código do modelo pode precisar lidar com URLs (integração com sistemas externos, etc.).

Aaron Longwell
fonte
Um caso de uso seria: para gerar url encurtada do goo.gl em uma afterSave,
lulalala
2
Provavelmente, você deve agrupar seu modelo em um apresentador, se desejar adicionar lógica de exibição, isso manterá as camadas do MVC separadas. Consulte Draper ( github.com/jcasimir/draper ).
Kris
2
Consulte também a seção "Geração de URL para rotas nomeadas" na documentação em api.rubyonrails.org/classes/ActionDispatch/Routing/UrlFor.html
Backo

Respostas:

698

Nos Rails 3, 4 e 5 você pode usar:

Rails.application.routes.url_helpers

por exemplo

Rails.application.routes.url_helpers.posts_path
Rails.application.routes.url_helpers.posts_url(:host => "example.com")
Paul Horsfall
fonte
14
O Ypu pode incluir isso em qualquer módulo e, depois, você pode acessar os auxiliares de URL lá. Thx
Viacheslav Molokov
41
Salve-se de copiar sua :hostopção em qualquer lugar e defina-a uma vez nos arquivos de configuração do seu ambiente:Rails.application.routes.default_url_options[:host] = 'localhost:3000'
Andrew
5
include Rails.application.routes.url_helpersfunciona para mim no Rails 4.1
Jan
11
Se você apenas precisar do caminho, poderá usar: only_path => true como uma opção sem passar o parâmetro host.
trueinViso
11
este parece ser boa opção: hawkins.io/2012/03/...
Renars Sirotins
182

Eu encontrei a resposta sobre como fazer isso sozinho. Dentro do código do modelo, basta colocar:

Para Rails <= 2:

include ActionController::UrlWriter

Para Rails 3:

include Rails.application.routes.url_helpers

Magicamente, isso thing_path(self)retorna o URL da coisa atual ou other_model_path(self.association_to_other_model)retorna outro URL.

Aaron Longwell
fonte
27
Apenas uma atualização, pois o acima foi descontinuado. Esta é a inclusão atual:include Rails.application.routes.url_helpers
yalestar
2
Recebo NoMethodError (indefinido método `optimize_routes_generation 'for # <ActionView :: Base: 0x007fe8c0eecbd0>?) Quando eu tento isso
moger777
118

Você também pode achar a seguinte abordagem mais limpa do que incluir todos os métodos:

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

  def url
    url_helpers.thing_path(self)
  end
end
matthuhiggins
fonte
7
A documentação sobre isso parecia difícil de encontrar, então aqui está um link decente sobre o uso de delegado no ActiveSupport. simonecarletti.com/blog/2009/12/inside-ruby-on-rails-delegate
tlbrack
2
Eu recebo um undefined local variable or method 'url_helpers' for Event:Classerro ... :(
Augustin Riedinger 14/02
Eu entendo undefined method url_helpers. O que eu vou fazer?
Asiniy 25/04/2015
@asiniy, você tem certeza de que usou a opção delegar para url_helpers escritos após a declaração da classe?
Krzysztof Witczak
Não funciona, mas pode ser que isso funcione apenas se você criar o seu próprio class, conforme mostrado na resposta. Se sua classe Model já estender < ApplicationRecordisso não funcionará?
Skplunkerin
13

Qualquer lógica relacionada ao que é exibido na exibição deve ser delegada a um método auxiliar, pois os métodos no modelo são estritamente para manipular dados.

Aqui está o que você poderia fazer:

# 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
fonte
11
O que eu não gosto nos métodos auxiliares neste caso: Quando eu olho para link_to_thing (), tenho que decidir se é um auxiliar específico de uma coisa ou de todo o aplicativo (pode ser facilmente). Eu preciso considerar a verificação de 2 arquivos para a fonte. thing.link não deixa dúvidas sobre o arquivo de origem.
Aaron Longwell
Além disso, e se eu precisar usar essa funcionalidade em uma tarefa Rake (para exportar um arquivo CSV de URLs de coisas talvez) ... ir diretamente para o modelo seria muito melhor nesse caso também.
Aaron Longwell
Mas a diferença é que o link_to não estaria disponível no modelo, pois é um auxiliar do ActionView. Então, isso não funcionaria. Você pode hackear alguns padrões de atributo no modelo; portanto, se não estiver definido, o padrão será algo, mas isso depende de você.
Josh Delsman 04/04/08
12
Com o crescimento das APIs de serviços da Web, muitas vezes os modelos precisam entrar em contato com recursos externos com seus próprios dados e fornecer URLs de retorno de chamada. Por exemplo, um objeto de foto precisa se postar no Socialmod, que retornará ao URL da foto quando a moderação for executada. Um gancho after_create () faria sentido postar no Socialmod, mas como você sabe o URL de retorno de chamada? Eu acho que a resposta do próprio autor faz sentido.
21909 JasonSmith
3
Embora você esteja certo no sentido estritamente técnico, há momentos em que as pessoas precisam apenas que as coisas funcionem sem ter que raspar todos os iaques para evitar violar algum padrão sagrado de design ou o que você tem, talvez eles precisem obter o URL e realmente armazenar no banco de dados, seria útil, então, que um modelo soubesse fazer isso, em vez de passar as coisas para frente e para trás, às vezes as regras podem ser violadas.
nitecoder
1

Embora possa haver uma maneira, eu tenderia a manter esse tipo de lógica fora do Modelo. Concordo que você não deve colocar isso em vista ( mantenha-o esbelto ), mas, a menos que o modelo esteja retornando um URL como um dado para o controlador, o material de roteamento deve estar no controlador.

Ryan Montgomery
fonte
4
Re: "A menos que o modelo esteja retornando uma URL como um dado". É exatamente o que está acontecendo aqui ... o Modelo "possui" esses dados, que são um link para um URL local ou externo. Em alguns casos, o URL é gerado a partir do roteamento do Rails. Em outros casos, o URL são dados fornecidos pelo usuário.
Aaron Longwell
0

(Editar: esqueça minha tagarelice anterior ...)

Ok, pode haver situações em que você iria para o modelo ou para algum outro URL ... Mas eu realmente não acho que isso pertença ao modelo, a exibição (ou talvez o modelo) parece mais apropriada.

Sobre as rotas, até onde eu sei, as rotas são para as ações nos controladores (que geralmente "usam magicamente" uma visualização), não diretamente nas visualizações. O controlador deve lidar com todas as solicitações, a visualização deve apresentar os resultados e o modelo deve manipular os dados e servi-los na visualização ou controlador. Já ouvi muitas pessoas falando sobre rotas para modelos (até o ponto em que estou começando a acreditar), mas como eu entendo: as rotas vão para os controladores. É claro que muitos controladores são controladores de um modelo e geralmente são chamados <modelname>sController(por exemplo, "UsersController" é o controlador do modelo "User").

Se você se encontra escrevendo quantidades desagradáveis ​​de lógica em uma exibição, tente mover a lógica para algum lugar mais apropriado; a lógica de solicitação e comunicação interna provavelmente pertence ao controlador, a lógica relacionada aos dados pode ser colocada no modelo (mas não a lógica de exibição, que inclui tags de link etc.) e a lógica puramente relacionada à exibição seria colocada em um auxiliar.

Stein G. Strindhaug
fonte
Digamos que você tenha um modelo de imagem. Se a imagem tiver um URL externo associado, aponte para esse URL. Caso contrário, aponte para a página de exibição da imagem para fazer upload de uma imagem? Apenas uma ideia.
Josh Delsman 04/12/08
Que tal esse exemplo menos genérico: link_to "Imagem em tamanho real", image.link O método link no modelo vincularia ao URL do site (caminho_da_imagem) ou, digamos, a um URL do Flickr se o usuário fornecesse um e .url foi definido na imagem.
Aaron Longwell