おぐらです、RubyKaigi 2013の会場からこんにちは。
現在、弊社で開発中のqnypというサービスは、少し前にRails 3.2.13からRails 4.0.0.rc1へとアップデートを行いました(サービス自体はまだ非公開なのでアクセスはできません)。
このアップデート作業を通じて、
- Railsに依存するgemを減らしていく
- JavaScriptライブラリをラップしただけのgemを使わないようにする
という点について重視するようになりました。
1. Railsに依存するgemを減らしていく
特にActiveRecordなどRailsの内部に深く関わるようなgemにおいて、Railsのメジャーアップデート時の対応速度が迅速ではなさそうなものがあったため、できるだけそれらのgemを利用しないようになりました。例えばauditedは、現時点の最新版がactiverecord ~> 3.0
という依存性を持っていたり、Rails 4.0で廃止となったObserverを利用しているため、使わないようにしました。
本質的なコードが数十行程度のgemであれば同等の機能をアプリケーション内で持つようにしたり(これはこれで別な問題をはらむことになるとも言えますが)、できるだけ依存関係がシンプルなgemを優先して利用するようにしています。
2. JavaScriptライブラリをラップしたgemを使わないようにする
今回、特に大きな方針転換だったのは2つ目の「JavaScriptライブラリをラップしたgemを使わないようにする」でした。
具体的に対象となったのは、momentjs-railsやspinjs-rails、jquery-raty-railsといったgemで、このようなgemには管理が容易になるメリットがある一方で、以下のようなデメリットもあります。
rails ~> 3.2.3
やrailties >= 3.1
のようなRailsに対する依存関係を持っている- 本家のJavaScriptライブラリのアップデートに迅速に追随しないことがある
- リリースサイクルがgemメンテナーのモチベーションに依存する
ただし、単純なラッパーではなくRubyレベルのDSLを含んだgemもあるため、そういったものについては、メリットとデメリットを考慮したうえで対応することになります。
BowerによるJavaScriptライブラリの管理
qnypのRailsアプリケーションでは、
- 必要なJavaScriptライブラリがインストールできること
- できるだけシンプルな仕組みであること
- プロジェクトにある程度の継続性が望めること
といった点を考慮して、JavaScriptライブラリの依存性管理にBowerを採用することにしました。
RailsアプリケーションにBowerを導入する手順は、おおまかに以下の通りです。
- Nodeを利用できるようにする
- Bowerをインストールする
.bowerrc
を作成するcomponent.json
を作成するbower install
を実行する- Asset Pipelineの設定を変更する
1. Nodeを利用できるようにする
あらかじめ、環境に合わせてNodeをインストールしておきます。
2. Bowerをインストールする
npm
コマンドでbower
をインストールします。
$ npm install -g bower
3. .bowerrcを作成する
Bowerの設定を記述した.bowerrc
ファイルを作成して、Railsアプリケーションのルートディレクトリに配置します。
{
"directory": "vendor/assets/components",
"json": "component.json"
}
directory
には、JavaScriptライブラリがインストールされるディレクトリパスを指定します(このディレクトリは.gitignore
に指定してリポジトリの管理対象外にしておきます)。
json
には、インストールするJavaScriptライブラリの名前とバージョンが記述されるファイルのパスを指定します。
パスはいずれもRails.root
からの相対パスを記述しておきます。
4. component.jsonを作成する
Railsアプリケーションが利用するJavaScriptライブラリの情報をcomponent.json
に記述します。
qnypのアプリケーションの場合は以下のようになっています。
{
"dependencies": {
"jquery": "1.9.1",
"morris.js": "0.4.1",
"raphael": "2.1.0",
"raty": "2.5.2",
"moment": "2.0.0",
"spin.js": "1.3"
}
}
Bowerでインストールできるライブラリについては、Bower componentsを参照してください。
5. bower installを実行する
ここまでの設定が済んでいると、bower install
でJavaScriptライブラリがインストールできるようになります。
$ bower install
bower cloning git://github.com/components/jquery.git
bower cached git://github.com/components/jquery.git
bower fetching jquery
(snip)
ここでインストールされるファイルはリポジトリに含めないため、この作業は開発者毎の環境およびデプロイ先の環境で必要に応じて行うことになります(bundle install
と同様)。
6. Asset Pipelineの設定を変更する
Asset Pipelineを利用している場合は、インストールされたJavaScriptライブラリを読み込む指定をapplication.js
に記述します。
例えば、vendor/assets/components/foo
というライブラリをインストールし、vendor/assets/components/foo/lib/foo.min.js
というファイルをAsset Pipelineに加える場合は、以下のようにvendor/assets/components
からの相対パスを指定します。
//= require foo/lib/foo.min
これにより、Asset PipelineでJavaScriptファイルが読み込まれます。パスの指定が正しいかどうかは、アセットのプリコンパイルを実行することで確認できます。
また、Bowerによってvendor/assets/components
以下にインストールされたJavaScriptライブラリに含まれるCSSファイルや画像ファイルは、ディレクトリ構成がそのままhttp://example.com/assets
以下にコピーされます。
例えば、vendor/assets/components/foo/img/foo.png
というファイルは、http://example.com/assets/foo/img/foo.png
としてアクセスできるようになります。アセットファイル用のヘルパーを利用する場合は、以下のように記述することで参照することができます。
<%= asset_path('foo/img/foo') %>
jQueryについて、jquery-rails.gem
に含まれるものではなくBowerでインストールしたものを読み込む場合は、以下のようにパスを変更します。
-//= require jquery
+//= require jquery/jquery
カスタムBuildpackによるHerokuへのデプロイ
前述のとおり、Bowerを導入した場合はデプロイ時にbower install
を実行する必要があります。
qnypはHeroku上で開発・運用をしていますが、標準のRuby用BuildpackではBowerに関する処理は行われません。これを解消するため、弊社ではheroku-buildpack-ruby-bowerというBuildpackを作成して、Herokuへのデプロイ時に、
- Nodeのインストール(あらかじめビルドしたものをS3から取得して展開)
- Bowerのインストール(あらかじめ作成した
node_modules
をS3から取得して展開) bower install
の実行
が行われるようにしています。
デプロイ時にこのBuildpackが利用されるようにするには、以下のようにHerokuアプリケーションが利用するBuildpackを変更しておきます。
$ heroku config:set \
BUILDPACK_URL='git://github.com/qnyp/heroku-buildpack-ruby-bower.git#run-bower'
そしてHerokuへのpushを行うと、Bowerを利用したデプロイが行われます。
$ git push heroku master
(snip)
-----> Fetching custom git buildpack... done
-----> Ruby/Rails app detected
(snip)
Your bundle is complete! It was installed into ./vendor/bundle
Cleaning up the bundler cache.
-----> Using bower version: 0.9.2
-----> Installing JavaScript dependencies using bower 0.9.2
bower cloning git://github.com/components/jquery.git
bower caching git://github.com/components/jquery.git
(snip)
まとめ
Railsアプリケーションに新しい機能を加えるときに新しいgemを導入して対処するのは、とても簡単ですし、将来的な手間も一見少なくなりそうに思えます。
しかし、長期的には(とくに今回のようなRailsのメジャーアップデート時には)負債としての側面が顕在化することも少なくないため、寿命の長いアプリケーションを開発している場合は、導入しようとするgemがどのような依存関係を持っているのかを考慮することも重要になってきます。
Railsアプリケーションから依存性を減らしていく考え方は、FingertipsのブログにあるA quick note on dependencies in Ruby on Rails projectsという記事において “minimal dependency policy” として紹介されているので、そちらも一読をおすすめします。
また、この記事はRubyKaigi 2013の会場で書いているのですが、今まさに聽いている@sikachuさんの発表 You have to test multiple versions of your gem’s dependencies. You used Appraisal. It’s super affective! (video) もそのあたりに関係のありそうな内容です。