RoR: неопределенный метод "url_for" для nil:NilClass
У меня есть стандартное приложение Rails.
Когда Совет создан, я хотел бы создать Сообщение для каждого Пользователя, который заинтересован в этом Совете.
Это звучит просто, верно? Так должно быть...
Итак, начнем с Tip Observer:
class TipObserver < ActiveRecord::Observer
def after_save(tip)
# after the tip is saved, we'll create some messages to inform the users
users = User.interested_in(tip) # get the relevant users
users.each do |u|
m = Message.new
m.recipient = u
link_to_tip = tip_path(tip)
m.body = "Hello #{u.name}, a new tip: #{link_to_tip}"
m.save!
end
end
end
Ошибки:
tip_observer.rb:13:in `after_save': undefined method `tip_path' for #<TipObserver:0xb75ca17c> (NoMethodError)
Итак, TipObserver необходим доступ к методам UrlWriter. Это должно быть довольно просто исправить, верно?
class TipObserver < ActiveRecord::Observer
include ActionController::UrlWriter
Теперь он работает (!) И выводит:
Hello dave18, a new tip: /tips/511
Здорово, что работает!! Ну, вроде как, на самом деле мы хотим, чтобы это была ссылка для кликов. Опять же, это должно быть легко, верно?
link_to_tip = link_to tip.name, tip_path(tip)
Ошибки:
tip_observer.rb:13:in `after_save': undefined method `link_to' for #<TipObserver:0xb75f7708> (NoMethodError)
Итак, на этот раз TipObserver нужен доступ к методам UrlHelper. Это должно быть довольно просто исправить, верно?
class TipObserver < ActiveRecord::Observer
include ActionController::UrlWriter
include ActionView::Helpers::UrlHelper
Ошибки:
whiny_nil.rb:52:in `method_missing': undefined method `url_for' for nil:NilClass (NoMethodError)
Хорошо, кажется, добавление, которое вмешалось в объявление url_for. Давайте попробуем включить в другом порядке:
class TipObserver < ActiveRecord::Observer
include ActionView::Helpers::UrlHelper
include ActionController::UrlWriter
Ошибки:
url_rewriter.rb:127:in `merge': can't convert String into Hash (TypeError)
Хм, нет очевидного способа обойти это. Но после прочтения некоторые умные засорения предполагают, что Sweepers такие же, как Observers, но имеют доступ к помощникам url. Итак, давайте преобразуем Observer в Sweeper и удалим UrlHelper и UrlWriter.
class TipObserver < ActionController::Caching::Sweeper
observe Tip
#include ActionView::Helpers::UrlHelper
#include ActionController::UrlWriter
Хорошо, это позволяет ему работать, но вот вывод:
Hello torey39, a new tip:
Итак, ошибки нет, но URL не генерируется. Дальнейшее расследование с помощью консоли показывает, что:
tip_path => nil
и поэтому:
tip_path(tip) => nil
Хорошо, я понятия не имею, как решить эту проблему, так что, возможно, мы можем атаковать это в другом направлении. Если мы переместим содержимое в шаблон erb и представим Message.body как представление - это дает два преимущества - во-первых, содержимое "Просмотр" размещается в правильном месте, и это может помочь нам избежать этих проблем *_path.
Итак, давайте изменим метод after_save:
def after_save(tip)
...
template_instance = ActionView::Base.new(Rails::Configuration.new.view_path)
m.body = template_instance.render(:partial => "messages/tip", :locals => {
:user=>user,
:tip=>tip
})
m.save!
end
Ошибки:
undefined method `url_for' for nil:NilClass (ActionView::TemplateError)
Отлично, но теперь мы снова вернулись к этому кровавому url_for. Так что на этот раз жаловаться на ActionView. Давайте попробуем исправить это тогда:
def after_save(tip)
...
template_instance = ActionView::Base.new(Rails::Configuration.new.view_path)
template_instance.extend ActionController::UrlWriter
Ошибки:
undefined method `default_url_options' for ActionView::Base:Class
Отлично, что бы мы ни делали, в итоге мы получим ошибки. Я перепробовал много способов назначения default_url_options
внутри template_instance
безуспешно.
До сих пор это не кажется очень "Railsy", на самом деле это кажется сложным.
Итак, мой вопрос:
- Я пытаюсь получить квадратный колышек в круглой дыре? Если да, то как мне адаптировать архитектуру для обеспечения этой функциональности? Я не могу поверить, что это не то, что существует на других сайтах.
- Должен ли я отказаться от попыток использовать Observer или Sweeper?
- Должен ли я пытаться создавать новые сообщения с помощью MessagesController, и если да, то как я могу вызывать MessagesController напрямую и несколько раз из Observer/Sweeper?
Буду очень благодарен за любые советы или советы, я уже несколько дней бьюсь головой об эту кирпичную стену и медленно теряю желание жить.
ТИА
Кит
1 ответ
Ну, вы правы, что ваш подход не очень Rails-ы. Вы пытаетесь смешивать методы модели, контроллера и представления так, как они не предназначены, и это всегда немного шатко.
Если бы я начал свой путь, я бы, наверное, сдался на link_to
проблема и (я признаю, что это не "путь Rails") вручную закодировал HTML для ссылки. Так link_to_tip = link_to tip.name, tip_path(tip)
становится link_to_tip = '<a href="#{tip_path(tip)}">#{tip.name}</a>
- быстрое и грязное решение, если вы ищете его;-)
Но по моему опыту, Rails довольно аккуратен, пока вы не захотите делать что-то нестандартным образом. Тогда это может укусить вас:-)
Проблема в том, что вы пишете и храните текст в своей модели сообщений, которого там быть не должно. Модель сообщения должна belong_to Tips
и представление должно быть ответственно за представление текста сообщения, включая ссылку на подсказку. Если Сообщение может быть о чем-то отличном от Tips, вы можете создать полиморфную ассоциацию в модели Message следующим образом:
belongs_to :source, :polymorphic => true
Модель Tip будет включать в себя:
has_many :messages, :as => :source
Затем вы делаете это (используя ваш код в качестве примера):
m = Message.new
m.source = tip
m.save!
Представление, которое отображает сообщение, отвечает за создание ссылки, например:
<%= "Hello #{u.name}, a new tip: #{link_to m.source.name, tip_path(m.source)}" %>