RoR:nil的未定义方法`url_for':NilClass

时间:2010-08-21 20:22:13

标签: ruby-on-rails ruby observer-pattern actionviewhelper

我有一个标准的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)
嗯,没有明显的办法解决这个问题。但在阅读了一些clever-clogs suggestion that Sweepers are the same as Observers之后却可以访问网址助手。 因此,我们将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呈现为视图 - 这有两个好处 - 首先将“View”内容放在正确的位置,这可能有助于我们避免这些* _path问题。 / p>

所以我们改变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中多次?

任何提示建议或建议都会非常感激地收到,我已经在这个砖墙上撞了好几天了,然后慢慢失去了生活的意愿。

TIA

基思

1 个答案:

答案 0 :(得分:2)

你是对的,你的方法不是很好Rails-y。你试图以一种他们不设计的方式混合模型,控制器和视图方法,而且总是有点摇摇欲坠。

如果我开始走下去,我可能会放弃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非常整洁,直到你想以非标准的方式做事。然后它可以咬你: - )

问题是你在Message模型中编写和存储文本,而不应该存在。消息模型应belong_to Tips,视图应负责显示消息文本,包括提示链接。如果消息可以是除提示之外的其他内容,则可以在消息模型中建立多态关联,如下所示:

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)}" %>