Dive into Rubygems
2017/8/24(木) Rails Developers Meetup #4
https://rails-developers-meetup.connpass.com/event/62792/
自己紹介
桑原 仁雄 (pocke)
- Actcat, Inc. / SideCI
-
RuboCop's core developer
- Issue に label をつけて回る人
- Reek collaborator
- GitHub @pocke
- Twitter @p_ck_
- RubyKaigi 2017 Speaker
[宣伝] SideCI
弊社ではSideCIというサービスを作っています。
GitHub で Pull-Request を投げると、それに対して自動でレビュー
[宣伝] アビシニアン
SideCIではアビシニアンという便利UIで、ツールの指摘を無視することが可能
例: ツールの誤検知を無視したい時
本題: 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 のアップデートをしやすくなる
- Monkey Patch ではなく Gem を修正する
- Gem を作りたい時
Gem の歩き方
Gem の歩き方
8割ぐらいのGemでは通用する方法を紹介します
- clone
- ディレクトリ構成
- 依存関係
- インストール
- Gemの作り方
Gem を clone する
Gem を clone する
- コードを読む際、手元にコードがあると便利
- Gem としてインストールはされているけど、Git管理下にあるコードも持っていると更に便利
- grep しやすい
- すぐに手を加えられる
- 変更履歴とかも追える
- Gem としてインストールはされているけど、Git管理下にあるコードも持っていると更に便利
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.rb
とGEMNAME/
しか置かれていない -
$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に書いておけば、
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 などでは使えないこともある。
- 例) Brakeman
- そんな時は、
gem build
する。
$ gem build GEMNAME.gemspec
# gem がビルドされ、GEMNAME-VERSION.gem ファイルができる
$ gem install --local GEMNAME-VERSION.gem
tmpgem の紹介
Gem を一時的に install するための gem
- デバッグ用に
binding.pry
を仕込んだGemを、インストールしたままにする事故を防げる - Gem の開発と、Gemの使用を分離できる
DEMO
Gem を作る
Gem を作る
基本的な流れ
bundle gem GEMNAME
- Edit
GEMFNAME.gemspec
- Write gem code
bundle exec rake install:local
bundle exec rake release
時間があったら、DEMOしながらやる
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
- Edit gemspec
bundle gem
しただけでは、gemをビルドすることは出来ない。
- descriptionなどから
TODO
を消す- => description, summary, homepage を書く
-
if spec.respond_to?(:metadata)...
の部分をなんとかする- rubygems.org に公開するなら、バッサリ消す
- 公開しないなら、ホスト名を指定する
- Write gem code
-
lib
下にコードを書いていく - Rails のことは忘れろ
- ActiveSupport なんてない
- 定数の autoload なんてない
-
exe/
,bin/
下のファイルには極力何も書くな
- install
-
bundle exec rake install:local
すると、gem install
したものと同じように扱える - install したらデバッグしよう
- release
- gem が出来たら、rubygems.org にpublishしよう
まとめ
まとめ
- Railsアプリケーションを作る上でも、Gemのコードを読み書きすることは結構ある
- このスライドはTips集なので、なにかあったら読み返して欲しい
ご清聴ありがとうございました。