Phusion Passenger: permission denied で ruby を実行できない問題

問題

Rails あるいは Sinatra を Phusion Passenger で動かしている状況で、permission denied により ruby を実行できない。

解決策 (1)

Rails なら config/environment.rbSinatra なら config.ru の持ち主が ruby を実行できるユーザーかどうかを確認し、そうでなければ直す。

デフォルトで Phusion Passengerはこのユーザーとして起動する。ただし root ユーザーが持ち主の場合は nobody ユーザーで動く。

解決策(2)

ps auwx | grep Passenger で Phusion Passenger の実行ユーザーを調べて、そのユーザーが ruby を実行できるようにする。

例えば、そのユーザーが apache で、ruby/home/app/.rbenv ディレクトリの下にあり、/home/app の user:group が app:app なら、sudo gpasswd -a apache appapacheapp グループに追加する。また、chmod g+rx /home/app も実行する。

解決策 (3)

PhusionPassenger の設定ファイルで PassengerUser を設定する。

Rails: send_data/send_file でデータの中身がテキストとして表示される問題

問題

次のようなRailsのアクションにより files/example.pdf の中身をユーザーにダウンロードさせたい。

class FilesController < ApplicationController
  def show
    filename = 'example.pdf'
    path = Rails.root.join('files', filename)
    send_file(path, type: 'application/pdf', filename: filename)
  end
end

しかし、ブラウザでこのアクションにアクセスするとファイルの中身がテキストとして表示される。

解決法

<a> タグに data-turbolinks="false" 属性を付ける。例えば、

<%= link_to "example.pdf", file_path %>

<%= link_to "example.pdf", file_path, 
  data: { turbolinks: false } %>

に変える。

参考資料

OmniAuth: ロードバランサ配下での redirect_uri_mismatch 問題

問題

最近、OmniAuthを使ってGithub経由でのユーザー認証機能を既存のRailsアプリに組み込んだ。

その際、開発環境ではすんなり動いたのだが、本番環境にデプロイすると redirect_uri_mismatch エラーが出てしまう。

解決法

config/initializers/omniauth.rb に次のような記述を追加する。

if Rails.env.production?
  OmniAuth.config.full_host = "https://example.com"
end

原因

本番環境のRailsアプリがロードバランサ(Nginx)配下で動いていたためであった。OmniAuth は request.url が返す文字列を使って、redirect_url を作る。例えば、https://example.com/shopping_cart なら https://example.com/auth/github/callback になる。

しかし、私の環境では Nginx が httpshttp に置き換えていたため、s のない http://example.com/auth/github/callbackredirect_url となり、Github の「Authorization callback URL」に登録してあった URL と食い違ってしまうのである。

参考資料

Ruby: case 式における何もしない else の扱い

先週末に WEB+DB PRESS Vol.99の「良いコード」を本気でコードレビューしてみた という興味深いブログ記事が書かれました。

はてなブックマークでのコメントを見ると、多くの人がブログ記事の内容に賛同しているようですが、「P12: 何もしないelseは必要か?」の節には異論が散見されました。


ブログ記事全体の趣旨は、WEB+DB PRESS Vol.99の「Rubyで学ぶ!良いコードって何だろう?」という特集記事への批判です。

特集記事では、次のような例が掲載されていて、

case hour
when 9
  say_hello()
when 18
  say_goodbye()
end

これは意図が伝わりにくいから、次のように空の else 節を加えるべしと書かれています。

case hour
when 9
  say_hello()
when 18
  say_goodbye()
else
  # 何もしなくてよい
end

これに対して、ブログ記事の筆者(伊藤氏)は空の else 節は不要だと主張しています。


プログラミング初心者が else 節を書き忘れる可能性を考えると、このケースでは空の else 節を書いたほうが親切かなと私は思います。

しかし、まったく空の else 節が残ることに「座りの悪さ」を感じるのも確かです。

はてなブックマークのコメントで知ったのですが、Python には「何もしない」 pass 文というのがあるそうですね。

これが使えればいいのですが、残念ながら Ruby には存在しません。


特集記事があげている例があまりよくなかったのかもしれません。関数 say_hellosay_goodbye の中身まで含めたリファクタリングを検討すべき場面じゃないでしょうか。

おそらく、ソースコード全体はこんな感じです。

def say_hello
  puts "Hello!"
end

def say_goodbye
  puts "Good bye!"
end

hour = Time.now.hour

case hour
when 9
  say_hello()
when 18
  say_goodbye()
end

であるとすれば、まずこう書き換えるべきです。

def say(message)
  puts "#{message}!"
end

hour = Time.now.hour

case hour
when 9
  say("Hello")
when 18
  say("Good bye")
end

すると、次のように自然な形で else 節を導入できます。

def say(message)
  puts "#{message}!"
end

hour = Time.now.hour

message = case hour
  when 9
    "Hello"
  when 18
    "Good bye"
  else
    nil
  end

say(message) if message

元のコードの問題点は、(1)メッセージを選択する論理と(2)メッセージを出力するかどうかを決定する論理を単独の case 式に混在させてしまった点にあります。

私のコードでは、二つの論理が明確に分離されています。(1)の論理は case 式で表現され、(2)の論理は「後置の if」で表現されています。

いかがでしょうかね。

PostgreSQL: ポイントインタイムリカバリ(PITR) と Transaction ID

PostgreSQL の「ポイントインタイムリカバリ(PITR)」について調べていて、ドキュメントの中に気になる記述を見つけた。

もし以前のある時点まで復旧させたい場合(例えば、経験不足のデータベース管理者が主トランザクションテーブルを消去した直前)、recovery.confに要求する停止時点を指定するだけです。 停止時点は、「recovery target」として既知の停止時点で指定することも、日付と時刻で指定することも、リストアポイントか完了した特定のトランザクションIDで指定することもできます。 本ドキュメントの執筆時点では使用するトランザクションIDの識別を補助するツールがありませんので、ほとんどの場合は日付と時刻による指定のみを使用することになるでしょう。 https://www.postgresql.jp/document/9.6/html/continuous-archiving.html

データベースに対して何か致命的な操作をしてしまったとき、例えば WHERE を付けずに DELETE してしまったときに、その操作の直前の状態にデータベースを戻したいとする。

このドキュメントによれば、recovery.conf に復旧したい時刻を指定すべきであるそうだ。例えば、こんな風に。

recovery_target_time = '2017-05-02 09:00:00 JST'

私は recovery.confrecovery_target_xid という設定項目があることを知っていたので、不思議に思った。致命的な操作のトランザクションID(xid)を調べて、そのひとつ前のトランザクションIDをここに指定すればいいのではないか。

しかし、これはふたつの理由でうまく行かない。

ひとつの理由は、さきほどの引用にもある通り「トランザクションIDの識別を補助するツール」が存在しないためである。

もうひとつの理由は、「トランザクションIDはトランザクションの開始時に順番に割り振られ、トランザクションはそれとは異なる順番で完了し得る(PosgreSQL のドキュメント)」からである。


トランザクションIDの識別に関しては、ログにトランザクションIDとSQLステートメントを記録すればいいんじゃないかとも考えたけれど、これもダメである。

postgresql.conflog_line_prefix という設定項目があり、ここに %x というプレースホルダーを指定すれば「トランザクションID」が記録されるのだが、トランザクション内で 2 番目以降に発行された変更ステートメントにしか記録されない、という重大な制限がある。

http://stackoverflow.com/q/39585279/513554 を参照。

本当かなぁと疑って、私自身で実際にやってみたけれど、その通りであった。トランザクション内の 1 番目に発行されたステートメントの「トランザクションID」は 0 と記録される。

PostgreSQL のアップグレード時に明らかになる pg_wrapper の役割

PostgreSQL 9.5 のサーバーがデフォルトの 5432 番ポートで動いている状態で、PostgreSQL 9.6 のサーバーをインストールするとどうなるか。

$ apt-get install postgresql-9.6

結果。PostgreSQL 9.6 の main クラスタが 5433 番ポートで立ち上がる。

この状態で psql コマンドのバージョンを調べると、9.5.4 のように古いバージョン番号が報告される。

なぜか。

実は、psqlpg_dumppg_restorepg_basebackup などのコマンドは /usr/share/postgresql-common/pg_wrapper へのリンクとなっていて、この pg_wrapper がさらに適切なバージョンの「本物」のコマンドを実行する、という仕組みになっているからである。

PostgreSQL 9.6 用の「本物」の psql/usr/lib/postgresql/9.6/bin にある。

pg_wrapper が「本物」のコマンドを選択する方法については、man pg_wrapper に書いてある。その仕組みはかなり複雑だが、私たちが特に何もしていない場合、デフォルトの 5432 番ポートを listen している PostgreSQL サーバーのバージョンを使うことになる。つまり、現状では 9.5 だ。

ここで、PostgreSQL 9.5 をアンインストールしてみよう。

apt-get remove postgresql-9.5

5432 番を listen している PostgreSQL サーバーは停止されて、PostgreSQL 9.5 のプログラムは除去される。

そして psql --version を実行すると、次のようなエラーメッセージが出る。

Error: No existing local cluster is suitable as a default target.
Please see man pg_wrapper(1) how to specify one.

5432 番を listen している PostgreSQL サーバーが存在しないからだ。

しかし、psql --port=5433 --version というコマンドを実行すれば、正しく 9.6.2 という結果を返す。

だから、さきほどのエラーメッセージについては気にしなくてもいい。

もし気になるのであれば、いくつか回避方法がある。

  1. 環境変数 PGPORT5433 をセットする。
  2. /etc/postgresql/9.6/main/postgresql.confport 属性の値を 5432 に変更して、PostgreSQL サーバーを再起動する。

PostgreSQL: MySQL の SHOW CREATE DATABASE の代わりになるもの

私の知る限りにおいて、PostgreSQL のコンソールであるデータベースの CREATE DATABASE 文を知る方法はない。すなわち、MySQL コンソールの SHOW CREATE DATABASE コマンドに対応するコマンドはない。

しかし、代わりに次のようなコマンドを実行すればよい:

sudo -u postgres pg_dump --schema-only --create db0 \
| grep "CREATE DATABASE"

あるいは、もっと簡潔に

sudo -u postgres pg_dump -s -C db0 | grep "CREATE DATABASE"

ただし、db0 はデータベース名の例であるので、適宜変更する。

pg_dump はデータベースをダンプするコマンドである。--schema-only または -s オプションを付けると、テーブルやインデックスなどの「スキーマ定義」だけがダンプされる。--create オプションまたは -C オプションを付けると、ダンプの中に CREATE DATABASE 文が書き込まれる。それを grep コマンドで抜き出している。

結果として、ターミナルには次のような結果が出力される。

CREATE DATABASE db0 WITH TEMPLATE = template0 ENCODING = 'UTF8' 
LC_COLLATE = 'ja_JP.UTF-8' LC_CTYPE = 'ja_JP.UTF-8';