从零创建一个 gem


<p><a href="http://guides.rubygems.org/make-your-own-gem/">从头到尾学习如果将自己的代码打包成gem</a></p>

<ul>
<li>Introduction</li>
<li>Your first gem</li>
<li>Requiring more files</li>
<li>Adding an executable</li>
<li>Writing tests</li>
<li>Documenting your code</li>
<li>Wrapup</li>
</ul>

<!-- more -->

<h2 id="介绍">介绍</h2>

<p>通过rubygem内置的工具就能够轻松的打包代码为gem.让我们来创建一个简单的<code>hello world</code>的gem.</p>

<h2 id="第一个gem">第一个gem</h2>

<p>创建一个名为hola的gem.还有一个gemspec,目录结构如下:</p>

<pre><code>    -hola.gemspec
    `--lib
        `--hola.rb
</code></pre>

<p>其实关键的你代码是放在lib文件夹里面的,有一个约定就是要有一个和你的gem名称一样的ruby文件. <br>
这样的话,就可以直接通过<code>require 'hola'</code>来引用此gem了.此文件就是gem的入口. <br>
看看在<code>lib/hola.rb</code>的内容:</p>

<pre><code>class Hola
  def self.hi
    puts "Hello world!"
  end
end
</code></pre>

<p>下面来看一些gemspec文件里定义了什么内容,其他不外乎就是谁写的,版本,依赖等.</p>

<pre><code>Gem::Specification.new do |s|
  s.name        = 'hola'
  s.version     = '0.0.0'
  s.date        = '2010-04-28'
  s.summary     = "Hola!"
  s.description = "A simple hello world gem"
  s.authors     = ["Nick Quaranto"]
  s.email       = 'nick@quaran.to'
  s.files       = ["lib/hola.rb"]
  s.homepage    =
    'http://rubygems.org/gems/hola'
  s.license       = 'MIT'
end
</code></pre>

<p>看着眼熟?其实就是ruby代码了. <br>
关于到底有什么能够放在里面,可以<a href="http://guides.rubygems.org/specification-reference">参照这里</a>. <br>
创建gemspec之后,就可以build一个gem 了.之后就可以安装build之后的gem了. <br>
通过此命令</p>

<blockquote>
  <p>gem build hola.gemspec</p>
</blockquote>

<p>你可以看见如下的控制台输出:</p>

<pre><code>Successfully built RubyGem
Name: hola
Version: 0.0.0
File: hola-0.0.0.gem

% gem install ./hola-0.0.0.gem
Successfully installed hola-0.0.0
1 gem installed
</code></pre>

<p>当然要确保能够使用,还需实际的使用它啊,通过<code>reuqire</code></p>

<blockquote>
  <p>irb <br>
  require ‘hola’ <br>
  true <br>
  Hola.hi</p>
</blockquote>

<p>可以看见输出<em>hello world</em>了.那就表示成功了一大步.之后就可以准备发布到rubygem了. <br>
首先要配置登录到rubygem的信息:</p>

<pre><code>&gt; curl -u yourusername_on_rubygem
https://rubygems.org/api/v1/api_key.yaml &gt;
~/.gem/credentials; chmod 0600 ~/.gem/credentials

Enter host password for user 'yourusername_on_rubygem':

如果系统限制等,无法使用curl,openssl等,可以通过浏览器访问链接`https://rubygems.org/api/v1/api_key.yaml`,它会让你登录的,
成功则会下载一个认证信息文件`api_key.yaml`,完了直接把它放到~/.gem/文件夹下,重命名为**credentials**
</code></pre>

<p>一切设置妥当之后,就可以push你的gem了</p>

<blockquote>
  <p>gem push hola-0.0.0.gem</p>
</blockquote>

<pre><code>    Pushing gem to RubyGems.org...
    Successfully registered gem: hola (0.0.0)
</code></pre>

<p>上传过一会儿你就可以通过下面命令check到你的gem了:</p>

<blockquote>
  <p>gem list -r hola</p>
</blockquote>

<p>然后尝试安装:</p>

<p>gem install hola</p>

<p>一切OK~</p>

<h2 id="我们来个复杂点的">我们来个复杂点的</h2>

<p>修改<code>lib/hola.rb</code></p>

<pre><code>class Hola
  def self.hi(language = "english")
    translator = Translator.new(language)
    translator.hi
  end
end

class Hola::Translator
  def initialize(language)
    @language = language
  end

  def hi
    case @language
    when "spanish"
      "hola mundo"
    else
      "hello world"
    end
  end
end
</code></pre>

<p>上面的代码已经很拥挤了.我们把<code>Translator</code>这个类拆分到单独的文件中,之前提到的是把所有的ruby文件都放到lib <br>
目录. 我们可以把目录结构调整成下面的样子:</p>

<pre><code>    ├── hola.gemspec
    └── lib
        ├── hola
        │   └── translator.rb
        └── hola.rb
</code></pre>

<p>之后在原来lib/hola.rb的文件需要修改一下:</p>

<pre><code>    require 'lib/translator'
    class Hola
        def self.hi(language = "english")
            translator = Translator.new(language)
            translator.hi
      end
    end
</code></pre>

<p>接着还要修改一下gemspec文件,把新添加的目录和文件索引进去,不然到时候打包的时候新文件不会被打包进去:</p>

<pre><code>        ...
        s.files       = ["lib/hola.rb", "lib/hola/translator.rb"]
        ...
</code></pre>

<h2 id="添加一个可执行的文件">添加一个可执行的文件</h2>

<p>gem不仅仅只是提供一些ruby code. 同时还暴漏了一些可执行的文件到你当前的shell目录下.如<code>rake</code>. 还有一个比较有趣的工具就是 <br>
<code>prettify_json.rb</code>,包含在json的gem包,它能够用来format输出json.</p>

<pre><code>    curl -s http://jsonip.com/ | \
      prettify_json.rb
    {
      "ip": "24.60.248.134"
    }
</code></pre>

<p>其实往gem里添加可执行的文件也很简单.把他们放到<code>bin/</code>目录.然后在gemspec里添加他们.让我们继续在hola的gem里修改:</p>

<pre><code>    % mkdir bin
    % touch bin/hola
    % chmod a+x bin/hola
</code></pre>

<p>这个<code>bin/hola</code>文件第一行要添加<code>#!</code>来告诉机器用什么目录执行它.</p>

<pre><code>#!/usr/bin/env ruby

require 'hola'
puts Hola.hi(ARGV[0])    
</code></pre>

<p>之后在gemspec文件里添加可执行的文件配置说明:</p>

<pre><code>  s.name        = 'hola'
  s.version     = '0.0.1'
  s.executables &lt;&lt; 'hola'  #可执行文件
</code></pre>

<h2 id="编写测试">编写测试</h2>

<p>测试自己的gem非常重要,不仅仅是确保自己的代码能够工作,而且能够帮助别人.很多人通过测试用例的质量来衡量 <br>
代码质量.gem支持如果在gem里有测试的话在下载完之后自动执行测试的功能.</p>

<p><strong>TEST YOUR GEM!</strong></p>

<p><code>TEST::Unit</code>是ruby内置的测试框架,有很多的教程在网上可以找到.同样还有很多其他的测试框架如<code>RSpec</code>. <br>
 不管怎么样,我们来添加测试吧. <br>
 首先创建文件<code>Rakefile</code>和文件夹<code>test</code>:</p>

<pre><code>    .
    ├── Rakefile
    ├── bin
    │   └── hola
    ├── hola.gemspec
    ├── lib
    │   ├── hola
    │   │   └── translator.rb
    │   └── hola.rb
    └── test
        └── test_hola.rb
</code></pre>

<p>编辑<code>test_hola.rb</code>:</p>

<pre><code>require 'test/unit'
require 'hola'

class HolaTest &lt; Test::Unit::TestCase
  def test_english_hello
    assert_equal "hello world",
      Hola.hi("english")
  end

  def test_any_hello
    assert_equal "hello world",
      Hola.hi("ruby")
  end

  def test_spanish_hello
    assert_equal "hola mundo",
      Hola.hi("spanish")
  end
end
</code></pre>

<p>在<code>Rakefile</code>里添加一些简单的测试test task:</p>

<pre><code>require 'rake/testtask'

Rake::TestTask.new do |t|
  t.libs &lt;&lt; 'test'
end

desc "Run tests"
task :default =&gt; :test
</code></pre>

<p>之后执行<code>rake test</code>或者直接<code>rake</code>来执行测试:</p>

<pre><code>    % rake test
    (in /Users/qrush/Dev/ruby/hola)
    Loaded suite
    Started
    ...
    Finished in 0.000736 seconds.

    3 tests, 3 assertions, 0 failures, 0 errors, 0 skips

    Test run options: --seed 15331
</code></pre>

<p><strong>you made it!</strong></p>

<h2 id="文档">文档</h2>

<p>大部分的gem会使用默认内建RDoc来生成文档.有很多的<a href="http://docs.seattlerb.org/rdoc/RDoc/Markup.html">教程</a>可以学习.</p>

<pre><code># The main Hola driver
class Hola
  # Say hi to the world!
  #
  # Example:
  #   &gt;&gt; Hola.hi("spanish")
  #   =&gt; hola mundo
  #
  # Arguments:
  #   language: (String)

  def self.hi(language = "english")
    translator = Translator.new(language)
    puts translator.hi
  end
end
</code></pre>

<h2 id="结尾">结尾</h2>

<p>至此已经完成了一个简单的gem,很多东西需要继续学习深究,ENJOY IT!</p>


参考:
1. https://blog.engineyard.com/2015/its-raining-gems
2. http://bundler.io/v1.7/rubygems.html