【Rails】お問い合わせフォーム(確認画面付き)の実装手順【完全版】

はじめに

お問い合わせフォームの実装手順をいつも忘れてしまうので、完全な実装手順をまとめます。

メーラー作成

以下のコマンドを実行し、メーラーを作成します。
$ rails g mailer ContactMailer
作成されたメーラー「app/mailers/contact_mailer.rb」を編集します。
class ContactMailer < ApplicationMailer
  default from: 'noreply@example.com'
  default to: 'admin@example.com'
  layout 'mailer'

  def send_mail(contact)
    @contact = contact
    mail(from: contact.email, to: ENV['MAIL_ADDRESS'], subject: 'Webサイトより問い合わせが届きました') do |format|
      format.text
    end
  end
end
メール本文になるテンプレートファイル「app/views/contact_mailer/send_mail.text.erb」を編集します。
<%= @contact.content %>

<%= @contact.name %> (<%= @contact.email %>)

コントローラー作成

以下のコマンドを実行し、コントローラーを作成します。
$ rails g controller Contacts
作成されたコントローラー「app/controllers/contacts_controller.rb」を編集します。
class ContactsController < ApplicationController
  def index
      @contact = Contact.new
    end
   
    def confirm
      @contact = Contact.new(contact_params)
      if @contact.valid?
        render :action => 'confirm'
      else
        render :action => 'index'
      end
    end

    def done
      @contact = Contact.new(contact_params)
      if params[:back]
        render :action => 'index'
      else
        ContactMailer.send_mail(@contact).deliver_now
        render :action => 'done'
      end
    end

    private
      def contact_params
        params.require(:contact).permit(:name, :email, :content)
      end
end

モデル作成

メールを送信するだけならDBは必要ないので、ActiveModelを使います。
「app/models/contact.rb」を手動作成(rails generateは使わない)し、編集します。
class Contact include ActiveModel::Model
    attr_accessor :name, :email, :content
   
    validates :name, presence: true, length: { maximum: 20 }
    VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
    validates :email, presence: true, length: { maximum: 30 },
                      format: { with: VALID_EMAIL_REGEX }
    validates :content, presence: true, length: { maximum: 500 }
end

ビュー作成

以下の3ファイルを作成します。

app/views/contacts/index.html.erb
<% provide :title, 'お問い合わせ' %>
<div class="container mt-4">
  <div class="row">
    <div class="col-md-12 col-lg-6 offset-lg-3">
      <h2><%= icon('fas', 'envelope') %> お問い合わせ</h2>
      <nav aria-label="パンくずリスト" class="small">
        <ol class="breadcrumb">
          <li class="breadcrumb-item"><%= link_to 'トップページ', root_path %></li>
          <li class="breadcrumb-item active" aria-current="page">お問い合わせ</li>
        </ol>
      </nav>
      <hr />
      
      <%= form_with model: @contact, local: true, url: contact_confirm_path do |form| %>
        <% if @contact.errors.any? %>
          <div class="alert alert-danger" role="alert">
            <h5><strong>入力内容にエラーがあります。</strong></h5>
            <ul>
              <% @contact.errors.full_messages.each do |message| %>
                <li><%= message %></li>
              <% end %>
            </ul>
          </div>
        <% end %>

        <div class="form-group">
          <%= form.label :name, '名前' %>
          <span class="badge badge-info rounded-0">必須</span>
          <%= form.text_field :name, id: 'name', class: 'form-control', :placeholder => '名前' %>
        </div>

        <div class="form-group">
          <%= form.label :email, 'メールアドレス' %>
          <span class="badge badge-info rounded-0">必須</span>
          <small class="text-muted">例:user@example.com</small>
          <%= form.text_field :email, id: 'email', class: 'form-control', :placeholder => 'メールアドレス' %>
        </div>

        <div class="form-group">
          <%= form.label :content, 'お問い合わせ内容' %>
          <span class="badge badge-info rounded-0">必須</span>
          <small class="text-muted">パスワードなどの機密情報を含めないでください。</small>
          <%= form.text_area :content, id: 'content', class: 'form-control', :placeholder => 'お問い合わせ内容', :rows => '5' %>
        </div>

        <div class="float-right">
          <%= form.submit "確認", class: "btn btn-dark" %>
        </div>
      <% end %>

    </div>
  </div>
</div>

app/views/confirm.html.erb
<% provide :title, 'お問い合わせ' %>
<div class="container mt-4">
  <div class="row">
    <div class="col-md-12 col-lg-6 offset-lg-3">
      <h2><%= icon('fas', 'envelope') %> お問い合わせ</h2>
      <nav aria-label="パンくずリスト" class="small">
        <ol class="breadcrumb">
          <li class="breadcrumb-item"><%= link_to 'トップページ', root_path %></li>
          <li class="breadcrumb-item active" aria-current="page">お問い合わせ</li>
        </ol>
      </nav>
      <hr />

      <%= form_with model: @contact, local: true, url: contact_done_path do |form| %>

        <div class="form-group">
          <%= form.label :name, '名前' %>
          <%= form.text_field :name, value: @contact.name, id: 'name', class: 'form-control-plaintext', :placeholder => '名前' %>
        </div>

        <div class="form-group">
          <%= form.label :email, 'メールアドレス' %>
          <%= form.text_field :email, value: @contact.email, id: 'email', class: 'form-control-plaintext', :placeholder => 'メールアドレス' %>
        </div>

        <div class="form-group">
          <%= form.label :content, 'お問い合わせ内容' %>
          <%= form.text_area :content, value: @contact.content, id: 'content', class: 'form-control-plaintext', :placeholder => 'お問い合わせ内容', :rows => '5' %>
        </div>

        <p>この内容で送信します。よろしいですか?</p>

        <div class="float-right">
          <%= form.submit "戻る", name: 'back', class: "btn btn-dark" %>
          <%= form.submit "送信", class: "btn btn-dark" %>
        </div>
      <% end %>

    </div>
  </div>
</div>

app/views/done.html.erb
<% provide :title, 'お問い合わせ' %>
<div class="container mt-4">
  <div class="row">
    <div class="col-md-12 col-lg-6 offset-lg-3">
      <h2><%= icon('fas', 'envelope') %> お問い合わせ</h2>
      <nav aria-label="パンくずリスト" class="small">
        <ol class="breadcrumb">
          <li class="breadcrumb-item"><%= link_to 'トップページ', root_path %></li>
          <li class="breadcrumb-item active" aria-current="page">お問い合わせ</li>
        </ol>
      </nav>
      <hr />

      <p>ありがとうございます。お問い合わせ内容を送信しました。</p>

    </div>
  </div>
</div>

メール送信設定

本番環境と開発環境で手順が異なります。

開発環境
「config/environments/development.rb」に以下を追記します。
  config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
  config.action_mailer.delivery_method = :smtp
  config.action_mailer.smtp_settings = { address: 'localhost', port: 1025 }
  config.action_mailer.raise_delivery_errors = true

本番環境
「config/environments/development.rb」に以下を追記します。
  config.action_mailer.default_url_options = {  host: 'localhost', port: 3000 }
  config.action_mailer.delivery_method = :smtp
  config.action_mailer.raise_delivery_errors = true
  config.action_mailer.smtp_settings = {
    :address => "smtp.gmail.com",
    :port => 587,
    :user_name => ENV['MAIL_ADDRESS'],
    :password => ENV['MAIL_PASSWORD'],
    :authentication => :plain,
    :enable_starttls_auto => true
  }

環境変数の設定

送信先メールアドレスやパスワードをソースに直書きしてしまうとセキュリティ的に問題があるので、通常は環境変数に設定しておきます。
Gemfileに以下を追加し、bundle installを実行します。
gem 'dotenv-rails'
dotenv-railsを導入することで、.envに記載されている環境変数をENV['variable name']の形式で参照できるようになります。
.envを作成し以下を追加します。
「MAIL_ADDRESS」には送信先のメールアドレス(今回はGmail)、「MAIL_PASSWORD」にはGoogleのアプリパスワードを設定します。
MAIL_ADDRESS=Your mail address
MAIL_PASSWORD=Your mail password
.envをGithubなどのリモートサーバーにアップロードしてしまうと、やはりセキュリティ的に問題があるので、.envをコミット対象から除外します。
.gitignoreに以下を追記します。
/.env
.envをコミット対象から除外してしまうと、将来クローンしたときにどういう環境変数を設定したらいいかわからなくなってしまうので、環境変数名だけ記載したファイルを作成しておきます。
.env.sampleを作成し以下を追加します。
MAIL_ADDRESS=
MAIL_PASSWORD=

メール送信確認

ここまで行えばメール送信ができるようになっているはずです。最後にメール送信確認を行いましょう。

開発環境
開発環境では実際に設定したGmail宛に送信するのではなく、MailCatcherというGemが構築してくれるメールサーバー宛に送信します。
Gemfileに以下を追加し、bundle installを実行します。
gem 'mailcatcher'
ターミナルで以下のコマンドを実行します。
$ mailcatcher
ブラウザで「http://localhost:1080/」にアクセスすると、MailCatcherのメールボックスが表示されます。
この状態でお問い合わせフォームからメール送信するとMailCatcherのメールボックスにメールが届きます。

本番環境
デプロイ先がHerokuの場合、Herokuに環境変数を設定する必要があります。
ターミナルで以下を実行します。
$ heroku config:set MAIL_ADDRESS=Your mail address
$ heroku config:set MAIL_PASSWORD=Your mail password
本番環境では設定したGmail宛にメールが送信されるので、Gmailのメールボックスにメールが届くが確認します。

参考

関連記事

【Rails】RSpecを使った自動テスト《システムスペック編》
# はじめに Railsでアプリケーションを開発する際、品質を担保するためにテストは欠かせません。その中でも、RSpecは多くの開発者に愛用されているテストフレームワークです。 今回は、RSpecを使ったRailsアプリケーションのテスト方 [...]
2024年7月11日 17:07
【Rails】RSpecを使った自動テスト《リクエストスペック編》
# はじめに Railsでアプリケーションを開発する際、品質を担保するためにテストは欠かせません。その中でも、RSpecは多くの開発者に愛用されているテストフレームワークです。 今回は、RSpecを使ったRailsアプリケーションのテスト方 [...]
2024年7月11日 16:06
【Rails】RSpecを使った自動テスト《コントローラースペック編》
# はじめに Railsでアプリケーションを開発する際、品質を担保するためにテストは欠かせません。その中でも、RSpecは多くの開発者に愛用されているテストフレームワークです。 今回は、RSpecを使ったRailsアプリケーションのテスト方 [...]
2024年7月11日 15:39
【Rails】RSpecを使った自動テスト《モデルスペック編》
# はじめに Railsでアプリケーションを開発する際、品質を担保するためにテストは欠かせません。その中でも、RSpecは多くの開発者に愛用されているテストフレームワークです。 今回は、RSpecを使ったRailsアプリケーションのテスト方 [...]
2024年7月11日 11:50
【Rails】RSpecを使った自動テスト《基本編》
# はじめに Railsでアプリケーションを開発する際、品質を担保するためにテストは欠かせません。その中でも、RSpecは多くの開発者に愛用されているテストフレームワークです。 今回は、RSpecを使ったRailsアプリケーションのテスト方 [...]
2024年7月10日 17:49
【Rails】デザインパターン「Concern」の基本情報と実装方法
# はじめに Ruby on RailsなどのMVCフレームワークで構築したWebシステムにはアンチパターンというものが存在します。システム開発におけるアンチパターンとは、避けるべき悪い設計や実装方法のことを指します。 MVCフレームワーク [...]
2024年7月10日 13:32
【Rails】デザインパターン「Form Object」の基本情報と実装方法
# はじめに Ruby on RailsなどのMVCフレームワークで構築したWebシステムにはアンチパターンというものが存在します。システム開発におけるアンチパターンとは、避けるべき悪い設計や実装方法のことを指します。 MVCフレームワーク [...]
2024年7月6日 22:50
【Rails】デザインパターン「Service Object」の基本情報と実装方法
# はじめに Ruby on RailsなどのMVCフレームワークで構築したWebシステムにはアンチパターンというものが存在します。システム開発におけるアンチパターンとは、避けるべき悪い設計や実装方法のことを指します。 MVCフレームワーク [...]
2024年7月6日 19:59