読者です 読者をやめる 読者になる 読者になる

is Neet

ネトゲしながら暮したい

【gemの作り方】 Rubyのオブジェクトをグンマー県が制圧するgem書いた

2012-06-01 ruby gem

先日、ラクガキサービスLeenoAPIをラップしたgemを公開しました。

http://rubygems.org/gems/leeno

ソースはこちらから。
https://github.com/soplana/leeno


ということで、今回gemを作成する手順をサンプルgemを作成しながら備忘録として残しておきます。
今回サンプルで作成するgemはto_gunmaというgemで、最近流行りの「◯◯県は群馬県になりました。」というメッセージで有名な「ぐんまのやぼう」というアプリに肖って、[].to_gunmaみたいに呼び出すと、"Arrayは群馬県になりました。"ってメッセージが帰ってくるだけのショボイgemを作ろうと思います。
グンマーがRubyのオブジェクトを制圧するgemという事です。
グンマー凄い。
gemを作成する方法としてはいくつかあるようですが、今回はbundlerを使用して作成していきます。

また今回作成したgemは gem install to_gunma で実際install可能となっております。




gemプロジェクトを作成

まずはbundleでgemプロジェクトのひな形を作成します。

soplana ~/work $bundle gem to_gunma
      create  to_gunma/Gemfile
      create  to_gunma/Rakefile
      create  to_gunma/LICENSE
      create  to_gunma/README.md
      create  to_gunma/.gitignore
      create  to_gunma/to_gunma.gemspec
      create  to_gunma/lib/to_gunma.rb
      create  to_gunma/lib/to_gunma/version.rb
Initializating git repo in /Users/soplana/work/to_gunma

.



githubに登録

githubアカウントを取得してリポジトリを作成し、最初のcommitをしておきます。

soplana ~/work $cd to_gunma/
soplana ~/work/to_gunma $git init
Reinitialized existing Git repository in /Users/soplana/work/to_gunma/.git/
soplana ~/work/to_gunma $git add .
soplana ~/work/to_gunma $git commit -m "init"
[master (root-commit) c1dd106] init
 8 files changed, 99 insertions(+), 0 deletions(-)
 create mode 100644 .gitignore
 create mode 100644 Gemfile
 create mode 100644 LICENSE
 create mode 100644 README.md
 create mode 100644 Rakefile
 create mode 100644 lib/to_gunma.rb
 create mode 100644 lib/to_gunma/version.rb
 create mode 100644 to_gunma.gemspec
soplana ~/work/to_gunma $git remote add origin git@github.com:soplana/to_gunma.git
soplana ~/work/to_gunma $git push -u origin master
Counting objects: 12, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (9/9), done.
Writing objects: 100% (12/12), 2.16 KiB, done.
Total 12 (delta 0), reused 0 (delta 0)
To git@github.com:soplana/to_gunma.git
 * [new branch]      master -> master
Branch master set up to track remote branch master from origin.

.



.gemspecの編集

to_gunma.gemspecを開き、gem.descriptionとgem.summaryを編集します。
ついでにgem.homepageに先程作成したgithubページでも追加しておきましょう。
ここにTODOが残っているとエラーになりビルド出来ないので、取り敢えずなんでもいいので入れておきましょう。

# -*- encoding: utf-8 -*-
require File.expand_path('../lib/to_gunma/version', __FILE__)

Gem::Specification.new do |gem|
  gem.authors       = ["soplana"]
  gem.email         = ["sonosheet.jp@gmail.com"]
  gem.description   = %q{to_gunma}
  gem.summary       = %q{to_gunma}
  gem.homepage      = "https://github.com/soplana/to_gunma"

  gem.files         = `git ls-files`.split($\)
  gem.executables   = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
  gem.test_files    = gem.files.grep(%r{^(test|spec|features)/})
  gem.name          = "to_gunma"
  gem.require_paths = ["lib"]
  gem.version       = ToGunma::VERSION
end

ちなみに関連gemを定義しておきたい場合はこのファイルに`gem.add_dependency "gem-name"`と記述しておけばいいようです。
leenoのgemはfaradayに依存しているので.gemspecは以下の様になっています。

# -*- encoding: utf-8 -*-
require File.expand_path('../lib/leeno/version', __FILE__)

Gem::Specification.new do |gem|
  gem.authors       = ["soplana"]
  gem.email         = ["sonosheet.jp@gmail.com"]
  gem.description   = %q{LeenoAPI client for Ruby}
  gem.summary       = %q{LeenoAPI client for Ruby}
  gem.homepage      = "https://github.com/soplana/leeno"

  gem.files         = `git ls-files`.split($\)
  gem.executables   = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
  gem.test_files    = gem.files.grep(%r{^(test|spec|features)/})
  gem.name          = "leeno"
  gem.require_paths = ["lib"]
  gem.version       = Leeno::VERSION

  gem.add_dependency "faraday", "~>0.8.0"
  gem.add_dependency "faraday_middleware", "~>0.8.7"

end

.



テスト(rspec)の準備

gemの開発はテスト駆動開発を強いられます。
というのも、普通のスクリプトやRailsと違って、書いて試して書いて試してのフローが出来無いからです。(出来るの?)
実行するためには、ビルドしてローカルにインストールしてrequireして実行、といった手順になるため、例えば小さなif文一つ変更して動作確認したい、って場合なんかはかなり面倒です。
でも、テストを書いておけば小さな変更であろうと大きな変更であろうと、テストが通ればいいわけですから安心です。書きましょう。
今回、テストにはrspecを使います。

まずはspec_hepler.rbを作ります。

soplana ~/work/to_gunma $mkdir spec
soplana ~/work/to_gunma $vim spec/spec_helper.rb

中身

# encoding: utf-8
require 'rubygems'
require 'to_gunma'

.



具体的な実装

まずはversionの話から。
bundleでgemのひな形を作成した段階で、lib/to_gunma/version.rbというファイルが作成されます。
中身はとてもシンプルです。

module ToGunma
  VERSION = "0.0.1"
end

この状態でビルドすると、to_gunma(0.0.1)が作成される訳ですね。
今後変更を加えてバージョンアップしていく時は、このファイルを変更してリビルドして公開、という流れになります。

さて、gemはrequire時にlib/to_gunma.rbを読み込むようになっているそうです。
今回作りたいto_gunmaはRubyのObject Classを拡張して、全てのオブジェクトをグンマーにしてしまう凶悪なgemなので、lib/to_gunma.rbからObject Classをオープンしてインスタンスメソッドを追加するための.rbファイルをrequireするようにしておきます。

まずは、Object Class拡張用のファイルを作成します。
適当にextentionというディレクトリを作り、その下にobject.rbを作成。

soplana ~/work/to_gunma $mkdir lib/to_gunma/extention
soplana ~/work/to_gunma $vim lib/to_gunma/extention/object.rb

.

# -*- coding: utf-8 -*-
class Object
  def to_gunma
    "#{self.class}は群馬県になりました。"
  end
end

はい、取り敢えずこれだけ。
そしたら、作成したobject.rbをlib/to_gunma.rbへrequireします。

require "to_gunma/version"
require "to_gunma/extention/object"

module ToGunma
  # Your code goes here...
end

はい、これでRuby上の全てのオブジェクトをグンマーが制圧しました。




テストを書く

書きます。テスト。
まずspec_hepler.rbにto_gunmaした結果の文字列を返すだけのメソッドを定義しておきます。

# encoding: utf-8
require 'rubygems'
require 'to_gunma'

def gunma
  "は群馬県になりました。"
end

次に、object_spec.rbファイル作り、ゴリゴリテストを書きます。

soplana ~/work/to_gunma $mkdir spec/extention
soplana ~/work/to_gunma $vim spec/extention/object_spec.rb

.

# -*- encoding: UTF-8 -*-
require File.expand_path(File.join('../', 'spec_helper'), File.dirname(__FILE__))

describe Object do
  it "Array should be gunma"  do [].to_gunma.should  == [].class.to_s+gunma;  end
  it "nil should be gunma"    do nil.to_gunma.should == nil.class.to_s+gunma; end
  it "Hash should be gunma"   do {}.to_gunma.should  == {}.class.to_s+gunma;  end
  it "String should be gunma" do "".to_gunma.should  == "".class.to_s+gunma;  end
  it "Fixnum should be gunma" do 1.to_gunma.should   == 1.class.to_s+gunma;   end
  it "Float should be gunma"  do 1.1.to_gunma.should == 1.1.class.to_s+gunma; end
  it "Regexp should be gunma" do //.to_gunma.should  == //.class.to_s+gunma;  end
  it "Time should be gunma"   do Time.now.to_gunma.should    == Time.now.class.to_s+gunma;    end
  it "Proc should be gunma"   do Proc.new{}.to_gunma.should  == Proc.new{}.class.to_s+gunma;  end
end

実際、Rubyの組み込みライブラリはもっと沢山ありますが、取り敢えず主要なオブジェクトに対するテストだけ。
実行します。

soplana ~/work/to_gunma $rspec -c spec/extention/object_spec.rb
.........

Finished in 0.00178 seconds
9 examples, 0 failures

全て通りました。Rubyはグンマーに制圧されました。




versionを上げて、commit & pushしておく

取り敢えず、ここまでの作業で一旦まともに動く様になったという意味でgemをマイナーバージョンアップさせておきます。
バージョンアップはやってもやらなくてもいいのですが、lib/extention/object_spec.rbなど、新しく作成されたファイルはcommitしておかないとビルドされない現象が確認でき、しばらく悩みました。

まずlib/to_gunma/version.rbを開き、versionを0.0.1から0.0.1.1にあげておきます。

module ToGunma
  VERSION = "0.0.1.1"
end

commitしてpush。

soplana ~/work/to_gunma $git add .
soplana ~/work/to_gunma $git commit -m "v0.0.1.1"
[master 5a4737e] v0.0.1.1
 1 files changed, 1 insertions(+), 1 deletions(-)
soplana ~/work/to_gunma $git push origin master
Counting objects: 28, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (19/19), done.
Writing objects: 100% (22/22), 2.09 KiB, done.
Total 22 (delta 4), reused 0 (delta 0)
To git@github.com:soplana/to_gunma.git
   c1dd106..5a4737e  master -> master

.



ローカルにインストールしてみる

はい、してみましょう。
bndlerを使ってまずはビルドをします。

soplana ~/work/to_gunma $gem build to_gunma.gemspec
WARNING:  description and summary are identical
  Successfully built RubyGem
  Name: to_gunma
  Version: 0.0.1.1
  File: to_gunma-0.0.1.1.gem

warning出てますけど、ビルドは成功しました。
インストールしてみます。

soplana ~/work/to_gunma $rake install
to_gunma 0.0.1.1 built to pkg/to_gunma-0.0.1.1.gem
to_gunma (0.0.1.1) installed

完了しました。これでローカルでto_gunmaのgemが使えるはずです!

soplana ~/work/to_gunma $irb
irb(main):001:0> require "to_gunma"
=> true
irb(main):002:0> Time.now.to_gunma
=> "Timeは群馬県になりました。"

キタ━━━━(゚∀゚)━━━━ッ!!
Time.nowして現在時刻を取得したにも関わらずグンマーに支配される感じが堪らないですね。




rubygemsにgemを登録する

rubygemsに自分で作ったgemを登録することで、gem install to_gunmaとか出来るようになるので登録します。
まずはrubygemsに自分のアカウントを作成しておきます。

http://rubygems.org/sign_up

ここで入力するemailとpasswordはrubygemsにgemをpushする際、必要になってくるので覚えておきましょう。
本来なら、ビルドしてpushという流れになるのですが、ローカルでの動作確認の為、さきほどビルドはしていますのでそこは省略します。

soplana ~/work/to_gunma $gem push pkg/to_gunma-0.0.1.1.gem 
Enter your RubyGems.org credentials.
Don't have an account yet? Create one at http://rubygems.org/sign_up
   Email:   
Password:   
Pushing gem to https://rubygems.org...
Signed in.
Pushing gem to https://rubygems.org...
Successfully registered gem: to_gunma (0.0.1.1)

出来たっぽいですね。
さっそく試してみましょう!
先ほどローカルに入れたgemは削除してrubygemsから入れてみます。

soplana ~/work/to_gunma $gem uninstall to_gunma
Successfully uninstalled to_gunma-0.0.1.1
soplana ~/work/to_gunma $gem install to_gunma
Fetching: to_gunma-0.0.1.1.gem (100%)
Successfully installed to_gunma-0.0.1.1
1 gem installed
Installing ri documentation for to_gunma-0.0.1.1...
Installing RDoc documentation for to_gunma-0.0.1.1...

おおおお!!
入ったのでは!?
確認してみます。

soplana ~/work/to_gunma $irb
irb(main):001:0> require "to_gunma"
=> true
irb(main):002:0> [].to_gunma
=> "Arrayは群馬県になりました。"
irb(main):003:0> exit

やった!!グンマーがRubyのオブジェクトを制圧してる!

はい。rubygemsをこんなネタgemで汚してすみませんでした。

今後、to_gunma!メソッドも作りたいです。
一度呼び出すと、それ以降どのメソッド呼んでも群馬に制圧されてる感じの。
お疲れさまでした。