RoR:未定义方法“url_for”对于 nil:NilClass

发布于 2024-09-15 01:38:30 字数 3910 浏览 7 评论 0原文

我有一个标准的 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

太好了,所以无论我们做什么,最终都会遇到错误。我尝试了很多在 template_instance 内分配 default_url_options 的方法,但没有成功。

到目前为止,这感觉还不是很“Railsy”,事实上感觉非常困难。

所以我的问题是:

  • 我是否想在圆孔中插入方钉?如果是这样, 我应该如何调整架构来提供 这个功能?我不敢相信 它不存在于 其他网站。
  • 我应该放弃尝试使用观察者或清扫者吗?
  • 我应该尝试通过 MessagesController 创建新消息吗? 如果是这样,我如何直接调用 MessagesController 并 从观察者/清扫器中多次?

任何建议或建议都将非常感激,我已经用头撞这堵砖墙好几天了,慢慢地失去了生活的意愿。

蒂亚

·基思

I have a standard Rails application.

When a Tip is created, I would like to create a Message for each User who is interested in that Tip.

This sounds simple right? It should be...

So, we start with a 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

Errors:

tip_observer.rb:13:in `after_save': undefined method `tip_path' for #<TipObserver:0xb75ca17c> (NoMethodError)

Ok, so TipObserver needs access to the UrlWriter methods. This should be fairly straightforward to fix, right?

class TipObserver < ActiveRecord::Observer
  include ActionController::UrlWriter

Now it runs(!) and Outputs:

Hello dave18, a new tip: /tips/511

Great that works!!
Well it kinda, really we want that to be a click-able link. Again, that should be easy right?

link_to_tip = link_to tip.name, tip_path(tip)

Errors:

tip_observer.rb:13:in `after_save': undefined method `link_to' for #<TipObserver:0xb75f7708> (NoMethodError)

Ok, so this time TipObserver needs access to the UrlHelper methods. This should be fairly straightforward to fix, right?

class TipObserver < ActiveRecord::Observer
  include ActionController::UrlWriter
  include ActionView::Helpers::UrlHelper

Errors:

whiny_nil.rb:52:in `method_missing': undefined method `url_for' for nil:NilClass (NoMethodError)

Ok, it seems adding that has interfered with the url_for declaration. Lets try the includes in a different order:

class TipObserver < ActiveRecord::Observer
  include ActionView::Helpers::UrlHelper
  include ActionController::UrlWriter

Errors:

url_rewriter.rb:127:in `merge': can't convert String into Hash (TypeError)

Hmm, there's no obvious way around this. But after reading some clever-clogs suggestion that Sweepers are the same as Observers but have access to the url helpers.
So lets convert the Observer to a Sweeper and remove the UrlHelper and UrlWriter.

class TipObserver < ActionController::Caching::Sweeper
  observe Tip
  #include ActionView::Helpers::UrlHelper
  #include ActionController::UrlWriter

Well, that allows it to run, but here's the Output:

Hello torey39, a new tip:

So, there's no error, but the url is not generated. Further investigation with the console reveals that:

tip_path => nil

and therefore:

tip_path(tip) => nil

Ok well I have no idea how to fix that problem, so perhaps we can attack this from a different direction. If we move the content into an erb template, and render the Message.body as a view - that gives two benefits - firstly the "View" content is put in the correct location, and it might help us avoid these *_path problems.

So lets change the after_save method:

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

Errors:

undefined method `url_for' for nil:NilClass (ActionView::TemplateError)

Great, but now we're back to this bloody url_for again. So this time its the ActionView thats complaining. Lets try and fix this then:

def after_save(tip)
  ...
  template_instance = ActionView::Base.new(Rails::Configuration.new.view_path)
  template_instance.extend ActionController::UrlWriter

Errors:

undefined method `default_url_options' for ActionView::Base:Class

Great so whatever we do we end up with errors. I've tried many many way of assigning default_url_options inside the template_instance without success.

So far this doesn't feel very "Railsy", in fact it feels downright difficult.

So my question is:

  • Am I trying to get a square peg in a round hole? If so,
    how should I adapt the architecture to provide
    this functionality? I can't believe
    its not something that exists in
    other websites.
  • Should I give up trying to use an Observer or Sweeper?
  • Should I be trying to create new Messages via the MessagesController,
    and if so, how can I invoke the MessagesController directly and
    multiple times from within the Observer/Sweeper?

Any tips advice or suggestions would be very gratefully recieved, I have been banging my head against this brick wall for several days now and slowly losing the will to live.

tia

Keith

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

好听的两个字的网名 2024-09-22 01:38:30

嗯,你是对的,你的方法不是很 Rails-y。您试图以一种不符合设计目的的方式混合模型、控制器和视图方法,而且这总是有点不稳定。

如果我开始沿着你的道路走下去,我可能会放弃 link_to 问题,并且(我承认这不是“Rails 方式”)手动编码链接的 HTML。因此 link_to_tip = link_to Tip.name, Tip_path(tip) 变为 link_to_tip = '#{tip.name}< /a> - 如果您正在寻找一个快速但肮脏的解决方案;-)

但根据我的经验,Rails 非常简洁,除非您想以非标准方式做事。然后它就会咬你:-)

问题是你正在消息模型中编写和存储不应该存在的文本。消息模型应该belong_to Tips并且视图应该负责呈现消息文本,包括提示的链接。如果消息可以与提示以外的其他内容有关,则可以在消息模型中进行多态关联,如下所示:

belongs_to :source, :polymorphic => true

提示模型将包括:

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

Well you are right that your approach isn't very Rails-y. You are trying to mix model, controller and view methods in a way they aren't designed to and that's always a little shaky.

If I had started down your path, I probably would have given up at the link_to problem and (I admit it isn't "the Rails way") coded the HTML for the link manually. So link_to_tip = link_to tip.name, tip_path(tip) becomes link_to_tip = '<a href="#{tip_path(tip)}">#{tip.name}</a> - a quick and dirty solution if you're looking for one ;-)

But in my experience, Rails is pretty neat until you want to do things in a non-standard way. Then it can bite you :-)

The problem is you are writing and storing text in your Message model which shouldn't be there. The Message model should belong_to Tips and a view should be responsible for presenting the message text, including the link to the tip. If a Message can be about something other than Tips, you can make a polymorphic association in the Message model like this:

belongs_to :source, :polymorphic => true

The Tip model would include:

has_many :messages, :as => :source

Then you do this (using your code as an example):

m = Message.new
m.source = tip
m.save!

The view which renders the message is then responsible for creating the link, like this:

<%= "Hello #{u.name}, a new tip: #{link_to m.source.name, tip_path(m.source)}" %>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文