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"
実際に、通知のロジックを実装していきます。
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に処理が移ります。
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)が働きます。
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)で通知で送られる内容が生成されていきます。
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以外のロジック以外は切り出した方が良いのではないかといった点で切り出しました。といったように、責務などの点でも考えることができたので、とても勉強になりました。この記事が、何かの役に立てれば幸いです。
参考記事