Agile Web Development with Rails, Edition 5

14.2 Iteration I2: Connecting to a Slow Payment Processor with Active Job 13.2 Iteration H2: System testing

14.1 Iteration I1: Email Notifications

Create a mailer

rails generate mailer Order received shipped
      create  app/mailers/order_mailer.rb
      invoke  erb
      create    app/views/order_mailer
      create    app/views/order_mailer/received.text.erb
      create    app/views/order_mailer/received.html.erb
      create    app/views/order_mailer/shipped.text.erb
      create    app/views/order_mailer/shipped.html.erb
      invoke  test_unit
      create    test/mailers/order_mailer_test.rb
      create    test/mailers/previews/order_mailer_preview.rb

Edit development configuration

edit config/environments/development.rb
Rails.application.configure do
  # Settings specified here will take precedence over those in config/application.rb.
 
  # In the development environment your application's code is reloaded on
  # every request. This slows down response time but is perfect for development
  # since you don't have to restart the web server when you make code changes.
  config.cache_classes = false
 
  # Do not eager load code on boot.
  config.eager_load = false
 
  # Show full error reports.
  config.consider_all_requests_local = true
 
  # Enable/disable caching. By default caching is disabled.
  # Run rails dev:cache to toggle caching.
  if Rails.root.join('tmp/caching-dev.txt').exist?
    config.action_controller.perform_caching = true
 
    config.cache_store = :memory_store
    config.public_file_server.headers = {
      'Cache-Control' => "public, max-age=#{2.days.to_i}"
    }
  else
    config.action_controller.perform_caching = false
 
    config.cache_store = :null_store
  end
 
  # Store uploaded files on the local file system (see config/storage.yml for options)
  config.active_storage.service = :local
 
  # Don't care if the mailer can't send.
  config.action_mailer.raise_delivery_errors = false
 
  # Don't actually send emails
  config.action_mailer.delivery_method = :test
  #
  # Alternate configuration example, using gmail:
  #   config.action_mailer.delivery_method = :smtp
  #   config.action_mailer.smtp_settings = {
  #     address:        "smtp.gmail.com",
  #     port:           587, 
  #     domain:         "domain.of.sender.net",
  #     authentication: "plain",
  #     user_name:      "dave",
  #     password:       "secret",
  #     enable_starttls_auto: true
  #   } 
 
  config.action_mailer.perform_caching = false
 
  # Print deprecation notices to the Rails logger.
  config.active_support.deprecation = :log
 
  # Raise an error on page load if there are pending migrations.
  config.active_record.migration_error = :page_load
 
  # Debug mode disables concatenation and preprocessing of assets.
  # This option may cause significant delays in view rendering with a large
  # number of complex assets.
  config.assets.debug = true
 
  # Suppress logger output for asset requests.
  config.assets.quiet = true
 
  # Raises error for missing translations
  # config.action_view.raise_on_missing_translations = true
 
  # Use an evented file watcher to asynchronously detect changes in source code,
  # routes, locales, etc. This feature depends on the listen gem.
  # config.file_watcher = ActiveSupport::EventedFileUpdateChecker
end

Tailor the from address

edit app/mailers/order_mailer.rb
class OrderMailer < ApplicationMailer
  default from: 'Sam Ruby <depot@example.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
    @greeting = "Hi"
 
    mail to: "to@example.org"
  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
    @greeting = "Hi"
 
    mail to: "to@example.org"
  end
end

Tailor the confirm receipt email

rm app/views/order*/received.html.erb
edit app/views/order_mailer/received.text.erb
Dear <%= @order.name %>
 
Thank you for your recent order from The Pragmatic Store.
 
You ordered the following items:
 
<%= render @order.line_items -%>
 
We'll send you a separate e-mail when your order ships.

Text partial for the line items

edit app/views/line_items/_line_item.text.erb
<%= sprintf("%2d x %s",
            line_item.quantity,
            truncate(line_item.product.title, length: 50)) %>

Get the order, sent the confirmation

edit app/mailers/order_mailer.rb
class OrderMailer < ApplicationMailer
  default from: 'Sam Ruby <depot@example.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: 'Pragmatic Store 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: 'Pragmatic Store Order Shipped'
  end
end

Invoke mailer from the controller

edit app/controllers/orders_controller.rb
  def create
    @order = Order.new(order_params)
    @order.add_line_items_from_cart(@cart)
 
    respond_to do |format|
      if @order.save
        Cart.destroy(session[:cart_id])
        session[:cart_id] = nil
        session[:order_id] = @order.id
        OrderMailer.received(@order).deliver_later
        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

Tailor the confirm shipped email (this time in HTML)

rm app/views/order_mailer/shipped.text.erb
edit app/views/order_mailer/shipped.html.erb
<h3>Pragmatic Order Shipped</h3>
<p>
  This is just to let you know that we've shipped your recent order:
</p>
 
<table>
  <tr><th colspan="2">Qty</th><th>Description</th></tr>
<%= render @order.line_items -%>
</table>

Review HTML partial for the line items

edit app/views/line_items/_line_item.html.erb
<% if line_item == @current_item %>
<tr class="line-item-highlight">
<% else %>
<tr>
<% end %>
  <td class="quantity"><%= line_item.quantity %></td>
  <td><%= line_item.product.title %></td>
  <td class="price"><%= number_to_currency(line_item.total_price) %></td>
</tr>

Update the test case

edit test/mailers/order_mailer_test.rb
require 'test_helper'
 
class OrderMailerTest < ActionMailer::TestCase
  test "received" do
    mail = OrderMailer.received(orders(:one))
    assert_equal "Pragmatic Store Order Confirmation", mail.subject
    assert_equal ["dave@example.org"], mail.to
    assert_equal ["depot@example.com"], mail.from
    assert_match /1 x Programming Ruby 1.9/, mail.body.encoded
  end
 
  test "shipped" do
    mail = OrderMailer.shipped(orders(:one))
    assert_equal "Pragmatic Store Order Shipped", mail.subject
    assert_equal ["dave@example.org"], mail.to
    assert_equal ["depot@example.com"], mail.from
    assert_match /<td[^>]*>1<\/td>\s*<td>Programming Ruby 1.9<\/td>/,
      mail.body.encoded
  end
 
end
rails test test/mailers/order_mailer_test.rb
Run options: --seed 41481
 
# Running:
 
..
 
Finished in 0.729600s, 2.7412 runs/s, 13.7061 assertions/s.
2 runs, 10 assertions, 0 failures, 0 errors, 0 skips

14.2 Iteration I2: Connecting to a Slow Payment Processor with Active Job 13.2 Iteration H2: System testing