Ruby + SinatraでLINE Botを作ろう – Part 3
こんにちは。
この連載では、複数回に渡りRubyとSinatraを使って
- 本の裏にあるISBNコードを送信すると検索して本の画像を表示してくれる
- その本を記録しておいて、あとから参照できる
という機能を搭載した「ほんめも!」というLINE Botを作ってみたいと思います。
この記事はPart 2の続編です。まだ読んでいない方はぜひ読んでみてください!
初回の記事はPart 1です。
この記事で作るもの
この記事では、前回に作った検索結果を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つ、機能も保存と表示のみと比較的シンプルなものですが、ユーザを管理出来るようになると作れるものの幅が大きく広がるはずです!
Discussion
New Comments
No comments yet. Be the first one!