禾川資訊 Grass Brook

Text

撰寫與發表 rubygem — 以 Rails 3/Thor 的 generator 為例

開發 Rails 應用程式時,常會使用 generate scaffold 的指令來建立基本的 MVC 架構,安裝 rubygem 時也會有 generate spec 這類的指令要執行,這些都是利用 generator 來達到設定上的自動化,Rails 3 改寫了這部份的實作方法,有必要重新熟悉一下。

基本上 Rails 3 的 generator 是繼承自 thor 的 Thor::Group 物件,並整合了 Rails 2.3 原本 generator 和 template 的方法成為 Rails::Generators::Actions。實作方面,我們只需要在 lib/generators 下建立一個 _generator.rb 檔,宣告一個繼承 Rails::Generators::Base(或 Rails::Generators::NamedBase、Thor::Group)的 Class,並定義要執行的 tasks (public instance method) 就完成了。

使用 bundler 管理 Rails 應用程式所需的 rubygem 非常好用,而用 jeweler 產生 rubygem 也十分簡單,以下便介紹如何撰寫一個 generator 來抓取 jquery-ujs 及其使用方法,並說明打包 rubygem 的流程。

首先安裝 jeweler 並建立一個新的 rubygem,命名為 generator_demo,採用 rspec 來做為 test framework,預計打包到 rubygems.org:

gem install jeweler
jeweler —rspec —gemcutter generator_demo
cd generator_demo
git log

jeweler 會用它的 generator 幫你建立 generator_demo 這個目錄及相關檔案,並自動使用 git 做版本控管。接著我們需要一個名為 jquery_ujs_generator.rb 的檔案。Rails 3 本身提供建立 generator template 的 generate 指令,使用 rails generate generator jquery_ujs 會自動在 Rails 3 應用程式的 lib 下建立 generator 所需的檔案。但這裡我們必須手動建立:

mkdir -p lib/generators/jquery_ujs
mkdir -p lib/generators/jquery_ujs/templates
touch lib/generators/jquery_ujs/jquery_ujs_generator.rb
touch lib/generators/jquery_ujs/USAGE

編輯 jquery_ujs_generator.rb 填入以下內容:

require 'rails/generators/base'

class JqueryUjsGenerator < Rails::Generators::Base
  def self.source_root
    @source_root ||= File.expand_path('../templates', __FILE__)
  end

  def fetch_rails_jquery_ujs_file
    url = 'http://github.com/rails/jquery-ujs/raw/master/src/rails.js'
    get url, 'public/javascripts/rails.js'
  end
end

這裡我們定義一個 JqeuryUjsGenerator 類別,每個 instance method 將會依序直接被 invoke,而 method 的命名建議使用直述式的(類似 rpsec 的 it 敘述的命名)。其中 fetch_rails_jquery_ujs_file 使用了 thor 的 get 方法取得遠端的檔案,並存放於 public/javascript 底下。而 source_root 是定義本地端範本檔案所在的位置,因為我們直接從網路抓取檔案,所以用不到。這樣最簡單的 generator 便完成,其他常用的 method 請查閱 thor 的 rdoc 說明。

一般建議要養成撰寫測試的習慣,先將 spec/generator_demo_spec.rb 的 fail 敘述刪掉,再建立 generator 對應的 spec 檔案:

mkdir -p spec/generators
touch spec/generators/jquery_ujs_generator_spec.rb

編輯 jquery_ujs_generator_spec.rb,填入以下內容:

require 'spec_helper'
require 'generators/jquery_ujs/jquery_ujs_generator'

describe JqueryUjsGenerator do
  before do
    @generator = JqueryUjsGenerator.new([], {}, {})
  end

  it "fetch rails jquery-ujs file" do
    url = 'http://github.com/rails/jquery-ujs/raw/master/src/rails.js'
    @generator.should_receive(:get).with(url, 'public/javascripts/rails.js').and_return(true)
    @generator.fetch_rails_jquery_ujs_file
  end
end

如果使用 rspec 1.x 的話,直接在 spec/spec_helper.rb 加入:

require 'rails/generators'

便可檢測 spec 是否通過:

spec spec

但這裡打算直接使用 rspec 2,請先安裝好:

gem install rspec -v=2.0.0.beta.3

需要修改 spec/spec_helper.rb 成為:

$LOAD_PATH.unshift(File.dirname(__FILE__))
$LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
require 'rubygems'
require 'rspec'
require 'rspec/autorun'

require 'rails/generators'
require 'generator_demo'

Rspec.configure do |config|
  config.mock_framework = :rspec
end

改用新的指令進行檢測:

rspec spec

最後準備打包。由於 jeweler 提供不少相關 rake task 可配合使用,包含自動產生 generator_demo.gemspec 的指令,這個 .gemspec 檔案是用來定義將程式打包成 rubygem 所需的 Gem::Specification,透過撰寫 jeweler task 的方式來簡化流程,因此只要將相關資訊填入 Rakefile 即可:

begin
  require 'jeweler'
  Jeweler::Tasks.new do |gem|
    gem.name = "generator_demo"
    gem.summary = %Q{A rails generator demo}
    gem.description = %Q{tell you creating generator is quit simple}
    gem.email = "tsechingho@gmail.com"
    gem.homepage = "http://github.com/tsechingho/generator_demo"
    gem.authors = ["Tse-Ching Ho"]
    gem.add_development_dependency "rspec", ">= 2.0.0.beta.3"
  end
  Jeweler::GemcutterTasks.new
rescue LoadError
  puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
end

begin
  require 'rspec/core/rake_task'
  Rspec::Core::RakeTask.new(:spec)
  Rspec::Core::RakeTask.new(:rcov) do |spec|
    spec.rcov = true
  end
  task :spec => :check_dependencies
rescue LoadError
  task :spec do
    abort "Rspec 2 is not available. In order to run specs, you must: [sudo] gem install Rspec -v=2.0.0.beta.3"
  end
end

第一段是建立 rubygem 的 jeweler task 設定,第二段則是替換 jeweler 原本的 spec task 改使用 rspec 2 的 spec task 寫法。接著便可在 local 端將這個 rubygem 裝起來:

rake -T
rake spec
rake version:write
rake gemspec
(rake build)
rake install
git add .
git commit -m ‘release 0.0.0; add jquery-ujs generator.’

先跑完 spec 後,建立 jeweler 需要的 VERSION 檔案來描述 rubygem 的版號,再產生 gemspec,安裝後可用 gem search generator_demo 找到 0.0.0 的版本,記得將更動納入版本控管。

在 Rails 3 應用程式的 Gemfile 中加入:

gem 'generator_demo'

便能直接使用這個 generator 了:

rails generate jquery_ujs

後續可將程式碼放到 github.com 上。先設定好 github 的 ssh token 並在 github 上建立好同名的 repository(這裡是 generator_demo),依照 github 的指示設定 git config(jeweler 已經預設好同名的 origin remote),之後便可 push 到 github 上去了:

git push origin master

再將產生的 rubygem 放到 rubygems.org 上供人使用。先註冊好 rubygems.org 的帳號,通過測試、增加 rubygem 的版號、納入版本控管並包裝後,便可 push 出去:

rake spec
rake version:bump:minor
rake gemspec
rake install
git add .
git commit -m ‘release 0.1.0’
git push origin master
gem push pkg/generator_demo-0.1.0.gem

以上便是一整套撰寫與發表 rubygem 的流程,以及 Rails 3/Thor generator 的簡易例子,希望說明夠簡潔易懂。

Posted on Wednesday, March 10 2010. Tagged with: generatorjewelerrails 3rspec 2thorrubygemsjquery-ujs
禾川資訊 Grass Brook We are a studio focused on Ruby, Rails and Agile Development.
Ask me anything Submit
Previous Next