4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[Rails7]LINE通知機能の実装

4
Posted at

1.初めに

 今回はLINEの通知機能を実装してみましたので、記事にしてみました!
この記事は、LINEで登録する機能を既に実装済みで記事を書いていますので、もしLINEログイン機能の実装をされてなければこの記事を参考にしてみてください。(https://qiita.com/taka_01/items/664ae7f21268494f6675)

2.Messaging APIチャネルの作成

 まずは、LINEで登録してくれた人に対して通知するためのアカウントを作成します。(https://manager.line.biz/)
その後は、ヘッダーの上部右側にある設定ボタンを開いて、Messaging APIを有効にします。そして、LINE Developerツールで作成したプロバイダーと連携をさせます。ここまでできれば、LINE Developerにログインした際にLINE Messaging API用のチャンネルが作成されているはずです。
また、公式からこのあたりの内容について記載されているので、参照をお願いいします。(https://developers.line.biz/ja/docs/messaging-api/getting-started/)
※このあたりの話は、UIが変更されたりして、操作が変わったりするので、逐次、自分でも確認をお願いします。

3.実際にコードに落としてみよう!

 今回の大まかな通知のロジックは以下です。

「学習スケジュールを登録→ユーザー登録はLINEでしているか?ORそれ以外か?→もしLINEで登録されていることが確認できたらLINEで通知をする」

こんな感じですかね!
それでは実装の方に移ります。今回は前回のLINEログインの様に、自前実装はせずに、line-bot-api を使用していきます。Gemfileに以下のコードを記載して、bundle installを実行します。

gem "line-bot-api", "~> 2.0"

実際に、通知のロジックを実装していきます。

schedules_controller.rb
def create
   @schedule = current_user.schedules.build(schedule_params)

   if @schedule.save
     ScheduleNotifier.new(@schedule).notify_registered
     redirect_to profile_path, notice: "スケジュールを作成しました"
   else
     @study_units = StudyUnit.ordered
     render :new, status: :unprocessable_entity
   end
 end

ScheduleNotifier.new(@schedule).notify_registeredの部分で、schedule_notifier.rbに処理が移ります。

app/services/schedule_notifier.rb
def notify_registered
   if @user.line_provider?
     LineNotificationJob.perform_later(:schedule_registered, @schedule.id)
   else
     ScheduleMailer.registered(@schedule).deliver_later
   end
 end

もし、LINEでログインをしているかの判定は、userテーブルでline_providerを持っていると、LineNotificationJob.perform_later(:schedule_registered, @schedule.id)が働きます。

app/jobs/line_notification_job.rb
class LineNotificationJob < ApplicationJob
  queue_as :default # このジョブを default(デフォルト)キューに入れる指定。キューは「処理の待ち行列」で、優先度や種類で分けられます。ここでは特に分けず標準キューを使ってます。
  
  # スケジュールが削除済みなら通知不要なので捨てる
  discard_on ActiveJob::DeserializationError

  # ネットワーク一時障害などに備えてデフォルト5回までリトライ
  # (LineNotifier側で永続エラーは内部処理しているので主にDB/HTTP一時エラー用)
  retry_on StandardError, wait: :polynomially_longer, attempts: 5

#この下以降のコードが、今回の通知の処理を働くジョブのコードです。
  def perform(notification_type, schedule_id)
    schedule = Schedule.find_by(id: schedule_id)
    return unless schedule

    case notification_type.to_sym
    when :schedule_registered
      LineNotifier.new(schedule.user).schedule_registered(schedule)
    end
  end
end

上記のコード、LineNotifier.new(schedule.user)で通知で送られる内容が生成されていきます。

app/services/line_notifier.rb

require "line/bot/v2/messaging_api/api/messaging_api_client"
require "line/bot/v2/messaging_api/model/push_message_request"
require "line/bot/v2/messaging_api/model/text_message"

class LineNotifier
  include Rails.application.routes.url_helpers

  WEEKDAY_NAMES = %w[月 火 水 木 金 土 日].freeze

  def initialize(user)
    @user = user
  end

#「profile_url」が作成できるようにホスト名を付与している。
  def default_url_options
    Rails.application.config.action_mailer.default_url_options
  end

  def schedule_registered(schedule)
    return unless @user.line_provider? #ユーザー登録がLINE登録か確認

    push_text(build_schedule_registered_text(schedule))
  end

  private

  def push_text(text)
    request = Line::Bot::V2::MessagingApi::PushMessageRequest.new(
      to: @user.uid,
      messages: [ Line::Bot::V2::MessagingApi::TextMessage.new(text: text) ]
    )
    _body, status_code, _headers = client.push_message_with_http_info(push_message_request: request)

    unless (200..299).cover?(status_code)
      Rails.logger.error("[LineNotifier] push failed: status=#{status_code} uid=#{@user.uid}")
    end
  rescue StandardError => e
    Rails.logger.error("[LineNotifier] push exception: #{e.class}: #{e.message}")
  end

#clinentオブジェクトを作成して、LINE側と通信するためのオブジェクトを作成しています。
  def client
    @client ||= Line::Bot::V2::MessagingApi::ApiClient.new(
      channel_access_token: ENV.fetch("LINE_MESSAGING_CHANNEL_ACCESS_TOKEN")
    )
  end

  def build_schedule_registered_text(schedule)
    lines = [ "📚 学習予定を登録しました!", "" ]
    lines << "■ 学習期間"
    lines << "#{format_date(schedule.start_date)}#{format_date(schedule.end_date)}"
    lines << ""
    lines << "■ 1日の目標学習時間"
    lines << "#{schedule.daily_study_hours}時間"

    if schedule.weekdays.present?
      lines << ""
      lines << "■ 学習曜日"
      lines << schedule.weekdays.compact.map { |i| WEEKDAY_NAMES[i] }.join("、")
    end

    if schedule.study_units.any?
      lines << ""
      lines << "■ 学習ユニット"
      lines << schedule.study_units.map(&:name).join("、")
    end

    if schedule.memo.present?
      lines << ""
      lines << "■ メモ・目標"
      lines << schedule.memo
    end

    lines << ""
    lines << "詳細はこちら:"
    lines << profile_url
    lines.join("\n")
  end

# 日付を「4月9日」と日本語表記に変換するのと、date.blank? で nil や空のときは空文字を返して、nil月nil日 みたいな変な表示を防いでる。
  def format_date(date)
    return "" if date.blank?
    "#{date.month}#{date.day}日"
  end
end

つらつらとコードが書かれていますが、大事なのは、以下のコード達になります。

def initialize(user)
    @user = user
end

LineNotifier.new(user) で呼ばれたとき、対象ユーザーをインスタンス変数 @user に保存して、そのユーザーに対して通知を送る。

def schedule_registered(schedule)
    return unless @user.line_provider? 

    push_text(build_schedule_registered_text(schedule))
end

上記のコードが、送信の起爆剤になります。

def push_text(text)
    request = Line::Bot::V2::MessagingApi::PushMessageRequest.new(
      to: @user.uid,
      messages: [ Line::Bot::V2::MessagingApi::TextMessage.new(text: text) ]
    )
    _body, status_code, _headers = client.push_message_with_http_info(push_message_request: request)

    unless (200..299).cover?(status_code)
      Rails.logger.error("[LineNotifier] push failed: status=#{status_code} uid=#{@user.uid}")
    end
  rescue StandardError => e
    Rails.logger.error("[LineNotifier] push exception: #{e.class}: #{e.message}")
end

そして、さっきのコードのpush_textの部分が上記のコードで、実際にline-bot-apiを叩いて、LINEで通知を送ります。

def build_schedule_registered_text(schedule)
    lines = [ "📚 学習予定を登録しました!", "" ]
    lines << "■ 学習期間"
    lines << "#{format_date(schedule.start_date)}〜#{format_date(schedule.end_date)}"
    lines << ""
    lines << "■ 1日の目標学習時間"
    lines << "#{schedule.daily_study_hours}時間"

    if schedule.weekdays.present?
      lines << ""
      lines << "■ 学習曜日"
      lines << schedule.weekdays.compact.map { |i| WEEKDAY_NAMES[i] }.join("、")
    end

    if schedule.study_units.any?
      lines << ""
      lines << "■ 学習ユニット"
      lines << schedule.study_units.map(&:name).join("、")
    end

    if schedule.memo.present?
      lines << ""
      lines << "■ メモ・目標"
      lines << schedule.memo
    end

    lines << ""
    lines << "詳細はこちら:"
    lines << profile_url
    lines.join("\n")
end

 そして、push_text(build_schedule_registered_text(schedule))の引数の部分が通知の内容になっているので、上記のコードが通知の本文を組み立てています。
つまり、schedule_registeredでユーザー登録がLINE登録か確認して、build_schedule_registered_text(schedule)で本文を作成し、push_textでメッセージを送信するといった流れになります。

5.最後に

 今回、LINEでの通知機能を実装してみてのですが、gemを使用すると送信のロジックを考えなくていいので、かなり負担の軽減になるのではないでしょうか?一方で、UIの変更があったので、Messaging APIを有効化させるまでのLINE側での操作が大変だった記憶があります(-_-;)
また、LINE通知の機能に関するものはserviceオブジェクトに切り出していますが、fat controllerやfat modelを避けるとともに、CURD以外のロジック以外は切り出した方が良いのではないかといった点で切り出しました。といったように、責務などの点でも考えることができたので、とても勉強になりました。この記事が、何かの役に立てれば幸いです。

参考記事

LINEのbotでメッセージを送受信するまでの実装の流れ(Rails+Messaging API)

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?