Ruboty の Plugin のつくりかた #ruboty

# Ruboty の Plugin のつくりかた #ruboty
## <i class="fa fa-cubes" style="font-size:1em;"></i> 概要
Ruboty の Plugin のつくりかたについて説明します

※今回説明するのは Handler の Plugin です。Adapter の Plugin を作成する方は少数だと思いますので。

## <i class="fa fa-cube" style="font-size:1em;"></i> 前提
下記記事で、 Ruboty の 基本構成と Handler + Action について理解してから読み進めてください。

* <i class="fa fa-external-link" style="font-size:1em;"></i> [Ruboty の基本構成](http://qiita.com/tbpgr/items/7f08569dd8828bae6527)
* <i class="fa fa-external-link" style="font-size:1em;"></i> [Ruboty Handlers と Actions](http://qiita.com/tbpgr/items/9b416529df6ce212b86d)

## <i class="fa fa-cube" style="font-size:1em;"></i> 作成する前に・・・
Ruboty の Plugin 作成を支援するツール類を使わずに、あえて手動で Plugin を作成してみます。
特に、 Ruboty 未経験者は最初に 1 回手動で作成しておくことで、
自動生成ツールがどういった部分をカバーしてくれているのか理解しやすくなります。

また、Ruboty の Plugin は Handler のみでも作成できますが、
構成の統一、テスタビリティなどを考え、 Handler + Action 構成を推奨します。
そのため、 Handler + Action 構成で説明します。

## <i class="fa fa-files-o" style="font-size:1em;"></i> 手順
### <i class="fa fa-book" style="font-size:1em;"></i> 仕様
* FizzBuzz Plugin を作成します

### <i class="fa fa-tint" style="font-size:1em;"></i> gem のひな形を生成
* bundler で gem のひな形を生成します。gem 名のプリフィックスとして 「ruboty-」 をつけます

~~~bash
$ bundle gem ruboty-fizzbuzz
~~~

### <i class="fa fa-tint" style="font-size:1em;"></i> Handler を作成します

* lib/ruboty/handlers/ ディレクトリを作成します

~~~bash
$ cd ruboty-fizzbuzz
$ cd ruboty
$ mkdir handlers
~~~

* lib/ruboty/handlers/fizzbuzz.rb を作成します

~~~bash
$ cd handlers
$ touch fizzbuzz.rb
~~~

* lib/ruboty/handlers/fizzbuzz.rb を実装します

Handler の作成時は、 Ruboty::Handlers::Base を継承します。

~~~ruby
require "ruboty/fizzbuzz/actions/fizzbuzz"

module Ruboty
  module Handlers
    class Fizzbuzz < Base
    end
  end
end
~~~

* `on` メソッドを実装します

`on` メソッドに 呼び出しパターン（正規表現）, アクション名, アクションの説明を記述します
`on` の内容は、 `ruboty help` で呼び出されるヘルプにも利用されます。

~~~ruby
require "ruboty/fizzbuzz/actions/fizzbuzz"

module Ruboty
  module Handlers
    class Fizzbuzz < Base
      on /fizzbuzz (?<number>.*?)\z/, name: 'fizzbuzz', description: 'output fizzbuzz result'
    end
  end
end
~~~

ここ指定した正規表現中の名前付きキャプチャは、後続の処理で `message[:your_key]` として参照できます。
Action の作成時に登場します。

* `on` に設定したアクション名と同じ public メソッドをアクション数分追加します。

メソッド内容は、 Action の呼び出しのみです。
Handler は名前の通り、コマンドに応じた Action に専念します。
単一責務。

Action の呼び出しは call メソッドで統一するようになっています。
gem 名と同一のアクション名なのでちょっとわかりにくいですが、
Ruboty::Fizzbuzz::Actions::Fizzbuzz の最初の Fizzbuzz は gem の名前、
最後の Fizzbuzz はアクションの名前です。

例えば、文字列を大文字・小文字にする Plugin StringConverter があったとしたら

Ruboty::StringConverter::Actions::Upcase
Ruboty::StringConverter::Actions::Downcase

のようになります。

以下は、 Ruboty::Handlers::Fizzbuzz のコードです。

~~~ruby
require "ruboty/fizzbuzz/actions/fizzbuzz"

module Ruboty
  module Handlers
    class Fizzbuzz < Base
      on /fizzbuzz (?<number>.*?)\z/, name: 'fizzbuzz', description: 'output fizzbuzz result'

      def fizzbuzz(message)
        Ruboty::Fizzbuzz::Actions::Fizzbuzz.new(message).call
      end
    end
  end
end
~~~

### <i class="fa fa-tint" style="font-size:1em;"></i> Action を作成します
* lib/ruboty/fizzbuzz/actions/ ディレクトリを作成します

~~~bash
# プロジェクトルートからスタートしたとします
$ cd lib/ruboty/fizzbuzz
$ mkdir actions
~~~

* lib/ruboty/fizzbuzz/actions/fizzbuzz.rb を作成します

~~~bash
$ cd actions
$ touch fizzbuzz.rb
~~~

* lib/ruboty/fizzbuzz/actions/fizzbuzz.rb を実装します

Action の作成時は、 Ruboty::Actions::Base を継承します。

~~~ruby
module Ruboty
  module Fizzbuzz
    module Actions
      class Fizzbuzz < Ruboty::Actions::Base
      end
    end
  end
end
~~~

今回は 1 Action しかありませんが、 複数ある場合は件数分 actions ディレクトリ配下に作成します。

* `call` メソッドを実装します。 これで、 Handler から `call` を呼び出し可能になりました

`message.reply` は文字列を引数にとり、チャットメッセージを送るメソッドです。

~~~ruby
module Ruboty
  module Fizzbuzz
    module Actions
      class Fizzbuzz < Ruboty::Actions::Base
        def call
          message.reply('dummy')
        end
      end
    end
  end
end
~~~

* FizzBuzz の処理本体を作成します。 private メソッドとして作成してみます

Handler の部分で説明した 名前付きキャプチャの値を `message[:number]` から取得しています

~~~ruby
module Ruboty
  module Fizzbuzz
    module Actions
      class Fizzbuzz < Ruboty::Actions::Base
        def call
          message.reply(fizzbuzz)
        end

        private

        def fizzbuzz
          case message[:number].to_i
          when fizzbuzz? then "FizzBuzz"
          when buzz? then "Buzz"
          when fizz? then "Fizz"
          else message[:number]
          end
        end

        def fizzbuzz?; ->(v){v % 15 == 0}; end
        def buzz?; ->(v){v % 5 == 0}; end
        def fizz?; ->(v){v % 3 == 0}; end
      end
    end
  end
end
~~~

### <i class="fa fa-tint" style="font-size:1em;"></i> gem の公開に必要な情報を設定します
* ./README.md

Plugin の内容を説明する内容を記述してください。
最低限 TODO になっている

description (概要説明)
と
Usage

の部分を記述してください。

* ./ruboty-fizzbuzz.gemspec

bundle gem 直後は以下のようになっています。

~~~ruby
# coding: utf-8
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'ruboty/fizzbuzz/version'

Gem::Specification.new do |spec|
  spec.name          = "ruboty-fizzbuzz"
  spec.version       = Ruboty::Fizzbuzz::VERSION
  spec.authors       = ["your name"]
  spec.email         = ["your mail address"]
  spec.summary       = %q{TODO: Write a short summary. Required.}
  spec.description   = %q{TODO: Write a longer description. Optional.}
  spec.homepage      = ""
  spec.license       = "MIT"

  spec.files         = `git ls-files -z`.split("\x0")
  spec.executables   = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
  spec.test_files    = spec.files.grep(%r{^(test|spec|features)/})
  spec.require_paths = ["lib"]

  spec.add_development_dependency "bundler", "~> 1.7"
  spec.add_development_dependency "rake", "~> 10.0"
end
~~~

gem の仕様を記述します。

spec.authors
spec.email
spec.summary
spec.description

に任意の内容を設定し、

spec.add_development_dependency の直前に

spec.add_runtime_dependency "ruboty"

を追加してください。

GitHub に公開する場合は、 

spec.homepage      = ""

に、 URL を設定します。


### <i class="fa fa-tint" style="font-size:1em;"></i> GitHub に公開する
* git に登録します

~~~bash
$ git init
$ git add -A
$ git commit -m "first commit"
~~~

* GitHub で `ruboty-fizzbuzz` プロジェクトを作成します。

* remote に 作成した GitHub のリポジトリを登録します

~~~bash
$ git remote add origin git@github.com:tbpgr/ruboty-fizzbuzz.git
~~~

* GitHub に push します

~~~bash
$ git push origin master
~~~

### <i class="fa fa-tint" style="font-size:1em;"></i> Shell Adapter で Ruboty の動作確認をする

* ruboty の作業ディレクトリを作成

~~~bash
# 好きなディレクトリに移動
$ ruboty --generate
$ cd ruboty
~~~

* Gemfile に今作成した ruboty-fizzbuzz を追加

~~~ruby
source "https://rubygems.org"

gem "ruboty"
gem 'ruboty-fizzbuzz', :git => "git://github.com/your_account_name/ruboty-fizzbuzz.git"
~~~

※ Windows / OSX 環境の方は rb-readline gem も追加

RubyGems に公開せずに gem を利用する方法について、詳しくは下記を参照。
<i class="fa fa-external-link" style="font-size:1em;"></i> [Ruboty Plugin を RubyGems に公開せずに利用する](http://qiita.com/tbpgr/items/e3c6544e6bd9533bc71f)

* ruboty-fizzbuzz をインストールします

~~~bash
$ bundle install
~~~

* 動作確認をします

~~~bash
$ bundle exec ruboty
> ruboty help
ruboty /fizzbuzz (?<number>.*?)\z/ - output fizzbuzz result
ruboty /help( me)?\z/i             - Show this help message
ruboty /ping\z/i                   - Return PONG to PING
ruboty /who am i\?/i               - Answer who you are
> ruboty fizzbuzz 1
1
> ruboty fizzbuzz 2
2
> ruboty fizzbuzz 3
Fizz
> ruboty fizzbuzz 5
Buzz
> ruboty fizzbuzz 15
FizzBuzz
~~~

## <i class="fa fa-bell" style="font-size:1em;"></i> お気づきですか？ <i class="fa fa-bell" style="font-size:1em;"></i>
Ruboty の Plugin を2回以上作ろうとした場合、  WET (Write Every Twice) な部分が多いことに気が付きます。
※ WET は DRY (Don't repeat yourself) の対義語として用いられます
ディレクトリ構成、クラス・モジュールの宣言、決まりきった継承などなど。

ちょっとしたジョーク Plugin を作成する場合などは、実装部はごくわずかで、ひな型部分の作成がメインになりかねません。
コピペでもいいのですが、コピペはタイポによるミスを誘発しやすく、人間はミスをするものです。

そこで、ツールの出番です。
@blockgiven さんや私が boilertemplate となる重複した部分を生成するジェネレータを作成しました。

* <i class="fa fa-github-square" style="font-size:1em;"></i> [ruboty-gen](https://github.com/blockgiven/ruboty-gen)
* <i class="fa fa-github-square" style="font-size:1em;"></i> [ruboty-megen](https://github.com/tbpgr/ruboty-megen)
* <i class="fa fa-github-square" style="font-size:1em;"></i> [ruboty-articlegen](https://github.com/tbpgr/ruboty-articlegen)

各ジェネレータに関する詳しい情報は下記の記事を参照ください。

#### プロジェクトテンプレート、コード生成
* <i class="fa fa-external-link" style="font-size:1em;"></i> [Ruboty 【怠惰は正義】ruboty-gen で Handlers と Actions のひな形を作成](http://qiita.com/tbpgr/items/39c754bb17193c9b27e9)

#### README の生成
* <i class="fa fa-external-link" style="font-size:1em;"></i> [Ruboty Handler Plugin 作成時の README テンプレートジェネレータ ruboty-megen を作成した](http://qiita.com/tbpgr/items/33ccf5709cb553b2095f)
* <i class="fa fa-external-link" style="font-size:1em;"></i> [Ruboty Handler Plugin 作成時の README テンプレートジェネレータ ruboty-megen の REAMDE 内の Commands から Usage へのページ内リンクを追加 #ruboty](http://qiita.com/tbpgr/items/ef4aeddc176c77ac6479)
* <i class="fa fa-external-link" style="font-size:1em;"></i> [Ruboty ruboty-megen の command ブロックに example を追加](http://qiita.com/tbpgr/items/b3e7c401ea2a360a255f)

#### Qiita の記事の生成
* <i class="fa fa-external-link" style="font-size:1em;"></i> [Ruboty Ruboty Handler Plugin 作成時に公開するための Markdown 形式の技術エントリ テンプレートジェネレータ ruboty-articlegen]()

#### 上記の全ツールをまとめて紹介
* <i class="fa fa-external-link" style="font-size:1em;"></i> [Ruboty | 各種 Generator を利用して Ruboty のプロダクトコード・README・Qiita記事の作成コストを下げる](http://qiita.com/tbpgr/items/1987fef9bb228b07e5eb)

## <i class="fa fa-book" style="font-size:1em;"></i> 外部資料
Ruboty の Plugin だけではなく gem の作りかたについては下記サイトが入門内容としてわかりやすいです

* <i class="fa fa-external-link-square" style="font-size:1em;"></i> [酒と泪とRubyとRailsと | RubyGemはめっちゃ簡単に作れる！](http://morizyun.github.io/blog/ruby-gem-easy-publish-library-rails/)

## Ruboty Advent Calendar
明日は @r82 さんです