Передовая практика CFWheels (или RoR, или любая другая структура) отправки электронной почты

Из моего исследования кажется, что есть общее согласие, что отправка электронной почты - это то, что принадлежит Контроллеру. Так, например, если у меня есть форма регистрации для людей, когда пользователь отправляет форму регистрации, я проверяю Person, а затем, когда Person сохраняется, контроллер People будет делать больше вещей - например, отправлять подтверждение по электронной почте. сообщение, отправьте приветственное письмо с вложениями и отправьте письмо администратору.

Это нормально, пока есть еще одна часть приложения, которая ТАКЖЕ создает людей. Достаточно легко вызвать модель Person и создать (), но как насчет всего того дополнительного, что может (или не может!) Произойти... если разработчик должен помнить, чтобы делать все это в любом контроллере приложения? Как вы храните свой код СУХОЙ в этом случае?

Я хотел создать фильтр "после создания" в модели Person и, возможно, добавить дополнительный параметр, который отключит отправку электронной почты при создании Person, но тестирование станет кошмаром и т. Д.

Как избежать того, чтобы все другие части приложения знали так много правил о создании новой персоны? Я хочу провести рефакторинг, но не уверен, в каком направлении идти.

4 ответа

Решение

Итак, вы создаете пользователей в контроллерах и создаете их где-то еще, и вы хотите сохранить СУХОЙ? Это требует строителя!

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() только для контроллеров, а не для моделей.

Это рубин, он имеет встроенные возможности электронной почты. Но хорошо, я буду подыгрывать. В этом случае я бы добавил немного соуса для прослушивания событий.

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

Это, вероятно, лучше подходит для разработки программного обеспечения StackExchange.

Вы должны учитывать, что отправка последующих электронных писем (например, приветственного сообщения) не должна быть связана с созданием нового человека. Функция всегда должна делать только одну вещь и не должна зависеть или требовать выполнения других функций.

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

Ваша цель состоит в том, чтобы отделить каждый шаг в потоке процесса, чтобы вы могли легко изменять детали потока процесса без необходимости изменять функции.

Если ваш процесс происходит в разных местах вашего приложения, вы должны заключить процесс в функцию и вызвать его. Представьте приведенный выше код в функции с именем createPersonWithNotifictions(data), Теперь вы обладаете гибкостью и можете легко обернуть поток создания альтернативного человека в другую функцию.

Просто добавьте объект Mail в модель. Он будет нести ответственность за отправку сообщения определенного типа (приветствие, уведомление, ошибка) человеку, администратору или отладчику. Контролер будет тогда обязан сотрудничать с такими объектами, как Персона, Почта и Сообщение, чтобы отправлять соответствующие типы сообщений.

Мое окончательное решение в CFWheels состояло в том, чтобы создать модельный объект под названием "PeopleManager". Сначала я ненавидел использовать "менеджер" в названии, но теперь это имеет смысл для меня. Однако, если это соответствует определенному шаблону / названию дизайна, я - все уши.

По сути, в моем приложении принято, что все различные модули, которым нужен "новый человек", должны пройти через менеджера, чтобы получить его. Таким образом, легко контролировать, что происходит, когда создается новый человек, и для каких областей приложения. Например, если пользователь создает новый комментарий, а его адрес электронной почты еще не является записью в таблице "Люди", контроллер комментариев будет запрашивать нового человека. Когда он выполняет этот запрос PeopleManager, именно в этом объекте будет существовать бизнес-логика "Когда новый человек создается из комментария, отправьте приветственное сообщение". Хотя я еще не уверен, как будут выглядеть имена методов, пока я собираюсь пойти по пути "getNewPersonForComment"... и у каждого модуля будут свои типы вызовов. Повторный код в PeopleManager (т. Е. Некоторые из этих отдельных функций могут использовать одни и те же шаги) будет абстрагирован в частные методы.

Это обеспечивает слой между модулями и уровнем доступа к данным, а также не дает объектам типа DAO становиться слишком "умными" и отклоняться от принципа единой ответственности.

Я еще не проработал все детали. В частности, должен ли контроллер, который будет использовать Manager, явно "вручаться" этому менеджеру, или достаточно просто рассматривать Manager как объект, как любой другой (в cfwheels, модель ("PeopleManager"). DoSomething().

Что касается различий между RoR и CFWheels, когда дело доходит до электронной почты, CFW не имеет понятия "почтовой программы", как у RoR, а функция sendMail() является функцией только для контроллера. Итак, я в основном разработал функцию почтовой очереди, которая вместо этого обрабатывается асинхронно, и будет (надеюсь) действовать так же, как аналог RoR. Это может стать плагином CFWheels. У меня есть ощущение, что необходимость такого обходного решения вращается вокруг того факта, что контроллеры в CFW не могут легко вызывать другие контроллеры, и отладка превращается в кошмар.

Это все еще развивается, и я приветствую комментарии к моему решению.

Другие вопросы по тегам