Ruby + SinatraでLINE Botを作ろう – Part 3

2020年9月13日Webサービス開発

こんにちは。
この連載では、複数回に渡りRubyとSinatraを使って

  • 本の裏にあるISBNコードを送信すると検索して本の画像を表示してくれる
  • その本を記録しておいて、あとから参照できる

という機能を搭載した「ほんめも!」というLINE Botを作ってみたいと思います。

この記事はPart 2の続編です。まだ読んでいない方はぜひ読んでみてください!
初回の記事はPart 1です。

Sponsored Links

この記事で作るもの

この記事では、前回に作った検索結果をDBに保存する機能、そして検索履歴を一覧にして返信する機能を作ります。
プログラムの流れは下記の通りです。

コードを整理しよう

post '/callback' doのコードが増えてきて分かりづらくなってきているので、まずは今まで書いたコードを整理しましょう。

現在のコードは以下のようになっています。


events.each do |event|
  if event.is_a?(Line::Bot::Event::Message)
    if event.type === Line::Bot::Event::MessageType::Text
      # 中略
    end
  end
end

2行目で「もしイベントがメッセージ送信なら」3行目で「もしイベント種別がテキスト送信なら」という条件分岐をしています。
このようにif文の中にif文を書くとどんどんネストしていって今どのifの中に居るのかが分かりづらくなってしまいます。

なので今回は「早期リターン」という書き方を使ってスマートにしていきます。
早期リターンとは、「これ以降実行する動作がない条件」に合致した場合、処理を終了してしまうという書き方です。

それではまず2行目のif文を見てみましょう。


events.each do |event|
  if event.is_a?(Line::Bot::Event::Message)
    if event.type === Line::Bot::Event::MessageType::Text
      # 中略
    end
  end
end

「もしイベントがメッセージ送信なら」という条件ですが、もしこれがfalseだった場合を考えてみます。
else句が無いのでfalseだった場合実行される処理はありません。そして、if文以降に処理もないので「falseだった場合は何も実行されない」ことが分かります。

このようにfalseだった場合に何も実行されない場合は「早期リターン」に書き換えてしまいましょう。
具体的には以下のような感じになります。


events.each do |event|
  next if !event.is_a?(Line::Bot::Event::Message)
  if event.type === Line::Bot::Event::MessageType::Text
    # 中略
  end
end

「next」というのはeach文用のreturnだと思ってください。nextを呼び出すことで現在の処理を終了し次のループに切り替わります。

このように書くことで「もしイベントがメッセージ送信でないならnextを実行する」という処理が実現できました。

この調子で2つ目のifも早期リターンに書き換えましょう。


events.each do |event|
  next if !event.is_a?(Line::Bot::Event::Message)
  next if event.type != Line::Bot::Event::MessageType::Text
  # 中略
end

こうすることで中略部分(メインのコード)のインデントを2段減らすことができました!
これからもif文は増えていくので、早期リターンを使って記述していきます。

データベースを準備しよう

それでは、実際に機能を作ってきます!
まずはデータベースを準備していきましょう。

Rakefileを作ろう

データベースの操作にはrakeというコマンドを使用します。
rakeコマンドを使用するためにはRakefileというファイルを作る必要があります。
プロジェクト直下にRakefileという名前でファイルを作り、以下の内容を記述しましょう!


require 'sinatra/activerecord'
require 'sinatra/activerecord/rake'
require './models'

models.rbを作ろう

続いて、モデルファイルというファイルを準備します。
先程のRakefileはrakeコマンドを使えるようにする為の準備だったのに対し、models.rbはプログラムの中からデータベースにアクセスする為に必要なファイルです。
Rakefileと同じようにプロジェクト直下にmodels.rbを作り、以下の内容を記述しましょう!


require 'bundler/setup'
Bundler.require
 
ActiveRecord::Base.establish_connection(ENV['DATABASE_URL']||"sqlite3:db/development.db")

データベースに必要なライブラリをインストールしよう

次は、必要なライブラリをインストールしていきます。
Gemfileに以下の内容を追記してください。


gem "dotenv"
gem "line-bot-api"
 
gem 'rake'
gem 'sinatra-activerecord'
gem 'activerecord' , '5.2.3'
 
group :development do
  gem 'sqlite3' , '1.4.1'
end
 
group :production do
  gem 'pg' , '0.21.0'
end

追記できたら、bundleコマンドを実行してインストールしましょう!

また、app.rbにもライブラリを使用する設定をする必要があります。下記のように下2行を先頭に追記しましょう。


require 'bundler/setup'
Bundler.require
require 'sinatra/reloader' if development?
Dotenv.load
 
require 'sinatra/activerecord'
require './models'

マイグレーションしよう

今回はBooksテーブルを用意します。

rakeコマンドでマイグレーションファイルを作ります。


$ rake db:create_migration NAME=create_books

作られたマイグレーションにcreate_tableを記述していきます。


class CreateBooks < ActiveRecord::Migration[5.2]
  def change
    create_table :books do |t|
      t.string :line_id
      t.string :book_name
      t.string :isbn
      t.timestamps null: false
    end
  end
end

マイグレーションファイルが出来上がったらDBをマイグレーションしましょう!


$ rake db:migrate

プログラムからデータベースを使えるようにしよう

最後にプログラムからデータベースを呼び出す為のコードを書きます。
先程書いたmodels.rbを開き、以下のコードを追記しましょう。


require 'bundler/setup'
Bundler.require
 
ActiveRecord::Base.establish_connection(ENV['DATABASE_URL']||"sqlite3:db/development.db")
 
class Book < ActiveRecord::Base
end

これでデータベースを使用する準備は完了です!

検索履歴をデータベースに記録しよう

検索履歴をデータベースに記録するには「# 書籍が見つかった場合」の所に追記すれば良さそうですね。


if book.nil?
  # 書籍が空だった場合
  messages.push({
    type: 'text',
    text: '書籍が見つかりませんでした'
  })
else
  # 書籍が見つかった場合
  if !book['summary']['cover'].empty? 
    # 表紙画像があった場合
    messages.push({
      type: 'image',
      originalContentUrl: book['summary']['cover'],
      previewImageUrl: book['summary']['cover'],
    })
  end
  messages.push({
    type: 'text',
    text: book['summary']['title']
  })
end

コードはこのようになっているので、「書籍が見つかった場合」の直後に書いてみることにします。


if book.nil?
  # 書籍が空だった場合
  messages.push({
    type: 'text',
    text: '書籍が見つかりませんでした'
  })
else
  # 書籍が見つかった場合
  Book.create({
    line_id: event['source']['userId'],
    book_name: book['summary']['title'],
    isbn: event.message['text']
  })
  if !book['summary']['cover'].empty?
    # 表紙画像があった場合
    messages.push({
      type: 'image',
      originalContentUrl: book['summary']['cover'],
      previewImageUrl: book['summary']['cover'],
    })
  end

デプロイしよう

データベースに記録するプログラムが完成しました!
下記コマンドを入力してデプロイしてみましょう。


git add -A
git commit -m "add database"
git push heroku master

デプロイが完了したら、続けてherokuにデータベースを使えるように設定しましょう。


heroku addons:create heroku-postgresql:hobby-dev

そのあと、マイグレーションを実行しましょう。
この2つのコマンドを実行しないとデータベースが使えないためBotが動作しません。


heroku run rake db:migrate

マイグレーションが終わったらLINE BotにISBNを送信してみましょう。
動作に変わりはありませんが、エラーが発生することなく動作すればOKです!

履歴を教えてくれる機能を作ろう

それでは、検索した履歴を教えてくれる機能を作っていきましょう。

"履歴"というメッセージで分岐しよう

まず、「"履歴"というメッセージが来たら」という条件でif文を書きましょう。


events = client.parse_events_from(body)
events.each do |event|
  next if !event.is_a?(Line::Bot::Event::Message)
  next if event.type != Line::Bot::Event::MessageType::Text
 
  messages = [] # 返信するメッセージ用の変数
 
  if event.message['text'] == "履歴"
    # 履歴の処理をする
  else
    book = getBookByISBN(event.message['text']) # ISBNを検索して書籍情報を変数に代入する
    # ここにある messages = [] は削除
 
    if book.nil?
      # 書籍が空だった場合
      messages.push({
        type: 'text',
        text: '書籍が見つかりませんでした'
      })
    else
      # 書籍が見つかった場合
      Book.create({
        line_id: event['source']['userId'],
        book_name: book['summary']['title'],
        isbn: event.message['text']
      })
      if !book['summary']['cover'].empty? 
        # 表紙画像があった場合
        messages.push({
          type: 'image',
          originalContentUrl: book['summary']['cover'],
          previewImageUrl: book['summary']['cover'],
        })
      end
      messages.push({
        type: 'text',
        text: book['summary']['title']
      })
    end
  end
 
  client.reply_message(event['replyToken'], messages)
end

履歴を取得して送信しよう

次に、データベースから「ユーザーの検索履歴」を取得し、その内容を元にメッセージを生成します。
ユーザーのIDはevent['source']['userId']で取得でき、データベースにはline_idとして保存されているので、以下のような処理になります。


if event.message['text'] == "履歴"
  # 履歴の処理をする
  books = Book.where(line_id: event['source']['userId'])
  book_titles = books.map{ |book| book['book_name'] } # booksを書籍名の配列に変換する
  book_list = book_titles.join("\n") # book_titlesを改行で区切った文字列に変換する
  messages.push({
    type: 'text',
    text: book_list
  })
end

デプロイしよう

これで履歴を表示するプログラムが完成しました!
下記コマンドを入力してデプロイしてみましょう。


git add -A
git commit -m "add database"
git push heroku master

デプロイできたら、LINE Botに「履歴」と送信してみましょう。
画像のように履歴が返ってきたら成功です!

まとめ

この記事では検索した履歴をユーザごとにデータベースに記録し、それを出力する機能を実装しました。
テーブルは1つ、機能も保存と表示のみと比較的シンプルなものですが、ユーザを管理出来るようになると作れるものの幅が大きく広がるはずです!

連載記事一覧

  1. Ruby + SinatraでLINE Botを作ろう - Part 1 - オウム返しするBotを作ろう
  2. Ruby + SinatraでLINE Botを作ろう - Part 2 - 書籍を検索して情報を返そう
  3. Ruby + SinatraでLINE Botを作ろう - Part 3 - 検索した書籍を記録しよう ← イマココ