最佳实践CFWheels(或RoR,或任何其他框架)发送电子邮件

时间:2016-11-18 21:00:38

标签: ruby-on-rails oop coldfusion cfwheels

根据我的研究,似乎普遍认为发送电子邮件属于Controller中的内容。例如,如果我有People的注册表单,当用户提交注册表单时,我会验证Person,然后一旦Person被保存,People控制器会做更多的事情 - 例如,发送电子邮件确认消息,发送带附件的欢迎电子邮件,并向管理员发送电子邮件。

这很好,直到应用程序的另一部分也创建了人。很容易调用Person模型和create(),但是那些可能(或可能不会)需要发生的额外内容呢?开发人员必须记住在应用程序的任何控制器中执行所有这些操作?在这种情况下,如何保持代码DRY?

我倾向于在Person模型中创建一个“after create”过滤器,并且可能添加一个可选参数,该参数将在创建Person时禁止发送电子邮件,但测试变成了一场噩梦等等。

如何让应用程序的所有其他部分都知道有关创建新Person的许多规则?我想重构,但不确定要走哪条路。

4 个答案:

答案 0 :(得分:1)

这可能更适合StackExchange's Software Engineering

您必须考虑发送后续电子邮件(如欢迎讯息)不得与创建新人相关联。函数应始终只做一件事,不应依赖或要求执行其他函数。

// Pseudocode
personResult = model.createPerson(data)
if personResult.Successful {
    sendWelcomeMessage(personResult.Person)
    sendAdminNotification(personResult.Person)
} else {
    sendErrorNotification(personResult.Debug)
}

您的目标是解耦流程流中的每个步骤,这样您就可以轻松更改流程详细信息,而无需更改功能。

如果您的流程在应用程序的不同位置发生,您应该将流程包装在一个函数中并调用它。想象一下名为createPersonWithNotifictions(data)的函数中的上述代码。现在,您可以灵活地将另一个人创建流程包装在另一个函数中。

答案 1 :(得分:1)

因此,您在控制器中创建用户并在其他地方创建用户,并且您想要保持DRY?这需要建设者!

class UserBuilder
  attr_reader :user_params, :user, :send_welcome_email

  def initialize(user_params, send_welcome_email: true)
    @user_params = user_params
    @send_welcome_email = send_welcome_email
  end

  def build
    instantiate_user
  end

  def create
    instantiate_user

    before_create(user)
    return false unless user.save
    after_create(user)
  end

  private

  def instantiate_user
    @user ||= User.new(user_params)
  end

  def before_create(user)

  end

  def after_create(user)
    # or do whatever other check you can imagine
    UserMailer.welcome_email(user) if send_welcome_email 
  end
end

用法:

# in controller
UserBuilder.new(params[:user]).create

# somewhere else
user_params = { email: 'blah@example.com' }
UserBuilder.new(user_params, send_welcome_email: false)

RE附加信息

  

此外,CFWheels仅为控制器提供sendEmail(),而不是模型

这是红宝石,它具有内置的电子邮件功能。但很好,我会一起玩。在这种情况下,我会在顶部添加一些event/listener sauce

class UserBuilder
  include Wisper::Publisher

  ... 

  def after_create(user)
    # do whatever you always want to be doing when user is created

    # then notify other potentially interested parties
    broadcast(:user_created, user)
  end
end

# in controller
builder = UserBuilder.new(params[:user])
builder.on(:user_created) do |user|
  sendEmail(user) # or whatever
end
builder.create

答案 2 :(得分:0)

只需将Mail实体添加到模型中即可。它将负责向Person,Admin或Debugger发送特定类型的消息(欢迎,通知,错误)。然后,Controller将负责与Person,Mail和Message等实体进行协作,以发送相应类型的消息。

答案 3 :(得分:0)

我在CFWheels 中的最终解决方案是创建一个名为" PeopleManager"的模型对象。我讨厌使用"经理"起初在名字中,但现在对我有意义。但是,如果这符合特定的设计模式/名称,那我就是所有耳朵。

基本上,我的应用程序中的约定将是所有需要"新人的各种模块"将需要通过经理来获得它。通过这种方式,可以轻松控制创建新人员时所发生的情况以及应用程序的哪些区域。例如,如果用户创建新的评论并且他们的电子邮件地址不是人员表中的记录,则评论控制器将请求新人。当它发出PeopleManager的请求时,在该对象中就是业务逻辑"当从一个Comment创建一个新人时,发出一条欢迎消息"将存在。虽然我还不确定方法名称将如何发挥作用,但到目前为止,我正在考虑采用" getNewPersonForComment" ...并且每个模块将拥有它自己的类型调用。 PeopleManager中的重复代码(即这些不同的函数中的一些可能都使用相同的步骤)将被抽象为私有方法。

这在模块和数据访问层之间提供了一个层,并且还使得DAO类型的轮子对象不会过于“智能”#34;并偏离单一责任原则。

我还没有完成所有细节。特别是,是否应该明确地使用管理员的控制器。那个经理或者是否只是简单地将经理视为一个对象(在cfwheels,model(" PeopleManager")。doSomething()就足够了。

关于电子邮件发送时RoR和CFWheels之间的差异,CFW没有" Mailer"像RoR一样,sendMail()函数是一个仅限控制器的函数。所以,我基本上已经开发了一个异步处理的邮件队列功能,并且(希望)就像RoR模拟一样。这可能会成为CFWheels插件。我觉得这种类型的解决方案需要围绕这样一个事实:CFW中的控制器不能轻易地调用其他控制器,调试变成了噩梦。

它仍在不断发展,我欢迎对我的解决方案发表评论。