Можно ли использовать в моделях помощники маршрутизации Rails (т.е. mymodel_path (model))?

368

Скажем, у меня есть модель Rails под названием Thing. У Thing есть атрибут url, который по желанию можно установить как URL где-то в Интернете. В коде представления мне нужна логика, которая делает следующее:

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

Эта условная логика в представлении некрасива. Конечно, я мог бы создать вспомогательную функцию, которая изменила бы представление на это:

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

Это решает проблему многословия, но я бы действительно предпочел иметь функциональность в самой модели. В этом случае код представления будет:

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

Для этого, очевидно, потребуется метод связи на модели. Вот что это должно было бы содержать:

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

Кстати, thing_path () - это неопределенный метод внутри кода модели. Я предполагаю, что в модель можно добавить некоторые вспомогательные методы, но как? И есть ли реальная причина, по которой маршрутизация работает только на контроллере и уровне просмотра приложения? Я могу вспомнить множество случаев, когда коду модели может потребоваться URL-адреса (интеграция с внешними системами и т. Д.).

Аарон Лонгвелл
источник
Вариант использования: создать сокращенный URL-адрес из goo.gl после сохранения,
lulalala
2
Вы, вероятно, должны обернуть свою модель в презентатор, если вы хотите добавить логику представления, это будет держать слои MVC разделенными. Смотрите Дрейпер ( github.com/jcasimir/draper ).
Крис
2
См. Также раздел «Генерация URL для именованных маршрутов» в документации по адресу api.rubyonrails.org/classes/ActionDispatch/Routing/UrlFor.html
Backo

Ответы:

698

В Rails 3, 4 и 5 вы можете использовать:

Rails.application.routes.url_helpers

например

Rails.application.routes.url_helpers.posts_path
Rails.application.routes.url_helpers.posts_url(:host => "example.com")
Пол Хорсфолл
источник
14
Ypu может включить это в любой модуль, и после него вы можете получить доступ к url-помощникам там. Спасибо
Вячеслав Молоков
41
Спасите себя от копирования вашего :hostварианта везде и установите его один раз в конфигурационных файлах вашей среды:Rails.application.routes.default_url_options[:host] = 'localhost:3000'
Эндрю
5
include Rails.application.routes.url_helpersу меня работает в Rails 4.1
января
1
Если вам просто нужен путь, вы можете использовать: only_path => true в качестве опции без передачи параметра хоста.
trueinViso
1
кажется, это хороший вариант: hawkins.io/2012/03/…
Ренарс Сиротинс
182

Я нашел ответ относительно того, как сделать это сам. Внутри кода модели просто поместите:

Для Rails <= 2:

include ActionController::UrlWriter

Для Rails 3:

include Rails.application.routes.url_helpers

Это волшебным образом thing_path(self)возвращает URL для текущей вещи или other_model_path(self.association_to_other_model)возвращает какой-то другой URL.

Аарон Лонгвелл
источник
27
Просто обновление, поскольку вышесказанное устарело. Это текущее включает в себя:include Rails.application.routes.url_helpers
yalestar
2
Я получаю NoMethodError (неопределенный метод «optimize_routes_generation?» Для # <ActionView :: Base: 0x007fe8c0eecbd0>), когда я пытаюсь это сделать
moger777
118

Вы также можете найти следующий подход чище, чем каждый метод:

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

  def url
    url_helpers.thing_path(self)
  end
end
matthuhiggins
источник
7
Документация по этому вопросу казалась трудной для поиска, поэтому здесь есть достойная ссылка на использование делегата в ActiveSupport. simonecarletti.com/blog/2009/12/inside-ruby-on-rails-delegate
tlbrack
2
Я получаю сообщение undefined local variable or method 'url_helpers' for Event:Classоб ошибке ... :(
Августин Ридингер
Я получаю undefined method url_helpers. Что я буду делать?
2015 г.
@asiniy, Вы уверены, что использовали опцию делегата для url_helpers, которая пишется после объявления класса?
Кшиштоф Витчак
Не работает, но, возможно, это работает, только если вы создаете свой собственный class, как показано в ответе. Если ваш класс Model уже расширяется, < ApplicationRecordэто не сработает?
skplunkerin
13

Любая логика, связанная с тем, что отображается в представлении, должна быть делегирована вспомогательному методу, так как методы в модели предназначены исключительно для обработки данных.

Вот что вы могли бы сделать:

# 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
Что мне не нравится в вспомогательных методах в этом случае: когда я смотрю на link_to_thing (), я должен решить, является ли это вспомогательным средством для конкретной вещи или для всего приложения (это легко может быть либо). Мне нужно рассмотреть возможность проверки 2 файлов на источник. thing.link не оставляет вопросов о исходном файле.
Аарон Лонгвелл
Кроме того, что если мне понадобится использовать эту функциональность в задаче Rake (возможно, для экспорта файла CSV с URL-адресами вещей) ... в этом случае было бы гораздо лучше перейти непосредственно к модели.
Аарон Лонгвелл
Но разница в том, что link_to не будет доступен в модели, поскольку он является помощником ActionView. Так что это не сработает. Вы можете взломать некоторые значения по умолчанию атрибута в модели, поэтому, если он не установлен, он по умолчанию что-то, но это зависит от вас.
Джош Делсман
12
С ростом API веб-сервисов моделям часто приходится связываться с внешними ресурсами со своими собственными данными и предоставлять URL-адреса обратного вызова. Например, объект фотографии должен опубликовать себя в Socialmod, который будет выполнять обратный вызов URL этой фотографии при выполнении модерации. Хук after_create () имеет смысл публиковать в Socialmod, но как узнать URL обратного вызова? Я думаю, что собственный ответ автора имеет смысл.
Джейсон Смит
3
Хотя вы правы в чисто техническом смысле, бывают ситуации, когда людям просто нужно, чтобы вещи работали без необходимости брить каждого яка, чтобы не нарушать какой-то священный шаблон дизайна или что-то еще у вас, возможно, им нужно получить URL-адрес и на самом деле сохранить тогда в базе данных было бы удобно, чтобы модель просто знала, как это сделать, вместо того, чтобы передавать вещи взад и вперед, иногда правила могут быть нарушены.
nitecoder
1

Хотя мог бы быть способ, которым я склонен держать такую ​​логику вне Модели. Я согласен с тем, что вы не должны помещать это в представление ( сохранять его скиновым ), но если модель не возвращает URL-адрес как часть данных в контроллер, то маршрутизация должна быть в контроллере.

Райан Монтгомери
источник
4
Re: «Если модель не возвращает URL как часть данных». Это именно то, что здесь происходит ... Модель «владеет» этим фрагментом данных, который является либо ссылкой на локальный, либо сторонний URL. В некоторых случаях URL генерируется из Rails Routing. В других случаях URL является предоставленными пользователем данными.
Аарон Лонгвелл
0

(Изменить: Забудь мою предыдущую болтовню ...)

Хорошо, могут быть ситуации, когда вы переходите либо к модели, либо к другому URL-адресу ... Но я не думаю, что это относится к модели, вид (или, возможно, модель) звучит более подходящим.

О маршрутах, насколько я знаю, маршруты предназначены для действий в контроллерах (которые обычно «магически» используют представление), а не непосредственно к представлениям. Контроллер должен обрабатывать все запросы, представление должно представлять результаты, а модель должна обрабатывать данные и передавать их представлению или контроллеру. Я слышал, что много людей здесь говорят о маршрутах к моделям (до такой степени, что я начинаю верить в это), но, насколько я понимаю, маршруты идут к контролерам. Конечно, многие контроллеры являются контроллерами для одной модели и часто называются <modelname>sController(например, «UsersController» является контроллером модели «User»).

Если вы обнаруживаете, что пишете отвратительное количество логики в представлении, попробуйте переместить логику куда-то более подходящую; Логика запросов и внутренней связи, вероятно, принадлежит контроллеру, логика, связанная с данными, может быть размещена в модели (но не логика отображения, которая включает теги ссылок и т. д.), а логика, которая связана исключительно с отображением, будет помещена в помощник.

Стейн Г. Стриндхауг
источник
Скажем, у вас есть модель изображения. Если с изображением связан внешний URL-адрес, укажите этот URL-адрес. В противном случае, укажите на страницу показа изображения, чтобы загрузить изображение? Просто идея.
Джош Делсман
Как насчет этого менее общего примера: link_to "Full Size Image", image.link Метод link в модели будет либо ссылаться на локальный URL (image_path), либо, скажем, на Flickr URL, если пользователь предоставил один и .url был установлен на изображении.
Аарон Лонгвелл