脳汁portal

アメリカ在住(だった)新米エンジニアがその日学んだIT知識を書き綴るブログ

Gemfile/Gemfile.lock/gemspec/Rakefileそれぞれの違い・役割

f:id:portaltan:20151130171550j:plain

Gemfile

  • gemの取得先を記述する
  • 通常はsourceとgemspecの2行、もしくはsourceの1行だけでよい
source "https://rubygems.org"

gemspec

gemspec

  • 実際の情報を記述するファイル
Gem::Specification.new do |s|
  s.authors = []
  s.homepage = ''
・
・
・
  • gemの依存関係をやgemの情報を記述するファイル
s.add_dependency '*****'
s.add_development_dependency '***'

Rakefile

  • makeのRuby版("R"ake)
  • bundlerが上記のgemファイル群を利用して環境構築するためのファイル
  • bundle install するとgroupに関係なく全てのgemがinstallされる
    • 防ぎたいときはwithoutオプションを使用する
    • withoutオプションを使うと.bundle/configに設定が保存され、以降withoutをつけなくても設定が引き継がれるので注意

(Gemfile.lock)

  • bundleで自動生成される
  • installしたgemのversionや取得先が記録される
  • このファイルがある状態でbundle installを行うと、厳密に同じversionをinstallする
  • ペアプロ等でまったく同じ環境で作業したいときなど便利
  • 公開する際にリポジトリに含めるか含めないかはケースバイケースで意見が分かれる

Rubyのpack, unpackでエンディアンを指定する方法

前回に引き続きエンディアン関連のポストです。
ネットワークバイトオーダー・ビッグエンディアン・リトルエンディアンとは - 脳汁portal

BOMをデータの先頭に付与することで、データを受け取る側がエンディアンを判別できるようになると書きましたが、Rubyでは標準ライブラリでエンディアンの指定ができるのでその方法を書きます

コマンド

基本形
Array.pack(format)
String.unpack(format)
  • pack時のdataはarrayで指定する
  • unpack時のdataはStringで指定する
  • formatは以下参照


ビッグエンディアン
array.pack("N*")
string.unpack("N*")
  • Nの数はarrayのサイズ
    • *で無制限
    • 足りない場合はエラーになる
    • arrayの数の方が少ない場合は無視される


トルエンディアン
array.pack("V*")
string.unpack("V*")
  • VもNとほぼ一緒

ビッグエンディアン
$ irb
2.1.2 :001 > [1].pack("N")
 => "\x00\x00\x00\x01"
2.1.2 :002 > [1,2].pack("NN")
 => "\x00\x00\x00\x01\x00\x00\x00\x02"
2.1.2 :003 > "\x00\x00\x00\x01\x00\x00\x00\x02".unpack("NN")
 => [1, 2]


トルエンディアン
$ irb
2.1.2 :001 > [1].pack("V")
 => "\x01\x00\x00\x00"
2.1.2 :002 > [1,2].pack("V*")
 => "\x01\x00\x00\x00\x02\x00\x00\x00"
2.1.2 :003 > "\x01\x00\x00\x00\x02\x00\x00\x00".unpack("VV")
 => [1, 2]

# ちなみにformatを間違えると・・・
2.1.2 :004 > "\x01\x00\x00\x00\x02\x00\x00\x00".unpack("NN")
 => [16777216, 33554432] ### 正しい値がとれません

文字列で返ってきたHashやArrayをそれぞれのクラスに再変換する'to_array'と'to_h'メソッド

APITCP socketsの返り値が強制的にstringになってしまう場合に、それを再び正しいクラスへ戻すメソッドです。

to_array

install 方法
gem install to_array
require 'to_array'

array = ['foo', 'bar', 'hoge', 'fuga']
str = array.to_s

p str        # => "[\"foo\", \"bar\", \"hoge\", \"fuga\"]"
p str.class  # => String
p str.size   # => 30
  
re_converted = str.to_array

p re_converted        # => ["foo", "bar", "hoge", "fuga"]
p re_converted.class  # => Array
p re_converted.size   # => 4




to_h

install 方法
gem install str_to_hash
require 'str_to_hash'

hash = {'foo'=>'bar', 'hoge'=>'fuga'}
str = hash.to_s

p str        # => "{\"foo\"=>\"bar\", \"hoge\"=>\"fuga\"}"
p str.class  # => String
p str.size   # => 30
  
re_converted = str.to_h

p re_converted        # => {"foo"=>"bar", "hoge"=>"fuga"}
p re_converted.class  # => Hash
p re_converted.size   # => 2

念のために・・・

一応既存のメソッドをoverwriteしちゃってないか確認

2.1.2 :001 > String.new.public_methods.include?(:to_array)
 => false
2.1.2 :002 > String.new.public_methods.include?(:to_h)
 => false

2.1.2 :003 > String.new.public_methods.size
 => 164
2.1.2 :004 > require 'str_to_hash'
 => true
2.1.2 :005 > require 'to_array'
 => true
2.1.2 :006 > String.new.public_methods.size
 => 166

OK!

gemファイルを作成して公開する方法

自作のgemを開発してrubygems.orgへ公開する方法です。
RubyGems.org | your community gem host

  • 今回はto_arrayという名前のgemを作りたいと思います。

to_array

arrayがto_strメソッドによってstringに変換されてしまったり、文字列で返ってきた場合に文字列からArrayに変換する

実装

1. 開発環境準備
$ bundle gem to_array -t

Creating gem 'to_array'...
MIT License enabled in config
      create  to_array/Gemfile
      create  to_array/.gitignore
      create  to_array/lib/to_array.rb
      create  to_array/lib/to_array/version.rb
      create  to_array/to_array.gemspec
      create  to_array/Rakefile
      create  to_array/README.md
      create  to_array/bin/console
      create  to_array/bin/setup
      create  to_array/LICENSE.txt
      create  to_array/.travis.yml
      create  to_array/.rspec
      create  to_array/spec/spec_helper.rb
      create  to_array/spec/to_array_spec.rb
2. 実際の処理を実装する
  • lib/to_array.rbへ実装します。

最初はこのようなファイル内容です。

require "to_array/version"

module ToArray
  # Your code goes here...
end

↓これをこのように変更しました

class String
  def to_array
    if self[0] != "[" || self[-1] != "]"
      raise ArgumentError.new("invalid value for `str_to_array': '#{self}'")
    end

    begin
      arr = self.chomp.gsub(/"|^\[|\]$/, '')
      arr = arr.split(/,[\s]*/)
      return arr
    rescue
      raise ArgumentError.new("invalid value for `str_to_array': '#{self}'")
    end
  end
end
3. 実際の動きを確認する

rubyのファイルを作成してもいいですが、今回はirbで確認します。

$ irb
### 作成したruby ファイルをrequire
2.1.2 :001 > require_relative './lib/to_array'
 => true

### その後は実装したメソッドを確認する
2.1.2 :005 > str = ['foo', 'bar', 'fizz', 'bazz'].to_s
 => "[\"foo\", \"bar\", \"fizz\", \"bazz\"]"
2.1.2 :007 > str.class
 => String
2.1.2 :008 > str.size
 => 30

2.1.2 :009 > reconverted = str.to_array
 => ["foo", "bar", "fizz", "bazz"]
2.1.2 :011 > reconverted.class
 => Array
2.1.2 :012 > reconverted.size
 => 4

問題ないですね。

# テストの作成

  • spec/to_array_spec.rb内にテスト内容を記述します
  • テストライブラリはrspecです

今回は以下のようにしました

require 'spec_helper'

describe ToArray do
  arr1 = ['foo', 'bar', 'fizz', 'bazz']
  str1 = arr1.to_s

  context "success case" do
    it 'should send back ArrayClass' do
      expect(str1.to_array).to be_a_kind_of(Array)
    end

    it 'should array size is same' do
      expect(str1.to_array.size).to eq(arr1.size)
    end
  end

  context "failure case(Other Object)" do
    it 'nil' do
      expect{nil.to_array}.to raise_error(NoMethodError)
    end
    it 'boolean' do
      expect{true.to_array}.to raise_error(NoMethodError)
    end
    it 'Already Array' do
      expect{arr1.to_array}.to raise_error(NoMethodError)
    end
    it 'Interger' do
      expect{100.to_array}.to raise_error(NoMethodError)
    end
    it 'Hash' do
      expect{{'foo'=>'bar', 'fizz'=>'bazz'}.to_array}.to raise_error(NoMethodError)
    end
  end

  context "failure case(Other Object)" do
    it 'nil' do
      expect{nil.to_array}.to raise_error(NoMethodError)
    end
    it 'boolean' do
      expect{true.to_array}.to raise_error(NoMethodError)
    end
    it 'Already Array' do
      expect{arr1.to_array}.to raise_error(NoMethodError)
    end
    it 'Interger' do
      expect{100.to_array}.to raise_error(NoMethodError)
    end
    it 'Hash' do
      expect{{'foo'=>'bar', 'fizz'=>'bazz'}.to_array}.to raise_error(NoMethodError)
    end
  end
end

実際に確認します

$ rspec

ToArray
  success case
    should send back ArrayClass
    should array size is same
  failure case(Other Object)
    nil
    boolean
    Already Array
    Interger
    Hash
  failure case(Other Object)
    nil
    boolean
    Already Array
    Interger
    Hash

Finished in 0.00638 seconds (files took 0.06421 seconds to load)
12 examples, 0 failures

ここまで出来れば大体OKです。
あとはリリースの準備をします

リリース準備

README.mdを作成する
  • 既にテンプレートが作成されてありますが、こちらを編集して使い方やダウンロード方法等を記入しておきましょう。
  • 記法はMARKDOWN記法です
version番号の設定(任意)
  • lib/to_array/version.rbのversion番号を変更しましょう。
  • 最初なら1.0.0とかでいいでしょう。
module ToArray
  VERSION = "1.0.0"
end
gemの情報を入力する

to_array.gemspecの中のTODOと書かれている部分を修正しましょう

$ grep -r 'TODO' to_array.gemspec
  spec.authors       = ["TODO: Write your name"]
  spec.email         = ["TODO: Write your email address"]
  spec.summary       = %q{TODO: Write a short summary, because Rubygems requires one.}
  spec.description   = %q{TODO: Write a longer description or delete this line.}
  spec.homepage      = "TODO: Put your gem's website or public repo URL here."
    spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"

リリース

確認
$ rake -T
rake build          # Build to_array-1.0.0.gem into the pkg directory
rake install        # Build and install to_array-1.0.0.gem into system gems
rake install:local  # Build and install to_array-1.0.0.gem into system gems without network access
rake release        # Create tag v1.0.0 and build and push to_array-1.0.0.gem to Rubygems
rake spec           # Run RSpec code examples

$ rake install:local
to_array 1.0.0 built to pkg/to_array-1.0.0.gem.
to_array (1.0.0) installed.

$ irb
2.1.2 :001 > require 'to_array'
 => true
2.1.2 :002 >  ['foo', 'bar'].to_s.to_array.class
 => Array
2.1.2 :003 > ['foo', 'bar'].to_s.to_array.size
 => 2

$ rake spec
ToArray
  success case
    should send back ArrayClass
    should array size is same
  failure case(Other Object)
    nil
    boolean
    Already Array
    Interger
    Hash
  failure case(Other Object)
    nil
    boolean
    Already Array
    Interger
    Hash

Finished in 0.00833 seconds (files took 0.06756 seconds to load)
12 examples, 0 failures

問題なければupload

upload
$ gem push pkg/to_array-1.0.0.gem
Enter your RubyGems.org credentials.
Don't have an account yet? Create one at https://rubygems.org/sign_up
   Email:   ******************
Password:

Signed in.
Pushing gem to https://rubygems.org...
Successfully registered gem: to_array (1.0.0)

これで無事リリースすることが出来ました。
to_array | RubyGems.org | your community gem host

to_boolメソッドをgem化して公開しました。

以前以下のように文字列で返ってきた"true"や"false"をBooleanに変換するto_boolメソッドの実装方法を紹介しましたが、それをgem化して公開しました。portaltan.hatenablog.com

たが問題として、「to_bool」も「to_boolean」も「to_b」も全部既にgemの名前として使われていました。。
苦肉の策としてgem名は「to-bool」にしました。

Install方法

gem install to-bool
  Fetching: to-bool-1.2.0.gem (100%)
  Successfully installed to-bool-1.2.0
  Parsing documentation for to-bool-1.2.0
  Installing ri documentation for to-bool-1.2.0
  Done installing documentation for to-bool after 0 seconds
  1 gem installed

gem list to-bool
  *** LOCAL GEMS ***
  to-bool (1.2.0)

使い方

test.rb
require 'to-bool'

p 'true'.to_bool        #=> true
p 'true'.to_bool.class  #=> TrueClass
p 'false'.to_bool       #=> false
p 'false'.to_bool.class #=> FalseClass

p true.to_bool          #=> true
p true.to_bool.class    #=> TruelClass
p false.to_bool         #=> false
p false.to_bool.class   #=> FalseClass
NoMethodError

対応していないObjectの場合はNoMethodErrorを返すようにしています

require 'to-bool'

[].to_bool #=>undefined method `to_bool' for []:Array (NoMethodError)
ArgumentError

文字列が'true'か'false'の場合はArgumentErrorを返すようにしています。

require 'to-bool'

p 'foo'.to_bool      #=>invalid value for `to_bool': 'foo' (ArgumentError)
p 'truetrue'.to_bool #=>invalid value for `to_bool': 'truetrue' (ArgumentError)
p ''.to_bool         #=>invalid value for `to_bool': '' (ArgumentError)

rubyのデフォルトのUnit testの使い方

Rubyにデフォルトで入っているテストフレームワークのUnit testの使い方です。
ちなみに以前書きましたがrubyのデフォルトのテストフレームワークは各versionで中身が違います。portaltan.hatenablog.com

使い方

準備

unit.rb
require 'test/unit'

class SampleTest < Test::Unit::TestCase

end

1. まずはテストフレームワークを使用するために'test/unit'をrequireします
2. つぎにTest::Unit::TestCaseを継承したclassを作成します

  • rubyではTest::Unit::TestCaseを継承することで自動でそのクラスはテスト実行ファイルになります

これで基本的な設定は完了です。現時点で実行すると以下のようになります。

$ ruby unit.rb
Run options:

# Running tests:

Finished tests in 0.002284s, 0.0000 tests/s, 0.0000 assertions/s.
0 tests, 0 assertions, 0 failures, 0 errors, 0 skips

ruby -v: ruby 2.1.2p95 (2014-05-08 revision 45877) [x86_64-linux]


テストの作成

次に実際のテスト処理を書きます

require 'test/unit'

class SampleTest < Test::Unit::TestCase

  def test_sample1
    foo = '111'
    assert_equal('111', foo)
  end

end

1. test_*という名前のメソッドを作成します。

  • 上のほうでTest::Unit::TestCaseを継承したクラスは自動でテスト実行ファイルになると書きましたが、実際はその中のtest_*というメソッドが、自動で実行されるテストになります。

2. メソッド内でチェックメソッドを書きます。書式は以下です。

  assert_equal(予想される値, 変数やメソッド等)
  • assert_equalでは変数の値やメソッドの返り値が予想と一致することを確認します。

上記を実行すると・・・

$ ruby unit.rb
Run options:

# Running tests:

Finished tests in 0.004152s, 240.8293 tests/s, 240.8293 assertions/s.
1 tests, 1 assertions, 0 failures, 0 errors, 0 skips

ruby -v: ruby 2.1.2p95 (2014-05-08 revision 45877) [x86_64-linux]
  • 先ほどは0だったtestsとassertionsが1件増えました。

予想とは違う場合は以下のようになります。

メソッド
  def test_sample1
    foo = '111'
    assert_equal('111', foo)
  end
実行結果
]$ ruby unit.rb
Run options:

# Running tests:

[1/1] SampleTest#test_sample1 = 0.00 s
  1) Failure:
SampleTest#test_sample1 [unit.rb:17]:
<"222"> expected but was
<"111">.

Finished tests in 0.003316s, 301.5818 tests/s, 301.5818 assertions/s.
1 tests, 1 assertions, 1 failures, 0 errors, 0 skips
  • failuresが1件増えています。


テストの前処理・後処理

class SampleTest < Test::Unit::TestCase
  @@i = 0

  def setup
    @@i += 1
    puts "\r\nstart test #{@@i}========================================"
  end

  def teardown
    puts "\r\nfinish test #{@@i}========================================"
  end

  def test_sample1
    assert_equal('111', foo)
  end

  def test_sample2
    assert_equal('222', foo)
  end

end
実行結果
$ ruby unit.rb
Run options:

# Running tests:

[1/2] SampleTest#test_sample1
start test 1========================================

finish test 1========================================
[2/2] SampleTest#test_sample2
start test 2========================================

finish test 2========================================
 = 0.00 s
  1) Failure:
SampleTest#test_sample2 [unit.rb:23]:
<"222"> expected but was
<"111">.

Finished tests in 0.003935s, 508.2782 tests/s, 508.2782 assertions/s.
2 tests, 2 assertions, 1 failures, 0 errors, 0 skips

ruby -v: ruby 2.1.2p95 (2014-05-08 revision 45877) [x86_64-linux]
  • テスト毎に前処理・後処理が実行されているのがわかります。


Tips

動的ディスパッチ

Test::Unitはめいめい規則を使ってテストメソッドを判別しているが、TestCase自身が自身のpublicメソッドをチェックして名前がtestではじまるメソッドを選択している。

method_names = public_instance_methods(true)
tests = method_names.delte_if{|method_name| method_name !~ /^test./*}

この配列を__send__()を使って後で実行している。

他のチェックメソッド

正規表現
assert_match(/予想される正規表現/, チェックしたい値)
nilチェック
assert_nil(チェックしたい値)






今までは最初に使ったのがrspecなんでrspecを使うことが多かったですが、意外とデフォルトのUnit testも使いやすいかもしれない。

Rubyの特殊変数($@とか$`とか$数字とか・・・)

特殊変数

Rubyの組み込み変数の一部は、通常の変数としては使用できない特殊な名前を持っています。
例えば、 $' や $& あるいは $1, $2, $12345678901234567890 がそうです。
このように 「'$' + 特殊文字一文字」、または「'$' + 10進数字」という名前を持つ変数を特殊変数と呼びます。
また、 $-F や $-I のような変数もあります。 これらは Ruby の起動オプションと -F や -I などと対応しており、オプション変数と呼ばれます。

とのことです。

ちょこちょこ忘れて調べるので、自分用メモも兼ねて例と一緒にまとめておきたいと思います。

特殊変数一覧

正規表現関係

$', $&, &`
  • $' : マッチした文字列より前の文字列
  • $& : マッチした文字列
  • &` : マッチした文字列より後ろの文字列
str = 'abcdefghijk'

/efg/.match(str)

puts $`   # => abcd
puts $&   # => efg
puts $'   # => hijk
$~
  • マッチしたMatchDataオブジェクトを表示する
str = 'abcdefghijk'

/efg/.match(str)

puts $~.class  # => MatchData
puts $~        # => efg
puts $&.class  # => String
puts $&        # => efg
$+, $数字
  • $数字 : 正規表現中の 1 番目の括弧にマッチした文字列
  • $+ : 正規表現中の 最後の括弧にマッチした文字列
str = 'abcdefghijklmnopqrstuvwxyz'

/a(b)c(d)e(f)g(h)i(j)k(l)m(n)o(p)q(r)s(t)u(v)w(x)yz/.match(str)

puts $1   # b
puts $2   # d
puts $11  # v
puts $12  # x
puts $13  # nil
puts $+   # x

ファイル・ライブラリ関係

$"
  • requireしたライブラリの一覧を帰す
require_relative "lib_sample1"
require_relative "lib_sample2"

puts $"
  # => [
  #       "enumerator.so", 
  #       .
  #       .
  #       "/home/roma/lib_sample2.rb"
  #     ]
$:

requireが検索するディレクトリ一覧を返す

puts $:
  # => [
  #       "/usr/local/rvm/rubies/ruby-2.1.2/lib/ruby/site_ruby/2.1.0", 
  #       .
  #       .
  #       "/usr/local/rvm/rubies/ruby-2.1.2/lib/ruby/2.1.0/x86_64-linux"
  #     ]
$-I
  • ロードパスの一覧を返す
p $-I
  # => [
  #      "/usr/local/rvm/rubies/ruby-2.1.2/lib/ruby/site_ruby/2.1.0", 
  #      .
  #      .
  #      "/usr/local/rvm/rubies/ruby-2.1.2/lib/ruby/2.1.0/x86_64-linux"]
  #    ]
$*
### ruby test.rb foo barで実行した場合\
p $* # => ['foo', 'bar']
$0
  • ファイル名を表示する
### ruby test.rbの時
puts $0 # => test.rb

例外関係

$@
  • 例外のバックトレース
begin
  raise
rescue
  puts $@ # => test.rb:2:in `<main>'
end