bundle gemコマンドから始めてgemを作る

gemを1個作った事があります。
でも、ネットの情報を寄せ集めて雰囲気で実装していましたが、ファイルの配置などかなりふわふわしたままでした。

##　目的
目的は、元のドキュメントに参照しながらしっかりgemの作ることです。

主に、こちらの[How to create a Ruby gem with Bundler](https://bundler.io/v2.0/guides/creating_gem.html)をみながら、ドキュメントを参照しつつgemを作りました。

## 環境

```SHELL
$ ruby -v            
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin18]
$ bundle -v
Bundler version 2.1.4
```

## 手順
空のgemを作って→機能を実装して→動作確認して→テストコード書いて→readme書いて→githubにpushして→他のプロジェクトから試して →完了

という流れです。

### 空のgemを作る
まずは、bundlerを使ってgemのひな形を作ります。
今回は、csvを出力するためのgemをつくります。
名前は `junara_export_csv` にしました。

```shell
$ bundle gem junara_export_csv 
Creating gem 'junara_export_csv'...
      create  junara_export_csv/Gemfile
      create  junara_export_csv/lib/junara_export_csv.rb
      create  junara_export_csv/lib/junara_export_csv/version.rb
      create  junara_export_csv/junara_export_csv.gemspec
      create  junara_export_csv/Rakefile
      create  junara_export_csv/README.md
      create  junara_export_csv/bin/console
      create  junara_export_csv/bin/setup
      create  junara_export_csv/.gitignore
      create  junara_export_csv/.travis.yml
      create  junara_export_csv/.rspec
      create  junara_export_csv/spec/spec_helper.rb
      create  junara_export_csv/spec/junara_export_csv_spec.rb
Initializing git repo in /Users/junara/IdeaProjects/junara_export_csv
Gem 'junara_export_csv' was successfully created. For more information on making a RubyGem visit https://bundler.io/guides/creating_gem.html
```

作ったフォルダに移動します。

```shell
$ cd junara_export_csv/
$ pwd
/Users/junara/IdeaProjects/junara_export_csv
```

なにも作っていませんが、ここでgemのinstallをしてみます。

> the install task will build and install the gem to our system (just like it would do if we gem install‘d it)

```shell
rake install                                                                                [master]
rake aborted!
WARNING:  See http://guides.rubygems.org/specification-reference/ for help
ERROR:  While executing gem ... (Gem::InvalidSpecificationException)
    metadata['homepage_uri'] has invalid link: "TODO: Put your gem's website or public repo URL here."

Tasks: TOP => install => build
(See full trace by running task with --trace)

```

`metadata['homepage_uri']` がないといわれます。設定ファイルを記入します。

```
require_relative 'lib/junara_export_csv/version'

Gem::Specification.new do |spec|
  spec.name          = "junara_export_csv"
  spec.version       = JunaraExportCsv::VERSION
  spec.authors       = ["junara"]
  spec.email         = ["jun5araki@gmail.com"]

  spec.summary       = %q{Export CSV for personal.}
  spec.description   = %q{Export CSV for personal.}
  spec.homepage      = "https://github.com/junara"
  spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")

  spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"

  spec.metadata["homepage_uri"] = spec.homepage
  spec.metadata["source_code_uri"] = "https://github.com/junara"
  spec.metadata["changelog_uri"] = "https://github.com/junara"

  # Specify which files should be added to the gem when it is released.
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
  spec.files         = Dir.chdir(File.expand_path('..', __FILE__)) do
    `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
  end
  spec.bindir        = "exe"
  spec.executables   = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
  spec.require_paths = ["lib"]
end

```

下記を編集しました。

`spec.summary` `spec.description` `spec.homepage` `spec.metadata["source_code_uri"]`  ` spec.metadata["changelog_uri"]`


ここれで 再度installします

```shell
$ rake install                                                                                                                   +[master]
junara_export_csv 0.1.0 built to pkg/junara_export_csv-0.1.0.gem.
junara_export_csv (0.1.0) installed.
```

成功します。 `pkg/junara_export_csv-0.1.0.gem`  ここにgemが出力されます。
これで最低限のgem 空のgemの作成が終わりました。


### 機能を実装する

改めて今回作るgemはCSＶを出力するgemです。
具体的には、arrayとその処理をblockで渡すとarrayの要素を1行ずつblockで処理してcsv出力する事ができます。
イメージはこんなかんじです

```
JunaraExportCsv.run(array) {|item| item.[0], item[1], item[2]}
#=> ./2020-03-20_10:00.csv にarrayのitemが出力される
```

RubyGemsによると gemを読み込んだ時には、 `lib` 配下のファイルが読み込まれるとあります。
 
https://guides.rubygems.org/rubygems-basics/

> REQUIRING CODE
> RubyGems modifies your Ruby load path, which controls how your Ruby code is found by the require statement. When you require a gem, really you’re just placing that gem’s lib directory onto your $LOAD_PATH.

そして、lib直下には1つのファイルが普通とあります。更に複雑なgemを作る場合は複数ファイルに分割する必要があり、その場合は、gemと同じ名前のフォルダ内に複数ファイルを配置します。

> The lib directory itself normally contains only one .rb file and a directory with the same name as the gem which contains the rest of the files.

しかし、今回はシンプルなgemなので1ファイルに収まります。よって、 `lib/junara_export_csv.rb`に実装を書きます。

```lib/junara_export_csv.rb
# frozen_string_literal: true

require 'junara_export_csv/version'
require 'csv'
module JunaraExportCsv
  # usage
  # JunaraExportCsv.run([[1, 2, 3], [3, 4, 5]], header: ['1st', '2nd', '3rd']) { |r| [r[0], r[1], r[2]] }
  def self.run(records, header: [], filename: nil)
    data = read(records, header) { |r| yield(r) }
    filename ||= default_filename
    export(filename, data)
    filename
  end

  def self.read(records, header)
    CSV.generate do |csv|
      csv << header if header.is_a?(Array) && !header.empty?
      records.each do |r|
        csv << yield(r)
      end
    end
  end

  def self.export(filename, data)
    File.open(filename, 'w') do |f|
      f.write(data)
    end
  end

  def self.default_filename
    "./#{Time.now}.csv"
  end

  private_class_method(:default_filename, :export, :read)
end
```

### 機能を動かして試してみる
実装した機能を試します。そのため、先ほど実装した機能をインストールします。
bundlerのドキュメントを見ると、 `bundle install` とすると `this library` がインストールされるとありますのでやってみます。

https://bundler.io/v2.0/guides/creating_gem.html#testing-our-gem

> When we run bundle install, rspec will be installed for this library and any other library we use with Bundler, but not for the system. 

```
$ bundle install
Using rake 12.3.3
Using ast 2.4.0
Using bundler 2.0.2
Using csv 3.1.2
Using diff-lcs 1.3
Using jaro_winkler 1.5.4
Using junara_export_csv 0.1.0 from source at `.`
Using parallel 1.19.1
Using parser 2.7.0.5
Using rainbow 3.0.0
Using rexml 3.2.4
Using rspec-support 3.9.2
Using rspec-core 3.9.1
Using rspec-expectations 3.9.1
Using rspec-mocks 3.9.1
Using rspec 3.9.0
Using ruby-progressbar 1.10.1
Using unicode-display_width 1.6.1
Using rubocop 0.80.1
Bundle complete! 4 Gemfile dependencies, 19 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.
```

`Using junara_export_csv 0.1.0 from source at ` という表示に注目です。bundle installのコマンドの結果、 先ほど作成した機能がinstall されました。

次に コンソールで機能を確かめます。

irbを起動して、

```shell
bundle exec irb 
```

`junara_export_csv` を読み込み、実行します。

```irb
irb(main):004:0> require 'junara_export_csv'
irb(main):004:0> JunaraExportCsv.run([[1, 2, 3], [3, 4, 5]], header: ['1st', '2nd', '3rd']) { |r| [r[0], r[1], r[2]] }
=> "./2020-03-22 23:14:35 +0900.csv"
```

`./2020-03-22 23:14:35 +0900.csv` というファイル名で以下の内容の出力がなされれば正常に動いています。


```2020-03-22xxxxxxx.csv
1st,2nd,3rd
1,2,3
3,4,5
```


### 機能のテストを書く
実装した機能のテストコードを書きます。

https://bundler.io/v2.0/guides/creating_gem.html#testing-our-gem

> For this guide, we’re going to use RSpec to test our gem. 

`bundle gem rspec` で書く例があるので素直にrspecでテストコードを書きます

今回は、 `self.run` 特異メソッドについてのテストコードを書きました。

```spec/junara_export_csv_spec.rb
# frozen_string_literal: true

require 'tempfile'
RSpec.describe JunaraExportCsv do
  it 'has a version number' do
    expect(JunaraExportCsv::VERSION).not_to be nil
  end
  describe '#run' do
    let(:subject) do
      described_class.run(rows, filename: filename, header: header) { |r| [r[0], r[1], r[2]] }
    end
    context 'with parameters' do
      let(:temp_out_file) { Tempfile.new('csv') }
      let(:rows) { [%w[a b c], %w[d e f]] }
      let(:filename) { temp_out_file.path }
      let(:header) { %w[1st 2nd 3rd] }
      after { temp_out_file.unlink }
      it 'export filename' do
        expect(subject).to eq temp_out_file.path
      end
      it 'export header' do
        subject
        lines = CSV.readlines(temp_out_file.path)
        expect(lines[0]).to eq header
      end
      it 'export data' do
        subject
        lines = CSV.readlines(temp_out_file.path)
        expect(lines[1]).to eq rows[0]
        expect(lines[2]).to eq rows[1]
      end
    end
  end
end
```

こちらのコードが通ることをrspecを実行して確認します。

```shell
$ bundle exec rspec spec                                                                                                 ?[master]

JunaraExportCsv
  has a version number
  #run
    with parameters
      export filename
      export header
      export data

Finished in 0.00749 seconds (files took 0.20855 seconds to load)
4 examples, 0 failures
```

テストコードが全て成功したことを確認できました。

### ReadMeを書く
gemの説明を書きます。 `bundle gem` で作成したgemの場合はある程度templateが書かれているので、適宜変更すれば良いです。

https://github.com/junara/junara_export_csv/blob/master/README.md

すぐに試せる様なusageを書くと、理解しやすいかなーと思うのでそこら辺は丁寧に書きました。

### GitHubに公開する
* 普通にpushしてください

### 使う
githubのソースを指定すれば、bundlerで管理されている他のプロジェクトでも使う事ができます。

https://guides.rubygems.org/publishing/

> The simplest way (from the author’s perspective) to share a gem for other developers’ use is to distribute it in source code form.

具体的には、 https://github.com/junara/junara_export_csv/blob/master/README.md#installation こちらの通りです。

使いたいところのGemfileに下記を追加します。

```Gemfile
gem 'junara_export_csv', git: 'git://github.com/junara/junara_export_csv.git'
```

そしてbundle installすればinstallされます。


```shell
$ bundle install
```

 使い方は、  `機能を動かして試してみる` もしくは、 https://github.com/junara/junara_export_csv/blob/master/README.md#usage をご覧ください。

## その他
これまでの内容で公開し、使う事ができるgemを公開する事ができました。
ただ、sourceをgithubで指定しないといけないのでとても不便です。
一般的なgemは https://rubygems.org/ に公開されます。 https://rubygems.org/ に公開すれば、 `gem 'junara_export_csv'` と書くだけでinstallされます。
方法はこちらのページを参照ください。

https://guides.rubygems.org/publishing/