Dive into Rubygems

  • 21
    Like
  • 0
    Comment

Dive into Rubygems

2017/8/24(木) Rails Developers Meetup #4
https://rails-developers-meetup.connpass.com/event/62792/


自己紹介

pocke.png

桑原 仁雄 (pocke)


[宣伝] SideCI

弊社ではSideCIというサービスを作っています。

https://sideci.com

170821153556.png

GitHub で Pull-Request を投げると、それに対して自動でレビュー


[宣伝] アビシニアン

SideCIではアビシニアンという便利UIで、ツールの指摘を無視することが可能

例: ツールの誤検知を無視したい時

170821172327.png

無視する理由を書いてclose
170821172407.png


本題: Dive into Rubygems


Agenda

  • 何故 Gem に飛び込むのか
    • Gem のコードを読み書きする機会がある
  • Gem の歩き方
    • Gemの読み書き
    • Gemの作り方

  • RuboCop の話はしません
  • Rails の話はしません

何故 Gem に飛び込むのか


Ruby on Rails で作られたアプリケーションは、沢山の Gem の上に成り立っている。

# rails new ホヤホヤでも71個
$ rails new hogehoge
$ cd hogehoge
$ bundle exec gem list | wc -l 
71

# sideci のレポジトリでは251個
$ cd sideci
$ bundle exec gem list | wc -l 
251

沢山のGemに依存している分、Gemのコードを読み書きする機会も多い


Gem のコードを読む機会


Gem のコードを読む機会

Gemのコードを読むことで、素早く問題解決をすることが出来ることがある


例: アプリケーションの挙動が怪しい時

  • 自分の書いたコードが悪いのかも知れない
    • この場合は自分のコードを読めば良い
  • Gem のバグかも知れない
  • Gem の使い方が悪いのかも知れない
    • この場合はGemのコードを読んだほうがいいかも知れない

Gem のコードを読んだ実例

docker-api gem を使って、プライベートなdocker repositoryの検索をしたかった。


こんな感じのコードが既にあったので、

# 認証をする
Docker.authenticate!(username: name, password: pass)
# docker pull をする
Docker::Image.create(fromImage: image_name)

こんな感じに行けるのでは!

# 認証をする
Docker.authenticate!(username: name, password: pass)
# 検索をする
Docker::Image.search(term: image_name)

と思ったけど、何故か public なレポジトリしか検索できない。


原因

しばらく混乱していたけど、docker-api のコードを読んだら分かった。

Docker::Image.create の定義 (GitHub)

    def create(opts = {}, creds = nil, conn = Docker.connection, &block)
      credentials = creds.nil? ? Docker.creds : creds.to_json
      headers = credentials && Docker::Util.build_auth_header(credentials) || {}

Docker::Image.search の定義 (GitHub)

    def search(query = {}, connection = Docker.connection)
      body = connection.get('/images/search', query)
      hashes = Docker::Util.parse_json(body) || []

.search の方はクレデンシャルが使われていなかった……!

Gem のコードを眺めることで問題が解決できた


Gem のコードを書く機会


Gem のコードを書く機会

Gem のコードを読む機会も多いけど、書く機会も多い。

  • 踏んだバグを直す時
  • 機能追加をする時
    • Monkey Patch ではなく Gem を修正する
      • 多くの人がそのコードの恩恵を受けることが出来る
      • Gem のアップデートをしやすくなる
  • Gem を作りたい時

Gem の歩き方


Gem の歩き方

8割ぐらいのGemでは通用する方法を紹介します

  • clone
  • ディレクトリ構成
  • 依存関係
  • インストール
  • Gemの作り方

Gem を clone する


Gem を clone する

  • コードを読む際、手元にコードがあると便利
    • Gem としてインストールはされているけど、Git管理下にあるコードも持っていると更に便利
      • grep しやすい
      • すぐに手を加えられる
      • 変更履歴とかも追える

amatsuda/gem-src

  • gem install した時に git clone が走る
    • 何も考えなくても手元にコードがあって便利
  • motemen/ghq と組み合わせると更に便利
    • Go like なレポジトリ管理ツール
    • Go を使っていなくても便利

詳しくは
http://koic.hatenablog.com/entry/2016/10/07/000000
https://www.slideshare.net/koic/ghq-gemsrc-andmore?ref=http://koic.hatenablog.com/entry/2016/10/07/000000

DEMO


Gemのディレクトリ構成


Gemのディレクトリ構成

  • lib/
  • test / spec
  • exe / bin

lib/

  • メインのコードが置かれている
  • ここがrequireする際のエントリーポイント
    • require 'foo' すると、各Gemのlib/foo から最初に見つかったものが require される
    • そのため、基本的にlib/直下にはGEMNAME.rbGEMNAME/しか置かれていない
    • $LOAD_PATH について調べるといいと思う
  • また、それ以下のディレクトリ構成はクラス名の階層と対応する。
    • 例: foo gem の Foo::BarBaz::Hoge class は、lib/foo/bar_baz/hoge.rbに定義されている(ことが多い)
      • "慣習"なので、必ずしもこうなっているとは限らない

spec/, test/

  • テストコードが書かれている。Rails と同じですね
  • test の場合は minitest, spec の場合は RSpec のことが多い
  • ディレクトリ構造はlib/とだいたい同じ
  • テストコードを読むことで Gem の使い方が分かることもあるので、意外と読む機会がある
    • テストコードは動くexample

exe / bin

  • Gem をインストールした際に一緒にインストールされる実行ファイルが置いてある
  • 例) RuboCop であれば、bin/rubocopにコマンドの定義が置いてある。 See. bin/rubocop
  • 最近作られた Gem だとexeに置かれていることが多い

Gemの依存関係


Gemの依存関係

主に登場人物は3人。

  • GEMNAME.gemspec
  • Gemfile
  • Gemfile.lock

gemspec

Gemの情報が色々書いてあるファイル。

  • gemの名前
  • author
  • license
  • etc...

依存するGemの情報もここに書く

# gem install した時に、一緒にインストールされるgem
s.add_runtime_dependency('rainbow', '>= 2.2.2', '< 3.0')
# gem の開発時のみに使用されるgem。Railsで言うところのdevelopment groupみたいなやつ。
s.add_development_dependency('rspec', '~> 3.6.0')

Gemfile

  • Rails application では定番
  • Gemの開発においては殆ど使われないことも多い
  • Gem のディレクトリ直下で bundle install した時に見られるのはこいつ
  • そのため、ここに開発時にのみ使うGemを書くこともある(add_development_dependencyと同じ)
    • Gemfileに書いておけば、gem installされた時には使われない

Gemfile.lock

  • Rails application ではだいたいVCSにcommitされている
    • 何故ならば、Gemのバージョンを固定してアプリケーションを運用したいから
  • Gem ではVCSにcommitすることは避けられている
    • 何故ならば、多くのアプリケーション(==多くのGemのバージョン)で動いてほしいから

Gem をインストールする


Gem をインストールする

Gem をインストールする為のrake taskが用意されている。
これを使って手を加えたGemをローカルにインストールすることが出来る。

# Gem のコードを編集
$ bundle install
$ bundle exec rake install:local

8割ぐらいのGemではこの方法が使える


Gem をインストールする(manually)

  • rake task は bundler によって提供されているものなので、bundler を使って作られていない gem などでは使えないこともある。
  • そんな時は、gem buildする。
$ gem build GEMNAME.gemspec
# gem がビルドされ、GEMNAME-VERSION.gem ファイルができる
$ gem install --local GEMNAME-VERSION.gem

tmpgem の紹介

Gem を一時的に install するための gem

https://github.com/pocke/tmpgem

  • デバッグ用にbinding.pryを仕込んだGemを、インストールしたままにする事故を防げる
  • Gem の開発と、Gemの使用を分離できる

DEMO


Gem を作る


Gem を作る

基本的な流れ

  1. bundle gem GEMNAME
  2. Edit GEMFNAME.gemspec
  3. Write gem code
  4. bundle exec rake install:local
  5. bundle exec rake release

時間があったら、DEMOしながらやる


1. bundle gem

  • gem の雛形を作る。
  • rails new みたいな。
$ bundle gem foo
Creating gem 'foo'...
      create  foo/Gemfile
      create  foo/lib/foo.rb
      create  foo/lib/foo/version.rb
      create  foo/foo.gemspec
      create  foo/Rakefile
      create  foo/README.md
      create  foo/bin/console
      create  foo/bin/setup
      create  foo/.gitignore
Initializing git repo in /path/to/foo

2. Edit gemspec

bundle gemしただけでは、gemをビルドすることは出来ない。

  • descriptionなどからTODOを消す
    • => description, summary, homepage を書く
  • if spec.respond_to?(:metadata)... の部分をなんとかする
    • rubygems.org に公開するなら、バッサリ消す
    • 公開しないなら、ホスト名を指定する

3. Write gem code


4. install

  • bundle exec rake install:local すると、gem installしたものと同じように扱える
  • install したらデバッグしよう

5. release

  • gem が出来たら、rubygems.org にpublishしよう

まとめ


まとめ

  • Railsアプリケーションを作る上でも、Gemのコードを読み書きすることは結構ある
  • このスライドはTips集なので、なにかあったら読み返して欲しい

ご清聴ありがとうございました。