更多内容请访问 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 应用程序中的任何位置可用。

在本例中,您将向 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

运行 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 向 Active Record 添加一个“acts_as”方法

插件中常见的一种模式是向模型添加一个名为 acts_as_something 的方法。在本例中,您想要编写一个名为 acts_as_yaffle 的方法,该方法会向您的 Active Record 模型添加一个 squawk 方法。

首先,设置您的文件,使其具有

# 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 模型,使其知道它们应该像 yaffles 一样工作。

# 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 添加一个实例方法

此插件将向任何调用 acts_as_yaffle 的 Active Record 对象添加一个名为“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

运行测试以确保最后两个测试失败,并出现包含“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 注释。按照惯例,还要向不包含在公共 API 中的代码部分添加 # :nodoc: 注释。

一旦您的注释准备就绪,请导航到您的插件目录并运行

$ bundle exec rake rdoc

7.1 参考文献



返回顶部