1. RDoc
Rails API 文档是使用 RDoc 生成的。要生成它,请确保您位于 rails 根目录中,运行 bundle install 并执行
$ bundle exec rake rdoc
生成的 HTML 文件可以在 ./doc/rdoc 目录中找到。
请查阅 RDoc 标记参考 以获取语法帮助。
2. 链接
Rails API 文档不应在 GitHub 上查看,因此链接应使用相对于当前 API 的 RDoc 链接标记。
这是由于 GitHub Markdown 和发布在 api.rubyonrails.org 和 edgeapi.rubyonrails.org 上的 RDoc 生成之间存在差异。
例如,我们使用 [link:classes/ActiveRecord/Base.html] 来创建指向 RDoc 生成的 ActiveRecord::Base 类的链接。
这比绝对 URL(例如 [https://api.rubyonrails.cn/classes/ActiveRecord/Base.html])更可取,因为绝对 URL 会将读者带离其当前的文档版本(例如 edgeapi.rubyonrails.org)。
3. 措辞
编写简单、陈述性的句子。简洁是优点。直奔主题。
# BAD
# Caching may interfere with being able to see the results
# of code changes.
# GOOD
# Caching interferes with seeing the results of code changes.
使用现在时
# BAD
# Returned a hash that...
# Will return a hash that...
# Return a hash that...
# GOOD
# Returns a hash that...
注释以大写字母开头。遵循常规标点符号规则
# BAD
# declares an attribute reader backed by an internally-named
# instance variable
# GOOD
# Declares an attribute reader backed by an internally-named
# instance variable.
以显式和隐式的方式向读者传达当前的做事方式。使用 edge 中推荐的惯用法。如果需要,重新排序章节以强调首选方法等。文档应作为最佳实践和规范、现代 Rails 用法的典范。
# BAD
# Book.where('name = ?', "Where the Wild Things Are")
# Book.where('year_published < ?', 50.years.ago)
# GOOD
# Book.where(name: "Where the Wild Things Are")
# Book.where(year_published: ...50.years.ago)
文档必须简洁但全面。探索并记录边缘情况。如果模块是匿名的会发生什么?如果集合为空会发生什么?如果参数为 nil 会发生什么?
3.1. 命名
Rails 组件的正确名称在单词之间有一个空格,例如“Active Support”。ActiveRecord 是一个 Ruby 模块,而 Active Record 是一个 ORM。所有 Rails 文档都应始终使用其正确名称来指代 Rails 组件。
# GOOD
# Active Record classes can be created by inheriting from
# ActiveRecord::Base.
当引用“Rails 应用程序”时,与“引擎”或“插件”相对,始终使用“应用程序”。Rails 应用程序不是“服务”,除非专门讨论面向服务的架构。
# BAD
# Production services can report their status upstream.
# Devise is a Rails authentication application.
# GOOD
# Production applications can report their status upstream.
# Devise is a Rails authentication engine.
正确拼写软件名称。如有疑问,请查阅其官方文档等权威来源。
# GOOD
# Arel
# ERB
# Hotwire
# HTML
# JavaScript
# minitest
# MySQL
# npm
# PostgreSQL
# RSpec
对“SQL”使用冠词“an”
# BAD
# Creates a SQL statement.
# Starts a SQLite database.
# GOOD
# Creates an SQL statement.
# Starts an SQLite database.
3.2. 代词
避免使用“you”和“your”的措辞。
# BAD
# If you need to use +return+ statements in your callbacks, it is
# recommended that you explicitly define them as methods.
# GOOD
# If +return+ is needed, it is recommended to explicitly define a
# method.
话虽如此,当使用代词指代一个假设的人,例如“a user with a session cookie”时,应使用中性代词(they/their/them)。而不是
- 他或她...使用他们。
- 他或她...使用他们。
- 他或她...使用他们的。
- 他或她...使用他们的。
- 他自己或她自己...使用他们自己。
3.3. 美式英语
请使用美式英语(color, center, modularize 等)。请参阅此处列出的美式和英式英语拼写差异。
3.4. 牛津逗号
请使用牛津逗号("red, white, and blue",而不是 "red, white and blue")。
4. 示例代码
选择有意义的示例,描绘并涵盖基础知识以及有趣的要点或陷阱。
为了正确渲染,代码从左边距缩进两个空格。示例本身应使用 Rails 编码约定。
简短的文档不需要明确的“示例”标签来引入代码片段;它们只是跟在段落后面
# Converts a collection of elements into a formatted string by
# calling +to_s+ on all elements and joining them.
#
# Blog.all.to_fs # => "First PostSecond PostThird Post"
另一方面,大块结构化文档可能有一个单独的“示例”部分
# ==== Examples
#
# Person.exists?(5)
# Person.exists?('5')
# Person.exists?(name: "David")
# Person.exists?(['name LIKE ?', "%#{query}%"])
表达式的结果跟在它们后面,并以“# => ”引入,垂直对齐
# For checking if an integer is even or odd.
#
# 1.even? # => false
# 1.odd? # => true
# 2.even? # => true
# 2.odd? # => false
如果一行太长,注释可以放在下一行
# label(:article, :title)
# # => <label for="article_title">Title</label>
#
# label(:article, :title, "A short title")
# # => <label for="article_title">A short title</label>
#
# label(:article, :title, "A short title", class: "title_label")
# # => <label for="article_title" class="title_label">A short title</label>
避免为此目的使用任何打印方法,如 puts 或 p。
另一方面,常规注释不使用箭头
# polymorphic_url(record) # same as comment_url(record)
4.1. SQL
在文档化 SQL 语句时,结果不应在输出前有 =>。
例如,
# User.where(name: 'Oscar').to_sql
# # SELECT "users".* FROM "users" WHERE "users"."name" = 'Oscar'
4.2. IRB
在文档化 Ruby 交互式 REPL IRB 的行为时,始终以 irb> 为命令前缀。输出应以 => 为前缀。
例如,
# Find the customer with primary key (id) 10.
# irb> customer = Customer.find(10)
# # => #<Customer id: 10, first_name: "Ryan">
4.3. Bash / 命令行
对于命令行示例,始终以 $ 为命令前缀。输出不需要任何前缀。
# Run the following command:
# $ bin/rails new zomg
# ...
5. 布尔值
对于谓词和标志,优先文档化布尔语义而不是精确值。
当“true”或“false”在 Ruby 中被定义为词语时,使用常规字体。单例 true 和 false 需要等宽字体。请避免使用“truthy”等术语。Ruby 定义了语言中的 true 和 false,因此这些词具有技术含义,无需替代。
根据经验,除非绝对必要,否则不要文档化单例。这可以防止人工构造,如 !! 或三元运算符,允许重构,并且代码不需要依赖于实现中被调用方法返回的精确值。
例如
# +config.action_mailer.perform_deliveries+ specifies whether mail
# will actually be delivered and is true by default
用户不需要知道该标志的实际默认值是什么,因此我们只文档化其布尔语义。
一个谓词的例子
# Returns true if the collection is empty.
#
# If the collection has been loaded it is equivalent to
# +collection.size.zero?+. If the collection has not been loaded,
# it is equivalent to +!collection.exists?+. If the collection has
# not already been loaded and you are going to fetch the records
# anyway, it is better to check +collection.length.zero?+.
def empty?
if loaded?
size.zero?
else
@target.blank? && !scope.exists?
end
end
API 小心翼翼地不承诺任何特定值,该方法具有谓词语义,这已足够。
6. 文件名
根据经验,使用相对于应用程序根目录的文件名:config/routes.rb 而不是 routes.rb 或 RAILS_ROOT/config/routes.rb。
7. 字体
7.1. 等宽字体
使用等宽字体用于
- 常量,特别是类名和模块名
- 方法名
- 字面量,如
nil,false,true,self - 符号
- 方法参数
- 文件名
- HTML 标签和属性
- CSS 选择器、属性和值
class Array
# Calls +to_param+ on all its elements and joins the result with
# slashes. This is used by +url_for+ in Action Pack.
def to_param
collect { |e| e.to_param }.join "/"
end
end
使用 +...+ 表示等宽字体仅适用于简单内容,如普通类、模块、方法名、符号、路径(带正斜杠)等。对于其他所有内容,请使用 <tt>...</tt>。
您可以使用以下命令快速测试 RDoc 输出
$ echo "+:to_param+" | rdoc --pipe
# => <p><code>:to_param</code></p>
例如,包含空格或引号的代码应使用 <tt>...</tt> 形式。
7.2. 常规字体
当“true”和“false”是英语单词而不是 Ruby 关键字时,使用常规字体
# Runs all the validations within the specified context.
# Returns true if no errors are found, false otherwise.
#
# If the argument is false (default is +nil+), the context is
# set to <tt>:create</tt> if <tt>new_record?</tt> is true,
# and to <tt>:update</tt> if it is not.
#
# Validations with no <tt>:on</tt> option will run no
# matter the context. Validations with # some <tt>:on</tt>
# option will only run in the specified context.
def valid?(context = nil)
# ...
end
8. 描述列表
在选项、参数等列表中,在项和其描述之间使用连字符(比冒号读起来更好,因为选项通常是符号)
# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+.
描述以大写字母开头,以句号结束——这是标准的英语。
另一种方法是,当您想提供额外细节和示例时,使用选项部分样式。
ActiveSupport::MessageEncryptor#encrypt_and_sign 是一个很好的例子。
# ==== Options
#
# [+:expires_at+]
# The datetime at which the message expires. After this datetime,
# verification of the message will fail.
#
# message = encryptor.encrypt_and_sign("hello", expires_at: Time.now.tomorrow)
# encryptor.decrypt_and_verify(message) # => "hello"
# # 24 hours later...
# encryptor.decrypt_and_verify(message) # => nil
9. 动态生成的方法
使用 (module|class)_eval(STRING) 创建的方法旁边有一个注释,其中包含一个生成的代码实例。该注释距离模板 2 个空格
如果结果行太宽,例如 200 列或更多,请将注释放在调用上方
# def self.find_by_login_and_activated(*args)
# options = args.extract_options!
# ...
# end
self.class_eval %{
def self.#{method_id}(*args)
options = args.extract_options!
...
end
}, __FILE__, __LINE__
10. 方法可见性
在为 Rails 编写文档时,区分面向用户的 API 和内部 API 非常重要。
处于 Ruby private 作用域中的方法被排除在面向用户的 API 之外。然而,某些内部 API 方法必须处于 Ruby public 作用域中,以便它们可以在框架的其他地方调用。要将此类方法排除在面向用户的 API 之外,请使用 RDoc 的 :nodoc: 指令。
一个例子是 ActiveRecord::Core::ClassMethods#arel_table
module ActiveRecord::Core::ClassMethods
def arel_table # :nodoc:
# do some magic..
end
end
尽管它是一个公共方法,但用户不应依赖它。此方法的名称可能会更改,或者返回值可能会更改,或者此方法可能会完全删除。通过用 :nodoc: 标记它,它将从面向用户的 API 文档中删除。
作为贡献者,考虑一个 API 应该是面向用户的还是内部的很重要。Rails 团队承诺在不经过完整的弃用周期的情况下,不对面向用户的 API 进行破坏性更改。因此,您应该将 :nodoc: 添加到任何内部方法或模块,除非它们已经是私有的。(将 :nodoc: 添加到模块或类表示所有方法都是内部 API,并且应将其从面向用户的 API 文档中删除。)
11. 关于 Rails 堆栈
在文档化 Rails API 的部分时,重要的是要留意整个 Rails 堆栈。您正在文档化的方法或类的行为可能会因上下文而异。
一个这样的例子是 ActionView::Helpers::AssetTagHelper#image_tag
# image_tag("icon.png")
# # => <img src="/assets/icon.png" />
单独来看,image_tag 将返回 /images/icon.png。但是,当我们考虑整个 Rails 堆栈,包括资产管道时,我们可能会看到上述结果。
我们想要文档化 框架 的行为,而不仅仅是孤立的方法。我们关心的是用户在使用完整的默认 Rails 堆栈时所体验到的行为。
如果您对 Rails 团队如何处理某些 API 有疑问,请随时在问题跟踪器上开一个工单或发送补丁。
