Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

ちょっと待った! Railsのgitリポジトリから Gemfile.lockとdb/schema.rbを除外してはいけない

こんにちは、hachi8833です。

Railsをgitで管理するのであれば、ログファイルや、パスワード入りdatabase.ymlなどの登録したくないファイルを.gitignoreに記載してリポジトリから除外するのが普通です。しかし実際の案件では、除外すべきでないファイルが除外されていることがたまにあります。言うまでもないような話ですが、心当たりのある方は念のためチェックしてみましょう。

gitリポジトリから除外すべきでないファイル

以下では、誤ってgitリポジトリから除外されがちなGemfile.lockとdb/schema.rbについて説明します。代表的なものであり、すべてを網羅しているわけではないのでご注意ください。

Gemfile.lock

「GemfileがあればGemfile.lockはなくてもいいんじゃね?どうせbundle installするんだから」または「Gemfile.lockがあるとどうもデプロイがうまくいかない」などの理由でGemfile.lockが除外されていることがありますが、Gemfile.lockは基本的にリポジトリから除外すべきではありません。StackOverflowを見ると、英語圏でも同様の思い違いが頻発しているようです。

Gemfile.lockは、「実際にインストールされたGemセット」のスナップショット+依存関係情報が記載されているファイルです。そしてデプロイ時にbundle installを実行すると、実際に使用されるのはGemfileではなくGemfile.lockです。

bundle installは、状況によって以下のように動作します。

Gemfile.lockがない場合(通常は開発最初期)
Gemfile.lockが作成され、Gemfileに従ってgemがインストールされる。そのときに実際にインストールされたgemとその依存関係がGemfile.lockに保存される。
Gemfileにgemが追加された場合(通常は開発時)
Gemfileに追加されたgemとそれに依存する未インストールgemのみがインストールされ、Gemfile.lockはそれらのgemについてのみ更新される。
Gemfileに変更がない場合(通常はデプロイ時)
Gemfile.lockに従ってインストールされる。Gemfileは参照されず、Gemfile.lockは更新されない。

上の動作からわかるように、開発環境で動作確認されたgemセットを、デプロイ先でもまったく同じように揃えるためにGemfile.lockが使用されています。大事な大事なGemfile.lockがリポジトリにないと、開発時とデプロイ時の間にgemが新しくなった場合にデプロイ側だけgemが新しくなってしまいます。

補足: bundle updateについて

ここで注意すべきはbundle updateです。bundle updateを実行すると現状のGemfile.lockの内容は無視され、上で言う「Gemfile.lockがない場合」と同じことが行われます。当然ながら、Gemfile.lockは、現時点の最新バージョンと依存関係を持つgemによってごっそり更新されてしまいます。

かく言う私も、当初はbundle installbundle updateの違いに気付かないままカジュアルにbundle updateを実行してしまってました。gemの紹介記事などでbundle updateを指示していることが時たまあり、それに釣られてしまっていたようです。恐ろしいことです。

アプリ開発の初期段階では、bundle installとbundle updateのどちらを実行していても矛盾は顕在化しません。しかし、しばらく更新が行われていない間にgemのバージョンが進んでしまうと、bundle updateを実行したときにgemとGemfile.lockが一斉更新されてしまい、以前動作したgemが動作しなくなるようなことがあります。

開発初期のローカルアプリならともかく、実際に動いているシステムのメンテ中にうかつにbundle updateを実行すると死を招きます。そしてGemfile.lockがリポジトリに登録されておらず、前回デプロイ時のバックアップもなければ、動いていた状態を再現できなくなって詰んで終わります。

Gemfile.lockはリポジトリに登録しましょう。そしてbundle updateは基本的に実行せず、特定のgemのみを最新にする目的でbundle update gem名のようにgem名を指定して実行するぐらいにとどめましょう。

なお、Gemfile.lockがあるとデプロイがうまくいかないのは、開発・本番環境間の不整合が原因である可能性が高いので、Gemfile.lockを除外するのではなく不整合を解決する必要があります。

例外

弊社CTOから補足: gemを作るときには逆にGemfile.lockをリポジトリに含めないのが定石です。ライブラリでlockするとえらいことになりますヨ。

db/schema.rb

「db/migration以下のマイグレーションファイルがあればschema.rbいらなくね?どうせrake db:migrateするんだから」などの理由でschema.rbが除外されていることがありますが、これも基本的に除外すべきではありません。

schema.rbはマイグレーションファイルから生成され、Railsは最終的にこのスキーマ情報を使用してデータベースを扱います。言い方を変えれば、マイグレーションはこのschema.rbを作成するために行っているわけです。具体的には、rake db:migrateを実行するとrake db:schema:dumpも実行され、これによってdb/schema.rbが更新されます。

アプリケーションの規模が大きくなってマイグレーションファイルが増えてきたり、マイグレーション/ロールバックを繰り返すうちに、マイグレーションの累積結果とスキーマが必ずしも一致しなくなることがあります。その場合におかしくなっている可能性が高いのはマイグレーションの方であり、頼りにすべきは現状のシステムで動いているschema.rbの方です。

実際、RailsGuidesにもしっかり書いてあります。

Because schema dumps are the authoritative source for your database schema, it is strongly recommended that you check them into source control.

追伸: schema.rb自体にも書いてありました。

# It's strongly recommended to check this file into your version control system.

マイグレーションファイルを追加する時には、過去のマイグレーションを参照するより先にschema.rbで現状を確認しながら行うぐらいの方が確実です。

また、rake db:resetなどのコマンドはschema.rbに依存しているので、schema.rbがないと実行できません。

gitリポジトリから除外すべきファイル

以下も見落とされがちなものを挙げます。

  • .rvmrcは環境依存するので、よほど特殊な事情がなければリポジトリに登録すべきではありません。
  • database.ymlにはパスワードが記載され、さらにデータベース環境に依存するので、通常は登録しません。代りにそのテンプレートとしてdatabase.yml.sampleを登録しておき、開発開始時にこれをリネームして環境情報とパスワードを記入するという運用が一般的です。

普通除外するファイル

オフィシャルなgitignore / Rails.gitignoreをベースにしておけば大丈夫でしょう。

除外するかどうか時と場合によるファイル

以下も見落とされがちなものを挙げます。

  • tmpディレクトリは通常登録しませんが、たまにアプリがロックファイルなどを置いてたりすることがあるので、その場合はそのファイルを登録します。
  • .bundleは、bundle/configに--pathなどのbundle installのオプションが保存されます。作業者が全員gemのインストール先について承知しているなら登録してもよいですが、そうでなければ混乱を避けるため登録しないようにします。

参考

  1. http://bundler.io/v1.5/bundle_install.html
  2. http://bundler.io/v1.5/bundle_update.html
  3. ツールを使いたいだけの人のための bundler 入門 (例: vagrant + veewee)
  4. RailsGuides / Active Record Migrations / 7 Schema Dumping and You

CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。