更多資訊請參考 rubyonrails.org:

建立 Rails 插件的基本概念

Rails 插件是核心框架的擴展或修改。插件提供

  • 一種讓開發人員分享前瞻性想法,而不會損害穩定程式碼庫的方法。
  • 一種分段式架構,讓程式碼單元可以按照自己的發布時程進行修正或更新。
  • 核心開發人員的一個出口,讓他們不必將所有酷炫的新功能都納入其中。

閱讀本指南後,您將了解

  • 如何從頭開始建立插件。
  • 如何為插件編寫和執行測試。

本指南說明如何建立一個測試驅動的插件,該插件將

  • 擴展核心 Ruby 類別,例如 Hash 和 String。
  • acts_as 插件的傳統方式,將方法新增至 ApplicationRecord
  • 為您提供有關將產生器放置在插件中的資訊。

為了本指南的目的,請暫時假裝您是一位熱衷的賞鳥人士。您最喜歡的鳥是 Yaffle,而且您想要建立一個插件,讓其他開發人員也能分享 Yaffle 的美好。

1 設定

目前,Rails 插件是以 gem 的形式建置,即gemified 插件。如果需要,它們可以使用 RubyGems 和 Bundler 在不同的 Rails 應用程式之間共享。

1.1 產生一個 Gemified 插件

Rails 隨附 rails plugin new 命令,該命令會建立一個骨架,用於開發任何類型的 Rails 擴展,並能夠使用虛擬 Rails 應用程式執行整合測試。使用下列命令建立您的插件

$ rails plugin new yaffle

透過請求說明來查看用法和選項

$ rails plugin new --help

2 測試您新產生的插件

導覽至包含該插件的目錄,並編輯 yaffle.gemspec 以取代任何具有 TODO 值的行

spec.homepage    = "http://example.com"
spec.summary     = "Summary of Yaffle."
spec.description = "Description of Yaffle."

...

spec.metadata["source_code_uri"] = "http://example.com"
spec.metadata["changelog_uri"] = "http://example.com"

然後執行 bundle install 命令。

現在您可以使用 bin/test 命令執行測試,您應該會看到

$ bin/test
...
1 runs, 1 assertions, 0 failures, 0 errors, 0 skips

這會告訴您所有項目都已正確產生,而且您已準備好開始新增功能。

3 擴展核心類別

本節將說明如何將方法新增至 String,該方法將在您的 Rails 應用程式中的任何地方可用。

在此範例中,您將新增一個名為 to_squawk 的方法至 String。首先,建立一個新的測試檔案,其中包含一些斷言

# 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

執行 bin/test 以執行測試。此測試應該會失敗,因為我們尚未實作 to_squawk 方法

$ bin/test
E

Error:
CoreExtTest#test_to_squawk_prepends_the_word_squawk:
NoMethodError: undefined method `to_squawk' for "Hello World":String


bin/test /path/to/yaffle/test/core_ext_test.rb:4

.

Finished in 0.003358s, 595.6483 runs/s, 297.8242 assertions/s.
2 runs, 1 assertions, 0 failures, 1 errors, 0 skips

太棒了 - 現在您已準備好開始開發。

lib/yaffle.rb 中,新增 require "yaffle/core_ext"

# yaffle/lib/yaffle.rb

require "yaffle/version"
require "yaffle/railtie"
require "yaffle/core_ext"

module Yaffle
  # Your code goes here...
end

最後,建立 core_ext.rb 檔案並新增 to_squawk 方法

# yaffle/lib/yaffle/core_ext.rb

class String
  def to_squawk
    "squawk! #{self}".strip
  end
end

為了測試您的方法是否如其所言,請從您的插件目錄中使用 bin/test 執行單元測試。

$ bin/test
...
2 runs, 2 assertions, 0 failures, 0 errors, 0 skips

若要查看實際效果,請變更為 test/dummy 目錄,啟動 bin/rails console,然後開始呱呱叫

irb> "Hello World".to_squawk
=> "squawk! Hello World"

4 將 "acts_as" 方法新增至 Active Record

插件中常見的模式是將名為 acts_as_something 的方法新增至模型。在此案例中,您想要撰寫一個名為 acts_as_yaffle 的方法,該方法會將 squawk 方法新增至您的 Active Record 模型。

首先,設定您的檔案,讓您具有

# yaffle/test/acts_as_yaffle_test.rb

require "test_helper"

class ActsAsYaffleTest < ActiveSupport::TestCase
end
# yaffle/lib/yaffle.rb

require "yaffle/version"
require "yaffle/railtie"
require "yaffle/core_ext"
require "yaffle/acts_as_yaffle"

module Yaffle
  # Your code goes here...
end
# yaffle/lib/yaffle/acts_as_yaffle.rb

module Yaffle
  module ActsAsYaffle
  end
end

4.1 新增類別方法

此插件會預期您已將名為 last_squawk 的方法新增至您的模型。但是,插件使用者可能已在其模型上定義一個名為 last_squawk 的方法,他們會將其用於其他用途。此插件將允許透過新增一個名為 yaffle_text_field 的類別方法來變更名稱。

首先,撰寫一個會顯示您想要行為的失敗測試

# 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

當您執行 bin/test 時,您應該會看到以下內容

$ bin/test
# Running:

..E

Error:
ActsAsYaffleTest#test_a_wickwalls_yaffle_text_field_should_be_last_tweet:
NameError: uninitialized constant ActsAsYaffleTest::Wickwall


bin/test /path/to/yaffle/test/acts_as_yaffle_test.rb:8

E

Error:
ActsAsYaffleTest#test_a_hickwalls_yaffle_text_field_should_be_last_squawk:
NameError: uninitialized constant ActsAsYaffleTest::Hickwall


bin/test /path/to/yaffle/test/acts_as_yaffle_test.rb:4



Finished in 0.004812s, 831.2949 runs/s, 415.6475 assertions/s.
4 runs, 2 assertions, 0 failures, 2 errors, 0 skips

這告訴我們,我們沒有我們嘗試測試的必要模型 (Hickwall 和 Wickwall)。我們可以透過從 test/dummy 目錄執行以下命令,輕鬆地在我們的「虛擬」Rails 應用程式中產生這些模型

$ 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/rails db:migrate

當您在這裡時,請變更 Hickwall 和 Wickwall 模型,讓它們知道它們應該像 yaffle 一樣運作。

# test/dummy/app/models/hickwall.rb

class Hickwall < ApplicationRecord
  acts_as_yaffle
end
# test/dummy/app/models/wickwall.rb

class Wickwall < ApplicationRecord
  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

    class_methods do
      def acts_as_yaffle(options = {})
      end
    end
  end
end
# test/dummy/app/models/application_record.rb

class ApplicationRecord < ActiveRecord::Base
  include Yaffle::ActsAsYaffle

  self.abstract_class = true
end

然後,您可以返回您插件的根目錄 (cd ../..),並使用 bin/test 重新執行測試。

$ bin/test
# Running:

.E

Error:
ActsAsYaffleTest#test_a_hickwalls_yaffle_text_field_should_be_last_squawk:
NoMethodError: undefined method `yaffle_text_field' for #<Class:0x0055974ebbe9d8>


bin/test /path/to/yaffle/test/acts_as_yaffle_test.rb:4

E

Error:
ActsAsYaffleTest#test_a_wickwalls_yaffle_text_field_should_be_last_tweet:
NoMethodError: undefined method `yaffle_text_field' for #<Class:0x0055974eb8cfc8>


bin/test /path/to/yaffle/test/acts_as_yaffle_test.rb:8

.

Finished in 0.008263s, 484.0999 runs/s, 242.0500 assertions/s.
4 runs, 2 assertions, 0 failures, 2 errors, 0 skips

越來越近了... 現在我們將實作 acts_as_yaffle 方法的程式碼,以讓測試通過。

# yaffle/lib/yaffle/acts_as_yaffle.rb

module Yaffle
  module ActsAsYaffle
    extend ActiveSupport::Concern

    class_methods do
      def acts_as_yaffle(options = {})
        cattr_accessor :yaffle_text_field, default: (options[:yaffle_text_field] || :last_squawk).to_s
      end
    end
  end
end
# test/dummy/app/models/application_record.rb

class ApplicationRecord < ActiveRecord::Base
  include Yaffle::ActsAsYaffle

  self.abstract_class = true
end

當您執行 bin/test 時,您應該會看到測試全部通過

$ bin/test
...
4 runs, 4 assertions, 0 failures, 0 errors, 0 skips

4.2 新增實例方法

此插件會將名為 'squawk' 的方法新增至任何呼叫 acts_as_yaffle 的 Active Record 物件。 '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

執行測試,以確保最後兩個測試因包含「NoMethodError: undefined method `squawk'」的錯誤而失敗,然後更新 acts_as_yaffle.rb,使其如下所示

# yaffle/lib/yaffle/acts_as_yaffle.rb

module Yaffle
  module ActsAsYaffle
    extend ActiveSupport::Concern

    included do
      def squawk(string)
        write_attribute(self.class.yaffle_text_field, string.to_squawk)
      end
    end

    class_methods do
      def acts_as_yaffle(options = {})
        cattr_accessor :yaffle_text_field, default: (options[:yaffle_text_field] || :last_squawk).to_s
      end
    end
  end
end
# test/dummy/app/models/application_record.rb

class ApplicationRecord < ActiveRecord::Base
  include Yaffle::ActsAsYaffle

  self.abstract_class = true
end

最後執行一次 bin/test,您應該會看到

$ bin/test
...
6 runs, 6 assertions, 0 failures, 0 errors, 0 skips

使用 write_attribute 寫入模型中的欄位只是插件如何與模型互動的一個範例,而且並非總是適合使用的方法。例如,您也可以使用

send("#{self.class.yaffle_text_field}=", string.to_squawk)

5 產生器

只要在插件的 lib/generators 目錄中建立產生器,就可以將產生器包含在您的 gem 中。有關產生器建立的詳細資訊,可以在產生器指南中找到。

6 發布您的 Gem

目前正在開發中的 Gem 插件可以輕鬆地從任何 Git 儲存庫共享。若要與他人共享 Yaffle gem,只需將程式碼提交至 Git 儲存庫 (例如 GitHub),並將一行程式碼新增至相關應用程式的 Gemfile

gem "yaffle", git: "https://github.com/rails/yaffle.git"

執行 bundle install 之後,您的 gem 功能將可供應用程式使用。

當 gem 準備好以正式版本共享時,可以將其發布至 RubyGems

或者,您也可以從 Bundler 的 Rake 工作中獲益。您可以使用以下命令查看完整清單

$ bundle exec rake -T

$ bundle exec rake build
# Build yaffle-0.1.0.gem into the pkg directory

$ bundle exec rake install
# Build and install yaffle-0.1.0.gem into system gems

$ bundle exec rake release
# Create tag v0.1.0 and build and push yaffle-0.1.0.gem to Rubygems

若要瞭解更多關於發佈 gem 至 RubyGems 的資訊,請參閱:發佈你的 gem

7 RDoc 文件

一旦你的外掛程式穩定,並且準備好部署,請幫大家一個忙,將其文件化!幸運的是,撰寫外掛程式的文件很容易。

第一步是更新 README 檔案,其中包含有關如何使用你的外掛程式的詳細資訊。以下是一些需要包含的重點:

  • 你的姓名
  • 如何安裝
  • 如何將功能新增至應用程式(幾個常見用例的範例)
  • 可能可以幫助使用者並節省時間的警告、注意事項或提示

一旦你的 README 內容完善,請瀏覽並將 RDoc 註解新增至開發人員將使用的所有方法。通常也會將 # :nodoc: 註解新增至未包含在公開 API 中的程式碼部分。

一旦你的註解準備好,請導覽至你的外掛程式目錄並執行

$ bundle exec rake rdoc

7.1 參考資料



返回頂端