# Rails 插件入門
一個Rails插件既可以是核心框架庫某個功能擴展,也可以是對核心框架庫的修改。插件提供了如下功能:
* 為開發者分享新特性又保證不影響穩定版本的功能提供了支持;
* 松散的代碼組織架構為修復、更新局部模塊提供了支持;
* 為核心成員開發局部模塊功能特性提供了支持;
讀完本章節,您將學到:
* 如何構造一個簡單的插件;
* 如何為插件編寫和運行測試用例;
本指南將介紹如何通過測試驅動的方式開發插件:
* 擴展核心類庫功能,比如`Hash`和`String`;
* 給`ActiveRecord::Base`添加`acts_as`插件功能;
* 提供創建自定義插件必需的信息;
假定你是一名狂熱的鳥類觀察愛好者,你最喜歡的鳥是Yaffle,你希望創建一個插件和開發者們分享有關Yaffle的信息。
### Chapters
1. [準備工作](#%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C)
* [生成一個gem化的插件](#%E7%94%9F%E6%88%90%E4%B8%80%E4%B8%AAgem%E5%8C%96%E7%9A%84%E6%8F%92%E4%BB%B6)
2. [讓新生成的插件支持測試](#%E8%AE%A9%E6%96%B0%E7%94%9F%E6%88%90%E7%9A%84%E6%8F%92%E4%BB%B6%E6%94%AF%E6%8C%81%E6%B5%8B%E8%AF%95)
3. [擴展核心類庫](#%E6%89%A9%E5%B1%95%E6%A0%B8%E5%BF%83%E7%B1%BB%E5%BA%93)
4. [為Active Record添加"acts_as"方法](#%E4%B8%BAactive-record%E6%B7%BB%E5%8A%A0%22acts_as%22%E6%96%B9%E6%B3%95)
* [添加一個類方法](#%E6%B7%BB%E5%8A%A0%E4%B8%80%E4%B8%AA%E7%B1%BB%E6%96%B9%E6%B3%95)
* [添加一個實例方法](#%E6%B7%BB%E5%8A%A0%E4%B8%80%E4%B8%AA%E5%AE%9E%E4%BE%8B%E6%96%B9%E6%B3%95)
5. [生成器](#%E7%94%9F%E6%88%90%E5%99%A8)
6. [發布Gem](#%E5%8F%91%E5%B8%83gem)
7. [RDoc 文檔](#rdoc-%E6%96%87%E6%A1%A3)
* [參考文獻](#%E5%8F%82%E8%80%83%E6%96%87%E7%8C%AE)
### 1 準備工作
目前,Rails插件是被當作gem來使用的(gem化的插件)。不同Rails應用可以通過RubyGems和Bundler命令來使用他們。
#### 1.1 生成一個gem化的插件
Rails使用`rails plugin new`命令為開發者創建各種Rails擴展,以確保它能使用一個簡單Rails應用進行測試。創建插件的命令如下:
```
$ bin/rails plugin new yaffle
```
如下命令可以獲取創建插件命令的使用方式:
```
$ bin/rails plugin --help
```
### 2 讓新生成的插件支持測試
打開新生成插件所在的文件目錄,然后在命令行模式下運行`bundle install`命令,使用`rake`命令生成測試環境。
你將看到如下代碼:
```
2 tests, 2 assertions, 0 failures, 0 errors, 0 skips
```
上述內容告訴你一切就緒,可以開始為插件添加新特性了。
### 3 擴展核心類庫
本章節將介紹如何為`String`添加一個方法,并讓它在你的Rails應用中生效。
下面我們將為`String`添加一個名為`to_squawk`的方法。開始前,我們可以先創建一些簡單的測試函數:
```
# yaffle/test/core_ext_test.rb
require 'test_helper'
class CoreExtTest < ActiveSupport::TestCase
def test_to_squawk_prepends_the_word_squawk
assert_equal "squawk! Hello World", "Hello World".to_squawk
end
end
```
運行`rake`命令運行測試,測試將返回錯誤信息,因為我們還沒有完成`to_squawk`方法的功能實現:
```
1) Error:
test_to_squawk_prepends_the_word_squawk(CoreExtTest):
NoMethodError: undefined method `to_squawk' for [Hello World](String)
test/core_ext_test.rb:5:in `test_to_squawk_prepends_the_word_squawk'
```
好吧,現在開始進入正題:
在`lib/yaffle.rb`文件中, 添加 `require 'yaffle/core_ext'`:
```
# yaffle/lib/yaffle.rb
require 'yaffle/core_ext'
module Yaffle
end
```
最后,新建一個`core_ext.rb`文件,并添加`to_squawk`方法:
```
# yaffle/lib/yaffle/core_ext.rb
String.class_eval do
def to_squawk
"squawk! #{self}".strip
end
end
```
為了測試你的程序是否符合預期,可以在插件目錄下運行`rake`命令,來測試一下。
```
3 tests, 3 assertions, 0 failures, 0 errors, 0 skips
```
看到上述內容后,用命令行導航到test/dummy目錄,使用Rails控制臺來做個測試:
```
$ bin/rails console
>> "Hello World".to_squawk
=> "squawk! Hello World"
```
### 4 為Active Record添加"acts_as"方法
一般來說,在插件中為某模塊添加方法的命名方式是`acts_as_something`,本例中我們將為Active Record添加一個名為`acts_as_yaffle`的方法實現`squawk` 功能。
首先,新建一些文件:
```
# yaffle/test/acts_as_yaffle_test.rb
require 'test_helper'
class ActsAsYaffleTest < ActiveSupport::TestCase
end
```
```
# yaffle/lib/yaffle.rb
require 'yaffle/core_ext'
require 'yaffle/acts_as_yaffle'
module Yaffle
end
```
```
# yaffle/lib/yaffle/acts_as_yaffle.rb
module Yaffle
module ActsAsYaffle
# your code will go here
end
end
```
#### 4.1 添加一個類方法
假如插件的模塊中有一個名為 `last_squawk` 的方法,與此同時,插件的使用者在其他模塊也定義了一個名為 `last_squawk` 的方法,那么插件允許你添加一個類方法 `yaffle_text_field` 來改變插件內的 `last_squawk` 方法的名稱。
開始之前,先寫一些測試用例來保證程序擁有符合預期的行為。
```
# yaffle/test/acts_as_yaffle_test.rb
require 'test_helper'
class ActsAsYaffleTest < ActiveSupport::TestCase
def test_a_hickwalls_yaffle_text_field_should_be_last_squawk
assert_equal "last_squawk", Hickwall.yaffle_text_field
end
def test_a_wickwalls_yaffle_text_field_should_be_last_tweet
assert_equal "last_tweet", Wickwall.yaffle_text_field
end
end
```
運行`rake`命令,你將看到如下結果:
```
1) Error:
test_a_hickwalls_yaffle_text_field_should_be_last_squawk(ActsAsYaffleTest):
NameError: uninitialized constant ActsAsYaffleTest::Hickwall
test/acts_as_yaffle_test.rb:6:in `test_a_hickwalls_yaffle_text_field_should_be_last_squawk'
2) Error:
test_a_wickwalls_yaffle_text_field_should_be_last_tweet(ActsAsYaffleTest):
NameError: uninitialized constant ActsAsYaffleTest::Wickwall
test/acts_as_yaffle_test.rb:10:in `test_a_wickwalls_yaffle_text_field_should_be_last_tweet'
5 tests, 3 assertions, 0 failures, 2 errors, 0 skips
```
上述內容告訴我們,我們沒有提供必要的模塊(Hickwall 和 Wickwall)進行測試。我們可以在test/dummy目錄下使用命令生成必要的模塊:
```
$ cd test/dummy
$ bin/rails generate model Hickwall last_squawk:string
$ bin/rails generate model Wickwall last_squawk:string last_tweet:string
```
接下來為簡單應用創建測試數據庫并做數據遷移:
```
$ cd test/dummy
$ bin/rake db:migrate
```
至此,修改Hickwall和Wickwall模塊,把他們和yaffles關聯起來:
```
# test/dummy/app/models/hickwall.rb
class Hickwall < ActiveRecord::Base
acts_as_yaffle
end
# test/dummy/app/models/wickwall.rb
class Wickwall < ActiveRecord::Base
acts_as_yaffle yaffle_text_field: :last_tweet
end
```
同時定義`acts_as_yaffle`方法:
```
# yaffle/lib/yaffle/acts_as_yaffle.rb
module Yaffle
module ActsAsYaffle
extend ActiveSupport::Concern
included do
end
module ClassMethods
def acts_as_yaffle(options = {})
# your code will go here
end
end
end
end
ActiveRecord::Base.send :include, Yaffle::ActsAsYaffle
```
在插件的根目錄下運行`rake`命令:
```
1) Error:
test_a_hickwalls_yaffle_text_field_should_be_last_squawk(ActsAsYaffleTest):
NoMethodError: undefined method `yaffle_text_field' for #<Class:0x000001016661b8>
/Users/xxx/.rvm/gems/ruby-1.9.2-p136@xxx/gems/activerecord-3.0.3/lib/active_record/base.rb:1008:in `method_missing'
test/acts_as_yaffle_test.rb:5:in `test_a_hickwalls_yaffle_text_field_should_be_last_squawk'
2) Error:
test_a_wickwalls_yaffle_text_field_should_be_last_tweet(ActsAsYaffleTest):
NoMethodError: undefined method `yaffle_text_field' for #<Class:0x00000101653748>
Users/xxx/.rvm/gems/ruby-1.9.2-p136@xxx/gems/activerecord-3.0.3/lib/active_record/base.rb:1008:in `method_missing'
test/acts_as_yaffle_test.rb:9:in `test_a_wickwalls_yaffle_text_field_should_be_last_tweet'
5 tests, 3 assertions, 0 failures, 2 errors, 0 skips
```
現在離目標已經很近了,我們來完成`acts_as_yaffle`方法,以便通過測試。
```
# yaffle/lib/yaffle/acts_as_yaffle.rb
module Yaffle
module ActsAsYaffle
extend ActiveSupport::Concern
included do
end
module ClassMethods
def acts_as_yaffle(options = {})
cattr_accessor :yaffle_text_field
self.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s
end
end
end
end
ActiveRecord::Base.send :include, Yaffle::ActsAsYaffle
```
運行`rake`命令后,你將看到所有測試都通過了:
```
5 tests, 5 assertions, 0 failures, 0 errors, 0 skips
```
#### 4.2 添加一個實例方法
本插件將為所有Active Record對象添加一個名為`squawk`的方法,Active Record 對象通過調用`acts_as_yaffle`方法來間接調用插件的`squawk`方法。 `squawk`方法將作為一個可賦值的字段與數據庫關聯起來。
開始之前,可以先寫一些測試用例來保證程序擁有符合預期的行為:
```
# yaffle/test/acts_as_yaffle_test.rb
require 'test_helper'
class ActsAsYaffleTest < ActiveSupport::TestCase
def test_a_hickwalls_yaffle_text_field_should_be_last_squawk
assert_equal "last_squawk", Hickwall.yaffle_text_field
end
def test_a_wickwalls_yaffle_text_field_should_be_last_tweet
assert_equal "last_tweet", Wickwall.yaffle_text_field
end
def test_hickwalls_squawk_should_populate_last_squawk
hickwall = Hickwall.new
hickwall.squawk("Hello World")
assert_equal "squawk! Hello World", hickwall.last_squawk
end
def test_wickwalls_squawk_should_populate_last_tweet
wickwall = Wickwall.new
wickwall.squawk("Hello World")
assert_equal "squawk! Hello World", wickwall.last_tweet
end
end
```
運行測試后,確保測試結果中包含2個"NoMethodError: undefined method `squawk'"的測試錯誤,那么我們可以修改'acts_as_yaffle.rb'中的代碼:
```
# yaffle/lib/yaffle/acts_as_yaffle.rb
module Yaffle
module ActsAsYaffle
extend ActiveSupport::Concern
included do
end
module ClassMethods
def acts_as_yaffle(options = {})
cattr_accessor :yaffle_text_field
self.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s
include Yaffle::ActsAsYaffle::LocalInstanceMethods
end
end
module LocalInstanceMethods
def squawk(string)
write_attribute(self.class.yaffle_text_field, string.to_squawk)
end
end
end
end
ActiveRecord::Base.send :include, Yaffle::ActsAsYaffle
```
運行`rake`命令后,你將看到如下結果: `7 tests, 7 assertions, 0 failures, 0 errors, 0 skips`
提示: 使用`write_attribute`方法寫入字段只是舉例說明插件如何與模型交互,并非推薦的使用方法,你也可以用如下方法實現: `ruby send("#{self.class.yaffle_text_field}=", string.to_squawk)`
### 5 生成器
插件可以方便的引用和創建生成器。關于創建生成器的更多信息,可以參考[Generators Guide](generators.html)
### 6 發布Gem
Gem插件可以通過Git代碼托管庫方便的在開發者之間分享。如果你希望分享Yaffle插件,那么可以將Yaffle放在Git代碼托管庫上。如果你想在Rails應用中使用Yaffle插件,那么可以在Rails應用的Gem文件中添加如下代碼:
```
gem 'yaffle', git: 'git://github.com/yaffle_watcher/yaffle.git'
```
運行`bundle install`命令后,Yaffle插件就可以在你的Rails應用中使用了。
當gem作為一個正式版本分享時,它就可以被發布到[RubyGems](http://www.rubygems.org)上了。想要了解更多關于發布gem到RubyGems信息,可以參考[Creating and Publishing Your First Ruby Gem](http://blog.thepete.net/2010/11/creating-and-publishing-your-first-ruby.html)。
### 7 RDoc 文檔
插件功能穩定并準備發布時,為用戶提供一個使用說明文檔是必要的。很幸運,為你的插件寫一個文檔很容易。
首先更新說明文件以及如何使用你的插件等詳細信息。文檔主要包括以下幾點:
* 你的名字
* 安裝指南
* 如何安裝gem到應用中(一些使用例子)
* 警告,使用插件時需要注意的地方,這將為用戶提供方便。
當你的README文件寫好以后,為用戶提供所有與插件方法相關的rdoc注釋。通常我們使用'#:nodoc:'注釋不包含在公共API中的代碼。
當你的注釋編寫好以后,可以到你的插件目錄下運行如下命令:
```
$ bin/rake rdoc
```
#### 7.1 參考文獻
* [Developing a RubyGem using Bundler](https://github.com/radar/guides/blob/master/gem-development.md)
* [Using .gemspecs as Intended](http://yehudakatz.com/2010/04/02/using-gemspecs-as-intended/)
* [Gemspec Reference](http://docs.rubygems.org/read/chapter/20)
* [GemPlugins: A Brief Introduction to the Future of Rails Plugins](http://www.intridea.com/blog/2008/6/11/gemplugins-a-brief-introduction-to-the-future-of-rails-plugins)
### 反饋
歡迎幫忙改善指南質量。
如發現任何錯誤,歡迎修正。開始貢獻前,可先行閱讀[貢獻指南:文檔](http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#contributing-to-the-rails-documentation)。
翻譯如有錯誤,深感抱歉,歡迎 [Fork](https://github.com/ruby-china/guides/fork) 修正,或至此處[回報](https://github.com/ruby-china/guides/issues/new)。
文章可能有未完成或過時的內容。請先檢查 [Edge Guides](http://edgeguides.rubyonrails.org) 來確定問題在 master 是否已經修掉了。再上 master 補上缺少的文件。內容參考 [Ruby on Rails 指南準則](ruby_on_rails_guides_guidelines.html)來了解行文風格。
最后,任何關于 Ruby on Rails 文檔的討論,歡迎到 [rubyonrails-docs 郵件群組](http://groups.google.com/group/rubyonrails-docs)。
- Ruby on Rails 指南 (651bba1)
- 入門
- Rails 入門
- 模型
- Active Record 基礎
- Active Record 數據庫遷移
- Active Record 數據驗證
- Active Record 回調
- Active Record 關聯
- Active Record 查詢
- 視圖
- Action View 基礎
- Rails 布局和視圖渲染
- 表單幫助方法
- 控制器
- Action Controller 簡介
- Rails 路由全解
- 深入
- Active Support 核心擴展
- Rails 國際化 API
- Action Mailer 基礎
- Active Job 基礎
- Rails 程序測試指南
- Rails 安全指南
- 調試 Rails 程序
- 設置 Rails 程序
- Rails 命令行
- Rails 緩存簡介
- Asset Pipeline
- 在 Rails 中使用 JavaScript
- 引擎入門
- Rails 應用的初始化過程
- Autoloading and Reloading Constants
- 擴展 Rails
- Rails 插件入門
- Rails on Rack
- 個性化Rails生成器與模板
- Rails應用模版
- 貢獻 Ruby on Rails
- Contributing to Ruby on Rails
- API Documentation Guidelines
- Ruby on Rails Guides Guidelines
- Ruby on Rails 維護方針
- 發布記
- A Guide for Upgrading Ruby on Rails
- Ruby on Rails 4.2 發布記
- Ruby on Rails 4.1 發布記
- Ruby on Rails 4.0 Release Notes
- Ruby on Rails 3.2 Release Notes
- Ruby on Rails 3.1 Release Notes
- Ruby on Rails 3.0 Release Notes
- Ruby on Rails 2.3 Release Notes
- Ruby on Rails 2.2 Release Notes