Dive into Rubygems 

Dive into Rubygems
=========

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

------


自己紹介
====

![pocke.png](https://qiita-image-store.s3.amazonaws.com/0/33922/f8dcb673-d05f-218a-16f7-75e7f81eb41d.png)

桑原 仁雄 (pocke)



- Actcat, Inc. / [SideCI](https://sideci.com)
- [RuboCop](https://github.com/bbatsov/rubocop)'s core developer
    - Issue に label をつけて回る人
- [Reek](https://github.com/troessner/reek) collaborator
- GitHub [@pocke](https://github.com/pocke)
- Twitter [@p_ck_](https://twitter.com/p_ck_)
- [RubyKaigi 2017 Speaker](http://rubykaigi.org/2017/presentations/p_ck_.html)


-----


[宣伝] SideCI
=====

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

https://sideci.com


![170821153556.png](https://qiita-image-store.s3.amazonaws.com/0/33922/5dcc68f8-2617-daa5-c5fc-2c426ddf6fce.png)

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

----

[宣伝] アビシニアン
-----

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

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



![170821172327.png](https://qiita-image-store.s3.amazonaws.com/0/33922/c300739b-bf73-7e3a-f7d9-e5c0f3e564ec.png)

無視する理由を書いてclose
![170821172407.png](https://qiita-image-store.s3.amazonaws.com/0/33922/5620acd6-3379-1c6b-a56f-da45ffba1e9c.png)


----


本題: Dive into Rubygems
=====


-----





Agenda
=====


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


-----

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


-----


何故 Gem に飛び込むのか
======


----------




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

```bash
# 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](https://rubygems.org/gems/docker-api) gem を使って、プライベートなdocker repositoryの検索をしたかった。


----

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


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

----

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

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

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


-------


原因
----


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

`Docker::Image.create` の定義 ([GitHub](https://github.com/swipely/docker-api/blob/0800f8c014f5a895307a9aa0afde98393810b9f2/lib/docker/image.rb#L110-L112))

```ruby
    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](https://github.com/swipely/docker-api/blob/0800f8c014f5a895307a9aa0afde98393810b9f2/lib/docker/image.rb#L209-L211))

```ruby
    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](https://github.com/amatsuda/gem-src)
-----


- gem install した時に git clone が走る
    - 何も考えなくても手元にコードがあって便利
- [motemen/ghq](https://github.com/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](https://github.com/bbatsov/rubocop) であれば、`bin/rubocop`にコマンドの定義が置いてある。 See. [`bin/rubocop`](https://github.com/bbatsov/rubocop/blob/c243880cf6450542e11b99e50eb9093bd84c23ba/bin/rubocop)
- 最近作られた Gem だと`exe`に置かれていることが多い

-----


Gemの依存関係
========


-------


Gemの依存関係
========

主に登場人物は3人。

- `GEMNAME.gemspec`
- `Gemfile`
- `Gemfile.lock`



-----


`gemspec`
=======

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

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

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


```ruby
# 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をローカルにインストールすることが出来る。

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

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

-------


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


- rake task は bundler によって提供されているものなので、bundler を使って作られていない gem などでは使えないこともある。
    - 例) [Brakeman](https://github.com/presidentbeef/brakeman)
- そんな時は、`gem build`する。

```bash
$ 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
3. `bundle exec rake install:local`
4. `bundle exec rake release`


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

-----



1. `bundle gem`
---------

- gem の雛形を作る。
- `rails new` みたいな。


```bash
$ 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
-------


- `lib`下にコードを書いていく
- Rails のことは忘れろ
    - ActiveSupport なんてない
    - 定数の autoload なんてない
- `exe/`, `bin/` 下のファイルには極力何も書くな
    - テストしやすくするため
    - See https://github.com/pocke/tmpgem/blob/7e0444390ddb95c9b4e7e7e05159ed428bc0d505/exe/tmpgem


--------

4. install
-----


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






-------

5. release
-------


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


------




まとめ
=======



------


まとめ
======


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

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