HerokuにデプロイしたRailsアプリでタスクを定期実行する方法を紹介します。
ここで示す方法は、Google Apps Script (GAS)を使う方法です。
結論を先に述べると以下のようにします:
- Railsタスクを作成しておく
- GASでHerokuのAPI経由でコマンドをpostする関数を書く
- GASでスケジューラーを作成する(トリガーを作る)
例として、Railsの場合を示していますが、GAS経由で「herokuのコマンドを定期実行する」という点がポイントなので、他の用途にも使えます。言語がRubyである必要性もありません。
この記事では、Railsの記事投稿アプリを例にとります。
スケジューラーを使って、24時間ごとにデータベースのテーブルのレコードを全て削除し、初期データをデータベースに入れる方法を示します。
変更履歴
- 200229
runHerokuCommand("heroku run rails initialize_db:add_initial_blog");
の部分をrunHerokuCommand("rails initialize_db:add_initial_blog");
に修正。併せてcurlコマンドも"command": "rails initialize_db:add_initial_blog"
に修正。
対象読者:プログラミング初心者向け
- 私自身は転職活動中の未経験エンジニアです。内容には正確性を期しますが、間違いがあればご指摘くださると幸いです。
- 初学者がHerokuを使っていて、「Herkouでデータベースを定期的にリセットしたいが調べても方法が分からない」と疑問を持った際に、当記事の内容を少しでも活用してもらえればと思って書きました。
経緯 (経緯に興味のない人は飛ばしてください)
HerokuにRailsアプリをデプロイしていて、一定時間ごとにデータベースを初期化するために今回の方法に行き着きました。
前提として、無料プランで使用できるデータベースのHeroku Postgresでは保存できるレコード数が1万件までという制限があります。
そのため、ポートフォリオとして公開しているだけの(ブログアプリのような)webアプリなら、定期的にデータベースのデータを全て削除したいことがあるでしょう。
一方、アプリの体裁を整えるために、幾つかのデータ(例えば、投稿記事)をデータベースに見本として入れたいことがあり得ると思います。
今回の私がこのような状況でした。
これを解決するために以下のようにしました。
- Railsのタスクを作成する:このタスクの実行でテーブルを空にしてから、初期データを投入する
- Heroku APIを使い、httpリクエストでRailsタスクを実行する
- GASでhttpリクエストを定期実行する
これで何とか解決したので、そのぞれの方法を説明します。
前提となるサンプリアプリの作成
記事投稿機能
例として、テキストを投稿するサンプルアプリを以下のように作ります。
rails new blog-app
rails db:create
rails generate scaffold post content:text
rails db:migrate
最低限の内容ですが、これを記事投稿アプリの例とします。
記事投稿するためのRailsタスクを作成
rails g task initialize_db
として、タスクのファイルを以下のように編集します。
namespace :initialize_db do
desc "初期データとしてblogsテーブルにデータを入れる"
task add_initial_blog: :environment do
Post.destroy_all
Post.create!([
{content: "初めての投稿です"},
{content: "本日初めてqiitaに記事を投稿しました。"}
])
end
end
これで、ターミナルでrails initialize_db:add_initial_blog
とすれば、postsテーブルのレコードを全て削除して、2つの記事を投稿することができます。
ひとまず、ここまでの内容をHerokuにデプロイ済みだとします。
このタスクをスケジューラーで定期的に実行するのが今回の目的となります。
HerokuのコマンドをAPI経由で実行する
Herokuにアプリをデプロイする際や、ログを確認する際にはHeroku CLIを使って、コマンドを実行するのが一般的です。
しかし、コマンドの実行を自動化するにはAPIを使う方が(おそらく)便利です。
Heroku APIについては公式のリファレンスにまとまっています。
APIでone-off Dynoを作り、コマンドを実行
今回は、API経由でコマンドを実行する方法に着目します。
公式によれば以下のようにします: Platform API Reference | Heroku Dev Center
POST /apps/{app_id_or_name}/dynos
今回はherokuのCLIでheroku run rails initialize_db:add_initial_blog
と打つのをAPIで代替したいので、curlで以下のようにします。
$ curl -n -X POST https://api.heroku.com/apps/$APP_ID_OR_NAME/dynos \
-d '{
"command": "rails initialize_db:add_initial_blog",
}' \
-H "Content-Type: application/json" \
-H "Accept: application/vnd.heroku+json; version=3"
-H "Authorization: Bearer $HEROKU_API_KEY"
$APP_ID_OR_NAME
と$HEROKU_API_KEY
については個々のアプリに応じて読み替えてください。
Herokuの$APP_ID_OR_NAME
と$HEROKU_API_KEY
の確認方法
$APP_ID_OR_NAME
を確認するにはCLIでheroku apps
と打ちます。
もしくは、アプリの公開URLがhttps://<$APP_ID_OR_NAME>.herokuapp.com/
のような形式になっているので、そこから読み取ることもできます。
$HEROKU_API_KEY
を確認するには、heroku auth:token
とCLIで打ちます。このトークンは1年しか有効ではないので、半永久的に使いたい場合には、heroku authorizations:create
としてトークンを生成してください:Getting Started with the Platform API | Heroku Dev Center
GASでスケジューラーを作成
他のスケジューラーではダメなのか
herokuで特定の処理を一定時間ごとに実行する代表的な方法はHeroku Schedulerというアドオンを使うものです。
しかし、Heroku Schedulerの使用にはクレジットカードの登録が必要であり、無料Dynoの枠を超えると課金される可能性もあるので、完全に無料の方法として、GASを使うことにしました。
他の方法としてClock ProcessesというHerokuの機能を活用する手もあります: Scheduled Jobs and Custom Clock Processes | Heroku Dev Center。Clock Processesはクレジットカード登録なしで使用できますが、追加のdynoを使うため、今回は見送りました。
GASなら無料でスクリプトの定期実行ができる
Googleドライブで使用できるGoogle Apps Scriptは、無料でスクリプトを定期実行できます。
実際には実行回数に制限がありますが、無料プランでも十分な回数です:Quotas for Google Services | Apps Script | Google Developers。
例えば、今回利用するURL Fetch callsでは一日あたり20000回使用できます。
GASの環境設定
GASの実行環境を整えます。googleのアカウントを持っていれば、1分で完了します。
googleドライブで「Google Apps Script」というアプリを選択するだけです。
初回はメニューに登録されていないので、「アプリを追加」からGoogle Apps Script検索して、アプリを追加してください。
GASで定期実行するスクリプト
GASは、一部独自機能があるものの、文法はJavaScriptに準拠しています。
heroku APIをcurlで使用するためのコマンドをGASのコードに読み替えます。
結果だけ示すと、以下のようにrunHerokuTask関数を定義し、この関数を定期実行すればうまくいきます。
function runHerokuTask() {
runHerokuCommand("rails initialize_db:add_initial_blog");
}
function runHerokuCommand(command) {
var urlTemplate = "https://api.heroku.com/apps/%s/dynos";
var herokuToken = PropertiesService.getScriptProperties().getProperty("heroku_token");
var herokuAppName = PropertiesService.getScriptProperties().getProperty("heroku_app_name");
var herokuAuth = Utilities.formatString("Bearer %s", herokuToken);
var url = Utilities.formatString(urlTemplate, herokuAppName);
var headers = {
"Content-type": "application/json",
"Accept": "application/vnd.heroku+json; version=3",
"Authorization": herokuAuth
}
var data = {
"command": command
}
var options = {
"method": "post",
"payload": JSON.stringify(data),
"headers": headers
};
var response = UrlFetchApp.fetch(url, options);
var text = response.getContentText();
Logger.log(text);
}
このコードでのポイントを幾つか説明します。
- トークンやアプリ名などは、プロパティから取り出す
-
UrlFetchApp.fetch
でhttpリクエストをする
GASのコードをgithubで公開する場合、パスワードや環境依存の定数はコードに含めたくはありません。
そのため、GASの機能のプロパティサービスを活用します。
プロジェクトのプロパティというところからスクリプトのプロパティで「キーと値のペア」を登録します。
PropertiesService.getScriptProperties().getProperty("キーの名前")
として、キーに紐付いた値を取り出します。
またhttpリクエストには、UrlFetchAppを使います。
今回は、POSTメソッドなので、オプションが必要ですが、getメソッドであれば、UrlFetchApp.fetch("http://www.google.com/")
とするだけでwebサイトにアクセスできます。
GASのスクリプトの定期実行
GASのスクリプトの定期実行には、トリガーを登録します。
これで、毎日、0~1時ごろに登録した関数が自動実行されます。