Ruby on Railsのアプリに LINEログイン、LINE Messaging APIによるメッセージ送信を組み込む

32 minute read

現代のコミュニケーション手段として LINE はなくてはならないものになりました。今回は、その LINE を Ruby on Rails のアプリケーション (以降、Rails アプリ) から使えるようにします。

ソースコードは takaokouji/everdiary-line にあります。

環境

  • Apple M3 (MacBook Air 13 2024)
  • macOS Sonoma 14.6.1
  • Homebrew
  • Visual Studio Code 1.93.0
    • Dev Container 機能拡張をインストール済み
  • Docker Desktop 4.34.2
  • ruby 3.3.5
    • anyenvrbenv をインストール
    • ruby 3.3.5 を global に設定済み ( rbenv global 3.3.5 )
  • rails 7.2.1 gem
    • gem install rails
  • 他のソフトウェアは Docker コンテナ上にインストール
  • LINE アカウント
    • スマホで LINE を使えるようにしておきます。

Rails アプリ

今回 LINE を組み込むのは、1行日記を記録する Everdiary (エバーダイアリー) という Rails アプリです。

Everdiary は、ユーザー(users)と日記(diaries)の2つのテーブルのみ。そして、日記には、日記を書いた日(written_on)と255文字以下の日記(content)の2つのカラムのみ、という単純なものにします。

  • ユーザーテーブル (users)
    • Devise が自動生成するカラムのみ
  • 日記テーブル (diaries)
    • written_on: 日記を書いた日
    • content: 日記
    • user_id: ユーザーとの関連

早速、作っていきます。

初期セットアップ。ターミナルで実行します。

rails new everdiary --css=tailwind --javascript=esbuild --database=mysql --devcontainer --skip-bundle --skip-git

Visual Studio Code で everdiary を開き、Docker コンテナを構築。以降はコンテナ上で作業します。

code everdiary

Visual Studio Code でコンテナ起動

コンテナ上で Rails の再セットアップ。rm bin/rails がポイントです。こうすることで、tailwindcss や esbuild のインストールをコンテナ上で再現できます。

rm bin/rails
bundle exec rails new . -f -n everdiary --css=tailwind --javascript=esbuild --database=mysql --devcontainer
bin/bundle add tailwindcss-rails
bin/dev

ブラウザで表示後、 bin/dev を control-c で停止させます

モデルの作成やDeviseの導入。ターミナルで以下のコマンドを実行する。

bin/bundle add devise
bin/rails generate devise:install
bin/rails generate devise User
bin/rails generate scaffold Diary written_on:date:uniq content:string user:references --skip-jbuilder
bin/rails db:migrate

app/models/user.rb を修正する。

class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

  has_many :diaries, dependent: :destroy # ←この行を追加
end

app/controllers/application_controller.rb を修正する。

class ApplicationController < ActionController::Base
  # Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has.
  allow_browser versions: :modern

  before_action :authenticate_user!, unless: :devise_controller? # ←この行を追加
end

app/controllers/diaries_controller.rb の修正。1行日記とログインユーザーを紐づけます。

# (省略)
    def diary_params
      params.require(:diary).permit(:written_on, :content).merge(user: current_user) # ←この行を修正
    end
end

app/views/diaries/_form.html.erb の修正。user_id は入力不要なので削除します。

<%# 省略 %>
  <div class="my-5">
    <%= form.label :content %>
    <%= form.text_field :content, class: "block shadow rounded-md border border-gray-400 outline-none px-3 py-2 mt-2 w-full" %>
  </div>

  <%# ここに記述されていた user_id のフォームを削除する %>
  <div class="inline">
    <%= form.submit class: "rounded-lg py-3 px-5 bg-blue-600 text-white inline-block font-medium cursor-pointer" %>
  </div>
<%# 省略 %>

config/routes.rb を修正。 http://localhost:3000/ にアクセスしたときや、サインイン後に1行日記の一覧を表示するように修正。

# (省略)
  # Defines the root path route ("/")
  root "diaries#index"
end

app/views/layouts/_navbar.html.erb の作成。サインイン後にメールアドレスとサインアウト用のリンクを表示する。

<header class="flex items-center shadow-lg py-2 px-2 mb-10">
  <% if user_signed_in? %>
    <div class="font-bold mr-3">
      <%= current_user.email %>
    </div>
    <%= button_to "Sign out", destroy_user_session_path, method: :delete,
      class: "rounded-md py-1 px-3 border-solid border-[1px] border-black block" %>
  <% end %>
</header>

app/views/layouts/application.html.erb の修正。

<%# 省略 %>
  <body>
    <%= render "layouts/navbar" %> <%# ←この行を追加 %>
    <%= yield %>
  </body>
<%# 省略 %>

サーバーを起動して動作確認。

bin/dev

動作確認の内容は以下のようなものです。

  • http://localhost:3000/ にアクセスすると、サインイン画面にリダイレクト
    • サインイン画面と次のサインアップ画面にはスタイルが全くあたっていない。が、ここでは気にしないことにします。
  • サインアップを押して、メールアドレスとパスワードを入力して、ユーザーの作成

サインアップとサインイン

  • 日記の一覧画面を表示
  • 日記の作成・編集・削除

日記のCRUD

これで必要最低限の機能を持った Rails アプリケーションができました。

LINE ログイン・メッセージ送信の流れ

LINE のチャネルの登録から LINE ログインしてメッセージを送信するまでの流れは次のようになります。

LINE ログイン・メッセージ送信の流れ

LINE ログイン・Messaging APIチャンネルの作成

LINE ログインとメッセージ送信には次の情報が必要です。

  • LINE Developers/プロバイダー/LINEログイン
    • チャネルID
    • チャネルシークレット
  • LINE Developers/プロバイダー/Messaging API
    • チャネルシークレット
    • チャネルアクセストークン

まずは LINE Developersコンソール でプロバイダーと、LINE ログインチャネルを登録します。

  • LINE アカウントで LINE Developers コンソールにログイン
  • プロバイダーの登録
  • LINE ログインチャネルの登録
    • こちらは開発用と本番用の2つを作ることになるため、everdiary-login-dev、everdiary-login-prod にします。

これで、LINE ログインのチャネルIDとチャネルシークレットを用意できました。

次に LINE 公式アカウント で LINE 公式アカウントと Messaging API の利用を開始します。

  • LINE 公式アカウントの作成
  • LINE 公式アカウント上の設定から「Messaging APIを利用する」

もう一度 LINE Developersコンソール でチャネルアクセストークンを発行します。

  • LINE Developersコンソールでプロバイダーを選択する
  • Messaging APIのチャネルを選択する
  • Messaging API設定タブを表示して、チャンネルアクセストークン(長期)を発行する

これで、LINE Messaging APIのチャネルシークレットとチャネルアクセストークンを用意できました。

それぞれを .env を作成して記載します。

# LINE ログイン
LINE_KEY="チャネルID"
LINE_SECRET="チャネルシークレット"

# LINE Messaging API
LINE_CHANNEL_SECRET="チャネルシークレット"
LINE_CHANNEL_TOKEN="チャンネルアクセストークン"

ngrokのセットアップ

LINE ログインの動作確認で利用する ngrok をセットアップします。ngrok を使えば、http://localhost:3000/ にインターネットからアクセスできるようになります。

まずは ngrok にサインアップ(sign up)します。用途などのアンケートにいくつか回答すると、サインアップできます。

続いて、サインアップ後の画面に従って、ngrokアプリケーションをインストールします。

brew install ngrok/ngrok/ngrok
ngrok config add-authtoken xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# (↑のxxx...はngrokの画面のものに置き換えること。以下、実行結果)
# Authtoken saved to configuration file: /Users/kouji/Library/Application Support/ngrok/ngrok.yml

最後に config/environments/development.rb を修正して、 ngrok からアクセス可能にします。

# (省略)
  # Apply autocorrection by RuboCop to files generated by `bin/rails generate`.
  # config.generators.apply_rubocop_autocorrect_after_generate!

  config.hosts << /.*\.ngrok-free\.app/
end

これでセットアップできました。

今ではないのですが、今後 LINE ログインを試す前に、Visual Studio Code ではない (Docker 上ではない) 通常のターミナルで以下のコマンドを実行します。その後に表示された URL https://xxx-xxx-xxx-xxx-xxx.ngrok-free.app にアクセスすることで、インターネットから開発中のアプリケーションにアクセスできるようになります。

$ ngrok http http://localhost:3000

ngrok                                    (Ctrl+C to quit)

Share what you're building with ngrok https://ngrok.com/share-your-ngrok-story

Session Status                online
Account                       Kouji Takao (Plan: Free)
Version                       3.16.0
Region                        Japan (jp)
Latency                       25ms
Web Interface                 http://127.0.0.1:4040
Forwarding                    https://xxx-xxx-xxx-xxx-xxx.ngrok-free.app -> http://localhost:3000
                              ↑↑↑ この URL

LINE ログイン

これでようやく準備が整いました。
Everdiary を LINE ログインに対応させ、LINE のユーザーID (uid) を Rails アプリのユーザーに紐づけます。

まずは LINE のユーザーIDを格納するテーブルを作ります。

bin/rails generate model MyLineUser uid:string user:references:uniq
bin/rails db:migrate

次に LINE ログインで利用する gem をインストールします。

bin/bundle add omniauth-line omniauth-rails_csrf_protection dotenv

config/initializers/devise.rb に LINE ログインの設定を追加します。

# (省略)
  # ==> OmniAuth
  # Add a new OmniAuth provider. Check the wiki for more information on setting
  # up on your models and hooks.
  # config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo'
  config.omniauth :line, ENV["LINE_KEY"], ENV["LINE_SECRET"] # ← この行を追加

# (省略)

app/models/user.rb を修正。

# (省略)

  has_many :diaries, dependent: :destroy
  has_one :my_line_user, dependent: :destroy # ←この行を追加
end

app/models/my_line_user.rb を修正。

class MyLineUser < ApplicationRecord
  devise :omniauthable, omniauth_providers: %i[line] # ←この行を追加

  belongs_to :user
end

config/routes.rb を修正。

Rails.application.routes.draw do
  resources :diaries
  devise_for :users
  devise_for :my_line_users, controllers: { omniauth_callbacks: "omniauth_callbacks" } # ←この行を追加
# (省略)

LINE ログインによって呼び出されるコントローラーを作成します。

bin/rails generate controller omniauth_callbacks

app/controllers/omniauth_callbacks_controller.rb を修正します。ここではエラー処理は考えず、すべてうまくいく前提にしています。

class OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def line
    auth = request.env["omniauth.auth"]
    MyLineUser.create!(user: current_user, uid: auth["uid"])
    redirect_to root_path
  end
end

app/views/layouts/_navbar.html.erb を修正して LINE 連携用のリンクを追加します。 data: {turbo: "false"} がポイントで、これを指定しないと CORS のエラーで LINE ログインに失敗します。

<header class="flex items-center shadow-lg py-2 px-2 mb-10">
  <% if user_signed_in? %>
    <div class="font-bold mr-3">
      <%= current_user.email %>
    </div>
<%# ここから %>
    <% if current_user.my_line_user %>
      <div class="mr-3">
        LINE Linked
      </div>
    <% else %>
      <%= button_to "LINE Login", my_line_user_line_omniauth_authorize_path, data: {turbo: "false"},
        class: "rounded-md py-1 px-3 border-solid border-[1px] border-black block mr-3" %>
    <% end %>
<%# ここまで %>
    <%= button_to "Sign out", destroy_user_session_path, method: :delete,
      class: "rounded-md py-1 px-3 border-solid border-[1px] border-black block" %>
  <% end %>
</header>

なお、「LINE Linked」と「LINE Login」の日本語は、それぞれ「LINE連携済」「LINE連携」を想定しています。Everdiary は i18n 対応は行わないのですが、いちおう、日本語のラベルも考えています。

これで LINE ログイン対応の修正は終わりました。

動作確認のためにサーバーを起動して、

bin/dev

さらに ngrok を起動します。

$ ngrok http http://localhost:3000

ngrok                                                         (Ctrl+C to quit)

Share what you're building with ngrok https://ngrok.com/share-your-ngrok-story

Session Status                online
Account                       Kouji Takao (Plan: Free)
Version                       3.16.0
Region                        Japan (jp)
Latency                       25ms
Web Interface                 http://127.0.0.1:4040
Forwarding                    https://xxx-xxx-xxx-xxx-xxx.ngrok-free.app -> http://localhost:3000
                              ↑↑↑ この URL

そして、↑の Forwarding に表示されている https://xxx-xxx-xxx-xxx-xxx.ngrok-free.app/my_line_users/auth/line/callback を追加して、 LINE Developersコンソール のLINE ログインチャネルのコールバックURLに指定します。面倒ですが ngrok を起動するたびに設定してください

それでは、ブラウザで https://xxx-xxx-xxx-xxx-xxx.ngrok-free.app/ にアクセスしてサインインします。そして、LINE Login ボタンを押します。
ここでのポイントは http://localhost:3000/ ではなく ngrok の URL にアクセスすることです。

LINE ログイン

LINE ログインに成功して LINE のユーザーID (uid) を DB に保存できましたね。

LINE 連携を解除する方法を用意していないため、もう一度 LINE ログインを試す場合は、 rails console で MyLineUser を削除します。

$ bin/rails console
> MyLineUser.last.destroy

最後に LINE ログインを公開済みに変えます。これですべての LINE ユーザーがログインできるようになります。

  • LINE Developers コンソール にアクセスする
  • プロバイダーを選択する
  • LINE ログインチャネルを選択する
  • ↓の画像を参考にして公開済みに変更する

公開済みに変える

LINE メッセージ送信

続いて、日記を登録したときに LINE でメッセージ送信できるようにします。まったく便利な機能ではありませんが、メッセージ送信のテストなので割り切って作りましょう。

LINE のメッセージ送信で使う gem をインストールします。

bin/bundle add line-bot-api

app/models/my_line_messaging.rb の作成。これは MyLineMessaging.push_text_message(uid: current_user.my_line_user&.uid, text: "送信するメッセージ") のように使います。

class MyLineMessaging
  class << self
    def push_text_message(uid:, text:)
      new.push_text_message(uid:, message: { type: "text", text: })
    end
  end

  def push_text_message(uid:, message:)
    line_bot_client.push_message(uid, message)
  end

  private

  def line_bot_client
    @line_bot_client ||= Line::Bot::Client.new { |config|
      config.channel_secret = ENV["LINE_CHANNEL_SECRET"]
      config.channel_token = ENV["LINE_CHANNEL_TOKEN"]
    }
  end
end

app/controllers/diaries_controller.rb の修正。if current_user.my_line_user&.uid でチェックして、LINE ログイン済みであればメッセージを送ります。

# (省略)
  def create
    @diary = Diary.new(diary_params)

    respond_to do |format|
      if @diary.save
        # ここから
        if current_user.my_line_user&.uid
          text =  "#{@diary.written_on.strftime("%Y/%m/%d(%a)")} #{@diary.content}"
          MyLineMessaging.push_text_message(uid: current_user.my_line_user.uid, text:)
        end

        # ここまで
        format.html { redirect_to @diary, notice: "Diary was successfully created." }
        format.json { render :show, status: :created, location: @diary }
      else
        format.html { render :new, status: :unprocessable_entity }
        format.json { render json: @diary.errors, status: :unprocessable_entity }
      end
    end
  end
# (省略)

これで新しい日記を書くと LINE でメッセージが送信されます。イェイ
まぁ、とりあえず、メッセージを送信できることは確認できましたね。

LINEメッセージ送信

おわりに

LINEログインとメッセージ送信。ここまでは先人の知恵をお借りしながら、無事にたどり着くことができました。
問題はここからです。本番環境でLINE ログインを行うためには HTTPS 通信を実現しなければいけません。

ここまでのソースコードは takaokouji/everdiary-line に置いておきます。

次回は、 Everdiary を aws 上にデプロイして、 ngrok なしで HTTPS 通信を行い、 LINE ログインとメッセージ送信を実現したいと思います。

協力者の募集

スモウルビー (GitHub) の開発にご協力いただける方を常に募集しています。

ご協力いただける方は、 contact@smalruby.jp までご連絡いただいてもいいですし、連絡なしで「xxx のブロックに対応しました」というPRを作成してもらってもかまいません。むしろその方が好都合です。スポンサーも募集しています

また、 拙著:小学生から楽しむ きらきらRubyプログラミング をご購入いただけるとありがたいです。スモウルビーの使い方と教え方を学ぶことができる書籍です。特に小・中学校の先生に読んでいただきたいです。
小学生から楽しむ きらきらRubyプログラミング

日本中の小・中学生が学校の授業や地域のプログラミング教室でスモウルビーを使っています。みなさんのご協力で、たくさんの子どもたちがハッピーになります。ご協力、よろしくお願いします。