The Simplified Go Food Web App - Iteration 6

Sending Email & Deployment

Sending Email

The Requirements

It's your product manager again. He is happy with your progress so far. But as usual, he comes up with more features to develop. This time, he asks you to:

  1. Write a feature that will send a confirmation email when an Order is received by our app
  2. Write a feature that will send a confirmation email when an Order is shipped

Email Spec

For sending emails, Rails provides us with ActionMailer. However, to test ActionMailer we need to install more gem.

group :test do
  # -cut-
  gem 'email_spec'
end

Then run bundle install.

Spec Helper

require "email_spec"
require "email_spec/rspec"

Add these lines to your spec_helper.rb file.

require File.expand_path("../../config/environment", __FILE__)
require 'spec_helper'

And modify your rails_helper.rb file, change the order of these lines so they look like this.

Mailer Macros

module MailerMacros
  def last_email
    ActionMailer::Base.deliveries.last
  end

  def reset_email
    ActionMailer::Base.deliveries = []
  end
end

We will setup some macros for our Mailer specs. Create a file named "mailer_macros.rb" in folder "spec/support/" and fill it with these lines.

Mailer Specs

require 'rails_helper'

describe OrderMailer do
  describe "received message" do
    let(:order) { create(:order) }
    let(:mail) { OrderMailer.received(order) }

    it "sends confirmation email when order is received" do
      expect(mail.subject).to eq("Go Food Order Confirmation")
      expect(mail.to).to eq([order.email])
      expect(mail.from).to eq(["go.scholarship.kolla@gmail.com"])
    end
  end

  describe "shipped message" do
    let(:order) { create(:order) }
    let(:mail) { OrderMailer.shipped(order) }

    it "sends confirmation email when order is shipped" do
      expect(mail.subject).to eq("Go Food Order Shipped")
      expect(mail.to).to eq([order.email])
      expect(mail.from).to eq(["go.scholarship.kolla@gmail.com"])
    end
  end
end

After setting up all the necessary things, we will now write our mailer specs. Create a new folder "spec/mailers/" and add these lines to a file named "order_mailer_spec.rb".

Orders Controller Spec

describe OrdersController do
  # -cut-
  describe 'POST #create' do
    context "with valid attributes" do
      # -cut-
      it "sends order confirmation email" do
        post :create, params: { order: attributes_for(:order) }
        expect { 
          OrderMailer.received((assigns(:order))).deliver 
        }.to change { ActionMailer::Base.deliveries.count }.by(1)
      end
      # -cut-
    end
    # -cut-
  end
  # -cut-
end

Lastly, we need to modify our Orders controller spec to test if it sends an email after creating an order. Add "sends order confirmation email" spec before "redirects to store index page" spec in "POST #create" block.

Make It Pass

Now let's write the features to make it pass.

Configuring SMTP

Let's start by configuring our SMTP settings. Change your "development.rb" file to look like this.

Rails.application.configure do
  #-cut-
  # Send mailer
  config.action_mailer.delivery_method = :smtp
  config.action_mailer.smtp_settings = {
    address: "smtp.gmail.com",
    port: 587,
    authentication: "plain",
    user_name: "go.scholarship.kolla",
    password: "Kolaboras1",
    enable_starttls_auto: true
  }

  config.action_mailer.raise_delivery_errors = true
  config.action_mailer.perform_deliveries = true
  #-cut-
end

Generate Mailer

Next, generate mailer with this command.

rails generate mailer Order received shipped

Modifying Order Mailer

Then we need to modify our Order mailer.

class OrderMailer < ApplicationMailer
  default from: "Go Scholarship Kolla <go.scholarship.kolla@gmail.com>"

  # Subject can be set in your I18n file at config/locales/en.yml
  # with the following lookup:
  #
  #   en.order_mailer.received.subject
  #
  def received(order)
    @order = order

    mail to: order.email, subject: 'Go Food Order Confirmation'
  end

  # Subject can be set in your I18n file at config/locales/en.yml
  # with the following lookup:
  #
  #   en.order_mailer.shipped.subject
  #
  def shipped(order)
    @order = order

    mail to: order.email, subject: 'Go Food Order Shipped'
  end
end

Modifying Order Mailer

Then we need to modify our Order mailer.

class OrderMailer < ApplicationMailer
  default from: "Go Scholarship Kolla <go.scholarship.kolla@gmail.com>"

  # Subject can be set in your I18n file at config/locales/en.yml
  # with the following lookup:
  #
  #   en.order_mailer.received.subject
  #
  def received(order)
    @order = order

    mail to: order.email, subject: 'Go Food Order Confirmation'
  end

  # Subject can be set in your I18n file at config/locales/en.yml
  # with the following lookup:
  #
  #   en.order_mailer.shipped.subject
  #
  def shipped(order)
    @order = order

    mail to: order.email, subject: 'Go Food Order Shipped'
  end
end

Email Templates (1)

Lastly, we will change our email templates. First, we create a file named "app/views/mailer/recevied.text.erb"

Dear <%= @order.name %>

Thank you for your recent Go Food order. You ordered the following items:

<%= render @order.line_items %>

We'll send you a separate e-mail when your order ships.

Email Templates (2)

Then, we write partial for line_item in "app/views/line_items/_line_item.text.erb"

<%= sprintf("%2d x %s", 
      line_item.quantity,
      truncate(line_item.food.name, length: 50)) %>

Email Templates (3)

We can also use html template for our mailer. Write this one in a file named "app/views/mailer/shipped.html.erb"

<h3>Go Food Order Shipped</h3>
<p>
  This is just to let you know that we've shipped your recent Go Food order:
</p>

<table>
  <tr>
    <th colspan="2">Qty</th>
    <th>Description</th>
  </tr>
  <%= render @order.line_items %>
</table>

For the partial, it will automatically use "_line_item.html.erb" partial that we wrote back then.

Modifying Orders Controller

To actually send the mail, we need to modify our Orders controller.

class OrdersController < ApplicationController
  # -cut-
  # POST /orders
  # POST /orders.json
  def create
    @order = Order.new(order_params)
    @order.add_line_items(@cart)

    respond_to do |format|
      if @order.save
        Cart.destroy(session[:cart_id])
        session[:cart_id] = nil

        OrderMailer.received(@order).deliver!

        format.html { redirect_to store_index_url, notice: 'Thank you for your order.' }
        format.json { render :show, status: :created, location: @order }
      else
        format.html { render :new }
        format.json { render json: @order.errors, status: :unprocessable_entity }
      end
    end
  end
  # -cut-
end

Play Around

Now your specs will all pas. Play around with your app. Try to create an order and fill your email address as the buyer's email. See if your mail is delivered to your inbox.

Afterward, don't forget to commit your progress.

Deployment

The Simplified Go Food Web App - Iteration 6

By qblfrb

The Simplified Go Food Web App - Iteration 6

  • 220