ポリモーフィック関連で複数のテーブルの更新を通知する機能をRailsで実装

Rialsアプリで、複数のテーブルの更新を通知する機能を実装します。しかも、今回は、sorceryのActivity loggingを使って、前回のログアウト以降の更新だけを通知する機能を実装します。

ポリモーフィック関連というのは、複数の親モデルを持ち、親モデルが違っても構造が変わらない子テーブルのことです。

ポリモーフィック関連用のモデルを作成

今回はポリモーフィック関連用のモデルとしてnotificationモデルを作成します。ポリモーフィック関連用のモデルを作成するには、下記のように***able::references{polymorphic}を付け加えます。

rails g model notification notificable:references{polymorphic}

すると下記のようなマイグレーションファイルが生成されますので、それをrails db:migrateします。

class CreateNotifications < ActiveRecord::Migration[5.0]
  def change
    create_table :notifications do |t|
      t.references :notificable, polymorphic: true

      t.timestamps
    end
  end
end

sorceryのActivity loggingでログイン/ログアウト時間を管理をする

今回は、前回のログアウト以降だけの通知をしたいので、ログアウト時間を管理する必要があります。

Railsの認証のgemであるsorceryにはActivity loggingというモジュールがあり、これを使うと簡単にログイン/ログアウト時間の管理ができます。

sorcery自体の実装はこちらで紹介しています。
参考:SorceryでRailsアプリケーションに認証機能を実装する

すでにsorderyはインストールしている前提で、下記のコマンドを叩くと、

rails g sorcery:install activity_logging --only-submodules

こんな感じで、マイグレーションファイルが生成されますので、これをrails db:migrateします。

class SorceryActivityLogging < ActiveRecord::Migration[5.0]
  def change
    add_column :users, :last_login_at,     :datetime, :default => nil
    add_column :users, :last_logout_at,    :datetime, :default => nil
    add_column :users, :last_activity_at,  :datetime, :default => nil
    add_column :users, :last_login_from_ip_address, :string, :default => nil

    add_index :users, [:last_logout_at, :last_activity_at]
  end
end

これで、ログイン/ログアウト時間の管理ができるようになりました。

モデル間のアソシエーションを定義

今回は、board、commentを親モデル、notificationモデルをその子モデル(ポリモーフィック関連用のモデル)としたいと思います。

その関係をモデルファイルで定義しておきます。notificationモデルの定義の書き方が、ポリモーフィック関連特有の書き方です。

board.rb

class Board < ApplicationRecord

    has_one :notification, :as => :notificable
    
end

comment.rb

class Comment < ApplicationRecord

    has_one :notification, :as => :notificable
end

notification.rb

class Notification < ApplicationRecord
  belongs_to :notificable, polymorphic: true
  scope :recent, -> (last_logout_at) {where("created_at > ?", last_logout_at)}
end

※ログアウト時間より後に作成されたnotificationレコードだけを抽出する際に使うscopeもついでに実装しています。

notificationレコードを作成する機能の実装

今回は、掲示板(board)テーブルとコメント(comment)テーブルのレコードが作成されるたびに通知(notification)が作成されるように実装します。

掲示板とコメントのcreateアクションで、Notification.createを呼び出します。通知(notification)は、boardとcommentの子モデルとして保存します。

boards_controller.rb

    def create
        @board = Board.new(board_params)
        if @board.save
            @board.notification = Notification.create
            redirect_to boards_path
        else
            render :new
        end

    end

comments_controller.rb

    def create
        @board = Board.find_by(id: params[:board_id])
        @comment = @board.comments.build(comment_params)
        if @comment.save
            @comment.notification = Notification.create
            @comment = Comment.new
            @comments = @board.comments
        else
            render "boards/show"
        end
    end

notifications_controllerでは、notification.rbで定義したscopeのメソッドを使って、前回のログアウト後のレコードだけを抽出するようにしています。

notifications_controller.rb

    def index
                @notifications = Notification.recent(current_user.last_logout_at)
    end

通知のタイプで表示を出し分ける

view側では、掲示板とコメントのどちらのテーブルが作成されたのかによって、表示を変えてみます。

ポリモーフィック関連モデルを作成すると、***able_typeというカラムが作成され、どの親モデルを持つかが記録されています。それを使って、表示を変えられます。

<% @notifications.each do |n| %>
    <% if n.notificable_type == "Board" %>
        新しい掲示板が作成されました
    <% elsif   n.notificable_type == "Comment"%>
        掲示板にコメントが付きました
    <% end %>
<% end %>

あと、ミスりやすい点として、boardとcommentに親子関係がある場合(boardを親、commentが子の場合)、boardのtitle属性を表示させようとしたら、下記のように書きます。

言われてみれば当たり前ですが、最初、ちょっと混乱しました。

<% @notifications.each do |n| %>
    <% if n.notificable_type == "Board" %>
        新しい掲示板#{n.notificable.title}が作成されました。
    <% elsif   n.notificable_type == "Comment"%>
        掲示板#{n.notificable.board.title}にコメントが書き込まれました。
    <% end %>
<% end %>

通知機能は、継続的にユーザーに使ってもらうようなサービスでは、重要なので、覚えておきたいです。

Ruby on Railsをこれから本格的に勉強したい人は「エンジニアになるための600時間のプログラミング学習に耐え抜くコツ」という記事がおすすめです。

シェアする

  • このエントリーをはてなブックマークに追加

フォローする