SinatraからDataMapperを使う(5) HerokuのPostgreSQLで使う

前回までで、ローカルのSinatra+DataMapper+SQLite3の環境で、シャッフルツイート機能の実装が完了した。今回は、これを実際の運用サーバーであるHeroku上で動作させるためにコードを修正する。Herokuで使えるデフォルトのデータベースはPostgreSQLなので、ここでもPostgreSQLを使う。

PostgreSQL関連のgemを指定する

まず、前提として使ってるマシンにPosgreSQL自体が入ってないとダメっぽい。自分の環境では、PostgreSQLが入ってないと、DataMapperのPostgreSQL用アダプターをインストールする時にコケた。PostgreSQLのインストールはWin/Macそれぞれ以下のような感じで。

WindowsでPostgreSQLをXAMPPのPHPから使う HomebrewをインストールしてMacPortsをアンインストールする

Gemfile

Bundler用のGemfileファイルは以下のような感じになる。

source :rubygems
gem 'sinatra'
gem 'twitter'
gem 'dm-core'
gem 'dm-migrations'
gem 'dm-validations'
gem 'dm-validations-i18n'
gem 'dm-postgres-adapter'

bundle installして、Gemfile.lockファイルを生成する。

PostgresSQL用に修正

次に、app.rbに記述していたDataMapper.setupメソッドをtweet.rbに移動し、なおかつHerokuのPostgreSQLでも使えるように修正する。

tweet.rb

app.rbにあった以下の行を、

DataMapper.setup(:default, 'sqlite3:db.sqlite3')

tweet.rbのコンストラクタに移動して、以下のように修正する。

class Tweet
  def initialize
    DataMapper.setup(:default, ENV['DATABASE_URL'] || 'sqlite3:db.sqlite3')
# (以下省略)

heroku createコマンドを実行したときに、環境変数としてデータベースのURLが挿入されている1ので、そこを参照するように変えるだけ。環境変数が無ければ後者が使われるので、ローカル上では今まで通りSQLite3が使われる。

PostgreSQLとSQLite3の違いはO/RマッパーであるDataMapperが良しなにやってくれるので、コードに変更はない。

マイグレーション用Rakefile

前回までのローカルプレビュー時は、マイグレーションは直接migrate.rbスクリプトを、

$ ruby migrate.rb

として実行していた。が、こういうやり方はHeroku上ではできない(ということをこの段階になって知った)ので、マイグレーションを実行出来るようにするためのRakefileを作成する。実はこの情報を知るのに結構苦労して、理解するまでどうすれば良いのかわからずフラフラとネット上を彷徨っていた。最終的に理解できたのは、直接その処理を行なっているRubyコードを見ることが出来たから。

Lokkaの以下の部分のRubyコードを参考にさせてもらって、DataMapperでのマイグレーション用Rakefileを作ることが出来た。

Rakefile at master from komagata’s lokka - GitHub lib/lokka.rb at master from komagata’s lokka - GitHub lib/lokka/entry.rb at master from komagata’s lokka - GitHub

database.rb

というわけで、前回までmigrate.rbとして使用していたファイルを、まずdatabase.rbにリネームする。そしてRakefileから実行するのに適したクラスとメソッドに修正する。createメソッドは、ちょっと悪いネーミングな気がするけど、とりあえずこれで。2

require 'rubygems'
require 'dm-core'
require 'dm-migrations'
require 'model.rb'
class Database
  def connect
    DataMapper.setup(:default, ENV['DATABASE_URL'] || 'sqlite3:db.sqlite3')
    self
  end
  def migrate
    DataMapper.auto_migrate!
    self
  end
  def create
    WORDS.each do |data|
      post = Post.create(:title => data, :created_at => Time.now)
      puts post.errors.map {|e| "* * * #{e} :\n#{data}" } unless post.errors.empty?
    end
    self
  end
  WORDS = <<-EOF.split("\n")
私の最高傑作は次回作だ。
(中略)
"You?" "You can see now?" "Yes, I can see now."
  EOF
end

Rakefile

rakeコマンドで実行するマイグレーション用Rakefileは以下のような感じになる。cron用タスクは元々あったもので、それに手動ツイートするbot:tweetタスクと、マイグレーションと初期レコードの追加を行うdb:setタスクを追加した。

require 'tweet.rb'
task :cron do
  Tweet.new.tweet
end
desc 'Bot Tweet'
task 'bot:tweet' do
  puts 'Bot Tweeting...'
  Tweet.new.tweet
end
require 'database.rb'
desc 'Setup Database'
task 'db:set' do
  puts 'Setup Database...'
  Database.new.connect.migrate.create
end

database.rbRakefileが修正できたら準備完了。

rakeコマンドの実行

まずローカルで、このRakefileによるrakeコマンドが正常に動作するか確認する。既存のdb.sqlite3ファイルがあれば、それをゴミ箱に捨ててから以下のコマンドを実行する。

$ rake db:set

これでdb.sqlite3ファイルが生成されて、各レコードにデータが挿入されていれば成功。さらに、以下の手動ツイートのrakeコマンドを実行すると、実際にTwitterにツイートが投稿される。

$ rake bot:tweet

以上がローカルで確認できていればOK

デプロイ

作成・修正したファイルをコミットして、Herokuにプッシュする。

$ git add .
$ git commit -m 'lupin the third!'
$ git push heroku master
$ heroku open

この時点では、データベースのマイグレーションが行われていないので、サイトを開いてもInternal Server Errorが表示される。

Heroku上でrakeコマンドの実行

ローカルで試したrakeコマンドの頭にherokuと付けるだけでOK

$ heroku rake db:set
$ heroku rake bot:tweet

これでちゃんとTwitterに投稿されれば、おそらくcronでも正しく投稿が行われるはず。果報は寝て待つ。また、

$ heroku open

でサイトを開くと、今度はローカルで表示したときと同じように、正常に一覧が表示される。 ちなみに、このアクセスするとシャッフルツイートが行われて残りの名言が一覧表示される動作は、あくまでも動作チェック用なので最終的には削除しておく。

ここまでのソースコード

ここまでのchaplin_botのソースコードをversion 2.0 としてタグを切ったので、GitHubからダウンロード可能。

ruedap/chaplin at 2.0 - GitHub

以上で、単なるリピートツイートだったHeroku上のTwitter Botに、無事シャッフルツイート機能を実装できたので、このシリーズはこれで完結する。

  1. heroku configコマンドで見られる

  2. そもそもメソッドの配置場所もこのクラスには適さない気がするけど、とりあえずこれで。