v7.1.3.2
更多資訊請至 rubyonrails.org: 更多 Ruby on Rails

Active Support 核心擴充套件

Active Support 是 Ruby on Rails 元件,負責提供 Ruby 語言擴充和公用程式。

它在語言層級提供更豐富的底線,目標在於 Rails 應用程式的開發,以及 Ruby on Rails 本身的開發。

閱讀本指南後,您將會知道

1 如何載入核心擴充

1.1 獨立的 Active Support

為了讓預設的佔用空間最小,Active Support 預設載入最少的依賴項。它被切成小塊,以便只載入所需的擴充。它也有一些方便的進入點,可以一次載入相關的擴充,甚至全部載入。

因此,在進行像這樣的簡單 require 之後

require "active_support"

僅載入 Active Support 架構所需的擴充套件。

1.1.1 選取定義

此範例顯示如何載入 Hash#with_indifferent_access。此擴充套件可將 Hash 轉換為 ActiveSupport::HashWithIndifferentAccess,允許使用字串或符號存取金鑰。

{ a: 1 }.with_indifferent_access["a"] # => 1

對於每個定義為核心擴充套件的方法,本指南都有註解說明該方法的定義位置。在 with_indifferent_access 的情況下,註解寫道

這表示您可以像這樣需要它

require "active_support"
require "active_support/core_ext/hash/indifferent_access"

Active Support 已經過仔細修改,因此選取檔案時僅會載入嚴格需要的相依性(如果有)。

1.1.2 載入群組核心擴充套件

下一層級只需載入 Hash 的所有擴充套件。根據經驗法則,SomeClass 的擴充套件可透過載入 active_support/core_ext/some_class 一次取得。

因此,要載入 Hash 的所有擴充套件(包括 with_indifferent_access

require "active_support"
require "active_support/core_ext/hash"

1.1.3 載入所有核心擴充套件

您可能只想載入所有核心擴充套件,有一個檔案可以做到這件事

require "active_support"
require "active_support/core_ext"

1.1.4 載入所有 Active Support

最後,如果您想要使用所有 Active Support,只需執行

require "active_support/all"

這甚至不會將整個 Active Support 預先置於記憶體中,確實有些東西是透過 autoload 設定的,因此只有在使用時才會載入。

1.2 Ruby on Rails 應用程式中的 Active Support

除非 config.active_support.bare 為 true,否則 Ruby on Rails 應用程式會載入所有 Active Support。在這種情況下,應用程式將僅載入架構本身為其自身需求而選取的內容,並且仍可以根據前一節所述以任何層級選取。

2 所有物件的擴充套件

2.1 blank?present?

在 Rails 應用程式中,下列值會被視為空白

  • nilfalse

  • 僅由空白組成的字串(見下方註解),

  • 空的陣列和雜湊,以及

  • 任何其他回應 empty? 且為空的物件。

字串的判斷式使用 Unicode 認知的字元類別 [:space:],因此例如 U+2029(段落分隔符號)會被視為空白。

請注意數字並未列出。特別是,0 和 0.0 並非空白。

例如,ActionController::HttpAuthentication::Token::ControllerMethods 中的這個方法使用 blank? 檢查令牌是否存在

def authenticate(controller, &login_procedure)
  token, options = token_and_options(controller.request)
  unless token.blank?
    login_procedure.call(token, options)
  end
end

方法 present? 等同於 !blank?。此範例取自 ActionDispatch::Http::Cache::Response

def set_conditional_cache_control!
  return if self["Cache-Control"].present?
  # ...
end

2.2 presence

presence 方法會在 present? 時傳回接收者,否則傳回 nil。它適用於類似這樣的慣用語法

host = config[:host].presence || 'localhost'

2.3 duplicable?

從 Ruby 2.5 開始,大多數物件都可以透過 dupclone 複製

"foo".dup           # => "foo"
"".dup              # => ""
Rational(1).dup     # => (1/1)
Complex(0).dup      # => (0+0i)
1.method(:+).dup    # => TypeError (allocator undefined for Method)

Active Support 提供 duplicable? 來查詢物件的這個功能

"foo".duplicable?           # => true
"".duplicable?              # => true
Rational(1).duplicable?     # => true
Complex(1).duplicable?      # => true
1.method(:+).duplicable?    # => false

任何類別都可以透過移除 dupclone 或從中引發例外來禁止重複。因此,只有 rescue 可以判斷給定的任意物件是否可重複。duplicable? 取決於上述硬編碼清單,但它比 rescue 快得多。僅在您知道硬編碼清單在您的使用案例中足夠時才使用它。

2.4 deep_dup

deep_dup 方法會傳回給定物件的深度複製。通常,當您 dup 包含其他物件的物件時,Ruby 不會 dup 它們,因此它會建立物件的淺層複製。例如,如果您有一個包含字串的陣列,它看起來會像這樣

array     = ['string']
duplicate = array.dup

duplicate.push 'another-string'

# the object was duplicated, so the element was added only to the duplicate
array     # => ['string']
duplicate # => ['string', 'another-string']

duplicate.first.gsub!('string', 'foo')

# first element was not duplicated, it will be changed in both arrays
array     # => ['foo']
duplicate # => ['foo', 'another-string']

如您所見,在複製 Array 實例後,我們得到另一個物件,因此我們可以修改它,而原始物件將保持不變。然而,對於陣列的元素來說並非如此。由於 dup 沒有進行深度複製,因此陣列中的字串仍然是同一個物件。

如果您需要物件的深度複製,您應該使用 deep_dup。以下是一個範例

array     = ['string']
duplicate = array.deep_dup

duplicate.first.gsub!('string', 'foo')

array     # => ['string']
duplicate # => ['foo']

如果物件不可複製,deep_dup 只會傳回它

number = 1
duplicate = number.deep_dup
number.object_id == duplicate.object_id   # => true

2.5 try

當您只想在物件不為 nil 時呼叫其方法時,最簡單的方法是使用條件式陳述,增加不必要的混亂。另一種方法是使用 trytry 類似於 Object#public_send,但如果傳送給 nil,它會傳回 nil

以下是一個範例

# without try
unless @number.nil?
  @number.next
end

# with try
@number.try(:next)

另一個範例是來自 ActiveRecord::ConnectionAdapters::AbstractAdapter 的這段程式碼,其中 @logger 可能為 nil。你可以看到程式碼使用 try 並避免不必要的檢查。

def log_info(sql, name, ms)
  if @logger.try(:debug?)
    name = '%s (%.1fms)' % [name || 'SQL', ms]
    @logger.debug(format_log_entry(name, sql.squeeze(' ')))
  end
end

try 也可以在沒有參數但有區塊的情況下呼叫,如果物件不是 nil,則只會執行該區塊

@person.try { |p| "#{p.first_name} #{p.last_name}" }

請注意,try 會吞掉無方法錯誤,並返回 nil。如果你想要防止拼寫錯誤,請改用 try!

@number.try(:nest)  # => nil
@number.try!(:nest) # NoMethodError: undefined method `nest' for 1:Integer

2.6 class_eval(*args, &block)

你可以使用 class_eval 在任何物件的單例類別中評估程式碼

class Proc
  def bind(object)
    block, time = self, Time.current
    object.class_eval do
      method_name = "__bind_#{time.to_i}_#{time.usec}"
      define_method(method_name, &block)
      method = instance_method(method_name)
      remove_method(method_name)
      method
    end.bind(object)
  end
end

2.7 acts_like?(duck)

方法 acts_like? 提供一種方法來檢查某個類別是否根據一個簡單的慣例表現得像另一個類別:提供與 String 相同介面的類別定義

def acts_like_string?
end

這只是一個標記,其主體或回傳值無關緊要。然後,用戶端程式碼可以這樣查詢鴨子型別安全性

some_klass.acts_like?(:string)

Rails 有表現得像 DateTime 並遵循此契約的類別。

2.8 to_param

Rails 中的所有物件都會回應方法 to_param,其目的是回傳一些東西來表示它們作為查詢字串中的值,或作為 URL 片段。

預設情況下,to_param 只會呼叫 to_s

7.to_param # => "7"

to_param 的回傳值不應經過跳脫

"Tom & Jerry".to_param # => "Tom & Jerry"

Rails 中的幾個類別會覆寫此方法。

例如,niltruefalse 會傳回它們自己。 Array#to_param 會對元素呼叫 to_param,並使用「/」將結果串接起來

[0, true, String].to_param # => "0/true/String"

特別注意的是,Rails 路由系統會對模型呼叫 to_param,以取得 :id 佔位符的值。 ActiveRecord::Base#to_param 會傳回模型的 id,但您可以在模型中重新定義該方法。例如,假設

class User
  def to_param
    "#{id}-#{name.parameterize}"
  end
end

我們會取得

user_path(@user) # => "/users/357-john-smith"

控制器需要知道 to_param 的任何重新定義,因為當類似「357-john-smith」的請求進來時,params[:id] 的值就是這樣。

2.9 to_query

方法 to_query 會建構一個查詢字串,將指定的 keyto_param 的傳回值關聯起來。例如,使用下列 to_param 定義

class User
  def to_param
    "#{id}-#{name.parameterize}"
  end
end

我們會取得

current_user.to_query('user') # => "user=357-john-smith"

此方法會對 key 和值進行必要的轉譯

account.to_query('company[name]')
# => "company%5Bname%5D=Johnson+%26+Johnson"

因此其輸出可直接用於查詢字串中。

陣列會傳回將 to_query 套用至每個元素的結果,其中 key[] 為 key,並使用「&」將結果串接起來

[3.4, -45.6].to_query('sample')
# => "sample%5B%5D=3.4&sample%5B%5D=-45.6"

雜湊也會回應 to_query,但簽章不同。如果未傳遞參數,呼叫會產生一個已排序的 key/value 指派系列,並對其值呼叫 to_query(key)。然後,它會使用「&」將結果串接起來

{ c: 3, b: 2, a: 1 }.to_query # => "a=1&b=2&c=3"

方法 Hash#to_query 接受 key 的選用命名空間

{ id: 89, name: "John Smith" }.to_query('user')
# => "user%5Bid%5D=89&user%5Bname%5D=John+Smith"

2.10 with_options

方法 with_options 提供一個方法,可將一系列方法呼叫中的常見選項分解出來。

給定一個預設選項雜湊,with_options 會產生一個代理物件給區塊。在區塊中,呼叫代理物件的方法會將其選項合併後轉發給接收器。例如,您可以用

class Account < ApplicationRecord
  has_many :customers, dependent: :destroy
  has_many :products,  dependent: :destroy
  has_many :invoices,  dependent: :destroy
  has_many :expenses,  dependent: :destroy
end

這種方式

class Account < ApplicationRecord
  with_options dependent: :destroy do |assoc|
    assoc.has_many :customers
    assoc.has_many :products
    assoc.has_many :invoices
    assoc.has_many :expenses
  end
end

這種慣用語法也可以傳達給讀者分組。例如,假設您要寄送一封取決於使用者的語言的電子報。在郵件程式中,您可以像這樣將依據地區而定的部分分組

I18n.with_options locale: user.locale, scope: "newsletter" do |i18n|
  subject i18n.t :subject
  body    i18n.t :body, user_name: user.name
end

由於 with_options 會將呼叫轉發給其接收器,因此它們可以巢狀。每個巢狀層級除了自己的預設值之外,還會合併繼承的預設值。

2.11 JSON 支援

Active Support 提供了比 json 寶石通常為 Ruby 物件提供的 to_json 更佳的實作。這是因為某些類別,例如 HashProcess::Status 需要特殊處理才能提供適當的 JSON 表示。

2.12 執行個體變數

Active Support 提供了多種方法來簡化執行個體變數的存取。

2.12.1 instance_values

方法 instance_values 會傳回一個雜湊,將沒有「@」符號的執行個體變數名稱對應到其對應值。金鑰是字串

class C
  def initialize(x, y)
    @x, @y = x, y
  end
end

C.new(0, 1).instance_values # => {"x" => 0, "y" => 1}

2.12.2 instance_variable_names

方法 instance_variable_names 會傳回一個陣列。每個名稱都包含「@」符號。

class C
  def initialize(x, y)
    @x, @y = x, y
  end
end

C.new(0, 1).instance_variable_names # => ["@x", "@y"]

2.13 靜音警告和例外

方法 silence_warningsenable_warnings 會在區塊持續時間內相應變更 $VERBOSE 的值,並在之後重設

silence_warnings { Object.const_set "RAILS_DEFAULT_LOGGER", logger }

也可以使用 suppress 來抑制例外。此方法會接收任意數量的例外類別。如果在執行區塊期間引發例外,且其為 kind_of? 任何引數,suppress 會擷取並靜默傳回。否則不會擷取例外

# If the user is locked, the increment is lost, no big deal.
suppress(ActiveRecord::StaleObjectError) do
  current_user.increment! :visits
end

2.14 in?

謂詞 in? 測試物件是否包含在另一個物件中。如果傳遞的引數未回應 include?,將會引發 ArgumentError 例外。

in? 的範例

1.in?([1, 2])        # => true
"lo".in?("hello")   # => true
25.in?(30..50)      # => false
1.in?(1)            # => ArgumentError

3 對 Module 的擴充

3.1 屬性

3.1.1 alias_attribute

模型屬性有讀取器、寫入器和謂詞。您可以使用 alias_attribute 為模型屬性建立別名,並為您定義對應的三個方法。與其他別名方法一樣,新名稱是第一個引數,舊名稱是第二個引數(一種記憶方法是它們的順序與執行指定一樣)

class User < ApplicationRecord
  # You can refer to the email column as "login".
  # This can be meaningful for authentication code.
  alias_attribute :login, :email
end

3.1.2 內部屬性

當您在一個預計會被繼承的類別中定義屬性時,名稱衝突是一個風險。這對於函式庫來說非常重要。

Active Support 定義巨集 attr_internal_readerattr_internal_writerattr_internal_accessor。它們的行為與 Ruby 內建的 attr_* 對應項目類似,但它們以較不容易產生衝突的方式命名底層實例變數。

巨集 attr_internalattr_internal_accessor 的同義詞

# library
class ThirdPartyLibrary::Crawler
  attr_internal :log_level
end

# client code
class MyCrawler < ThirdPartyLibrary::Crawler
  attr_accessor :log_level
end

在先前的範例中,:log_level 可能不屬於函式庫的公開介面,而且只用於開發。用戶端程式碼不知道潛在衝突,因此建立子類別並定義其自己的 :log_level。多虧了 attr_internal,因此不會產生衝突。

預設情況下,內部實例變數會以底線開頭命名,如上方的範例中的 @_log_level。不過,這可透過 Module.attr_internal_naming_format 進行設定,您可以傳遞任何類似 sprintf 的格式字串,其中包含開頭的 @ 和某處的 %s,名稱將置於此處。預設值為 "@_%s"

Rails 在幾個地方使用內部屬性,例如檢視

module ActionView
  class Base
    attr_internal :captures
    attr_internal :request, :layout
    attr_internal :controller, :template
  end
end

3.1.3 模組屬性

巨集 mattr_readermattr_writermattr_accessor 與為類別定義的 cattr_* 巨集相同。事實上,cattr_* 巨集只是 mattr_* 巨集的別名。請查看 類別屬性

例如,Active Storage 的記錄器的 API 是使用 mattr_accessor 產生的

module ActiveStorage
  mattr_accessor :logger
end

3.2 父類別

3.2.1 module_parent

巢狀命名模組上的 module_parent 方法傳回包含其對應常數的模組

module X
  module Y
    module Z
    end
  end
end
M = X::Y::Z

X::Y::Z.module_parent # => X::Y
M.module_parent       # => X::Y

如果模組是匿名的或屬於頂層,module_parent 會傳回 Object

請注意,在這種情況下,module_parent_name 會傳回 nil

3.2.2 module_parent_name

巢狀命名模組上的 module_parent_name 方法傳回包含其對應常數的模組的完整限定名稱

module X
  module Y
    module Z
    end
  end
end
M = X::Y::Z

X::Y::Z.module_parent_name # => "X::Y"
M.module_parent_name       # => "X::Y"

對於頂層或匿名模組,module_parent_name 會傳回 nil

請注意,在這種情況下,module_parent 會傳回 Object

3.2.3 module_parents

方法 module_parents 對接收者呼叫 module_parent,並向上呼叫直到到達 Object。鏈會以由下至上的順序傳回陣列中

module X
  module Y
    module Z
    end
  end
end
M = X::Y::Z

X::Y::Z.module_parents # => [X::Y, X, Object]
M.module_parents       # => [X::Y, X, Object]

3.3 匿名

模組可能有或沒有名稱

module M
end
M.name # => "M"

N = Module.new
N.name # => "N"

Module.new.name # => nil

您可以使用謂詞 anonymous? 檢查模組是否有名稱

module M
end
M.anonymous? # => false

Module.new.anonymous? # => true

請注意,無法到達並不表示是匿名的

module M
end

m = Object.send(:remove_const, :M)

m.anonymous? # => false

儘管匿名模組根據定義是無法到達的。

3.4 方法委派

3.4.1 delegate

巨集 delegate 提供了一個轉發方法的簡易方式。

想像在某個應用程式中,使用者的登入資訊在 User 模型中,但姓名和其他資料在一個獨立的 Profile 模型中

class User < ApplicationRecord
  has_one :profile
end

有了這個設定,你可以透過使用者的個人資料取得使用者的姓名,user.profile.name,但直接存取此類屬性可能會更方便

class User < ApplicationRecord
  has_one :profile

  def name
    profile.name
  end
end

這就是 delegate 為你做的事

class User < ApplicationRecord
  has_one :profile

  delegate :name, to: :profile
end

它更簡短,而且意圖更明顯。

方法在目標中必須是公開的。

delegate 巨集接受多種方法

delegate :name, :age, :address, :twitter, to: :profile

當內插到字串中時,:to 選項應成為一個表達式,用於評估方法委派到的物件。通常是字串或符號。此類表達式會在接收者的內容中評估

# delegates to the Rails constant
delegate :logger, to: :Rails

# delegates to the receiver's class
delegate :table_name, to: :class

如果 :prefix 選項為 true,這會較不通用,請見下方。

預設情況下,如果委派引發 NoMethodError 且目標為 nil,例外狀況會傳播。你可以使用 :allow_nil 選項要求改為傳回 nil

delegate :name, to: :profile, allow_nil: true

使用 :allow_nil 時,如果使用者沒有個人資料,呼叫 user.name 會傳回 nil

選項 :prefix 會在產生的方法名稱中加上一個前綴。這可能很方便,例如用於取得更好的名稱

delegate :street, to: :address, prefix: true

前一個範例會產生 address_street 而不是 street

由於在這個案例中,產生的方法名稱是由目標物件和目標方法名稱組成,因此 :to 選項必須是一個方法名稱。

也可以設定自訂的前綴

delegate :size, to: :attachment, prefix: :avatar

在之前的範例中,巨集會產生 avatar_size 而不是 size

選項 :private 會變更方法範圍

delegate :date_of_birth, to: :profile, private: true

委派的方法預設為公開。傳遞 private: true 來變更。

3.4.2 delegate_missing_to

想像一下,您想要將 User 物件中所有遺失的內容委派給 Profile 物件。巨集 delegate_missing_to 讓您可以輕鬆實作這一點

class User < ApplicationRecord
  has_one :profile

  delegate_missing_to :profile
end

目標可以是物件中任何可呼叫的項目,例如實例變數、方法、常數等。只有目標的公開方法會被委派。

3.5 重新定義方法

在某些情況下,您需要使用 define_method 定義方法,但不知道是否已經存在具有該名稱的方法。如果存在,如果已啟用,則會發出警告。這不是什麼大問題,但也不乾淨。

方法 redefine_method 可防止此類潛在警告,並在需要時移除現有方法。

如果您需要自己定義替換方法(例如,因為您正在使用 delegate),您也可以使用 silence_redefinition_of_method

4 Class 的延伸

4.1 類別屬性

4.1.1 class_attribute

方法 class_attribute 宣告一個或多個可繼承的類別屬性,可以在階層中的任何層級覆寫。

class A
  class_attribute :x
end

class B < A; end

class C < B; end

A.x = :a
B.x # => :a
C.x # => :a

B.x = :b
A.x # => :a
C.x # => :b

C.x = :c
A.x # => :a
B.x # => :b

例如,ActionMailer::Base 定義

class_attribute :default_params
self.default_params = {
  mime_version: "1.0",
  charset: "UTF-8",
  content_type: "text/plain",
  parts_order: [ "text/plain", "text/enriched", "text/html" ]
}.freeze

也可以在實例層級存取和覆寫。

A.x = 1

a1 = A.new
a2 = A.new
a2.x = 2

a1.x # => 1, comes from A
a2.x # => 2, overridden in a2

可以透過將選項 :instance_writer 設為 false 來防止產生寫入實例方法。

module ActiveRecord
  class Base
    class_attribute :table_name_prefix, instance_writer: false, default: "my"
  end
end

模型可能會發現此選項很有用,可以防止大量指定設定屬性。

可以透過將選項 :instance_reader 設為 false 來防止產生讀取實例方法。

class A
  class_attribute :x, instance_reader: false
end

A.new.x = 1
A.new.x # NoMethodError

為了方便,class_attribute 也定義一個實例謂詞,它是否定實例讀取器回傳值的兩次否定。在上述範例中,它會稱為 x?

:instance_readerfalse 時,實例謂詞會回傳 NoMethodError,就像讀取器方法一樣。

如果您不想要實例謂詞,請傳遞 instance_predicate: false,它就不會被定義。

4.1.2 cattr_readercattr_writercattr_accessor

巨集 cattr_readercattr_writercattr_accessor 類似於其 attr_* 對應項,但適用於類別。它們會將類別變數初始化為 nil,除非它已經存在,並產生對應的類別方法來存取它。

class MysqlAdapter < AbstractAdapter
  # Generates class methods to access @@emulate_booleans.
  cattr_accessor :emulate_booleans
end

此外,您可以傳遞區塊給 cattr_* 來設定具有預設值的屬性。

class MysqlAdapter < AbstractAdapter
  # Generates class methods to access @@emulate_booleans with default value of true.
  cattr_accessor :emulate_booleans, default: true
end

為方便起見,也會建立實例方法,它們只是類別屬性的代理。因此,實例可以變更類別屬性,但無法覆寫它,就像使用 `class_attribute`(請參閱上方)一樣。例如,假設

module ActionView
  class Base
    cattr_accessor :field_error_proc, default: Proc.new {
      # ...
    }
  end
end

我們可以在檢視中存取 `field_error_proc`。

設定 `:instance_reader` 為 `false` 可以防止產生讀取實例方法,設定 `:instance_writer` 為 `false` 可以防止產生寫入實例方法。設定 `:instance_accessor` 為 `false` 可以防止產生這兩種方法。在所有情況下,值都必須完全為 `false`,而不是任何假值。

module A
  class B
    # No first_name instance reader is generated.
    cattr_accessor :first_name, instance_reader: false
    # No last_name= instance writer is generated.
    cattr_accessor :last_name, instance_writer: false
    # No surname instance reader or surname= writer is generated.
    cattr_accessor :surname, instance_accessor: false
  end
end

模型可能會發現將 `:instance_accessor` 設定為 `false` 有用,因為這可以防止大量指派設定屬性。

4.2 子類別和後代

4.2.1 `subclasses`

subclasses 方法會傳回接收者的子類別

class C; end
C.subclasses # => []

class B < C; end
C.subclasses # => [B]

class A < B; end
C.subclasses # => [B]

class D < C; end
C.subclasses # => [B, D]

這些類別的傳回順序未指定。

定義在 `active_support/core_ext/class/subclasses.rb` 中。

4.2.2 `descendants`

descendants 方法會傳回所有小於其接收者的類別

class C; end
C.descendants # => []

class B < C; end
C.descendants # => [B]

class A < B; end
C.descendants # => [B, A]

class D < C; end
C.descendants # => [B, A, D]

這些類別的傳回順序未指定。

定義在 `active_support/core_ext/class/subclasses.rb` 中。

5 擴充 `String`

5.1 輸出安全性

5.1.1 動機

將資料插入 HTML 範本需要特別小心。例如,您不能直接將 `@review.title` 插值到 HTML 頁面中。原因之一是,如果評論標題為「Flanagan & Matz rules!」,輸出將不會格式良好,因為必須將 & 字元轉譯為「&amp;」。更重要的是,這可能會造成重大的安全漏洞,具體取決於應用程式,因為使用者可以透過設定精心設計的評論標題來注入惡意 HTML。請參閱 安全性指南 中關於跨網站指令碼的區段,以進一步了解風險。

5.1.2 安全字串

Active Support 有 (html) 安全 字串的概念。安全字串標記為可插入 HTML。不論是否已跳脫,它都是可信賴的。

預設情況下,字串被視為 不安全

"".html_safe? # => false

你可以使用 html_safe 方法從給定的字串取得安全字串

s = "".html_safe
s.html_safe? # => true

了解 html_safe 不會執行任何跳脫非常重要,它只是一個聲明

s = "<script>...</script>".html_safe
s.html_safe? # => true
s            # => "<script>...</script>"

你應負責確保在特定字串上呼叫 html_safe 是沒問題的。

如果你附加到安全字串,不論是使用 concat/<< 原地附加,還是使用 +,結果都是安全字串。不安全的引數會被跳脫

"".html_safe + "<" # => "&lt;"

安全引數會直接附加

"".html_safe + "<".html_safe # => "<"

這些方法不應在一般檢視中使用。不安全的值會自動跳脫

<%= @review.title %> <%# fine, escaped if needed %>

若要逐字插入,請使用 raw 輔助工具,而不是呼叫 html_safe

<%= raw @cms.current_template %> <%# inserts @cms.current_template as is %>

或者,等效地,使用 <%==

<%== @cms.current_template %> <%# inserts @cms.current_template as is %>

raw 輔助工具會為你呼叫 html_safe

def raw(stringish)
  stringish.to_s.html_safe
end

5.1.3 轉換

根據經驗法則,除了如上所述的串接之外,任何可能變更字串的方法都會給你一個不安全的字串。這些方法包括 downcasegsubstripchompunderscore 等。

對於 gsub! 等原地轉換,接收者本身會變得不安全。

安全位元會永遠遺失,不論轉換是否實際變更某些內容。

5.1.4 轉換和強制轉型

在安全字串上呼叫 to_s 會傳回安全字串,但使用 to_str 強制轉型會傳回不安全的字串。

5.1.5 複製

呼叫安全字串上的 dupclone 會產生安全字串。

5.2 remove

方法 remove 會移除樣式的全部出現

"Hello World".remove(/Hello /) # => "World"

還有破壞性的版本 String#remove!

5.3 squish

方法 squish 會移除前導和尾隨空白,並將空白執行取代為單一空白

" \n  foo\n\r \t bar \n".squish # => "foo bar"

還有破壞性的版本 String#squish!

請注意,它會處理 ASCII 和 Unicode 空白。

5.4 truncate

方法 truncate 會傳回一個接收器副本,在給定的 length 之後會截斷

"Oh dear! Oh dear! I shall be late!".truncate(20)
# => "Oh dear! Oh dear!..."

省略符號可以使用 :omission 選項自訂

"Oh dear! Oh dear! I shall be late!".truncate(20, omission: '&hellip;')
# => "Oh dear! Oh &hellip;"

特別注意,截斷會考慮省略字串的長度。

傳遞 :separator 以在自然中斷處截斷字串

"Oh dear! Oh dear! I shall be late!".truncate(18)
# => "Oh dear! Oh dea..."
"Oh dear! Oh dear! I shall be late!".truncate(18, separator: ' ')
# => "Oh dear! Oh..."

選項 :separator 可以是正規表示法

"Oh dear! Oh dear! I shall be late!".truncate(18, separator: /\s/)
# => "Oh dear! Oh..."

在上述範例中,"dear" 會先被切斷,但 :separator 會阻止它。

5.5 truncate_bytes

方法 truncate_bytes 會傳回一個接收器副本,截斷為最多 bytesize 位元組

"👍👍👍👍".truncate_bytes(15)
# => "👍👍👍…"

省略符號可以使用 :omission 選項自訂

"👍👍👍👍".truncate_bytes(15, omission: "🖖")
# => "👍👍🖖"

5.6 truncate_words

方法 truncate_words 會傳回一個接收器副本,在給定的字數之後會截斷

"Oh dear! Oh dear! I shall be late!".truncate_words(4)
# => "Oh dear! Oh dear!..."

省略符號可以使用 :omission 選項自訂

"Oh dear! Oh dear! I shall be late!".truncate_words(4, omission: '&hellip;')
# => "Oh dear! Oh dear!&hellip;"

傳遞 :separator 以在自然中斷處截斷字串

"Oh dear! Oh dear! I shall be late!".truncate_words(3, separator: '!')
# => "Oh dear! Oh dear! I shall be late..."

選項 :separator 可以是正規表示法

"Oh dear! Oh dear! I shall be late!".truncate_words(4, separator: /\s/)
# => "Oh dear! Oh dear!..."

5.7 inquiry

inquiry 方法將字串轉換成 StringInquirer 物件,讓相等性檢查更美觀。

"production".inquiry.production? # => true
"active".inquiry.inactive?       # => false

5.8 starts_with?ends_with?

Active Support 定義了 String#start_with?String#end_with? 的第三人稱別名

"foo".starts_with?("f") # => true
"foo".ends_with?("o")   # => true

5.9 strip_heredoc

strip_heredoc 方法會移除 heredocs 中的縮排。

例如:

if options[:usage]
  puts <<-USAGE.strip_heredoc
    This command does such and such.

    Supported options are:
      -h         This message
      ...
  USAGE
end

使用者會看到使用訊息與左邊界對齊。

技術上來說,它會尋找整個字串中最少縮排的行,並移除該行開頭的空白。

5.10 indent

indent 方法會縮排接收者的行

<<EOS.indent(2)
def some_method
  some_code
end
EOS
# =>
  def some_method
    some_code
  end

第二個參數 indent_string 指定要使用的縮排字串。預設為 nil,這會讓方法查看第一個縮排行並做出明智的猜測,如果沒有縮排行則改用空白。

"  foo".indent(2)        # => "    foo"
"foo\n\t\tbar".indent(2) # => "\t\tfoo\n\t\t\t\tbar"
"foo".indent(2, "\t")    # => "\t\tfoo"

雖然 indent_string 通常是一個空白或一個 tab,但它可以是任何字串。

第三個參數 indent_empty_lines 是個旗標,表示是否要縮排空行。預設為 false。

"foo\n\nbar".indent(2)            # => "  foo\n\n  bar"
"foo\n\nbar".indent(2, nil, true) # => "  foo\n  \n  bar"

indent! 方法會就地縮排。

5.11 存取

5.11.1 at(位置)

at 方法傳回字串中位於 位置 的字元

"hello".at(0)  # => "h"
"hello".at(4)  # => "o"
"hello".at(-1) # => "o"
"hello".at(10) # => nil

5.11.2 from(位置)

from 方法傳回從 位置 開始的字串子字串

"hello".from(0)  # => "hello"
"hello".from(2)  # => "llo"
"hello".from(-2) # => "lo"
"hello".from(10) # => nil

5.11.3 to(位置)

to 方法傳回字串子字串到 位置 為止

"hello".to(0)  # => "h"
"hello".to(2)  # => "hel"
"hello".to(-2) # => "hell"
"hello".to(10) # => "hello"

5.11.4 first(限制 = 1)

first 方法傳回包含字串開頭 限制 個字元的子字串。

如果 n > 0,呼叫 str.first(n) 等同於 str.to(n-1),而當 n == 0 時則傳回空字串。

5.11.5 last(限制 = 1)

last 方法傳回包含字串結尾 限制 個字元的子字串。

如果 n > 0,呼叫 str.last(n) 等同於 str.from(-n),而當 n == 0 時則傳回空字串。

5.12 詞形變化

5.12.1 pluralize

pluralize 方法傳回其接收者的複數型態

"table".pluralize     # => "tables"
"ruby".pluralize      # => "rubies"
"equipment".pluralize # => "equipment"

如前一個範例所示,Active Support 認識一些不規則複數和不可數名詞。內建規則可以在 config/initializers/inflections.rb 中擴充。這個檔案預設由 rails new 指令產生,並在註解中提供說明。

pluralize 也能接受一個選用的 count 參數。如果 count == 1 會傳回單數型態。對於 count 的任何其他值,都會傳回複數型態

"dude".pluralize(0) # => "dudes"
"dude".pluralize(1) # => "dude"
"dude".pluralize(2) # => "dudes"

Active Record 使用此方法來計算對應於模型的預設資料表名稱

# active_record/model_schema.rb
def undecorated_table_name(model_name)
  table_name = model_name.to_s.demodulize.underscore
  pluralize_table_names ? table_name.pluralize : table_name
end

5.12.2 singularize

singularize 方法是 pluralize 的反向

"tables".singularize    # => "table"
"rubies".singularize    # => "ruby"
"equipment".singularize # => "equipment"

關聯使用此方法來計算對應的預設關聯類別的名稱

# active_record/reflection.rb
def derive_class_name
  class_name = name.to_s.camelize
  class_name = class_name.singularize if collection?
  class_name
end

5.12.3 camelize

方法 camelize 以駝峰式大小寫回傳其接收器

"product".camelize    # => "Product"
"admin_user".camelize # => "AdminUser"

根據經驗法則,您可以將此方法視為將路徑轉換為 Ruby 類別或模組名稱的方法,其中斜線分隔命名空間

"backoffice/session".camelize # => "Backoffice::Session"

例如,Action Pack 使用此方法來載入提供特定階段儲存的類別

# action_controller/metal/session_management.rb
def session_store=(store)
  @@session_store = store.is_a?(Symbol) ?
    ActionDispatch::Session.const_get(store.to_s.camelize) :
    store
end

camelize 接受一個選用參數,它可以是 :upper(預設值)或 :lower。後者會將第一個字母轉換為小寫

"visual_effect".camelize(:lower) # => "visualEffect"

這可能有助於計算遵循該慣例的語言(例如 JavaScript)中的方法名稱。

根據經驗法則,您可以將 camelize 視為 underscore 的反向,儘管在某些情況下並非如此:"SSLError".underscore.camelize 會回傳 "SslError"。為了支援此類情況,Active Support 允許您在 config/initializers/inflections.rb 中指定縮寫

ActiveSupport::Inflector.inflections do |inflect|
  inflect.acronym 'SSL'
end

"SSLError".underscore.camelize # => "SSLError"

camelize 別名為 camelcase

5.12.4 underscore

方法 underscore 反其道而行,從駝峰式大小寫轉換為路徑

"Product".underscore   # => "product"
"AdminUser".underscore # => "admin_user"

同時將「::」轉換回「/」

"Backoffice::Session".underscore # => "backoffice/session"

並理解以小寫開頭的字串

"visualEffect".underscore # => "visual_effect"

不過,underscore 不接受參數。

Rails 使用 underscore 來取得控制器類別的小寫名稱

# actionpack/lib/abstract_controller/base.rb
def controller_path
  @controller_path ||= name.delete_suffix("Controller").underscore
end

例如,該值是您在 params[:controller] 中取得的值。

根據經驗法則,您可以將 underscore 視為 camelize 的反向,儘管在某些情況下並非如此。例如,"SSLError".underscore.camelize 會回傳 "SslError"

5.12.5 titleize

方法 titleize 將接收者中的字詞大寫

"alice in wonderland".titleize # => "Alice In Wonderland"
"fermat's enigma".titleize     # => "Fermat's Enigma"

titleize 別名為 titlecase

5.12.6 dasherize

方法 dasherize 將接收者中的底線取代為破折號

"name".dasherize         # => "name"
"contact_data".dasherize # => "contact-data"

模型的 XML 序列器使用此方法將節點名稱破折號化

# active_model/serializers/xml.rb
def reformat_name(name)
  name = name.camelize if camelize?
  dasherize? ? name.dasherize : name
end

5.12.7 demodulize

給定一個包含限定常數名稱的字串,demodulize 會傳回該常數名稱,也就是字串最右邊的部分

"Product".demodulize                        # => "Product"
"Backoffice::UsersController".demodulize    # => "UsersController"
"Admin::Hotel::ReservationUtils".demodulize # => "ReservationUtils"
"::Inflections".demodulize                  # => "Inflections"
"".demodulize                               # => ""

例如,Active Record 使用此方法來計算 counter 快取欄位的名稱

# active_record/reflection.rb
def counter_cache_column
  if options[:counter_cache] == true
    "#{active_record.name.demodulize.underscore.pluralize}_count"
  elsif options[:counter_cache]
    options[:counter_cache]
  end
end

5.12.8 deconstantize

給定一個包含限定常數參考表達式的字串,deconstantize 會移除最右邊的區塊,通常會留下常數容器的名稱

"Product".deconstantize                        # => ""
"Backoffice::UsersController".deconstantize    # => "Backoffice"
"Admin::Hotel::ReservationUtils".deconstantize # => "Admin::Hotel"

5.12.9 parameterize

方法 parameterize 會以可用於美觀網址的方式正規化其接收者。

"John Smith".parameterize # => "john-smith"
"Kurt Gödel".parameterize # => "kurt-godel"

若要保留字串的大小寫,請將 preserve_case 參數設為 true。預設情況下,preserve_case 設為 false。

"John Smith".parameterize(preserve_case: true) # => "John-Smith"
"Kurt Gödel".parameterize(preserve_case: true) # => "Kurt-Godel"

若要使用自訂分隔符號,請覆寫 separator 參數。

"John Smith".parameterize(separator: "_") # => "john_smith"
"Kurt Gödel".parameterize(separator: "_") # => "kurt_godel"

5.12.10 tableize

方法 tableizeunderscore 接著 pluralize

"Person".tableize      # => "people"
"Invoice".tableize     # => "invoices"
"InvoiceLine".tableize # => "invoice_lines"

根據經驗法則,tableize 會傳回對應於給定模型的資料表名稱,適用於簡單的情況。Active Record 中的實際實作並非直接 tableize,因為它還會取消模組化類別名稱,並檢查可能影響傳回字串的幾個選項。

5.12.11 classify

方法 classifytableize 的反函數。它會提供對應於資料表名稱的類別名稱

"people".classify        # => "Person"
"invoices".classify      # => "Invoice"
"invoice_lines".classify # => "InvoiceLine"

該方法了解合格的資料表名稱

"highrise_production.companies".classify # => "Company"

請注意,classify 會傳回類別名稱作為字串。您可以呼叫 constantize 來取得實際的類別物件,如下所述。

5.12.12 constantize

方法 constantize 會解析其接收器中的常數參考表達式

"Integer".constantize # => Integer

module M
  X = 1
end
"M::X".constantize # => 1

如果字串評估為沒有已知常數,或其內容甚至不是有效的常數名稱,constantize 會引發 NameError

constantize 的常數名稱解析始終從頂層 Object 開始,即使沒有前導的「::」。

X = :in_Object
module M
  X = :in_M

  X                 # => :in_M
  "::X".constantize # => :in_Object
  "X".constantize   # => :in_Object (!)
end

因此,它通常不等於 Ruby 在同一個位置會執行的動作,如果評估一個真正的常數。

郵件測試案例會使用 constantize 從測試類別的名稱取得正在測試的郵件

# action_mailer/test_case.rb
def determine_default_mailer(name)
  name.delete_suffix("Test").constantize
rescue NameError => e
  raise NonInferrableMailerError.new(name)
end

5.12.13 humanize

方法 humanize 會調整屬性名稱,以顯示給最終使用者。

具體來說,它會執行以下轉換

  • 將人類的變格規則套用至參數。
  • 刪除前導底線(如果有)。
  • 移除「_id」字尾(如果有)。
  • 將底線替換為空格(如果有)。
  • 將所有字詞轉為小寫,縮寫詞除外。
  • 將第一個字詞大寫。

可以透過將 :capitalize 選項設定為 false 來關閉第一個字詞的大寫(預設為 true)。

"name".humanize                         # => "Name"
"author_id".humanize                    # => "Author"
"author_id".humanize(capitalize: false) # => "author"
"comments_count".humanize               # => "Comments count"
"_id".humanize                          # => "Id"

如果「SSL」被定義為縮寫詞

'ssl_error'.humanize # => "SSL error"

輔助方法 full_messages 會使用 humanize 作為備用方案,以包含屬性名稱

def full_messages
  map { |attribute, message| full_message(attribute, message) }
end

def full_message
  # ...
  attr_name = attribute.to_s.tr('.', '_').humanize
  attr_name = @base.class.human_attribute_name(attribute, default: attr_name)
  # ...
end

5.12.14 foreign_key

方法 foreign_key 從類別名稱給出一個外來鍵欄位名稱。為此,它會取消模組化、加上底線,並加上 "_id"

"User".foreign_key           # => "user_id"
"InvoiceLine".foreign_key    # => "invoice_line_id"
"Admin::Session".foreign_key # => "session_id"

如果你不想要 "_id" 中的底線,請傳遞一個 false 參數

"User".foreign_key(false) # => "userid"

關聯使用這個方法來推斷外來鍵,例如 has_onehas_many 會這樣做

# active_record/associations.rb
foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key

5.12.15 upcase_first

方法 upcase_first 將接收者的第一個字母大寫

"employee salary".upcase_first # => "Employee salary"
"".upcase_first                # => ""

5.12.16 downcase_first

方法 downcase_first 將接收者的第一個字母轉換為小寫

"If I had read Alice in Wonderland".downcase_first # => "if I had read Alice in Wonderland"
"".downcase_first                                  # => ""

5.13 轉換

5.13.1 to_dateto_timeto_datetime

方法 to_dateto_timeto_datetime 基本上是 Date._parse 周圍的便利包裝器

"2010-07-27".to_date              # => Tue, 27 Jul 2010
"2010-07-27 23:37:00".to_time     # => 2010-07-27 23:37:00 +0200
"2010-07-27 23:37:00".to_datetime # => Tue, 27 Jul 2010 23:37:00 +0000

to_time 接收一個可選參數 :utc:local,以指示你想要時間的時區

"2010-07-27 23:42:00".to_time(:utc)   # => 2010-07-27 23:42:00 UTC
"2010-07-27 23:42:00".to_time(:local) # => 2010-07-27 23:42:00 +0200

預設為 :local

有關進一步詳細資訊,請參閱 Date._parse 的文件。

它們三個都會傳回空白接收者的 nil

6 Symbol 的擴充

6.1 starts_with?ends_with?

Active Support 定義 Symbol#start_with?Symbol#end_with? 的第三人稱別名

:foo.starts_with?("f") # => true
:foo.ends_with?("o")   # => true

7 Numeric 的擴充

7.1 位元組

所有數字都會回應這些方法

使用轉換因子 1024,傳回對應的位元組數量

2.kilobytes   # => 2048
3.megabytes   # => 3145728
3.5.gigabytes # => 3758096384.0
-4.exabytes   # => -4611686018427387904

單數形式別名如下,讓您可以說

1.megabyte # => 1048576

7.2 時間

下列方法

啟用時間宣告和計算,例如 45.minutes + 2.hours + 4.weeks。其傳回值也可以加到 Time 物件或從 Time 物件減去。

這些方法可以與 from_nowago 等結合使用,進行精確的日期計算。例如

# equivalent to Time.current.advance(days: 1)
1.day.from_now

# equivalent to Time.current.advance(weeks: 2)
2.weeks.from_now

# equivalent to Time.current.advance(days: 4, weeks: 5)
(4.days + 5.weeks).from_now

對於其他時間長度,請參閱 Integer 的時間延伸。

7.3 格式化

啟用以各種方式格式化數字。

產生數字作為電話號碼的字串表示

5551234.to_fs(:phone)
# => 555-1234
1235551234.to_fs(:phone)
# => 123-555-1234
1235551234.to_fs(:phone, area_code: true)
# => (123) 555-1234
1235551234.to_fs(:phone, delimiter: " ")
# => 123 555 1234
1235551234.to_fs(:phone, area_code: true, extension: 555)
# => (123) 555-1234 x 555
1235551234.to_fs(:phone, country_code: 1)
# => +1-123-555-1234

產生數字作為貨幣的字串表示

1234567890.50.to_fs(:currency)                 # => $1,234,567,890.50
1234567890.506.to_fs(:currency)                # => $1,234,567,890.51
1234567890.506.to_fs(:currency, precision: 3)  # => $1,234,567,890.506

產生數字作為百分比的字串表示

100.to_fs(:percentage)
# => 100.000%
100.to_fs(:percentage, precision: 0)
# => 100%
1000.to_fs(:percentage, delimiter: '.', separator: ',')
# => 1.000,000%
302.24398923423.to_fs(:percentage, precision: 5)
# => 302.24399%

產生數字以分隔形式的字串表示

12345678.to_fs(:delimited)                     # => 12,345,678
12345678.05.to_fs(:delimited)                  # => 12,345,678.05
12345678.to_fs(:delimited, delimiter: ".")     # => 12.345.678
12345678.to_fs(:delimited, delimiter: ",")     # => 12,345,678
12345678.05.to_fs(:delimited, separator: " ")  # => 12,345,678 05

產生數字以四捨五入到特定精度的字串表示

111.2345.to_fs(:rounded)                     # => 111.235
111.2345.to_fs(:rounded, precision: 2)       # => 111.23
13.to_fs(:rounded, precision: 5)             # => 13.00000
389.32314.to_fs(:rounded, precision: 0)      # => 389
111.2345.to_fs(:rounded, significant: true)  # => 111

產生數字作為人類可讀的位元組數量的字串表示

123.to_fs(:human_size)                  # => 123 Bytes
1234.to_fs(:human_size)                 # => 1.21 KB
12345.to_fs(:human_size)                # => 12.1 KB
1234567.to_fs(:human_size)              # => 1.18 MB
1234567890.to_fs(:human_size)           # => 1.15 GB
1234567890123.to_fs(:human_size)        # => 1.12 TB
1234567890123456.to_fs(:human_size)     # => 1.1 PB
1234567890123456789.to_fs(:human_size)  # => 1.07 EB

產生數字以人類可讀的文字的字串表示

123.to_fs(:human)               # => "123"
1234.to_fs(:human)              # => "1.23 Thousand"
12345.to_fs(:human)             # => "12.3 Thousand"
1234567.to_fs(:human)           # => "1.23 Million"
1234567890.to_fs(:human)        # => "1.23 Billion"
1234567890123.to_fs(:human)     # => "1.23 Trillion"
1234567890123456.to_fs(:human)  # => "1.23 Quadrillion"

8 Integer 的擴充

8.1 multiple_of?

方法 multiple_of? 測試整數是否為參數的倍數

2.multiple_of?(1) # => true
1.multiple_of?(2) # => false

8.2 ordinal

方法 ordinal 傳回與接收者整數對應的序數字尾字串

1.ordinal    # => "st"
2.ordinal    # => "nd"
53.ordinal   # => "rd"
2009.ordinal # => "th"
-21.ordinal  # => "st"
-134.ordinal # => "th"

8.3 ordinalize

方法 ordinalize 傳回與接收者整數對應的序數字串。相較之下,請注意 ordinal 方法傳回字尾字串。

1.ordinalize    # => "1st"
2.ordinalize    # => "2nd"
53.ordinalize   # => "53rd"
2009.ordinalize # => "2009th"
-21.ordinalize  # => "-21st"
-134.ordinalize # => "-134th"

8.4 時間

下列方法

啟用時間宣告和計算,例如 4.months + 5.years。其傳回值也可以加到 Time 物件或從 Time 物件減去。

這些方法可以與 from_nowago 等結合使用,進行精確的日期計算。例如

# equivalent to Time.current.advance(months: 1)
1.month.from_now

# equivalent to Time.current.advance(years: 2)
2.years.from_now

# equivalent to Time.current.advance(months: 4, years: 5)
(4.months + 5.years).from_now

對於其他持續時間,請參閱 Numeric 的時間擴充。

9 BigDecimal 的擴充

9.1 to_s

方法 to_s 提供預設規格化器「F」。這表示對 to_s 的簡單呼叫會產生浮點數表示法,而不是工程記號

BigDecimal(5.00, 6).to_s       # => "5.0"

工程記號仍受支援

BigDecimal(5.00, 6).to_s("e")  # => "0.5E1"

10 Enumerable 的擴充

10.1 index_by

方法 index_by 產生一個雜湊,其中可列舉元素由某個鍵索引。

它會反覆遍歷集合,並將每個元素傳遞給區塊。元素將由區塊傳回的值作為鍵

invoices.index_by(&:number)
# => {'2009-032' => <Invoice ...>, '2009-008' => <Invoice ...>, ...}

鍵通常應該是唯一的。如果區塊傳回不同元素的相同值,則不會為該鍵建立集合。最後一個項目將會獲勝。

10.2 index_with

方法 index_with 會產生一個雜湊,其鍵為可列舉元素。值為傳遞的預設值或在區塊中傳回。

post = Post.new(title: "hey there", body: "what's up?")

%i( title body ).index_with { |attr_name| post.public_send(attr_name) }
# => { title: "hey there", body: "what's up?" }

WEEKDAYS.index_with(Interval.all_day)
# => { monday: [ 0, 1440 ], … }

10.3 many?

方法 many?collection.size > 1 的簡寫

<% if pages.many? %>
  <%= pagination_links %>
<% end %>

如果給予一個選用的區塊,many? 只會考慮傳回 true 的那些元素

@see_more = videos.many? { |video| video.category == params[:category] }

10.4 exclude?

謂詞 exclude? 會測試給定的物件是否屬於集合。它是內建 include? 的否定。

to_visit << node if visited.exclude?(node)

10.5 including

方法 including 會傳回一個新的可列舉,其中包含傳遞的元素

[ 1, 2, 3 ].including(4, 5)                    # => [ 1, 2, 3, 4, 5 ]
["David", "Rafael"].including %w[ Aaron Todd ] # => ["David", "Rafael", "Aaron", "Todd"]

10.6 excluding

方法 excluding 會傳回一個可列舉的副本,其中已移除指定的元素

["David", "Rafael", "Aaron", "Todd"].excluding("Aaron", "Todd") # => ["David", "Rafael"]

excluding 別名為 without

10.7 pluck

方法 pluck 會從每個元素中萃取給定的鍵

[{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pluck(:name) # => ["David", "Rafael", "Aaron"]
[{ id: 1, name: "David" }, { id: 2, name: "Rafael" }].pluck(:id, :name) # => [[1, "David"], [2, "Rafael"]]

10.8 pick

方法 pick 會從第一個元素中萃取給定的鍵

[{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pick(:name) # => "David"
[{ id: 1, name: "David" }, { id: 2, name: "Rafael" }].pick(:id, :name) # => [1, "David"]

11 Array 的擴充

11.1 存取

Active Support 擴充了陣列的 API,以簡化某些存取方式。例如,to 會傳回傳遞索引處元素的子陣列

%w(a b c d).to(2) # => ["a", "b", "c"]
[].to(7)          # => []

類似地,from 會傳回從傳遞索引處元素到結尾的尾部。如果索引大於陣列長度,它會傳回一個空陣列。

%w(a b c d).from(2)  # => ["c", "d"]
%w(a b c d).from(10) # => []
[].from(0)           # => []

方法 including 傳回包含傳入元素的新陣列

[ 1, 2, 3 ].including(4, 5)          # => [ 1, 2, 3, 4, 5 ]
[ [ 0, 1 ] ].including([ [ 1, 0 ] ]) # => [ [ 0, 1 ], [ 1, 0 ] ]

方法 excluding 傳回排除指定元素的陣列副本。這是 Enumerable#excluding 的最佳化,它使用 Array#- 而不是 Array#reject 以提升效能。

["David", "Rafael", "Aaron", "Todd"].excluding("Aaron", "Todd") # => ["David", "Rafael"]
[ [ 0, 1 ], [ 1, 0 ] ].excluding([ [ 1, 0 ] ])                  # => [ [ 0, 1 ] ]

方法 secondthirdfourthfifth 傳回對應的元素,second_to_lastthird_to_last 也是如此(firstlast 是內建的)。多虧了社會智慧和建設性的全面性,forty_two 也可用。

%w(a b c d).third # => "c"
%w(a b c d).fifth # => nil

11.2 萃取

方法 extract! 移除並傳回區塊傳回 true 值的元素。如果未提供區塊,則傳回列舉器。

numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
odd_numbers = numbers.extract! { |number| number.odd? } # => [1, 3, 5, 7, 9]
numbers # => [0, 2, 4, 6, 8]

11.3 選項萃取

當方法呼叫中的最後一個引數是雜湊時,除了 &block 引數外,Ruby 允許您省略括號

User.exists?(email: params[:email])

這種語法糖在 Rails 中大量使用,以避免位置引數過多,而是提供模擬命名參數的介面。特別是使用尾隨雜湊來表示選項是非常慣用的。

如果一個方法預期變數數量的引數,但在其宣告中使用 *,那麼此類選項雜湊最終會成為引數陣列的項目,而在其中它會失去其角色。

在這些情況下,你可以使用 extract_options! 為選項雜湊提供區別對待。此方法會檢查陣列最後一個項目的類型。如果它是雜湊,它會將其彈出並傳回,否則它會傳回一個空的雜湊。

讓我們以 caches_action 控制器巨集的定義為例

def caches_action(*actions)
  return unless cache_configured?
  options = actions.extract_options!
  # ...
end

此方法接收任意數量的動作名稱,以及一個選項雜湊作為最後一個引數。透過呼叫 extract_options!,你可以取得選項雜湊,並以簡單且明確的方式從 actions 中移除它。

11.4 轉換

11.4.1 to_sentence

方法 to_sentence 會將陣列轉換成一個包含列舉其項目的句子的字串

%w().to_sentence                # => ""
%w(Earth).to_sentence           # => "Earth"
%w(Earth Wind).to_sentence      # => "Earth and Wind"
%w(Earth Wind Fire).to_sentence # => "Earth, Wind, and Fire"

此方法接受三個選項

  • :two_words_connector:用於長度為 2 的陣列。預設為「 and 」。
  • :words_connector:用於串接具有 3 個或更多元素的陣列的元素,最後兩個元素除外。預設為「, 」。
  • :last_word_connector:用於串接具有 3 個或更多元素的陣列的最後幾個項目。預設為「, and 」。

這些選項的預設值可以在地化,其金鑰為

選項 I18n 金鑰
:兩個字連接詞 support.array.兩個字連接詞
:字詞連接詞 support.array.字詞連接詞
:最後一個字詞連接詞 support.array.最後一個字詞連接詞

11.4.2 to_fs

方法 to_fs 預設會像 to_s 一樣運作。

不過,如果陣列包含會回應 id 的項目,則可以將符號 :db 傳遞為引數。這通常用於 Active Record 物件的集合。傳回的字串為

[].to_fs(:db)            # => "null"
[user].to_fs(:db)        # => "8456"
invoice.lines.to_fs(:db) # => "23,567,556,12"

上面範例中的整數假設來自對 id 的各別呼叫。

11.4.3 to_xml

方法 to_xml 會傳回一個包含其接收者的 XML 表示形式的字串

Contributor.limit(2).order(:rank).to_xml
# =>
# <?xml version="1.0" encoding="UTF-8"?>
# <contributors type="array">
#   <contributor>
#     <id type="integer">4356</id>
#     <name>Jeremy Kemper</name>
#     <rank type="integer">1</rank>
#     <url-id>jeremy-kemper</url-id>
#   </contributor>
#   <contributor>
#     <id type="integer">4404</id>
#     <name>David Heinemeier Hansson</name>
#     <rank type="integer">2</rank>
#     <url-id>david-heinemeier-hansson</url-id>
#   </contributor>
# </contributors>

為此,它會依序將 to_xml 傳送給每個項目,並在根節點下收集結果。所有項目都必須會回應 to_xml,否則會引發例外狀況。

預設情況下,根元素的名稱是第一個項目的類別名稱的底線和破折號複數形式,前提是其他元素屬於該類型(使用 is_a? 檢查),而且它們不是雜湊。在上面的範例中,那是「contributors」。

如果任何元素不屬於第一個元素的類型,則根節點會變成「objects」

[Contributor.first, Commit.first].to_xml
# =>
# <?xml version="1.0" encoding="UTF-8"?>
# <objects type="array">
#   <object>
#     <id type="integer">4583</id>
#     <name>Aaron Batalion</name>
#     <rank type="integer">53</rank>
#     <url-id>aaron-batalion</url-id>
#   </object>
#   <object>
#     <author>Joshua Peek</author>
#     <authored-timestamp type="datetime">2009-09-02T16:44:36Z</authored-timestamp>
#     <branch>origin/master</branch>
#     <committed-timestamp type="datetime">2009-09-02T16:44:36Z</committed-timestamp>
#     <committer>Joshua Peek</committer>
#     <git-show nil="true"></git-show>
#     <id type="integer">190316</id>
#     <imported-from-svn type="boolean">false</imported-from-svn>
#     <message>Kill AMo observing wrap_with_notifications since ARes was only using it</message>
#     <sha1>723a47bfb3708f968821bc969a9a3fc873a3ed58</sha1>
#   </object>
# </objects>

如果接收者是雜湊的陣列,則根元素預設也是「objects」

[{ a: 1, b: 2 }, { c: 3 }].to_xml
# =>
# <?xml version="1.0" encoding="UTF-8"?>
# <objects type="array">
#   <object>
#     <b type="integer">2</b>
#     <a type="integer">1</a>
#   </object>
#   <object>
#     <c type="integer">3</c>
#   </object>
# </objects>

如果集合是空的,則根元素預設為「nil-classes」。這是一個陷阱,例如,如果集合是空的,則上面貢獻者清單的根元素不會是「contributors」,而是「nil-classes」。你可以使用 :root 選項來確保根元素一致。

子節點的名稱預設為根節點的名稱單數化。在以上的範例中,我們看過「contributor」和「object」。選項 :children 可讓您設定這些節點名稱。

預設的 XML 建構器是 Builder::XmlMarkup 的新實體。您可以透過 :builder 選項設定您自己的建構器。此方法也接受 :dasherize 和相關選項,它們會轉送至建構器

Contributor.limit(2).order(:rank).to_xml(skip_types: true)
# =>
# <?xml version="1.0" encoding="UTF-8"?>
# <contributors>
#   <contributor>
#     <id>4356</id>
#     <name>Jeremy Kemper</name>
#     <rank>1</rank>
#     <url-id>jeremy-kemper</url-id>
#   </contributor>
#   <contributor>
#     <id>4404</id>
#     <name>David Heinemeier Hansson</name>
#     <rank>2</rank>
#     <url-id>david-heinemeier-hansson</url-id>
#   </contributor>
# </contributors>

11.5 封裝

方法 Array.wrap 會將其引數封裝在陣列中,除非它已經是陣列(或類似陣列)。

具體來說

  • 如果引數是 nil,則會傳回一個空陣列。
  • 否則,如果引數回應 to_ary,則會呼叫它,如果 to_ary 的值不是 nil,則會傳回它。
  • 否則,會傳回一個陣列,其中引數為其單一元素。
Array.wrap(nil)       # => []
Array.wrap([1, 2, 3]) # => [1, 2, 3]
Array.wrap(0)         # => [0]

此方法的目的類似於 Kernel#Array,但有一些差異

  • 如果引數回應 to_ary,則會呼叫此方法。如果傳回的值是 nilKernel#Array 會繼續嘗試 to_a,但 Array.wrap 會立即傳回一個陣列,其中引數為其單一元素。
  • 如果 to_ary 傳回的值既不是 nil 也不是 Array 物件,Kernel#Array 會引發例外,而 Array.wrap 則不會,它只會傳回該值。
  • 如果引數未回應 to_ary,它不會在引數上呼叫 to_a,它會傳回一個陣列,其中引數為其單一元素。

最後一點對於某些可列舉值特別值得比較

Array.wrap(foo: :bar) # => [{:foo=>:bar}]
Array(foo: :bar)      # => [[:foo, :bar]]

還有一個相關的慣用語法使用展開運算子

[*object]

11.6 複製

方法 Array#deep_dup 會遞迴複製自身和內部所有物件,使用 Active Support 方法 Object#deep_dup。它運作方式類似於 Array#map,將 deep_dup 方法傳送給內部的每個物件。

array = [1, [2, 3]]
dup = array.deep_dup
dup[1][2] = 4
array[1][2] == nil   # => true

11.7 分組

11.7.1 in_groups_of(number, fill_with = nil)

方法 in_groups_of 會將陣列分割成特定大小的連續群組。它會傳回一個包含群組的陣列

[1, 2, 3].in_groups_of(2) # => [[1, 2], [3, nil]]

或是在傳遞區塊時逐一產生

<% sample.in_groups_of(3) do |a, b, c| %>
  <tr>
    <td><%= a %></td>
    <td><%= b %></td>
    <td><%= c %></td>
  </tr>
<% end %>

第一個範例顯示 in_groups_of 如何使用盡可能多的 nil 元素填滿最後一個群組,以符合要求的大小。您可以使用第二個選用參數變更這個填補值

[1, 2, 3].in_groups_of(2, 0) # => [[1, 2], [3, 0]]

而且您可以透過傳遞 false 來告知方法不要填滿最後一個群組

[1, 2, 3].in_groups_of(2, false) # => [[1, 2], [3]]

因此 false 無法用作填補值。

11.7.2 in_groups(number, fill_with = nil)

方法 in_groups 會將陣列分割成特定數量的群組。該方法會傳回一個包含群組的陣列

%w(1 2 3 4 5 6 7).in_groups(3)
# => [["1", "2", "3"], ["4", "5", nil], ["6", "7", nil]]

或是在傳遞區塊時逐一產生

%w(1 2 3 4 5 6 7).in_groups(3) { |group| p group }
["1", "2", "3"]
["4", "5", nil]
["6", "7", nil]

上述範例顯示 in_groups 會視需要使用尾隨 nil 元素填滿一些群組。一個群組最多只能取得這些額外元素中的其中一個,也就是最右邊的一個(如果有)。而且擁有這些元素的群組永遠都是最後面的那些。

您可以使用第二個選用參數變更這個填補值

%w(1 2 3 4 5 6 7).in_groups(3, "0")
# => [["1", "2", "3"], ["4", "5", "0"], ["6", "7", "0"]]

而且您可以透過傳遞 false 來告知方法不要填滿較小的群組

%w(1 2 3 4 5 6 7).in_groups(3, false)
# => [["1", "2", "3"], ["4", "5"], ["6", "7"]]

因此 false 無法用作填補值。

11.7.3 split(value = nil)

方法 split 會以分隔符號將陣列分割,並傳回分割後的區塊。

如果傳入區塊,分隔符號會是陣列中讓區塊傳回 true 的元素

(-5..5).to_a.split { |i| i.multiple_of?(4) }
# => [[-5], [-3, -2, -1], [1, 2, 3], [5]]

否則,接收到的引數值(預設為 nil)會是分隔符號

[0, 1, -5, 1, 1, "foo", "bar"].split(1)
# => [[0], [-5], [], ["foo", "bar"]]

請注意在先前的範例中,連續的分隔符號會產生空陣列。

12 Hash 的擴充

12.1 轉換

12.1.1 to_xml

方法 to_xml 會傳回一個字串,其中包含接收者的 XML 表示。

{ foo: 1, bar: 2 }.to_xml
# =>
# <?xml version="1.0" encoding="UTF-8"?>
# <hash>
#   <foo type="integer">1</foo>
#   <bar type="integer">2</bar>
# </hash>

為此,此方法會迴圈處理成對,並根據建立節點。給定成對的 keyvalue

  • 如果 value 是雜湊,則會使用 key 作為 :root 進行遞迴呼叫。

  • 如果 value 是陣列,則會使用 key 作為 :root,並將 key 單數化為 :children 進行遞迴呼叫。

  • 如果 value 是可呼叫物件,則它必須預期一個或兩個引數。根據引數個數,可呼叫物件會以 options 雜湊作為第一個引數(其中 key 作為 :root),並將 key 單數化為第二個引數來呼叫。它的傳回值會成為新的節點。

  • 如果 value 會回應 to_xml,則會使用 key 作為 :root 來呼叫此方法。

  • 否則,會建立一個節點,其中 key 作為標籤,而 value 的字串表示作為文字節點。如果 valuenil,則會加入一個屬性「nil」,並將其設定為「true」。除非選項 :skip_types 存在且為 true,否則也會根據下列對應加入一個屬性「type」

XML_TYPE_NAMES = {
  "Symbol"     => "symbol",
  "Integer"    => "integer",
  "BigDecimal" => "decimal",
  "Float"      => "float",
  "TrueClass"  => "boolean",
  "FalseClass" => "boolean",
  "Date"       => "date",
  "DateTime"   => "datetime",
  "Time"       => "datetime"
}

預設根節點為「hash」,但這可透過 :root 選項進行設定。

預設 XML 建構器為 Builder::XmlMarkup 的新實例。你可以使用 :builder 選項設定自己的建構器。此方法也接受 :dasherize 等選項,這些選項會轉發至建構器。

12.2 合併

Ruby 有內建方法 Hash#merge,可合併兩個 hash

{ a: 1, b: 1 }.merge(a: 0, c: 2)
# => {:a=>0, :b=>1, :c=>2}

Active Support 定義了更多合併 hash 的方式,可能會很方便。

12.2.1 reverse_mergereverse_merge!

如果發生衝突,merge 中引數 hash 中的鍵會獲勝。你可以使用此慣用語以簡潔的方式支援具有預設值的選項 hash

options = { length: 30, omission: "..." }.merge(options)

如果你偏好此替代標記法,Active Support 定義了 reverse_merge

options = options.reverse_merge(length: 30, omission: "...")

以及會就地執行合併的驚嘆號版本 reverse_merge!

options.reverse_merge!(length: 30, omission: "...")

請注意,reverse_merge! 可能會變更呼叫者中的 hash,這可能不是好主意。

12.2.2 reverse_update

方法 reverse_updatereverse_merge! 的別名,如上所述。

請注意,reverse_update 沒有驚嘆號。

12.2.3 deep_mergedeep_merge!

如前一個範例所示,如果兩個 hash 中都找到某個鍵,引數中的那個鍵的值會獲勝。

Active Support 定義 Hash#deep_merge。在深度合併中,如果在兩個雜湊中都找到一個鍵,並且它們的值又分別是雜湊,那麼它們的 合併 就會成為結果雜湊中的值

{ a: { b: 1 } }.deep_merge(a: { c: 2 })
# => {:a=>{:b=>1, :c=>2}}

方法 deep_merge! 就地執行深度合併。

12.3 深度複製

方法 Hash#deep_dup 使用 Active Support 方法 Object#deep_dup 遞迴複製自身以及所有鍵和值。它就像 Enumerator#each_with_object,會將 deep_dup 方法傳送給內部的每對。

hash = { a: 1, b: { c: 2, d: [3, 4] } }

dup = hash.deep_dup
dup[:b][:e] = 5
dup[:b][:d] << 5

hash[:b][:e] == nil      # => true
hash[:b][:d] == [3, 4]   # => true

12.4 使用鍵

12.4.1 exceptexcept!

方法 except 會傳回一個雜湊,其中已移除引數清單中的鍵(如果存在)

{ a: 1, b: 2 }.except(:a) # => {:b=>2}

如果接收器回應 convert_key,則會對每個引數呼叫該方法。這讓 except 能夠與具有無差別存取權限的雜湊順利互動

{ a: 1 }.with_indifferent_access.except(:a)  # => {}
{ a: 1 }.with_indifferent_access.except("a") # => {}

還有驚嘆號變體 except!,它會就地移除鍵。

12.4.2 stringify_keysstringify_keys!

方法 stringify_keys 會傳回一個雜湊,其中包含接收器中鍵的字串化版本。它會對這些鍵傳送 to_s

{ nil => nil, 1 => 1, a: :a }.stringify_keys
# => {"" => nil, "1" => 1, "a" => :a}

如果發生鍵衝突,值將會是最近插入雜湊中的值

{ "a" => 1, a: 2 }.stringify_keys
# The result will be
# => {"a"=>2}

這個方法可能很有用,例如可以輕鬆接受符號和字串作為選項。例如,ActionView::Helpers::FormHelper 定義

def to_check_box_tag(options = {}, checked_value = "1", unchecked_value = "0")
  options = options.stringify_keys
  options["type"] = "checkbox"
  # ...
end

第二行可以安全地存取「type」鍵,並讓使用者傳遞 :type 或「type」。

還有驚嘆號變體 stringify_keys!,它會就地將鍵字串化。

此外,可以使用 deep_stringify_keysdeep_stringify_keys! 將給定雜湊和嵌套在其中的所有雜湊中的所有鍵字串化。結果範例如下

{ nil => nil, 1 => 1, nested: { a: 3, 5 => 5 } }.deep_stringify_keys
# => {""=>nil, "1"=>1, "nested"=>{"a"=>3, "5"=>5}}

12.4.3 symbolize_keyssymbolize_keys!

方法 symbolize_keys 傳回一個雜湊,其中包含接收器中鍵的符號版本(如果可能)。它透過傳送 to_sym 給它們來執行此操作

{ nil => nil, 1 => 1, "a" => "a" }.symbolize_keys
# => {nil=>nil, 1=>1, :a=>"a"}

請注意,在先前的範例中,只有一個鍵被符號化。

如果發生鍵衝突,值將會是最近插入雜湊中的值

{ "a" => 1, a: 2 }.symbolize_keys
# => {:a=>2}

此方法可能很有用,例如可以輕鬆地接受符號和字串作為選項。例如,ActionText::TagHelper 定義

def rich_text_area_tag(name, value = nil, options = {})
  options = options.symbolize_keys

  options[:input] ||= "trix_input_#{ActionText::TagHelper.id += 1}"
  # ...
end

第三行可以安全地存取 :input 鍵,並讓使用者傳遞 :input 或「input」。

還有一個驚嘆號變體 symbolize_keys!,它會就地符號化鍵。

此外,可以使用 deep_symbolize_keysdeep_symbolize_keys! 將給定雜湊和嵌套在其中的所有雜湊中的所有鍵符號化。結果範例如下

{ nil => nil, 1 => 1, "nested" => { "a" => 3, 5 => 5 } }.deep_symbolize_keys
# => {nil=>nil, 1=>1, nested:{a:3, 5=>5}}

12.4.4 to_optionsto_options!

方法 to_optionsto_options! 分別是 symbolize_keyssymbolize_keys! 的別名。

12.4.5 assert_valid_keys

方法 assert_valid_keys 接收任意數量的引數,並檢查接收器是否有任何鍵不在該清單之外。如果有的話,會引發 ArgumentError

{ a: 1 }.assert_valid_keys(:a)  # passes
{ a: 1 }.assert_valid_keys("a") # ArgumentError

例如,Active Record 在建立關聯時不接受未知選項。它透過 assert_valid_keys 來實作該控制。

12.5 使用值

12.5.1 deep_transform_valuesdeep_transform_values!

方法 deep_transform_values 會傳回一個新的雜湊,其中所有值都已透過區塊運算轉換。這包含根雜湊和所有巢狀雜湊和陣列中的值。

hash = { person: { name: 'Rob', age: '28' } }

hash.deep_transform_values { |value| value.to_s.upcase }
# => {person: {name: "ROB", age: "28"}}

另外還有驚嘆號變體 deep_transform_values!,它會透過區塊運算破壞性地轉換所有值。

12.6 切片

方法 slice! 會將雜湊替換為僅包含指定鍵,並傳回一個包含已移除鍵/值對的雜湊。

hash = { a: 1, b: 2 }
rest = hash.slice!(:a) # => {:b=>2}
hash                   # => {:a=>1}

12.7 萃取

方法 extract! 會移除並傳回與指定鍵相符的鍵/值對。

hash = { a: 1, b: 2 }
rest = hash.extract!(:a) # => {:a=>1}
hash                     # => {:b=>2}

方法 extract! 會傳回與接收者相同的雜湊子類別。

hash = { a: 1, b: 2 }.with_indifferent_access
rest = hash.extract!(:a).class
# => ActiveSupport::HashWithIndifferentAccess

12.8 隨意存取

方法 with_indifferent_access 會從其接收者傳回一個 ActiveSupport::HashWithIndifferentAccess

{ a: 1 }.with_indifferent_access["a"] # => 1

13 Regexp 的延伸

13.1 multiline?

方法 multiline? 會指出正規表示式是否已設定 /m 旗標,亦即點是否與換行符號相符。

%r{.}.multiline?  # => false
%r{.}m.multiline? # => true

Regexp.new('.').multiline?                    # => false
Regexp.new('.', Regexp::MULTILINE).multiline? # => true

Rails 在單一位置使用這個方法,也在路由程式碼中。路由需求不允許多行正規表示式,而這個旗標可輕鬆執行該限制。

def verify_regexp_requirements(requirements)
  # ...
  if requirement.multiline?
    raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}"
  end
  # ...
end

14 Range 的擴充

14.1 to_fs

Active Support 將 Range#to_fs 定義為 to_s 的替代方案,它會理解一個可選的格式參數。在撰寫本文時,唯一受支援的非預設格式為 :db

(Date.today..Date.tomorrow).to_fs
# => "2009-10-25..2009-10-26"

(Date.today..Date.tomorrow).to_fs(:db)
# => "BETWEEN '2009-10-25' AND '2009-10-26'"

正如範例所示,:db 格式會產生一個 BETWEEN SQL 子句。Active Record 在其條件中的範圍值支援中使用它。

14.2 ===include?

方法 Range#===Range#include? 會說明某個值是否落在給定實例的兩端之間

(2..3).include?(Math::E) # => true

Active Support 擴充了這些方法,以便參數可以依序是另一個範圍。在這種情況下,我們會測試參數範圍的兩端是否屬於接收者本身

(1..10) === (3..7)  # => true
(1..10) === (0..7)  # => false
(1..10) === (3..11) # => false
(1...9) === (3..9)  # => false

(1..10).include?(3..7)  # => true
(1..10).include?(0..7)  # => false
(1..10).include?(3..11) # => false
(1...9).include?(3..9)  # => false

14.3 overlap?

方法 Range#overlap? 會說明任何兩個給定的範圍是否有非空交集

(1..10).overlap?(7..11)  # => true
(1..10).overlap?(0..7)   # => true
(1..10).overlap?(11..27) # => false

15 Date 的擴充

15.1 計算

下列計算方法在 1582 年 10 月有邊緣案例,因為 5..14 日根本不存在。為了簡潔起見,本指南不會記錄它們在那些日子周圍的行為,但足以說明它們會執行您預期的動作。也就是說,Date.new(1582, 10, 4).tomorrow 會傳回 Date.new(1582, 10, 15),以此類推。請在 Active Support 測試套件中查看 test/core_ext/date_ext_test.rb 以了解預期的行為。

15.1.1 Date.current

Active Support 定義 Date.current 為目前時區的今天。這類似於 Date.today,但會遵循使用者時區(如果已定義)。它也定義了 Date.yesterdayDate.tomorrow,以及實例謂詞 past?today?tomorrow?next_day?yesterday?prev_day?future?on_weekday?on_weekend?,所有這些都與 Date.current 相關。

使用遵循使用者時區的方法進行日期比較時,請務必使用 Date.current,而不是 Date.today。在某些情況下,使用者時區可能比系統時區(Date.today 預設使用的時區)更早。這表示 Date.today 可能等於 Date.yesterday

15.1.2 指定日期

15.1.2.1 beginning_of_weekend_of_week

方法 beginning_of_weekend_of_week 分別傳回星期開始和結束的日期。星期預設從星期一開始,但可以傳遞參數、設定執行緒本機的 Date.beginning_of_weekconfig.beginning_of_week 來變更。

d = Date.new(2010, 5, 8)     # => Sat, 08 May 2010
d.beginning_of_week          # => Mon, 03 May 2010
d.beginning_of_week(:sunday) # => Sun, 02 May 2010
d.end_of_week                # => Sun, 09 May 2010
d.end_of_week(:sunday)       # => Sat, 08 May 2010

beginning_of_week 別名為 at_beginning_of_week,而 end_of_week 別名為 at_end_of_week

15.1.2.2 mondaysunday

方法 mondaysunday 分別傳回上一個星期一和下一個星期日的日期。

d = Date.new(2010, 5, 8)     # => Sat, 08 May 2010
d.monday                     # => Mon, 03 May 2010
d.sunday                     # => Sun, 09 May 2010

d = Date.new(2012, 9, 10)    # => Mon, 10 Sep 2012
d.monday                     # => Mon, 10 Sep 2012

d = Date.new(2012, 9, 16)    # => Sun, 16 Sep 2012
d.sunday                     # => Sun, 16 Sep 2012
15.1.2.3 prev_weeknext_week

方法 next_week 接收一個英文星期名稱的符號(預設為執行緒本機的 Date.beginning_of_weekconfig.beginning_of_week,或 :monday),並傳回對應於該星期的日期。

d = Date.new(2010, 5, 9) # => Sun, 09 May 2010
d.next_week              # => Mon, 10 May 2010
d.next_week(:saturday)   # => Sat, 15 May 2010

方法 prev_week 類似

d.prev_week              # => Mon, 26 Apr 2010
d.prev_week(:saturday)   # => Sat, 01 May 2010
d.prev_week(:friday)     # => Fri, 30 Apr 2010

prev_week 別名為 last_week

當設定 Date.beginning_of_weekconfig.beginning_of_week 時,next_weekprev_week 都會如預期般運作。

15.1.2.4 beginning_of_monthend_of_month

方法 beginning_of_monthend_of_month 傳回月份開始和結束的日期

d = Date.new(2010, 5, 9) # => Sun, 09 May 2010
d.beginning_of_month     # => Sat, 01 May 2010
d.end_of_month           # => Mon, 31 May 2010

beginning_of_month 別名為 at_beginning_of_month,而 end_of_month 別名為 at_end_of_month

15.1.2.5 quarterbeginning_of_quarterend_of_quarter

方法 quarter 傳回接收者日曆年的季度

d = Date.new(2010, 5, 9) # => Sun, 09 May 2010
d.quarter                # => 2

方法 beginning_of_quarterend_of_quarter 傳回接收者日曆年季度的開始和結束日期

d = Date.new(2010, 5, 9) # => Sun, 09 May 2010
d.beginning_of_quarter   # => Thu, 01 Apr 2010
d.end_of_quarter         # => Wed, 30 Jun 2010

beginning_of_quarter 別名為 at_beginning_of_quarter,而 end_of_quarter 別名為 at_end_of_quarter

15.1.2.6 beginning_of_yearend_of_year

方法 beginning_of_yearend_of_year 傳回該年的開始和結束日期

d = Date.new(2010, 5, 9) # => Sun, 09 May 2010
d.beginning_of_year      # => Fri, 01 Jan 2010
d.end_of_year            # => Fri, 31 Dec 2010

beginning_of_year 別名為 at_beginning_of_year,而 end_of_year 別名為 at_end_of_year

15.1.3 其他日期計算

15.1.3.1 years_agoyears_since

方法 years_ago 接收年數,並傳回該年數前同一天的日期

date = Date.new(2010, 6, 7)
date.years_ago(10) # => Wed, 07 Jun 2000

years_since 則會向前移動時間

date = Date.new(2010, 6, 7)
date.years_since(10) # => Sun, 07 Jun 2020

如果沒有該日期,則會傳回對應月份的最後一天

Date.new(2012, 2, 29).years_ago(3)     # => Sat, 28 Feb 2009
Date.new(2012, 2, 29).years_since(3)   # => Sat, 28 Feb 2015

last_year#years_ago(1) 的簡寫。

15.1.3.2 months_agomonths_since

方法 months_agomonths_since 類似地適用於月份

Date.new(2010, 4, 30).months_ago(2)   # => Sun, 28 Feb 2010
Date.new(2010, 4, 30).months_since(2) # => Wed, 30 Jun 2010

如果沒有該日期,則會傳回對應月份的最後一天

Date.new(2010, 4, 30).months_ago(2)    # => Sun, 28 Feb 2010
Date.new(2009, 12, 31).months_since(2) # => Sun, 28 Feb 2010

last_month#months_ago(1) 的簡寫。

15.1.3.3 weeks_ago

方法 weeks_ago 類似地適用於星期

Date.new(2010, 5, 24).weeks_ago(1)    # => Mon, 17 May 2010
Date.new(2010, 5, 24).weeks_ago(2)    # => Mon, 10 May 2010
15.1.3.4 advance

跳到其他日期最通用的方法是 advance。此方法接收一個雜湊,其鍵為 :years:months:weeks:days,並傳回一個日期,其進位與目前的鍵所指示的一樣多

date = Date.new(2010, 6, 6)
date.advance(years: 1, weeks: 2)  # => Mon, 20 Jun 2011
date.advance(months: 2, days: -2) # => Wed, 04 Aug 2010

請注意,在先前的範例中,增量可能是負數。

15.1.4 變更組成部分

方法 change 允許您取得一個新的日期,其與接收者相同,但年份、月份或日期除外

Date.new(2010, 12, 23).change(year: 2011, month: 11)
# => Wed, 23 Nov 2011

此方法不允許不存在的日期,如果變更無效,則會引發 ArgumentError

Date.new(2010, 1, 31).change(month: 2)
# => ArgumentError: invalid date

15.1.5 持續時間

Duration 物件可以加到日期或從日期中減去

d = Date.current
# => Mon, 09 Aug 2010
d + 1.year
# => Tue, 09 Aug 2011
d - 3.hours
# => Sun, 08 Aug 2010 21:00:00 UTC +00:00

它們轉換為對 sinceadvance 的呼叫。例如,在這裡,我們在日曆改革中取得正確的跳躍

Date.new(1582, 10, 4) + 1.day
# => Fri, 15 Oct 1582

15.1.6 時間戳記

以下方法會傳回 Time 物件(如果可能),否則傳回 DateTime。如果已設定,它們會尊重使用者時區。

15.1.6.1 beginning_of_dayend_of_day

方法 beginning_of_day 傳回一天開始(00:00:00)的時間戳記

date = Date.new(2010, 6, 7)
date.beginning_of_day # => Mon Jun 07 00:00:00 +0200 2010

方法 end_of_day 會傳回一天結束的時間戳記 (23:59:59)

date = Date.new(2010, 6, 7)
date.end_of_day # => Mon Jun 07 23:59:59 +0200 2010

beginning_of_day 別名為 at_beginning_of_daymidnightat_midnight

15.1.6.2 beginning_of_hourend_of_hour

方法 beginning_of_hour 會傳回一小時開始的時間戳記 (hh:00:00)

date = DateTime.new(2010, 6, 7, 19, 55, 25)
date.beginning_of_hour # => Mon Jun 07 19:00:00 +0200 2010

方法 end_of_hour 會傳回一小時結束的時間戳記 (hh:59:59)

date = DateTime.new(2010, 6, 7, 19, 55, 25)
date.end_of_hour # => Mon Jun 07 19:59:59 +0200 2010

beginning_of_hour 別名為 at_beginning_of_hour

15.1.6.3 beginning_of_minuteend_of_minute

方法 beginning_of_minute 會傳回一分鐘開始的時間戳記 (hh:mm:00)

date = DateTime.new(2010, 6, 7, 19, 55, 25)
date.beginning_of_minute # => Mon Jun 07 19:55:00 +0200 2010

方法 end_of_minute 會傳回一分鐘結束的時間戳記 (hh:mm:59)

date = DateTime.new(2010, 6, 7, 19, 55, 25)
date.end_of_minute # => Mon Jun 07 19:55:59 +0200 2010

beginning_of_minute 別名為 at_beginning_of_minute

beginning_of_hourend_of_hourbeginning_of_minuteend_of_minute 是針對 TimeDateTime 實作的,不適用於 Date,因為在 Date 執行個體上要求一小時或一分鐘的開始或結束時間沒有意義。

15.1.6.4 agosince

方法 ago 接收一個數字作為參數,並回傳一個時間戳記,表示從午夜開始往前多少秒

date = Date.current # => Fri, 11 Jun 2010
date.ago(1)         # => Thu, 10 Jun 2010 23:59:59 EDT -04:00

類似地,since 則往前移動

date = Date.current # => Fri, 11 Jun 2010
date.since(1)       # => Fri, 11 Jun 2010 00:00:01 EDT -04:00

16 個 DateTime 的擴充

DateTime 不會意識到 DST 規則,因此當 DST 變更時,這些方法中有些會有臨界狀況。例如 seconds_since_midnight 可能不會在這種情況下回傳實際的數量。

16.1 計算

DateTime 類別是 Date 的子類別,因此透過載入 active_support/core_ext/date/calculations.rb,你可以繼承這些方法及其別名,但它們總是會回傳日期時間。

以下方法已重新實作,因此你不需要為這些方法載入 active_support/core_ext/date/calculations.rb

另一方面,advancechange 也已定義,並支援更多選項,它們的文件如下。

以下方法僅在 active_support/core_ext/date_time/calculations.rb 中實作,因為它們只有在與 DateTime 執行個體搭配使用時才有意義

16.1.1 命名日期時間

16.1.1.1 DateTime.current

Active Support 定義 DateTime.current 類似於 Time.now.to_datetime,但會尊重使用者時區(若有定義)。實例謂詞 past?future? 相對於 DateTime.current 定義。

16.1.2 其他擴充

16.1.2.1 seconds_since_midnight

方法 seconds_since_midnight 傳回自午夜以來的秒數

now = DateTime.current     # => Mon, 07 Jun 2010 20:26:36 +0000
now.seconds_since_midnight # => 73596
16.1.2.2 utc

方法 utc 提供接收器中以 UTC 表示的相同日期時間。

now = DateTime.current # => Mon, 07 Jun 2010 19:27:52 -0400
now.utc                # => Mon, 07 Jun 2010 23:27:52 +0000

此方法也別名為 getutc

16.1.2.3 utc?

謂詞 utc? 表示接收器是否以 UTC 作為其時區

now = DateTime.now # => Mon, 07 Jun 2010 19:30:47 -0400
now.utc?           # => false
now.utc.utc?       # => true
16.1.2.4 advance

跳轉至另一日期時間最通用的方法是 advance。此方法接收一個雜湊,其鍵為 :years:months:weeks:days:hours:minutes:seconds,並傳回一個日期時間,其進度與目前鍵值指示的一樣多。

d = DateTime.current
# => Thu, 05 Aug 2010 11:33:31 +0000
d.advance(years: 1, months: 1, days: 1, hours: 1, minutes: 1, seconds: 1)
# => Tue, 06 Sep 2011 12:34:32 +0000

此方法會先計算目標日期,將 :years:months:weeks:days 傳遞至上述文件中說明的 Date#advance。之後,它會呼叫 since 調整時間,並提供要進度的秒數。此順序很重要,不同的順序會在某些邊界情況中產生不同的日期時間。Date#advance 中的範例適用,我們可以延伸它以顯示與時間位元相關的順序相關性。

如果我們先移動日期位元(也有一個處理的相對順序,如前所述),然後再移動時間位元,我們會得到以下運算

d = DateTime.new(2010, 2, 28, 23, 59, 59)
# => Sun, 28 Feb 2010 23:59:59 +0000
d.advance(months: 1, seconds: 1)
# => Mon, 29 Mar 2010 00:00:00 +0000

但是如果我們以相反的方式計算它們,結果就會不同

d.advance(seconds: 1).advance(months: 1)
# => Thu, 01 Apr 2010 00:00:00 +0000

由於 DateTime 不支援 DST,因此您可能會在一個不存在的時間點結束,而沒有任何警告或錯誤告訴您。

16.1.3 變更組成部分

方法 change 允許您取得一個新的日期時間,它與接收者相同,但給定的選項除外,這些選項可能包括 :year:month:day:hour:min:sec:offset:start

now = DateTime.current
# => Tue, 08 Jun 2010 01:56:22 +0000
now.change(year: 2011, offset: Rational(-6, 24))
# => Wed, 08 Jun 2011 01:56:22 -0600

如果小時歸零,則分鐘和秒數也會歸零(除非它們有給定的值)

now.change(hour: 0)
# => Tue, 08 Jun 2010 00:00:00 +0000

類似地,如果分鐘歸零,則秒數也會歸零(除非它有給定的值)

now.change(min: 0)
# => Tue, 08 Jun 2010 01:00:00 +0000

此方法不允許不存在的日期,如果變更無效,則會引發 ArgumentError

DateTime.current.change(month: 2, day: 30)
# => ArgumentError: invalid date

16.1.4 持續時間

Duration 物件可以加到日期時間或從日期時間中減去

now = DateTime.current
# => Mon, 09 Aug 2010 23:15:17 +0000
now + 1.year
# => Tue, 09 Aug 2011 23:15:17 +0000
now - 1.week
# => Mon, 02 Aug 2010 23:15:17 +0000

它們轉換為對 sinceadvance 的呼叫。例如,在這裡,我們在日曆改革中取得正確的跳躍

DateTime.new(1582, 10, 4, 23) + 1.hour
# => Fri, 15 Oct 1582 00:00:00 +0000

17 對 Time 的擴充

17.1 計算

它們是類似的。請參閱它們上面的文件,並考慮以下差異

  • change 接受一個額外的 :usec 選項。
  • Time 了解 DST,因此您可以在其中取得正確的 DST 計算,如下所示
Time.zone_default
# => #<ActiveSupport::TimeZone:0x7f73654d4f38 @utc_offset=nil, @name="Madrid", ...>

# In Barcelona, 2010/03/28 02:00 +0100 becomes 2010/03/28 03:00 +0200 due to DST.
t = Time.local(2010, 3, 28, 1, 59, 59)
# => Sun Mar 28 01:59:59 +0100 2010
t.advance(seconds: 1)
# => Sun Mar 28 03:00:00 +0200 2010
  • 如果 sinceago 跳轉到無法用 Time 表示的時間,則會傳回一個 DateTime 物件。

17.1.1 Time.current

Active Support 定義 Time.current 為目前時區的今日。這類似於 Time.now,但它會尊重使用者時區(如果已定義)。它也定義了實例謂詞 past?today?tomorrow?next_day?yesterday?prev_day?future?,它們都相對於 Time.current

在使用會尊重使用者時區的方法進行時間比較時,請務必使用 Time.current 而不是 Time.now。在某些情況下,使用者時區可能比系統時區(Time.now 預設使用的)更進步。這表示 Time.now.to_date 可能等於 Date.yesterday

17.1.2 all_dayall_weekall_monthall_quarterall_year

方法 all_day 會傳回一個範圍,代表目前時間的整天。

now = Time.current
# => Mon, 09 Aug 2010 23:20:05 UTC +00:00
now.all_day
# => Mon, 09 Aug 2010 00:00:00 UTC +00:00..Mon, 09 Aug 2010 23:59:59 UTC +00:00

類似的,all_weekall_monthall_quarterall_year 都用於產生時間範圍。

now = Time.current
# => Mon, 09 Aug 2010 23:20:05 UTC +00:00
now.all_week
# => Mon, 09 Aug 2010 00:00:00 UTC +00:00..Sun, 15 Aug 2010 23:59:59 UTC +00:00
now.all_week(:sunday)
# => Sun, 16 Sep 2012 00:00:00 UTC +00:00..Sat, 22 Sep 2012 23:59:59 UTC +00:00
now.all_month
# => Sat, 01 Aug 2010 00:00:00 UTC +00:00..Tue, 31 Aug 2010 23:59:59 UTC +00:00
now.all_quarter
# => Thu, 01 Jul 2010 00:00:00 UTC +00:00..Thu, 30 Sep 2010 23:59:59 UTC +00:00
now.all_year
# => Fri, 01 Jan 2010 00:00:00 UTC +00:00..Fri, 31 Dec 2010 23:59:59 UTC +00:00

17.1.3 prev_daynext_day

prev_daynext_day 傳回前一天或下一天的時間

t = Time.new(2010, 5, 8) # => 2010-05-08 00:00:00 +0900
t.prev_day               # => 2010-05-07 00:00:00 +0900
t.next_day               # => 2010-05-09 00:00:00 +0900

17.1.4 prev_monthnext_month

prev_monthnext_month 傳回前一個月或下一個月同一天的時間

t = Time.new(2010, 5, 8) # => 2010-05-08 00:00:00 +0900
t.prev_month             # => 2010-04-08 00:00:00 +0900
t.next_month             # => 2010-06-08 00:00:00 +0900

如果沒有該日期,則會傳回對應月份的最後一天

Time.new(2000, 5, 31).prev_month # => 2000-04-30 00:00:00 +0900
Time.new(2000, 3, 31).prev_month # => 2000-02-29 00:00:00 +0900
Time.new(2000, 5, 31).next_month # => 2000-06-30 00:00:00 +0900
Time.new(2000, 1, 31).next_month # => 2000-02-29 00:00:00 +0900

17.1.5 prev_yearnext_year

prev_yearnext_year 傳回前一年或下一年同一天/月時間

t = Time.new(2010, 5, 8) # => 2010-05-08 00:00:00 +0900
t.prev_year              # => 2009-05-08 00:00:00 +0900
t.next_year              # => 2011-05-08 00:00:00 +0900

如果日期是閏年的 2 月 29 日,您將取得 28 日

t = Time.new(2000, 2, 29) # => 2000-02-29 00:00:00 +0900
t.prev_year               # => 1999-02-28 00:00:00 +0900
t.next_year               # => 2001-02-28 00:00:00 +0900

17.1.6 prev_quarternext_quarter

prev_quarternext_quarter 傳回前一個季度或下一個季度同一天的日期

t = Time.local(2010, 5, 8) # => 2010-05-08 00:00:00 +0300
t.prev_quarter             # => 2010-02-08 00:00:00 +0200
t.next_quarter             # => 2010-08-08 00:00:00 +0300

如果沒有該日期,則會傳回對應月份的最後一天

Time.local(2000, 7, 31).prev_quarter  # => 2000-04-30 00:00:00 +0300
Time.local(2000, 5, 31).prev_quarter  # => 2000-02-29 00:00:00 +0200
Time.local(2000, 10, 31).prev_quarter # => 2000-07-31 00:00:00 +0300
Time.local(2000, 11, 31).next_quarter # => 2001-03-01 00:00:00 +0200

prev_quarter 別名為 last_quarter

17.2 時間建構函數

Active Support 定義 Time.currentTime.zone.now,如果定義了使用者時區,則回退到 Time.now

Time.zone_default
# => #<ActiveSupport::TimeZone:0x7f73654d4f38 @utc_offset=nil, @name="Madrid", ...>
Time.current
# => Fri, 06 Aug 2010 17:11:58 CEST +02:00

類似於 DateTime,謂詞 past?future? 相對於 Time.current

如果要建構的時間超出執行時間平台中 Time 所支援的範圍,將會捨棄微秒並傳回 DateTime 物件。

17.2.1 持續時間

Duration 物件可以新增至時間物件或從時間物件中減去

now = Time.current
# => Mon, 09 Aug 2010 23:20:05 UTC +00:00
now + 1.year
# => Tue, 09 Aug 2011 23:21:11 UTC +00:00
now - 1.week
# => Mon, 02 Aug 2010 23:21:11 UTC +00:00

它們轉換為對 sinceadvance 的呼叫。例如,在這裡,我們在日曆改革中取得正確的跳躍

Time.utc(1582, 10, 3) + 5.days
# => Mon Oct 18 00:00:00 UTC 1582

18 File 的延伸

18.1 atomic_write

使用類別方法 File.atomic_write,您可以將內容寫入檔案,讓任何讀取者都無法看到寫入一半的內容。

檔案名稱會傳遞為引數,而方法會產生一個開啟用於寫入的檔案控制代碼。一旦區塊完成,atomic_write 會關閉檔案控制代碼並完成其工作。

例如,Action Pack 使用此方法來寫入資產快取檔案,例如 all.css

File.atomic_write(joined_asset_path) do |cache|
  cache.write(join_asset_file_contents(asset_paths))
end

為了達成此目的,atomic_write 會建立一個暫存檔案。區塊中的程式碼實際上會寫入該檔案。完成後,暫存檔案會重新命名,這是 POSIX 系統上的原子操作。如果目標檔案存在,atomic_write 會覆寫它並保留擁有者和權限。不過,在某些情況下,atomic_write 無法變更檔案擁有權或權限,此錯誤會被捕捉並略過,相信使用者/檔案系統可以確保檔案可供需要它的處理程序存取。

由於 atomic_write 執行 chmod 操作,如果目標檔案設定了 ACL,則此 ACL 會被重新計算/修改。

請注意,您無法使用 atomic_write 進行附加。

輔助檔案會寫入暫存檔案的標準目錄,但你可以將你選擇的目錄作為第二個參數傳遞。

19 個 NameError 的擴充

Active Support 新增 missing_name?NameError,用來測試例外是否因傳入參數的名稱而產生。

名稱可以是符號或字串。符號會與常數名稱進行測試,字串則會與完全限定的常數名稱進行測試。

符號可以表示完全限定的常數名稱,例如 :"ActiveRecord::Base",因此符號的行為是為方便而定義,而不是技術上必須如此。

例如,當呼叫 ArticlesController 的動作時,Rails 會樂觀地嘗試使用 ArticlesHelper。如果 helper 模組不存在,這沒有關係,因此如果產生該常數名稱的例外,應該將其靜音。但有可能是 articles_helper.rb 會因實際未知的常數而產生 NameError。這應該重新引發。missing_name? 方法提供了一種區分這兩種情況的方法

def default_helper_module!
  module_name = name.delete_suffix("Controller")
  module_path = module_name.underscore
  helper module_path
rescue LoadError => e
  raise e unless e.is_missing? "helpers/#{module_path}_helper"
rescue NameError => e
  raise e unless e.missing_name? "#{module_name}Helper"
end

20 個 LoadError 的擴充

Active Support 新增 is_missing?LoadError

給定路徑名稱,is_missing? 會測試例外是否因特定檔案(可能不包含 ".rb" 擴充)而產生。

例如,當呼叫 ArticlesController 的動作時,Rails 會嘗試載入 articles_helper.rb,但該檔案可能不存在。這沒關係,輔助模組不是強制性的,所以 Rails 會忽略載入錯誤。但輔助模組確實存在,且反過來需要另一個不存在的函式庫時,Rails 必須重新引發例外。is_missing? 方法提供一種方法來區分這兩種情況

def default_helper_module!
  module_name = name.delete_suffix("Controller")
  module_path = module_name.underscore
  helper module_path
rescue LoadError => e
  raise e unless e.is_missing? "helpers/#{module_path}_helper"
rescue NameError => e
  raise e unless e.missing_name? "#{module_name}Helper"
end

21 Pathname 的擴充功能

21.1 existence

existence 方法如果命名檔案存在,則傳回接收者,否則傳回 nil。它適用於像這樣的慣用語法

content = Pathname.new("file").existence&.read

回饋

我們鼓勵您協助提升本指南的品質。

如果您看到任何錯字或事實錯誤,請協助我們修正。首先,您可以閱讀我們的 文件貢獻 部分。

您也可能會發現不完整或過時的內容。請務必為 main 新增任何遺漏的文件。請務必先查看 Edge Guides,以驗證問題是否已在主分支中修正。查看 Ruby on Rails 指南指南,了解樣式和慣例。

如果您發現需要修正的事項,但無法自行修補,請 開啟問題

最後但並非最不重要的一點,任何有關 Ruby on Rails 文件的討論都非常歡迎在 官方 Ruby on Rails 論壇 上進行。