PostgreSQL を用いた Rails アプリケーション開発で
キャッシュした計画は結果型を変更してはなりません
あるいは
cached plan must not change result type
というエラーメッセージに遭遇することがある。
解決法は簡単で、Rails サーバーを再起動すればよい。
なぜこのエラーが出るのか知りたい方は、Qiitaの次の記事を読むとよい。
PostgreSQL を用いた Rails アプリケーション開発で
キャッシュした計画は結果型を変更してはなりません
あるいは
cached plan must not change result type
というエラーメッセージに遭遇することがある。
解決法は簡単で、Rails サーバーを再起動すればよい。
なぜこのエラーが出るのか知りたい方は、Qiitaの次の記事を読むとよい。
Rails あるいは Sinatra を Phusion Passenger で動かしている状況で、permission denied により ruby を実行できない。
Rails なら config/environment.rb
、Sinatra なら config.ru
の持ち主が ruby を実行できるユーザーかどうかを確認し、そうでなければ直す。
デフォルトで Phusion Passengerはこのユーザーとして起動する。ただし root
ユーザーが持ち主の場合は nobody
ユーザーで動く。
ps auwx | grep Passenger
で Phusion Passenger の実行ユーザーを調べて、そのユーザーが ruby を実行できるようにする。
例えば、そのユーザーが apache
で、ruby が /home/app/.rbenv
ディレクトリの下にあり、/home/app
の user:group が app:app
なら、sudo gpasswd -a apache app
で apache
を app
グループに追加する。また、chmod g+rx /home/app
も実行する。
PhusionPassenger の設定ファイルで PassengerUser
を設定する。
次のような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を使って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 が https
を http
に置き換えていたため、s
のない http://example.com/auth/github/callback
がredirect_url
となり、Github の「Authorization callback URL」に登録してあった URL と食い違ってしまうのである。
先週末に 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_hello
と say_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)」について調べていて、ドキュメントの中に気になる記述を見つけた。
もし以前のある時点まで復旧させたい場合(例えば、経験不足のデータベース管理者が主トランザクションテーブルを消去した直前)、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.conf
に recovery_target_xid
という設定項目があることを知っていたので、不思議に思った。致命的な操作のトランザクションID(xid)を調べて、そのひとつ前のトランザクションIDをここに指定すればいいのではないか。
しかし、これはふたつの理由でうまく行かない。
ひとつの理由は、さきほどの引用にもある通り「トランザクションIDの識別を補助するツール」が存在しないためである。
もうひとつの理由は、「トランザクションIDはトランザクションの開始時に順番に割り振られ、トランザクションはそれとは異なる順番で完了し得る(PosgreSQL のドキュメント)」からである。
トランザクションIDの識別に関しては、ログにトランザクションIDとSQLステートメントを記録すればいいんじゃないかとも考えたけれど、これもダメである。
postgresql.conf
に log_line_prefix
という設定項目があり、ここに %x
というプレースホルダーを指定すれば「トランザクションID」が記録されるのだが、トランザクション内で 2 番目以降に発行された変更ステートメントにしか記録されない、という重大な制限がある。
本当かなぁと疑って、私自身で実際にやってみたけれど、その通りであった。トランザクション内の 1 番目に発行されたステートメントの「トランザクションID」は 0 と記録される。
PostgreSQL 9.5 のサーバーがデフォルトの 5432 番ポートで動いている状態で、PostgreSQL 9.6 のサーバーをインストールするとどうなるか。
$ apt-get install postgresql-9.6
結果。PostgreSQL 9.6 の main
クラスタが 5433 番ポートで立ち上がる。
この状態で psql
コマンドのバージョンを調べると、9.5.4
のように古いバージョン番号が報告される。
なぜか。
実は、psql
、pg_dump
、pg_restore
、pg_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
という結果を返す。
だから、さきほどのエラーメッセージについては気にしなくてもいい。
もし気になるのであれば、いくつか回避方法がある。
PGPORT
に 5433
をセットする。/etc/postgresql/9.6/main/postgresql.conf
の port
属性の値を 5432
に変更して、PostgreSQL サーバーを再起動する。