Search code examples
ruby-on-railsoopcoldfusioncfwheels

Best practice CFWheels (or RoR, or any other framework) sending email


From my research, it seems there's a general consensus that sending email is something that belongs in the Controller. So for example, if I have a signup form for People, when a user submits a signup form, I would validate the Person, and then once the Person is saved, the People controller would do more stuff - for example, send an email confirmation message, send a welcome email with attachments, and send an email to an admin.

That's fine until there's another part of the application that ALSO creates people. Easy enough to call the Person model and create(), but what about all that extra stuff that might (or might not!) need to happen... should the developer have to remember to do all that stuff in any controller of the application? How do you keep your code DRY in this case?

My inclination was to make an "after create" filter in the Person model, and perhaps add an optional parameter that would disable sending of email when a Person is created, but testing becomes a nightmare, etc.

How do you keep from having all the other parts of the application have to know so many rules about creating a new Person? I want to refactor, but not sure which direction to go.


Solution

  • So, you create users in controllers and you create them somewhere else, and you want to keep DRY? This calls for a builder!

    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
    

    Usage:

    # in controller
    UserBuilder.new(params[:user]).create
    
    # somewhere else
    user_params = { email: '[email protected]' }
    UserBuilder.new(user_params, send_welcome_email: false)
    

    RE additional info

    Also, CFWheels only provides sendEmail() for controllers, not models

    This is ruby, it has built-in email capabilities. But fine, I'll play along. In this case, I would add some event/listener sauce on top.

    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