1

我有一个标准的 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”,实际上感觉非常困难。

所以我的问题是:

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

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

蒂亚

基思

4

1 回答 1

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)}" %>
于 2010-08-21T21:07:19.263 回答