Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

初めてのRuby Gem作成ガイド ― Railsアプリからコードを抽出して公開するまで

あなたのGitHubのコントリビューショングラフは真っ灰色になっていませんか?オープンソースプロジェクトに取り組めば解決できますが、ゼロから始める必要はありません。便利なサイドプロジェクトを作る最も簡単な方法は、すでに開発中のアプリケーションからコードを切り出すことです。実は、Rails自身もそうやって生まれたのです!

では、何を抽出すべきかはどうやって見分ければいいのでしょうか?また、普段の開発ワークフローを壊さずにgemへと変換するにはどうすればいいのでしょうか?

抽出したいコードを見つける

アプリの奥深くには、そこにあるべきではないコードが潜んでいます。アプリがなくても単体で機能するコードです。それはどこにあるのでしょうか?

時には勘で探すしかありませんが、経験上、抽出候補となるコードは決まった場所に現れることが多いようです。

  • カスタムバリデーション

    属性に対して独自のバリデーションを書いたことはありませんか?それらは優れたgemになります。

  • Railsへの変更・パッチ

    テストでコミットフックを動かしたり、空の属性をデータベースでNULLに変換したりするために、Railsに手を加えたことはありませんか?こうしたロジックをgemに移動すると、より安定し、理解しやすくなります。

  • ActiveRecordを使わないモデル

    メールアドレスや電話番号の解析処理が多すぎて、専用クラスに分離したことはありませんか?こうしたクラスは他のアプリでも役立ち、gem化も比較的簡単です。

  • モックオブジェクトとカスタムアサーション

    カスタムアサーションを使うと、テストの可読性が大きく向上します。あるライブラリや設計パターン向けに書いた優れたアサーションは、同じものを使うすべての人にとって有益です。

大きなものを考える必要はありません。私のお気に入りのgemの中には、たった1ファイルで構成されているものもあります!

それでも何を抽出すべきか決められない場合は、RubyToolboxのカテゴリ一覧を眺めてインスピレーションを得てみましょう。

gemらしいディレクトリ構成にコードを整理する

gem化するコードが決まったら、アプリ内でgemのような構造になるようにコードを配置し直しましょう。

Railsアプリでは、lib/を「gemのステージングエリア」として使っています。gem化の可能性があるコードはここに置きます。例えば「json_api_client」というgemを作る場合、Railsアプリのlib/ディレクトリは次のようになります:

...
my_rails_app/lib/json_api_client.rb
my_rails_app/lib/json_api_client/associations.rb
my_rails_app/lib/json_api_client/connection.rb
...

ほとんどのgemは、lib/直下にgem名と同じ名前のファイル(lib/json_api_client.rb)があり、gem名と同じ名前のディレクトリ(lib/json_api_client/以下)に関連ファイルを格納しています。この構造をそのままRailsアプリ内でも再現しておけば、後でコードをgemへ移動するときが格段に楽になります。

gemのレイアウトがよくわからない場合は、お気に入りのgemのソースコードをGitHubで覗いてみてください。パターンはすぐに掴めるはずです。

テストはどうする?

以前はlib/の構造に合わせて、test/unit/内にテストを配置していました:

...
my_rails_app/test/unit/json_api_client_test.rb
my_rails_app/test/unit/json_api_client/associations_test.rb
my_rails_app/test/unit/json_api_client/connection_test.rb
...

モデルとライブラリのテストが同じフォルダに入るのは少し散漫でしたが、それなりにうまく機能していました。

しかし現在のRailsは、test/unit/の代わりにtest/models/を使います。lib/のテストをtest/models/に置くのはあまり筋が良くありません。まだ確固たる規約は決めかねています。何か良いアイデアがあれば、ぜひ教えてください。

依存関係を断ち切る

コードがgemになると、元のアプリに依存できなくなります。つまり、lib/に置いたコードを見直し、アプリ固有のクラス、オブジェクト、振る舞いに依存している箇所を探す必要があります。

依存関係が見つかったら、それを断ち切らなければなりません。依存性の注入(DI)については優れた解説が数多くあるため、ここでは深入りしません。

gemを作成する

私はbundle gemコマンドでgemを作成しています。例えばbulk_cache_fetcherというgemを作るなら、次のように実行します:

bundle gem bulk_cache_fetcher -t minitest

-tオプションを付けると、テスト用のヘルパーファイルと、gemのRakefileにテストタスクが追加されます。

次に、.gemspecの記入、READMEの執筆、LICENSEの選択といった準備作業を行います。

そして、gemのコードはすでにRailsアプリのlib/フォルダにあるので、そのコードとテストを新しいgemのlib/test/フォルダに移動するだけで済みます。

多くの場合、見落としていた点や、gem内では成立しない前提を含むコードが見つかるはずです。先に進む前に、移動したテストを実行し、すべてパスすることを必ず確認しましょう。

新しいgemをアプリで使う

gemが完成したら、実際に使ってみたくなりますよね。しかし、Railsアプリ内でgemの変更をテストするのは、あっという間に面倒になります。毎回次の手順が必要になるからです:

  1. gemを修正する
  2. gemをビルドする
  3. システムからgemの痕跡を消すか、バージョンを更新する
  4. gemをインストールする
  5. サーバーを再起動する

これはかなり辛い作業です。幸い、Bundlerにはもっと簡単な方法が用意されています。gemを~/Source/bulk_cache_fetcherに作成した場合、テスト中はRailsアプリのGemfileに次のように書きます:

Gemfile
gem "bulk_cache_fetcher", path: "~/Source/bulk_cache_fetcher"

続けてbundle installを実行すれば、あたかもコードがまだアプリのlib/フォルダにあるかのように、gemを自由に編集できるようになります。

最後に一つだけ注意点:コードをコミットする前に、必ずpath:オプションを削除してください!そのパスは他の環境では正しい場所を指さない可能性が高く、自分のマシン以外では動作しないでしょう。

ビルドして、公開して、楽しもう!

gemの準備が整ったら、世界に向けて発信しましょう。

RubyGems.orgのアカウントをまだ持っていなければ登録し、変更をコミットして、rake releaseを実行します。おめでとうございます、これであなたもgem作者です!gemをGitHubにプッシュすれば、その日の緑色のマスも手に入ります。

あなたのアプリにも、抽出できそうな部分はありませんか?良いgemになりそうなコードはありますか?ぜひ下のコメント欄で教えてください。

さらに、gemの作成・管理・メンテナンスについて徹底的に学びたい方には、Brandon Hilkert著『Build a Ruby Gem』を強くおすすめします。この本で紹介されている手順はこの記事の流れに非常に近く、はるかに多くの内容を網羅しています。


  1. Rubyで実践する関数型プログラミング完全ガイド ― 純粋関数・イミュータブルデータ・カリー化の基本

    Rubyを書いていると、「関数型プログラミング」という言葉を目にする機会が増えてきます。しかし、実際にどんなものなのか、自分のコードに取り入れるべきなのか、疑問に感じている方も多いのではないでしょうか。 関数型プログラミングとは、具体的に何を指すのか? オブジェクト指向プログラミング(OOP)とは何が違うのか? Rubyでも関数型的な書き方を採用すべきなのか? この記事では、これらの疑問にわかりやすく答えながら、Rubyで今日から使える関数型プログラミングの考え方とテクニックを解説します。 関数型プログラミングとは? 関数型プログラミングは一時的な流行や難しい専門用語ではなく、長い歴史を持

  2. Rubyでの静的分析入門!parser gemでメソッド定義を抽出する方法

    ソースコードを解析して、すべてのメソッドがどこで定義され、どんな引数を受け取るのかを把握したいと思ったことはありませんか? どうすれば実現できるのでしょうか? 最初に思いつくのは、正規表現(regexp)を書くことかもしれません。 しかし、もっと良い方法があるとしたらどうでしょう? 答えは「あります」! 静的解析(Static Analysis)とは、ソースコードそのものから情報を抽出するためのテクニックです。 これは、ソースコードをトークンへと変換する(パースする)ことで実現されます。 それでは早速見ていきましょう! parser gemを使う Rubyには標準ライブラリとしてRipper