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

2022年6月15日 22:19

はじめに

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

メーラー作成

以下のコマンドを実行し、メーラーを作成します。
$ 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】M1チップ搭載MacでRuby on Railsの開発環境構築
# はじめに M1チップ搭載MacにRuby on Railsの開発環境を構築する手順を記載します。 - MacBook Air (M1, 2020) - macOS Monterey 12.3.1 # Homebrew ## [...]
2022年5月5日 11:56
【Rails】Rakeタスクの基本情報と作成・実行方法
# はじめに Railsには標準でRakeというGemが同梱されています。RakeはRubyで実装されたMake(UNIX系のOSで使用できるコマンド)のようなビルド作業を自動化するツールです。Ruby Make、略してRakeというわけですね。 [...]
2022年3月7日 22:12
【Rails】モデルに外部キーを設定する方法とよく起こるエラー内容について
# はじめに Railsでモデルに外部キーを設定する方法について説明します。 # モデルに外部キーを設定する ## リレーションシップ 今回は1つのブログ記事は複数のコメントを持つ1対多のリレーションシップを例に説明します。現在は` [...]
2022年2月10日 14:18
【Rails】Capybaraのfill_inメソッドを実行すると「既存レコードの内容+指定した内容」がセットされる事象の原因と対処【RSpec】
# はじめに RSpec + Capybaraを使用して、Railsアプリの統合テストを実装しています。とあるモデルの編集画面において、入力フォームの内容を書き換えた上で送信し、レコードが更新されることを確認します。 入力フォームの内容を書 [...]
2022年1月27日 21:22
【Rails】GitHubのセキュリティアラートで発見された脆弱性を解消する方法
# はじめに GitHubにはセキュリティアラートという機能があります。セキュリティアラートはリポジトリに含まれるライブラリやパッケージの脆弱性を定期的にチェックし、脆弱性のあるライブラリやパッケージが発見されたらアラートで知らせてくれるという機 [...]
2022年1月16日 10:36
【Rails】devise-two-factorを使った2段階認証の実装方法【初学者】
# はじめに Railsアプリで2段階認証を実装するには、「rotp」というGemを使う方法の他に、「devise-two-factor」というGemを使う方法があります。「devise-two-factor」はその名の通り、IDとパスワードによ [...]
2021年12月12日 17:58
【Rails】rotpを使った2段階認証の実装方法【初学者】
# はじめに 昨今はIDとパスワードによる認証だけでなく、ワンタイムパスワードによる2段階認証を導入するWebアプリが増えてきました。Railsで作成したWebアプリでも、IDとパスワードによる認証に加えて2段階認証を導入するニーズが高まっていま [...]
2021年11月27日 13:02
【Rails】deviseを使った認証機能の実装【初学者】
# はじめに Railsアプリに認証機能を導入するには「devise」というGemを使う方法が最も簡単です。「devise」は認証に係る機能をほとんどコードを書くことなく実装できる反面、処理がブラックボックス化されており、全容が把握しづらいという [...]
2021年11月27日 13:01