Action Mailerをつかいユーザー登録を行なった際にメール送信するようにする

Action Mailerとは

Action Mailerを使うと、アプリケーションのメイラークラスやビューでメールを送信することができます。メイラーの動作はコントローラときわめて似通っています。

引用: Action Mailer の基礎 - Railsガイド

メールを送信する際によく使われていて、パスワードリセット、お問い合わせ、ユーザー登録などなど様々な用途で使用されていて、誰もが利用したことがあると思います。 また、Rakeタスクを組み合わせると設定したタイミングでメールを自動送信することも可能です。
今回は、ユーザーの新規登録をした際に通知メールが届く機能を実装します。

実装手順

簡易的なアプリを使って説明していきます。

まずは簡易的なアプリをscaffoldで作成します。

まずはrails newしたあとにscaffoldでuserテーブルに今回の実装に必要なカラムを追加します。

rails generate scaffold user name email login

以下のコマンドを実行し適用させます。

rails db:migrate

これでアプリの設定は終わりです!


メイラー生成

メールを送信するにはメイラー(コントローラーと似ている)とビューを作成する必要があります。
これからメイラーとビューを作成していきます。
ターミナルで以下のコマンドを実行します。

rails g mailer UserMailer

下記のログがでているはずです!

   Running via Spring preloader in process 28545
      create  app/mailers/user_mailer.rb
      invoke  erb
      create    app/views/user_mailer
      invoke  test_unit
      create    test/mailers/user_mailer_test.rb
      create    test/mailers/previews/user_mailer_preview.rb

ちなみにscaffoldでアプリを作成した場合にはapp/mailers/application_mailer.rbが自動で生成されています。 scaffoldは便利ですね。
これでメイラー生成は終了です!


ビューを作成する

先程作成したapp/views/user_mailerディレクトリの中にビューを作成してきます。
app/views/user_mailerディレクトリ下のファイルがレイアウトファイルのapp/views/layouts/application.html.erbに読み込まれます。
また、 scaffoldでアプリを作成した際にはレイアウトファイルも自動生成されています!

以下のファイルを作成してください

# app/views/user_mailer/welcome_email.html.erb

<!DOCTYPE html>
<html>
  <head>
    <meta content='text/html; charset=UTF-8' http-equiv='Content-Type' />
  </head>
  <body>
    <h1><%= @user.name %>様、example.comへようこそ。</h1>
      <p>
      example.comへのサインアップが成功しました。
      your username is: <%= @user.login %>.<br>
    </p>
    <p>
      このサイトにログインするには、<%= @url %>をクリックしてください。
    </p>
    <p>ご入会ありがとうございます。どうぞお楽しみくださいませ。</p>
  </body>
</html>
# app/views/user_mailer/welcome_email.text.erb

<%= @user.name %>様、example.comへようこそ。
===============================================

example.comへのサインアップが成功しました。ユーザー名は「<%= @user.login %>」です。

このサイトにログインするには、<%= @url %>をクリックしてください。

本サイトにユーザー登録いただきありがとうございます。

html形式とtext形式のファイルを作成する理由は、顧客によってフォーマットを使い分けるためです。 これでビューの作成は終了です!

メイラー編集

app/mailers/user_mailer.rbに空のメイラーがあるので、メールを送信するためのメソッドを定義していきます。

# output_app/app/mailers/user_mailer.rb

class UserMailer < ApplicationMailer
  default from: 'notifications@example.com'

  def welcome_email
    @user = params[:user]
    @url  = 'http://example.com/login'
    mail(to: @user.email, subject: '私の素敵なサイトへようこそ')
  end
end

上記のコードを解説していきます。
default from:はメールの送信元名です。
メールにはユーザーのname、login、emailを表示したいので、メソッドの中にインスタンス変数を作成しておきます。
ここで定義したwelcome_emailメソッドをビューやRakeタスクで実行することでメール送信のタイミングを使い分けています。
mailメソッドで使われているsubjectはメールの件名になります。

controllerを編集する

ユーザーが新規登録された際に登録したメールアドレスに登録完了メールが送信されるようにコードを追記していきます。
scaffoldで生成されたoutput_app/app/controllers/users_controller.rbのcreateメソッドを編集します。

class UsersController < ApplicationController
    def create
    @user = User.new(user_params)

    respond_to do |format|
      if @user.save
        # 保存後にUserMailerを使ってwelcomeメールを送信
        UserMailer.with(user: @user).welcome_email.deliver_later

        format.html { redirect_to(@user, notice: 'ユーザーが正常に作成されました。') }
        format.json { render json: @user, status: :created, location: @user }
      else
        format.html { render action: 'new' }
        format.json { render json: @user.errors, status: :unprocessable_entity }
      end
    end
  end

実装確認

それでは実際にユーザーを作成しメールが送られてくるか確認します。 letter_opener_webを使いメールを受信します。
上記についても記事を書いたのでどうぞ

letter_opener_web実装方法 - Mekun’s blog


ユーザー新規登録

f:id:Mekun:20210925062208p:plain


letter_opener_web

f:id:Mekun:20210925062218p:plain


上記の通りユーザー新規登録時に入力した情報をもとにメールが届いていました。実装完了です!

letter_opener_web実装方法

letter_opener_web

letter_opener_webを導入すると、送信されたメールをwebサイトで確認することができます。
ログイン時のパスワードリセットなどなど、RailsのMailerと組み合わせて使われるケースが多いです。

実装方法

Gemfile

Gemfileに設定を行っていきます

# Gemfile

group :development do
  gem 'letter_opener_web'
end

ターミナルで下記コマンドを実行しgemを読み込みます。

bundle install

Gemfile.lockを見てみるとletter_opener_webが追加されています。

# Gemfile.lock

       launchy (2.5.0)
      addressable (~> 2.7)
    letter_opener (1.7.0)
      launchy (~> 2.2)
    letter_opener_web (1.4.0)
      actionmailer (>= 3.2)
      letter_opener (~> 1.0)
      railties (>= 3.2)

DEPENDENCIES
       letter_opener_web


config/routes.rb

config/routes.rbにも設定を記述していきます。

# config/routes.rb
Rails.application.routes.draw do
  root to: 'welcome#index'
  # (略)

  # letter opener の為に、一番最後に記述する
  mount LetterOpenerWeb::Engine, at: '/letter_opener' if Rails.env.development?
end


config/environments/development.rb

config/environments/development.rbにも設定を記述していきます。

# config/environments/development.rb

# letter opener の為に以下を追加
  config.action_mailer.default_url_options = { host: 'localhost:3000' }
  config.action_mailer.perform_caching = true
  config.action_mailer.delivery_method = :letter_opener_web
  config.action_mailer.perform_deliveries = true


使い方

ターミナルで下記コマンドを実行しサーバーを起動します。

rails s

ブラウザのURL欄にlocalhost:3000/letter_opener/と入力します。
すると下記のようにletter_opener_webが立ち上がるので実装完了です! f:id:Mekun:20210921052049p:plain

参考にしたサイト

https://github.com/fgrehm/letter_opener_web

Ruby on Rails の 開発環境で 簡単にメール送信機能の確認ができる letter_opener_web の使い方

has_many_attachedを使いUnpermitted parameter が発生した

Active Storageを使用して、複数のファイルを1つのレコードに添付する実装を行っていてエラーに遭遇したのでメモしておきます。

最初に結論

複数のファイルを1つのレコードに添付する場合は、permitメソッドを下記のように使う必要がありました。

params.permit(キー: [])

詳しく解説していきます。

エラーが発生した状況

# エラー内容

Unpermitted parameter: :main_images
# app/models/site.rb

has_many_attached :main_images
# edit.html.slim

= f.input :main_images, as: :file, input_html: {multiple: true}, hint: 'JPEG/PNG (1200x400)'

- if @site.main_images.attached?
        .main_images_box
          - @site.main_images.each do |main_image|
            .main_image
              = image_tag main_image.variant(resize:'300x100').processed
              = link_to '削除', admin_site_attachment_path(main_image.id), method: :delete, class: 'btn btn-danger'
# app/controllers/sites_controller.rb

def site_params
    params.require(:site).permit(:name, :subtitle, :description, :favicon, :og_image, :main_images)
end

経緯

複数の画像を一度に登録できるレコードを作成するのが目的です。
そこでActive Storagehas_many_attachedを使い、レコードとファイルの間に1対多の関係を設定しました。
viewも記述し、Webブラウザで複数の画像を登録してみたら上記に載せたUnpermitted parameter: :main_imagesというエラーが発生しました。
いつもなら_paramsメソッドのpermitの中身が抜けているorタイポ、この二択だったのですが、今回はどちらも当てはまりませんでした。

どのようにエラーを解決したか

困ったときのRailsガイド!を調べてみると載っていました。 
引用: Active Storage の概要 - Railsガイド

def message_params
  params.require(:message).permit(:title, :content, images: [])
end                                                                                                

images: []この記述だけ見ても基礎がなっていない自分には理解できなかったので、permitメソッドに載っているのかな?と思いpermitメソッドについて調べてみました。

わかりやすく解説している記事が見つかったので引用させていただきました。
【Rails】 | Pikawaka - ピカ1わかりやすいプログラミング用語サイト

バリューに複数の値が入るときは配列となってパラメーターに入ります。
例えばform_forやform_withでフォームを作成する際、collection_check_boxesで複数の値を保存するときなどに使います。

今回実装した、複数枚の画像を表示する箇所のHTMLを検証ツールを使い調べてみました。

<input multiple="multiple" class="file optional" type="file" name="site[main_images][]" id="site_main_images">

上記のようにnameのバリューが配列となっていることがわかりました。

今回の学び

  • バリューに複数の値が入るときは配列となってパラメーターに入るので、params.permit(キー: [])のような形で記述する。
  • フォームヘルバーを使った場合はHTMLを見てみると理解が深まる。
  • 実装したい機能がある場合は公式リファレンスを読み込む。

ポリモーフィック関連付けとは

動機

Railsで記事に埋め込み機能を実装する際に、ポリモーフィック関連付けが出てきたので、理解を深めるためにも備忘録としてメモを書いていきます。

以下のモデル構成で説明していきます。

# article_block.rb

class ArticleBlock < ApplicationRecord
  belongs_to :article
  belongs_to :blockable, polymorphic: true, dependent: :destroy
  # ...
end
# sentence.rb

class Sentence < ApplicationRecord
  has_one :article_block, as: :blockable, dependent: :destroy
  # ...
end
# medium.rb

class Medium < ApplicationRecord
  has_one :article_block, as: :blockable, dependent: :destroy
  # ...
end
# embed.rb

class Embed < ApplicationRecord
  has_one :article_block, as: :blockable, dependent: :destroy
  # ...
end
# article.rb

class Article < ApplicationRecord
  has_many :article_blocks, -> { order(:level) }, inverse_of: :article
  has_many :sentences, through: :article_blocks, source: :blockable, source_type: 'Sentence'
  has_many :media, through: :article_blocks, source: :blockable, source_type: 'Medium'
  has_many :embeds, through: :article_blocks, source: :blockable, source_type: 'Embed'
  # ...
end


ポリモーフィックとはなにか?

ポリモーフィック関連付けを使うと、ある1つのモデルが他の複数のモデルに属していることを、1つの関連付けだけで表現できます。

:引用 Active Record の関連付け - Railsガイド

上記のコードを例にすると、ArticleBlockモデルは、複数のモデルSentence, Medium, Embedに属していることを1つの関連付けのblockableだけで表現できています。

ポリモーフィックってなんなの?という人におすすめの記事です。
Railsのポリモーフィック関連とはなんなのか - Qiita

この記事を読んで、「ダックタイピング」、「インターフェース」という言葉がでてきてあまり理解できなかったので、調べようと思いました。

ポリモーフィック関連付けの方法

説明

  • belongs_to宣言に:polymorphicオプションを指定すると、ポリモーフィック関連になる
  • 参照先となるモデルをあらかじめ定義せず、参照元となるオブジェクトごとに指定する関連

    使い方

  • 参照元となるモデルに対応するテーブルに、参照先のIDと参照先クラスを指定するカラムをそれぞれ生成
  • 参照先のIDを保存するカラムは、
    「belongs_to宣言で渡す関連名_id」
  • 参照先クラスを指定するカラムは「関連名type」

:引用 ポリモーフィック関連 | Railsドキュメント


今回のモデル構成で説明してみます。

下記のArticleBlockクラスでポリモーフィック関連を利用するために、belongs_to :blockablepolymorphicオプションが指定されています。

# article_block.rb

class ArticleBlock < ApplicationRecord
  belongs_to :article
  belongs_to :blockable, polymorphic: true, dependent: :destroy
  # ...
end


asオプションとは?

:asオプションを設定すると、ポリモーフィック関連付けを指定できます。

:引用  Active Record の関連付け - Railsガイド

ポリモーフィック関連付けの利点

1つのモデルが他の複数のモデルに属していることを、一つの関連付けだけで表現できることです。 両者のコードを比べるとポリモーフィック関連付けありの方がコードが見やすくなっているのがわかると思います。

# article_block.rb

# ポリモーフィック関連付けなし

class ArticleBlock < ApplicationRecord
  belongs_to :article
  belongs_to :sentence
  belongs_to :medium
  belongs_to :embed
  # ...
end
# article_block.rb

# ポリモーフィック関連付けあり

class ArticleBlock < ApplicationRecord
  belongs_to :article
  belongs_to :blockable, polymorphic: true, dependent: :destroy
  # ...
end

参考にしたサイト

Active Record の関連付け - Railsガイド

ポリモーフィック関連 | Railsドキュメント

Railsのポリモーフィック関連とはなんなのか - Qiita

ポリモーフィック関連のコントローラー - 猫Rails

 

ActiveModelとは

ActiveModelとは

ActiveModelとはActiveRecodeモデルクラスの便利な機能を普通のクラスで実現することをサポートしてくれるモジュール郡です。

例えば一括代入、バリデーション、コールバックが例です。
「データベースに直接紐付かないけれども、フォームの入力を検証して何らかの操作を行う。」ようなモデルを作るために便利に使えます。 上記のような概念をFormObjectといいます。

ActiveModelは多くのサブモジュールから構成されています。
その中でもよく使われるActiveModel:Modelを例に説明していきます。

ActiveModel:Modelとは

ActiveModel::Modelをincludeすると、以下のような機能を使えるようになります。

  • モデル名の調査
  • 変換
  • 翻訳
  • バリデーション

これら上記の機能が使える理由は ActiveModel:Modelをincludeすれば、以下のモジュールが一度に利用できるようになるからです。

  • ActiveModel::AttributeAssignment 
  • ActiveModel::Naming
  • ActiveModel::Conversion
  • ActiveModel::Translation
  • ActiveModel::Validations

モジュールの詳細はこちらに

Active Model の基礎 - Railsガイド

まとめ

  • ActiveModelとはActiveRecordを継承しないクラスでもActiveRecordと同じような便利メソッドが使えるようになる

  • ActiveModelは多くのサブモジュールから構成されている。

  • ActiveModelはFormObjectという概念で使われることが多い

ActiveModelとは

ActiveModelとは

ActiveModelとはActiveRecodeモデルクラスの便利な機能を普通のクラスで実現することをサポートしてくれるモジュール郡です。

例えば一括代入、バリデーション、コールバックが例です。
「データベースに直接紐付かないけれども、フォームの入力を検証して何らかの操作を行う。」ようなモデルを作るために便利に使えます。 上記のような概念をFormObjectといいます。

ActiveModelは多くのサブモジュールから構成されています。
その中でもよく使われるActiveModel:Modelを例に説明していきます。

ActiveModel:Modelとは

ActiveModel::Modelをincludeすると、以下のような機能を使えるようになります。

  • モデル名の調査
  • 変換
  • 翻訳
  • バリデーション

これら上記の機能が使える理由は ActiveModel:Modelをincludeすれば、以下のモジュールが一度に利用できるようになるからです。

  • ActiveModel::AttributeAssignment 
  • ActiveModel::Naming
  • ActiveModel::Conversion
  • ActiveModel::Translation
  • ActiveModel::Validations

モジュールの詳細はこちらに

Active Model の基礎 - Railsガイド

まとめ

  • ActiveModelとはActiveRecordを継承しないクラスでもActiveRecordと同じような便利メソッドが使えるようになる

  • ActiveModelは多くのサブモジュールから構成されている。

  • ActiveModelはFormObjectという概念で使われることが多い

find_eachメソッドとは

find_eachメソッドは、分割してレコードを取得して1件ずつ処理します。
メモリの消費量を抑えることができるため、
大量のレコードをループ処理する際に使われます。
デフォルトでは、レコードを1000件ごとに取得して処理を実行します。

構文

find_eachメソッドは、モデルのクラスに対して呼び出すことができるメソッドです。

モデル.find_each([オプション]) do |f|
 処理内容
end