v7.0.0
更多資訊請前往 rubyonrails.org: 更多在 Ruby on Rails

Active Support 核心擴充套件

Active Support 是負責提供 Ruby 的 Rails 元件上的 Ruby 語言擴充套件和實用程式。

它在語言級別提供了更豐富的底線,既針對 Rails 應用程式的開發,也針對在 Rails 本身上開發 Ruby。

閱讀本指南後,您將瞭解:

1 如何載入核心擴充套件

1.1 獨立式 Active Support

為了儘可能減少預設佔用空間,Active Support 預設載入最小依賴項。它被分成小塊,以便只能載入所需的擴充套件。它還具有一些方便的入口點,可以一次性載入相關擴充套件,甚至是所有內容。

因此,在一個簡單的要求之後:

require "active_support"

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

1.1.1 挑選定義

此示例顯示如何載入 Hash#with_indifferent_access。此擴充套件允許將 Hash 轉換為 ActiveSupport::HashWithIndifferentAccess,允許以字串或 symbols 的形式訪問 keys。

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

對於定義為核心擴充套件的每個方法,本指南都有一個註釋,說明在哪裡定義了這樣的方法。在 with_indifferent_access 的情況下,註釋如下:

active_support/core_ext/hash/indifferent_access.rb 中定義。

這意味著您可以像這樣要求它:

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

Active Support 已經過仔細修改,以便挑選檔案僅載入嚴格需要的依賴項(如果有)。

1.1.2 載入分組核心擴充套件

下一級是簡單地將所有擴充套件載入到 Hash。根據經驗,可以透過載入 active_support/core_ext/some_class 一次性獲得對 SomeClass 的擴充套件。

因此,要將所有擴充套件載入到 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 Active Support 在 Ruby on Rails 應用程式中

除非 config.active_support.bare 為真,否則 Rails 應用程式上的 Ruby 會載入所有 Active Support。在這種情況下,應用程式將只加載框架本身根據自己的需要挑選的內容,並且仍然可以在任何粒度級別挑選自己,如上一節所述。

2 所有物件的擴充套件

2.1 blank?present?

以下 values 在 Rails 應用程式中被視為空白:

  • nilfalse

  • 僅由空格組成的字串(見下面的註釋),

  • 空陣列和雜湊,以及

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

資訊:字串的謂詞使用可識別 Unicode 的字元類 [:space:],因此例如 U+2029(段落分隔符)被認為是空格。

警告:請注意,未提及數字。特別是,0 和 0.0 是 not 空白。

例如,來自 ActionController::HttpAuthentication::Token::ControllerMethods 的這個方法使用 [blank?][Object#blank?] 來檢查 token 是否存在:

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

方法 [present?][Object#present?] 等價於 !blank?。此示例取自 ActionDispatch::Http::Cache::Response

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

active_support/core_ext/object/blank.rb 中定義。

2.2 presence

如果 present?,則 presence 方法返回其接收者,否則返回 nil。它對像這樣的習語很有用:

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

active_support/core_ext/object/blank.rb 中定義。

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 快得多。僅當您知道硬編碼列表在您的用例中就足夠了時才使用它。

active_support/core_ext/object/duplicable.rb 中定義。

2.4 deep_dup

[deep_dup][Object#deep_dup] 方法返回給定物件的深層副本。通常,當您在 dup 一個物件中包含其他物件時,Ruby 不會 dup 它們,因此它會建立該物件的淺複製。例如,如果您有一個帶有字串的陣列,它將如下所示:

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

duplicate.push 'another-string'

# 物件被複制,所以元素只新增到副本中
array     # => ['string']
duplicate # => ['string', 'another-string']

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

# 第一個元素沒有重複,它將在兩個陣列中更改
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

active_support/core_ext/object/deep_dup.rb 中定義。

2.5 try

當您想僅在物件不是 nil 時呼叫該物件的方法時,實現它的最簡單方法是使用條件語句,從而增加不必要的混亂。另一種方法是使用 [try][Object#try]。 tryObject#send 類似,只是它如果傳送到 nil 則返回 nil

下面是一個例子:

# 沒有嘗試
unless @number.nil?
  @number.next
end

# 嘗試
@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!][Object#try!]:

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

active_support/core_ext/object/try.rb 中定義。

2.6 class_eval(*args, &block)

您可以使用 [class_eval][Kernel#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

active_support/core_ext/kernel/singleton_class.rb 中定義。

2.7 acts_like?(duck)

方法 acts_like? 提供了一種根據簡單約定來檢查某個類是否像其他類一樣的方法:提供與 String 定義的相同介面的類

def acts_like_string?
end

這只是一個標記,它的主體或返回 value 無關緊要。然後,客戶端程式碼可以透過這種方式查詢duck-type-safeness:

some_klass.acts_like?(:string)

Rails 有類似 DateTime 的類,並遵循這個契約。

active_support/core_ext/object/acts_like.rb 中定義。

2.8 to_param

Rails 中的所有物件都回應方法 to_param,該方法的目標在於返回將它們表示為查詢字串中的 values 或 URL 片段的內容。

預設情況下,to_param 只調用 to_s

7.to_param # => "7"

to_param 的返回 value 應該被轉義:

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

Rails 中的幾個類覆蓋了這個方法。

例如 niltruefalse 返回自身。 Array#to_param 對元素呼叫 to_param 並將結果與​​“/”連線:

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

值得注意的是,Rails 路由系統在 models 上呼叫 to_param 以獲得 :id 佔位符的 value。 ActiveRecord::Base#to_param 返回模型的 id,但您可以在 models 中重新定義該方法。例如,給定

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

我們得到:

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

警告。 Controller 需要注意對 to_param 的任何重新定義,因為當這樣的請求進入“357-john-smith”時是 params[:id] 的 value。

active_support/core_ext/object/to_param.rb 中定義。

2.9 to_query

to_query 方法構造一個查詢字串,該字串將給定的 keyto_param 的返回 value 相關聯。例如,使用以下 to_param 定義:

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

我們得到:

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

此方法轉義了 key 和 value 所需的任何內容:

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

因此它的輸出已準備好用於查詢字串。

陣列返回將to_query應用於每個帶有key[]的元素的結果為key,並用“&”連線結果:

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

雜湊也回應 to_query,但具有不同的簽名。如果沒有傳遞引數,呼叫會生成一系列排序的 key/值分配,在其 values 上呼叫 to_query(key)。然後用“&”連線結果:

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

方法 Hash#to_query 接受 keys 的可選名稱空間:

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

active_support/core_ext/object/to_query.rb 中定義。

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 將呼叫轉發給其接收者,因此它們可以巢狀。除了自己的巢狀級別外,每個巢狀級別還將合併繼承的預設值。

active_support/core_ext/object/with_options.rb 中定義。

2.11 JSON 支援

Active Support 提供了比 json gem 通常為 Ruby 物件提供的更好的 to_json 實現。這是因為某些類,如 HashProcess::Status 需要特殊處理才能提供正確的 JSON 表示。

active_support/core_ext/object/json.rb 中定義。

2.12 實例變數

Active Support 提供了幾種方法來簡化對實例變數的訪問。

2.12.1 instance_values

方法 instance_values 返回一個雜湊值,將沒有“@”的實例變數名稱對映到它們的 對應的 values。 Keys 是字串:

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

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

active_support/core_ext/object/instance_variables.rb 中定義。

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"]

active_support/core_ext/object/instance_variables.rb 中定義。

2.13 靜默警告和異常

方法 [silence_warnings][Kernel#silence_warnings] 和 enable_warnings 相應地更改了 $VERBOSE 的 value 的塊持續時間,然後將其重置:

silence_warnings { Object.const_set "RAILS_DEFAULT_LOGGER", logger }

[suppress][Kernel#suppress] 也可以消除異常。此方法接收任意數量的異常類。如果在塊執行期間引發異常並且是 kind_of? 的任何引數,則 suppress 捕獲它並靜默返回。否則不捕獲異常:

# 如果使用者被鎖定,增量丟失,沒什麼大不了的。
suppress(ActiveRecord::StaleObjectError) do
  current_user.increment! :visits
end

active_support/core_ext/kernel/reporting.rb 中定義。

2.14 in?

謂詞 [in?][Object#in?] 測試一個物件是否包含在另一個物件中。如果傳遞的引數沒有回應 include?,則會引發 ArgumentError 異常。

in? 的示例:

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

active_support/core_ext/object/inclusion.rb 中定義。

3 Module 的擴充套件

3.1 屬性

3.1.1 alias_attribute

Model 屬性有一個讀者、一個作者和一個謂詞。您可以使用 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

active_support/core_ext/module/aliasing.rb 中定義。

3.1.2 內部屬性

當您在一個的目標在於被子類化的類中定義屬性時,名稱衝突是一種風險。這對圖書館來說非常重要。

Active Support 定義了宏 attr_internal_readerattr_internal_writer 和 [attr_internal_accessor][attr_internal_accessor][ZHTW_WTHZ#][ZHTW_internal_attr#]它們的行為與 Ruby 內建的 attr_* 對應物類似,只是它們以一種降低衝突可能性的方式命名底層實例變數。

attr_internalattr_internal_accessor 的同義詞:

 圖書館
class ThirdPartyLibrary::Crawler
  attr_internal :log_level
end

# 客戶端程式碼
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 在一些地方使用內部屬性,例如 views:

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

active_support/core_ext/module/attr_internal.rb 中定義。

3.1.3 Module 屬性

mattr_readermattr_writermattr_accessor 與 ZTH_3W 類定義的宏相同。事實上,cattr_* 宏只是 mattr_* 宏的別名。檢查類屬性

比如Active Storage的logger的API是用mattr_accessor生成的:

module ActiveStorage
  mattr_accessor :logger
end

active_support/core_ext/module/attribute_accessors.rb 中定義。

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 是匿名的或屬於頂級,則 module_parent 返回 Object

警告:請注意,在這種情況下 module_parent_name 返回 nil

active_support/core_ext/module/introspection.rb 中定義。

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"

對於頂級或匿名 modules module_parent_name 返回 nil

警告:請注意,在這種情況下 module_parent 返回 Object

active_support/core_ext/module/introspection.rb 中定義。

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]

active_support/core_ext/module/introspection.rb 中定義。

3.3 匿名

module 可能有也可能沒有名稱:

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

儘管根據定義無法訪問匿名 module。

active_support/core_ext/module/anonymous.rb 中定義。

3.4 方法委託

3.4.1 delegate

delegate 提供了一種簡單的方法來轉發方法。

假設某些應用程式中的使用者在 User model 中有登入資訊,但在單獨的 Profile model 中有姓名和其他資料:

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 選項應該成為一個表示式,該表示式計算方法被委託給的物件。通常是一個字串或 symbol。這樣的表示式是在接收者的上下文中計算的:

# 委託給 Rails 常量
delegate :logger, to: :Rails

# 委託給接收者的類
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 來改變它。

定義在 active_support/core_ext/module/delegation.rb

3.4.2 delegate_missing_to

想象一下,您想委託 User 物件中缺少的所有內容, 到 Profile 之一。 delegate_missing_to 宏讓你實現這個 在微風中:

class User < ApplicationRecord
  has_one :profile

  delegate_missing_to :profile
end

目標可以是物件內的任何可呼叫物件,例如實例變數, 方法、常量等。僅委託目標的公共方法。

active_support/core_ext/module/delegation.rb 中定義。

3.5 重新定義方法

在某些情況下,您需要使用 define_method 定義一個方法,但不知道該名稱的方法是否已經存在。如果是,則在啟用它們時發出警告。沒什麼大不了的,但也不乾淨。

方法 redefine_method 可以防止這種潛在的警告,如果需要,在之前刪除現有方法。

如果需要定義,也可以使用 silence_redefinition_of_method 自己的替換方法(因為您使用的是 delegate,對於 例子)。

active_support/core_ext/module/redefine_method.rb 中定義。

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

model 可能會發現該選項可用作防止批次分配設定屬性的方法。

可以透過將選項 :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,就像 reader 方法一樣。

如果你不想要實例謂詞,傳遞 instance_predicate: false 並且它不會被定義。

active_support/core_ext/class/attribute.rb 中定義。

4.1.2 cattr_readercattr_writercattr_accessor

cattr_readercattr_writer 和 [cattr_accessor][Module#cattr_accessor 類] 類似於 ZTH_W 類。他們將一個類變數初始化為 nil 除非它已經存在,並生成相應的類方法來訪問它:

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

此外,您可以將塊傳遞給 cattr_* 以使用預設的 value 設定屬性:

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

我們可以在 views 中訪問 field_error_proc

可以透過將 :instance_reader 設定為 false 來阻止讀取器實例方法的生成,可以透過將 :instance_writer 設定為 false 來阻止寫入器實例方法的生成。可以透過將 :instance_accessor 設定為 false 來阻止這兩種方法的生成。在所有情況下,value 必須完全是 false 而不是任何虛假的 value。

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

model 可能會發現將 :instance_accessor 設定為 false 作為防止批次分配設定屬性的方法很有用。

active_support/core_ext/module/attribute_accessors.rb 中定義。

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 頁面。一方面,如果 review 的標題是“Flanagan & Matz 規則!”輸出不會是格式正確的,因為必須將與號轉義為“&”。此外,根據應用程式,這可能是一個很大的安全漏洞,因為使用者可以將惡意 HTML 設定為手工製作的 review 標題。檢視安全指南 中有關跨站點指令碼的部分,瞭解有關風險的更多資訊。

5.1.2 安全字串

Active Support 有(html) 安全字串的概念。安全字串是標記為可按原樣插入 HTML 的字串。它是可信的,不管它是否被逃脫。

預設情況下,字串被認為是 unsafe

"".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 # => "<"

這些方法不應該在普通的views中使用。不安全的 values 會自動轉義:

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

要逐字插入內容,請使用 raw helper 而不是呼叫 html_safe

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

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

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

raw helper 為您呼叫 html_safe

def raw(stringish)
  stringish.to_s.html_safe
end

active_support/core_ext/string/output_safety.rb 中定義。

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!

active_support/core_ext/string/filters.rb 中定義。

5.3 squish

方法 squish 去除前導和尾隨空格,並用一個空格替換一行空格:

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

還有破壞性版本String#squish!

請注意,它同時處理 ASCII 和 Unicode 空格。

active_support/core_ext/string/filters.rb 中定義。

5.4 truncate

方法 truncate 返回在給定的 length 之後截斷的接收者的副本:

"Oh dear! Oh dear! I shall be late!".truncate(20)
# => “哦,天哪!天哪!...”

可以使用 :omission 選項自定義省略號:

"Oh dear! Oh dear! I shall be late!".truncate(20, omission: '&hellip;')
# => “哦,天哪!哦……”

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

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

"Oh dear! Oh dear! I shall be late!".truncate(18)
# => “哦,天哪!哦,死……”
"Oh dear! Oh dear! I shall be late!".truncate(18, separator: ' ')
# => “哦,天哪!哦……”

選項 :separator 可以是正則表示式:

"Oh dear! Oh dear! I shall be late!".truncate(18, separator: /\s/)
# => “哦,天哪!哦……”

在上面的例子中,“親愛的”首先被切斷,但隨後 :separator 阻止了它。

active_support/core_ext/string/filters.rb 中定義。

5.5 truncate_bytes

方法 truncate_bytes 返回其接收器的副本,最多被截斷為 bytesize 位元組:

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

可以使用 :omission 選項自定義省略號:

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

active_support/core_ext/string/filters.rb 中定義。

5.6 truncate_words

方法 truncate_words 返回在給定單詞數後截斷的接收者副本:

"Oh dear! Oh dear! I shall be late!".truncate_words(4)
# => “哦,天哪!天哪!...”

可以使用 :omission 選項自定義省略號:

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

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

"Oh dear! Oh dear! I shall be late!".truncate_words(3, separator: '!')
# => “哦,天哪!天哪!我要遲到了……”

選項 :separator 可以是正則表示式:

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

active_support/core_ext/string/filters.rb 中定義。

5.7 inquiry

inquiry 方法將字串轉換為 StringInquirer 物件,使相等性檢查更漂亮。

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

active_support/core_ext/string/inquiry.rb 中定義。

5.8 starts_with?ends_with?

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

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

active_support/core_ext/string/starts_ends_with.rb 中定義。

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

使用者將看到與左邊距對齊的使用訊息。

從技術上講,它會查詢整個字串中縮排最少的行,並刪除 大量的領先空白。

active_support/core_ext/string/strip.rb 中定義。

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 通常是一個空格或製表符,但它可以是任何字串。

第三個引數 indent_empty_lines 是一個標誌,表示是否應該縮排空行。預設為假。

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

indent! 方法執行就地縮排。

active_support/core_ext/string/indent.rb 中定義。

5.11 訪問

5.11.1 at(position)

at 方法返回字串在 position 位置的字元:

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

active_support/core_ext/string/access.rb 中定義。

5.11.2 from(position)

from 方法返回從位置 position 開始的字串的子字串:

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

active_support/core_ext/string/access.rb 中定義。

5.11.3 to(position)

to 方法返回字串的子字串,直到位置 position

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

active_support/core_ext/string/access.rb 中定義。

5.11.4 first(limit = 1)

first 方法返回包含字串的第一個 limit 字元的子字串。

如果 n > 0,則呼叫 str.first(n) 等價於 str.to(n-1),如果 n == 0,則返回空字串。

active_support/core_ext/string/access.rb 中定義。

5.11.5 last(limit = 1)

last 方法返回包含字串的最後一個 limit 字元的子字串。

如果 n > 0,則呼叫 str.last(n) 等價於 str.from(-n),如果 n == 0,則返回空字串。

active_support/core_ext/string/access.rb 中定義。

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 的任何其他 value,將返回複數形式:

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

Active Record 使用此方法計算對應於 model 的預設表名:

# 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

active_support/core_ext/string/inflections.rb 中定義。

5.12.2 singularize

singularize 方法是 pluralize 的逆方法:

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

Associations 使用此方法計算相應預設關聯類的名稱:

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

active_support/core_ext/string/inflections.rb 中定義。

5.12.3 camelize

方法 camelize 以駝峰形式返回其接收器:

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

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

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

例如,Action Pack 使用此方法載入提供某個 session 儲存的類:

# 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

active_support/core_ext/string/inflections.rb 中定義。

5.12.4 underscore

方法 underscore 反過來,從駝峰式到路徑:

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

還將 "::" 轉換回 "/":

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

並理解以小寫字母開頭的字串:

"visualEffect".underscore # => "visual_effect"

underscore 不接受任何引數。

Rails 使用 underscore 來獲取 controller 類的小寫名稱:

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

例如,value 就是您在 params[:controller] 中得到的那個。

資訊:根據經驗,您可以將 underscore 視為 camelize 的倒數,儘管在某些情況下並不成立。例如,"SSLError".underscore.camelize 返還 "SslError"

active_support/core_ext/string/inflections.rb 中定義。

5.12.5 titleize

方法 titleize 將接收器中的單詞大寫:

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

titleize 別名為 titlecase

active_support/core_ext/string/inflections.rb 中定義。

5.12.6 dasherize

方法 dasherize 用破折號替換接收器中的下劃線:

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

models 的 XML 序列化程式使用此方法對節點名稱進行破折:

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

active_support/core_ext/string/inflections.rb 中定義。

5.12.7 demodulize

給定一個帶有限定常量名的字串,demodulize 返回常量名,即最右邊的部分:

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

例如,Active Record 使用此方法計算計數器快取列的名稱:

# 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

active_support/core_ext/string/inflections.rb 中定義。

5.12.8 deconstantize

給定一個帶有限定常量引用表示式的字串,deconstantize 刪除最右邊的部分,通常保留常量容器的名稱:

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

active_support/core_ext/string/inflections.rb 中定義。

5.12.9 parameterize

方法 parameterize 以一種可用於漂亮 URL 的方式標準化其接收器。

"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"

active_support/core_ext/string/inflections.rb 中定義。

5.12.10 tableize

方法 tableizeunderscore 後跟 pluralize

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

根據經驗,對於簡單的情況,tableize 返回對應於給定 model 的表名。 Active Record 中的實際實現確實不是直接的 tableize,因為它還解調了類名並檢查了一些可能影響返回字串的選項。

active_support/core_ext/string/inflections.rb 中定義。

5.12.11 classify

方法 classifytableize 的逆方法。它為您提供與表名對應的類名:

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

該方法理解限定的表名:

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

請注意,classify 將類名作為字串返回。您可以透過在其上呼叫 constantize 來獲取實際的類物件,接下來解釋。

active_support/core_ext/string/inflections.rb 中定義。

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

active_support/core_ext/string/inflections.rb 中定義。

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"

helper 方法 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

active_support/core_ext/string/inflections.rb 中定義。

5.12.14 foreign_key

方法 foreign_key 給出來自類名的外部 key 列名。為此,它會解調、下劃線並新增“_id”:

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

如果您不想在“_id”中使用下劃線,則傳遞一個錯誤引數:

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

Associations 使用這個方法來推斷國外的 keys,例如 has_onehas_many 這樣做:

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

active_support/core_ext/string/inflections.rb 中定義。

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

active_support/core_ext/string/conversions.rb 中定義。

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

active_support/core_ext/symbol/starts_ends_with.rb 中定義。

7 Numeric 的擴充套件

7.1 位元組

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

  • bytes
  • [kilobytes][數字#千位元組]
  • [megabytes][數字#兆位元組]
  • [gigabytes][數字#gigabytes]
  • [terabytes][數字#TB]
  • [petabytes][數字#petabytes]
  • [exabytes][數字#exabytes]

它們使用 1024 的轉換因子返回相應的位元組數:

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

單數形式具有別名,因此您可以說:

1.megabyte # => 1048576

active_support/core_ext/numeric/bytes.rb 中定義。

7.2 時間

以下方法:

啟用時間宣告和計算,如 45.minutes + 2.hours + 4.weeks。它們的返回值 values 也可以新增到 Time 物件或從中減去。

這些方法可以結合 [from_now][Duration#from_now]、[ago][Duration#ago] 等進行精確的日期計算。例如:

# 相當於 Time.current.advance(days: 1)
1.day.from_now

# 相當於 Time.current.advance(weeks: 2)
2.weeks.from_now

# 相當於 Time.current.advance(days: 4, week: 5)
(4.days + 5.weeks).from_now

警告。其他時長請參考 Integer 的時間擴充套件。

active_support/core_ext/numeric/time.rb 中定義。

7.3 格式

以多種方式啟用數字格式。

生成一個數字的字串表示作為電話號碼:

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

生成一個數字的字串表示作為貨幣:

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

以百分比形式生成數字的字串表示形式:

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

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

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

生成四捨五入到精度的數字的字串表示形式:

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

生成一個數字的字串表示,作為人類可讀的位元組數:

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

以人類可讀的單詞生成數字的字串表示形式:

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

active_support/core_ext/numeric/conversions.rb 中定義。

8 Integer 的擴充套件

8.1 multiple_of?

方法 multiple_of? 測試一個整數是否是引數的倍數:

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

active_support/core_ext/integer/multiple.rb 中定義。

8.2 ordinal

[ordinal][Integer#ordinal]方法返回接收者整數對應的序數字尾字串:

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

active_support/core_ext/integer/inflections.rb 中定義。

8.3 ordinalize

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

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

active_support/core_ext/integer/inflections.rb 中定義。

8.4 時間

以下方法:

啟用時間宣告和計算,如 4.months + 5.years。它們的返回值 values 也可以新增到 Time 物件或從中減去。

這些方法可以結合 [from_now][Duration#from_now]、[ago][Duration#ago] 等進行精確的日期計算。例如:

# 相當於 Time.current.advance(months: 1)
1.month.from_now

# 相當於 Time.current.advance(years: 2)
2.years.from_now

# 相當於 Time.current.advance(月: 4, 年: 5)
(4.months + 5.years).from_now

警告。其他時長請參考 Numeric 的時間擴充套件。

active_support/core_ext/integer/time.rb 中定義。

9 BigDecimal 的擴充套件

9.1 to_s

to_s 方法提供了預設說明符“F”。這意味著對 to_s 的簡單呼叫將導致浮點表示而不是工程符號:

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

並且還支援 symbol 說明符:

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

仍然支援工程符號:

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

10 Enumerable 的擴充套件

10.1 sum

方法 sum 新增可列舉的元素:

[1, 2, 3].sum # => 6
(1..100).sum  # => 5050

新增僅假設元素回應 +

[[1, 2], [2, 3], [3, 4]].sum    # => [1, 2, 2, 3, 3, 4]
%w(foo bar baz).sum             # => "foobarbaz"
{a: 1, b: 2, c: 3}.sum          # => [:a, 1, :b, 2, :c, 3]

預設情況下,空集合的總和為零,但這是可自定義的:

[].sum    # => 0
[].sum(1) # => 1

如果給出了一個塊,則 sum 成為一個迭代器,它產生集合的元素並對返回的 values 求和:

(1..5).sum {|n| n * 2 } # => 30
[2, 4, 6, 8, 10].sum    # => 30

空接收器的總和也可以用這種形式定製:

[].sum(1) {|n| n**3} # => 1

active_support/core_ext/enumerable.rb 中定義。

10.2 index_by

方法 index_by 生成一個雜湊,其中包含由某些 key 索引的可列舉元素。

它遍歷集合並將每個元素傳遞給一個塊。該元素將被塊返回的 value 處理為 key:

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

警告。 Keys 通常應該是唯一的。如果塊為不同的元素返回相同的 value,則不會為該鍵構建集合。最後一項將獲勝。

active_support/core_ext/enumerable.rb 中定義。

10.3 index_with

方法 index_with 使用可列舉的元素作為 keys 生成雜湊。 value 是傳遞的預設值或在塊中返回。

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

%i( title body ).index_with { |attr_name| post.public_send(attr_name) }
# => { title: "嘿嘿", body: "怎麼了?" }

WEEKDAYS.index_with(Interval.all_day)
# => { 星期一: [ 0, 1440 ], ... }

active_support/core_ext/enumerable.rb 中定義。

10.4 many?

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

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

如果給出了一個可選塊,many? 只考慮那些返回 true 的元素:

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

active_support/core_ext/enumerable.rb 中定義。

10.5 exclude?

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

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

active_support/core_ext/enumerable.rb 中定義。

10.6 including

方法 including 返回一個包含傳遞元素的新列舉:

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

active_support/core_ext/enumerable.rb 中定義。

10.7 excluding

方法 [excluding][Enumerable# exclude] 返回具有指定元素的可列舉的副本 移除:

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

excluding 別名為 without

active_support/core_ext/enumerable.rb 中定義。

[可列舉#排除]:https://api.rubyonrails.org/classes/Enumerable.html#method-i- exclude

10.8 pluck

方法 pluck 從每個元素中提取給定的 key:

[{ 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"]]

active_support/core_ext/enumerable.rb 中定義。

10.9 pick

方法 pick 從第一個元素中提取給定的 key:

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

active_support/core_ext/enumerable.rb 中定義。

11 Array 的擴充套件

11.1 訪問

Active Support 增加了陣列的 API 以簡化訪問它們的某些方式。例如, to 返回元素的子陣列,直到傳遞的索引處的元素:

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

類似地, [from][Array#from] 返回從傳遞索引處的元素到結尾的尾部。如果索引大於陣列的長度,則返回一個空陣列。

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

方法 [including][Array#include] 返回一個包含傳遞元素的新陣列:

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

方法 [excluding][Array# exclude] 返回不包括指定元素的 Array 的副本。 這是對使用 Array#-Enumerable#excluding 的最佳化 出於效能原因而不是 Array#reject

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

方法 [second][Array#second]、thirdfourthfifth 返回相應的元素,[second_to_last][Array 也是如此#second_to_last] 和 third_to_lastfirstlast 是內建的)。由於社會智慧和積極的建設性,forty_two 也可用。

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

active_support/core_ext/array/access.rb 中定義。

[陣列#排除]:https://api.rubyonrails.org/classes/Array.html#method-i- exclude

11.2 提取

方法 extract! 移除並返回塊返回真 value 的元素。 如果沒有給出塊,則返回一個列舉器。

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]

active_support/core_ext/array/extract.rb 中定義。

11.3 期權 Extraction

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

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

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

然而,如果一個方法需要可變數量的引數並在其宣告中使用 *,那麼這樣的選項雜湊最終會成為引數陣列的一個專案,在那裡它失去了它的作用。

在這些情況下,您可以使用 extract_options! 對選項雜湊進行特殊處理。此方法檢查陣列最後一項的型別。如果它是一個雜湊,它彈出它並返回它,否則它返回一個空的雜湊。

例如,讓我們看看 caches_action controller 宏的定義:

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

此方法接收任意數量的 action 名稱,以及作為最後一個引數的可選雜湊選項。透過呼叫 extract_options!,您可以獲得選項雜湊並以簡單而明確的方式將其從 actions 中刪除。

active_support/core_ext/array/extract_options.rb 中定義。

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的陣列。預設為“和”。
  • :words_connector:用於連線3個或更多元素的陣列元素,除了最後兩個。預設為“,”。
  • :last_word_connector:用於連線具有 3 個或更多元素的陣列的最後一項。預設為“,和”。

這些選項的預設值可以本地化,它們的 keys 是:

選項 I18n key
:two_words_connector support.array.two_words_connector
:words_connector support.array.words_connector
:last_word_connector support.array.last_word_connector

active_support/core_ext/array/conversions.rb 中定義。

11.4.2 to_formatted_s

預設情況下,方法 to_formatted_s 的作用類似於 to_s

但是,如果陣列包含回應 id 的專案,則 symbol :db 可以作為引數傳遞。這通常與 Active Record 物件的集合。返回的字串是:

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

上面示例中的整數應該來自對 id 的相應呼叫。

active_support/core_ext/array/conversions.rb 中定義。

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">
# <貢獻者>
# <id type="integer">4356</id>
# <name>傑瑞米·肯珀</name>
# <rank type="integer">1</rank>
# <url-id>jeremy-kemper</url-id>
# </貢獻者>
# <貢獻者>
# <id type="integer">4404</id>
# <name>David Heinemeier Hansson</name>
# <rank type="integer">2</rank>
# <url-id>david-heinemeier-hansson</url-id>
# </貢獻者>
# </貢獻者>

為此,它依次將 to_xml 傳送到每個專案,並在根節點下收集結果。所有專案都必須回應 to_xml,否則會引發異常。

預設情況下,根元素的名稱是第一項的類名稱的下劃線和破折號複數形式,前提是其餘元素屬於該型別(使用 is_a? 檢查)並且它們不是雜湊。在上面的例子中是“貢獻者”。

如果有任何元素不屬於第一個元素的型別,則根節點將成為“物件”:

[Contributor.first, Commit.first].to_xml
# =>
# <?xml version="1.0" encoding="UTF-8"?>
# <objects type="array">
# <物件>
# <id type="integer">4583</id>
# <name>Aaron Batalion</name>
# <rank type="integer">53</rank>
# <url-id>aaron-batalion</url-id>
# </物件>
# <物件>
# <author>Joshua Peek</author>
# <authored-timestamp type="datetime">2009-09-02T16:44:36Z</authored-timestamp>
# <branch>起源/主</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>殺死 AMo 觀察 wrap_with_notifications 因為 ARes 只使用它</message>
# <sha1>723a47bfb3708f968821bc969a9a3fc873a3ed58</sha1>
# </物件>
# </物件>

如果接收者是雜湊陣列,則根元素預設也是“物件”:

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

警告。如果集合為空,則根元素預設為“nil-classes”。這是一個問題,例如,如果集合為空,則上述貢獻者列表的根元素將不是“貢獻者”,而是“nil-classes”。您可以使用 :root 選項來確保一致的根元素。

預設情況下,子節點的名稱是單數化的根節點的名稱。在上面的例子中,我們已經看到了“貢獻者”和“物件”。選項 :children 允許您設定這些節點名稱。

預設的 XML 構建器是 Builder::XmlMarkup 的新實例。您可以透過 :builder 選項設定您自己的構建器。該方法還接受諸如 :dasherize 和朋友之類的選項,將它們轉發給構建器:

Contributor.limit(2).order(:rank).to_xml(skip_types: true)
# =>
# <?xml version="1.0" encoding="UTF-8"?>
# <貢獻者>
# <貢獻者>
# <id>4356</id>
# <name>傑瑞米·肯珀</name>
# <rank>1</rank>
# <url-id>jeremy-kemper</url-id>
# </貢獻者>
# <貢獻者>
# <id>4404</id>
# <name>David Heinemeier Hansson</name>
# <rank>2</rank>
# <url-id>david-heinemeier-hansson</url-id>
# </貢獻者>
# </貢獻者>

active_support/core_ext/array/conversions.rb 中定義。

11.5 包裝

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

具體來說:

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

該方法的目的與 Kernel#Array 類似,但有一些差別:

  • 如果引數回應 to_ary,則呼叫該方法。如果返回的 value 是 nil,則 Kernel#Array 繼續嘗試 to_a,但 Array.wrap 立即返回一個以引數作為其單個元素的陣列。
  • 如果從 to_ary 返回的 value 既不是 nil 也不是 Array 物件,則 Kernel#Array 會引發異常,而 Array.wrap 不會,它只會返回 value。
  • 它不會在引數上呼叫 to_a,如果引數不回應 to_ary,它將返回一個以引數為單個元素的陣列。

最後一點特別值得對一些可列舉進行比較:

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

還有一個使用 splat 運算子的相關成語:

[*object]

active_support/core_ext/array/wrap.rb 中定義。

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

active_support/core_ext/object/deep_dup.rb 中定義。

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 元素填充最後一組以具有請求的大小。您可以使用第二個可選引數更改此填充 value:

[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 不能用作填充 value。

active_support/core_ext/array/grouping.rb 中定義。

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 元素填充某些組。一個組最多可以獲得這些額外元素中的一個,如果有的話,最右邊的一個。擁有它們的組總是最後一個。

您可以使用第二個可選引數更改此填充 value:

%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 不能用作填充 value。

active_support/core_ext/array/grouping.rb 中定義。

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]]

否則,作為引數接收的 value,預設為 nil,是分隔符:

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

提示:在前面的示例中觀察到,連續的分隔符會導致空陣列。

active_support/core_ext/array/grouping.rb 中定義。

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"?>
# <雜湊>
# <foo type="integer">1</foo>
# <bar type="integer">2</bar>
# </雜湊>

為此,該方法遍歷這些對並構建依賴於 values 的節點。給定一對 key, value

  • 如果 value 是一個雜湊,則遞迴呼叫 key 作為 :root

  • 如果 value 是一個數組,則遞迴呼叫 key:rootkey 單數化為 :children

  • 如果 value 是一個可呼叫物件,它必須期待一兩個引數。根據 arity,呼叫可呼叫物件時,將 options 雜湊作為第一個引數,將 key 作為 :root,將 key 作為第二個引數進行單數化。它的返回 value 成為一個新節點。

  • 如果 value 回應 to_xml,則呼叫該方法時將 key 作為 :root

  • 否則,建立一個帶有 key 作為標籤的節點,用字串表示的 value 作為文字節點。如果 valuenil,則新增屬性“nil”設定為“true”。除非選項 :skip_types 存在且為真,否則還會根據以下對映新增屬性“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 和朋友之類的選項,將它們轉發給構建器。

active_support/core_ext/hash/conversions.rb 中定義。

12.2 合併

Ruby 有一個內建方法 Hash#merge 合併兩個雜湊:

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

Active Support 定義了更多可能方便的合併雜湊的方法。

12.2.1 reverse_mergereverse_merge!

在衝突的情況下,引數雜湊中的 key 在 merge 中獲勝。您可以使用以下習慣用法以緊湊的方式支援具有預設 values 的選項雜湊:

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

Active Support 定義了 reverse_merge,以防您更喜歡這種替代符號:

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

還有一個 bang 版本 reverse_merge! 執行合併:

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

警告。考慮到 reverse_merge! 可能會改變呼叫者的雜湊值,這可能是也可能不是一個好主意。

active_support/core_ext/hash/reverse_merge.rb 中定義。

12.2.2 reverse_update

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

警告。請注意,reverse_update 沒有爆炸。

active_support/core_ext/hash/reverse_merge.rb 中定義。

12.2.3 deep_mergedeep_merge!

正如您在前面的示例中所看到的,如果在兩個雜湊中都找到了 key,則引數中的一個 value 獲勝。

Active Support 定義了 Hash#deep_merge。在深度合併中,如果在兩個雜湊中都找到了 key 並且它們的 values 依次是雜湊,則它們的 merge 成為結果雜湊中的值:

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

方法 deep_merge! 執行深度合併。

active_support/core_ext/hash/deep_merge.rb 中定義。

12.3 深度複製

方法 Hash#deep_dup 複製自身和所有 keys 和 values 內部遞迴使用 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

active_support/core_ext/object/deep_dup.rb 中定義。

12.4 使用 Keys

12.4.1 exceptexcept!

方法 [except][Hash#except] 返回一個雜湊,其中移除了引數列表中的 keys(如果存在):

{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") # => {}

還有 bang 變體 except! 將 keys 刪除到位。

active_support/core_ext/hash/except.rb 中定義。

12.4.2 stringify_keysstringify_keys!

方法 stringify_keys 返回一個雜湊,該雜湊在接收器中具有 keys 的字串化版本。它透過向他們傳送 to_s 來實現:

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

在 key 衝突的情況下,value 將是最近插入雜湊的一個:

{"a" => 1, a: 2}.stringify_keys
# 結果將是
# => {"a"=>2}

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

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

第二行可以安全地訪問“型別”key,並讓使用者傳遞:type或“型別”。

還有 bang 變體 stringify_keys! 將 keys 字串化到位。

除此之外,可以使用 deep_stringify_keysdeep_stringify_keys! 將給定雜湊中的所有 keys 以及巢狀在其中的所有雜湊字串化。結果的一個例子是:

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

active_support/core_ext/hash/keys.rb 中定義。

12.4.3 symbolize_keyssymbolize_keys!

在可能的情況下,方法 symbolize_keys 返回一個雜湊,該雜湊在接收器中具有 keys 的 symbolized 版本。它透過向他們傳送 to_sym 來實現:

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

警告。請注意,在前面的示例中,只有一個 key 被 symbol 化。

在 key 衝突的情況下,value 將是最近插入雜湊的一個:

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

此方法可能很有用,例如可以輕鬆接受 symbols 和字串作為選項。例如 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 key,讓使用者透過 :input 或“輸入”。

還有 bang 變體 [symbolize_keys!][Hash#symbolize_keys!] symbolizes keys 到位。

除此之外,還可以使用 deep_symbolize_keys 和 [deep_symbolize_keys!][Hash#deep_symbolize_keys!] 將所有給定的雜湊值和 ZHTW_230_WTHZ 中的所有給定雜湊值 ZHTZW_236_WHTZ 中的所有巢狀雜湊化。結果的一個例子是:

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

active_support/core_ext/hash/keys.rb 中定義。

12.4.4 to_optionsto_options!

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

active_support/core_ext/hash/keys.rb 中定義。

12.4.5 assert_valid_keys

方法 assert_valid_keys 接收任意數量的引數,並檢查接收者是否有該列表之外的任何鍵。如果是,則 ArgumentError 被提升。

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

例如,在構建 associations 時,Active Record 不接受未知選項。它透過 assert_valid_keys 實現該控制。

active_support/core_ext/hash/keys.rb 中定義。

12.5 使用 Values

12.5.1 deep_transform_valuesdeep_transform_values!

方法 deep_transform_values 返回一個新的雜湊,其中包含所有由區塊操作轉換的 values。這包括來自根雜湊和所有巢狀雜湊和陣列的 values。

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

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

還有 bang 變體 deep_transform_values! 使用塊操作破壞性地轉換所有 values。

active_support/core_ext/hash/deep_transform_values.rb 中定義。

12.6 切片

方法 slice! 僅用給定的 keys 替換雜湊,並返回包含刪除的鍵/value 對的雜湊。

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

active_support/core_ext/hash/slice.rb 中定義。

12.7 提取

方法 extract! 刪除並返回匹配給定 keys 的鍵/value 對。

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

extract! 方法返回與接收者相同的 Hash 子類。

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

active_support/core_ext/hash/slice.rb 中定義。

12.8 無差別訪問

方法 with_indifferent_access 從其接收器返回 ActiveSupport::HashWithIndifferentAccess:

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

active_support/core_ext/hash/indifferent_access.rb 中定義。

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

active_support/core_ext/regexp.rb 中定義。

14 Range 的擴充套件

14.1 to_s

Active Support 擴充套件了 Range#to_s 方法,以便它理解可選的格式引數。在撰寫本文時,唯一支援的非預設格式是 :db

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

(Date.today..Date.tomorrow).to_s(:db)
# => "在'2009-10-25'和'2009-10-26'之間"

如示例所示,:db 格式生成一個 BETWEEN SQL 子句。 Active Record 使用它來支援條件中的範圍 values。

active_support/core_ext/range/conversions.rb 中定義。

14.2 ===include?

方法 Range#===Range#include? 說明某些 value 是否落在給定實例的兩端之間:

(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

active_support/core_ext/range/compare_range.rb 中定義。

14.3 overlaps?

方法 Range#overlaps? 表示任何兩個給定的範圍是否有非空交集:

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

active_support/core_ext/range/overlaps.rb 中定義。

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.yesterday][Date.yesterday] 和 Date.tomorrow,以及實例謂詞 past?today? , tomorrow?, next_day?, yesterday?, [prev_day?][DateAndTime::day_day?] ?]、Active Supporton_weekday?on_weekend?,所有這些都相對於 ZHTZW_0_

使用尊重使用者時區的方法進行日期比較時,請確保使用 Date.current 而不是 Date.today。在某些情況下,使用者時區可能在未來與系統時區相比,Date.today 預設使用系統時區。這意味著 Date.today 可能等於 Date.yesterday

active_support/core_ext/date/calculations.rb 中定義。

15.1.2 命名日期
15.1.2.1 beginning_of_week, end_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_weekend_of_week 別名為 at_end_of_week

active_support/core_ext/date_and_time/calculations.rb 中定義。

15.1.2.2 monday, sunday

方法 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

active_support/core_ext/date_and_time/calculations.rb 中定義。

15.1.2.3 prev_week, next_week

方法 next_week 接收一個帶有英文日期名稱的 symbol(預設是執行緒本地 Date.beginning_of_week,或 config.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 都按預期工作。

active_support/core_ext/date_and_time/calculations.rb 中定義。

15.1.2.4 beginning_of_month, end_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_monthend_of_month 別名為 at_end_of_month

active_support/core_ext/date_and_time/calculations.rb 中定義。

15.1.2.5 beginning_of_quarter, end_of_quarter

方法 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

active_support/core_ext/date_and_time/calculations.rb 中定義。

15.1.2.6 beginning_of_year, end_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_yearend_of_year 別名為 at_end_of_year

active_support/core_ext/date_and_time/calculations.rb 中定義。

15.1.3 其他日期計算
15.1.3.1 years_ago, years_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) 的簡寫。

active_support/core_ext/date_and_time/calculations.rb 中定義。

15.1.3.2 months_ago, months_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) 的簡寫。

active_support/core_ext/date_and_time/calculations.rb 中定義。

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

active_support/core_ext/date_and_time/calculations.rb 中定義。

15.1.3.4 advance

跳轉到其他日期的最通用方法是 [advance][Date#advance]。此方法接收帶有 keys :years:months:weeks:days 的雜湊值,並返回與當前 keys 指示一樣多的日期:

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

請注意,在前面的示例中,增量可能為負。

為了執行計算,該方法首先遞增年,然後是月,然後是周,最後是天。這個命令在幾個月結束時很重要。比如說,我們現在是 2010 年 2 月底,我們想向前推進一個月零一天。

advance方法先提前一個月,然後一天,結果是:

Date.new(2010, 2, 28).advance(months: 1, days: 1)
# => 星期日,2010 年 3 月 29 日

而如果反過來,結果會有所不同:

Date.new(2010, 2, 28).advance(days: 1).advance(months: 1)
# => 星期四,2010 年 4 月 1 日

active_support/core_ext/date/calculations.rb 中定義。

15.1.4 更改元件

方法 [change][Date#change] 允許您獲取與接收者相同的新日期,除了給定的年、月或日:

Date.new(2010, 12, 23).change(year: 2011, month: 11)
# => 2011 年 11 月 23 日,星期三

此方法不容忍不存在的日期,如果更改無效 ArgumentError 引發:

Date.new(2010, 1, 31).change(month: 2)
# => ArgumentError: 無效日期

active_support/core_ext/date/calculations.rb 中定義。

15.1.5 持續時間

Duration 物件可以新增到日期和從日期中減去:

d = Date.current
# => 2010 年 8 月 9 日星期一
d + 1.year
# => 2011 年 8 月 9 日,星期二
d - 3.hours
# => 星期日,2010 年 8 月 8 日 21:00:00 UTC +00:00

它們轉換為對 sinceadvance 的呼叫。例如在這裡我們得到了日曆改革的正確跳轉:

Date.new(1582, 10, 4) + 1.day
# => 1582 年 10 月 15 日,星期五
15.1.6 時間戳

資訊:如果可能,以下方法返回 Time 物件,否則返回 DateTime。如果設定,則它們遵循使用者時區。

15.1.6.1 beginning_of_day, end_of_day

方法 [beginning_of_day][Date#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][Date#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_day][Date#at_beginning_of_day]、[midnight][Date#midnight]、[at_midnight][Date#at_midnight]。

active_support/core_ext/date/calculations.rb 中定義。

15.1.6.2 beginning_of_hour, end_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

active_support/core_ext/date_time/calculations.rb 中定義。

15.1.6.3 beginning_of_minute, end_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,因為在 ZHTZW_6 上請求小時或分鐘的開始或結束沒有意義。

active_support/core_ext/date_time/calculations.rb 中定義。

15.1.6.4 ago, since

方法 [ago][Date#ago] 接收秒數作為引數,並從午夜返回幾秒前的時間戳:

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

同樣, [since][Date#since] 向前移動:

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

active_support/core_ext/date/calculations.rb 中定義。

15.1.7 其他時間計算

15.2 轉換

16 DateTime 的擴充套件

DateTime 不知道 DST 規則,因此當 DST 更改正在進行時,其中一些方法具有邊緣情況。例如 seconds_since_midnight 可能不會返回這樣一天的實際金額。

16.1 計算

DateTimeDate 的子類,因此透過載入 active_support/core_ext/date/calculations.rb,您可以繼承這些方法及其別名,只是它們將始終返回日期時間。

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

另一方面,advance 和 [change][DateTime#change] 也被定義並支援更多選項,它們在下面記錄。

以下方法僅在 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 定義的。

active_support/core_ext/date_time/calculations.rb 中定義。

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

active_support/core_ext/date_time/calculations.rb 中定義。

16.1.2.2 utc

方法 [utc][DateTime#utc] 在接收器中為您提供以 UTC 表示的相同日期時間。

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

此方法也別名為 getutc

active_support/core_ext/date_time/calculations.rb 中定義。

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

active_support/core_ext/date_time/calculations.rb 中定義。

16.1.2.4 advance

跳轉到另一個日期時間的最通用方法是 advance。此方法接收帶有 keys :years:months:weeks:days:hours:minutes:seconds 的雜湊值,並返回與 ZHTW_3 一樣多的當前日期時間。

d = DateTime.current
# => 2010 年 8 月 5 日星期四 11:33:31 +0000
d.advance(years: 1, months: 1, days: 1, hours: 1, minutes: 1, seconds: 1)
# => 2011 年 9 月 6 日,星期二 12:34:32 +0000

此方法首先計算目標日期,將 :years:months:weeks:days 傳遞到上面記錄的 Date#advance。之後,它將呼叫 [since][DateTime#since] 的時間調整為提前的秒數。這個順序是相關的,在某些邊緣情況下,不同的順序會給出不同的日期時間。 Date#advance 中的示例適用,我們可以擴充套件它以顯示與時間位相關的順序相關性。

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

d = DateTime.new(2010, 2, 28, 23, 59, 59)
# => 星期日,2010 年 2 月 28 日 23:59:59 +0000
d.advance(months: 1, seconds: 1)
# => 2010 年 3 月 29 日星期一 00:00:00 +0000

但是如果我們反過來計算它們,結果就會不同:

d.advance(seconds: 1).advance(months: 1)
# => 2010 年 4 月 1 日星期四 00:00:00 +0000

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

active_support/core_ext/date_time/calculations.rb 中定義。

16.1.3 更改元件

方法 [change][DateTime#change] 允許您獲得與接收者相同的新日期時間,除了給定的選項,可能包括 :year:month:day:hour:min、ZTHWZHT_8TH6

now = DateTime.current
# => 2010 年 6 月 8 日,星期二 01:56:22 +0000
now.change(year: 2011, offset: Rational(-6, 24))
# => 2011 年 6 月 8 日,星期三 01:56:22 -0600

如果小時歸零,那麼分鐘和秒也歸零(除非他們給出了 values):

now.change(hour: 0)
# => 2010 年 6 月 8 日,星期二 00:00:00 +0000

類似地,如果分鐘歸零,那麼秒也歸零(除非它給出了 value):

now.change(min: 0)
# => 2010 年 6 月 8 日,星期二 01:00:00 +0000

此方法不容忍不存在的日期,如果更改無效 ArgumentError 引發:

DateTime.current.change(month: 2, day: 30)
# => ArgumentError: 無效日期

active_support/core_ext/date_time/calculations.rb 中定義。

16.1.4 持續時間

Duration 物件可以新增到日期時間和從中減去:

now = DateTime.current
# => 2010 年 8 月 9 日星期一 23:15:17 +0000
now + 1.year
# => 2011 年 8 月 9 日,星期二 23:15:17 +0000
now - 1.week
# => 2010 年 8 月 2 日星期一 23:15:17 +0000

它們轉換為對 sinceadvance 的呼叫。例如在這裡我們得到了日曆改革的正確跳轉:

DateTime.new(1582, 10, 4, 23) + 1.hour
# => 1582 年 10 月 15 日星期五 00:00:00 +0000

17 Time 的擴充套件

17.1 計算

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

  • [change][Time#change] 接受額外的 :usec 選項。
  • Time 瞭解 DST,因此您可以獲得正確的 DST 計算,如
Time.zone_default
# => #<ActiveSupport::TimeZone:0x7f73654d4f38 @utc_offset=nil, @name="Madrid", ...>

# 在巴塞羅那,由於夏令時,2010/03/28 02:00 +0100 變為 2010/03/28 03:00 +0200。
t = Time.local(2010, 3, 28, 1, 59, 59)
# => 2010 年 3 月 28 日星期日 01:59:59 +0100
t.advance(seconds: 1)
# => 2010 年 3 月 28 日星期日 03:00:00 +0200
  • 如果 [since][Time#since] 或 [ago][Time#ago] 跳轉到無法用 Time 表示的時間,則返回 DateTime 物件。
17.1.1 Time.current

Active Support 將 Time.current 定義為當前時區中的今天。這與 Time.now 類似,不同之處在於它尊重使用者時區(如果已定義)。它還定義了實例謂詞 past?today?tomorrow?next_day? ::Calculations#next_day?]、yesterday?prev_day?future?,所有這些相對於 Time.current

使用尊重使用者時區的方法進行時間比較時,請確保使用 Time.current 而不是 Time.now。在某些情況下,使用者時區可能在未來與系統時區相比,Time.now 預設使用系統時區。這意味著 Time.now.to_date 可能等於 Date.yesterday

active_support/core_ext/time/calculations.rb 中定義。

17.1.2 all_day, all_week, all_month, all_quarterall_year

方法 all_day 返回一個表示當前時間一整天的範圍。

now = Time.current
# => 2010 年 8 月 9 日星期一 23:20:05 UTC +00:00
now.all_day
# => 2010 年 8 月 9 日星期一 00:00:00 UTC +00:00.. 2010 年 8 月 9 日星期一 23:59:59 UTC +00:00

類似地,all_weekall_monthall_quarter 和 [all_year][Date#AllTime::year]Calculations服務於生成時間範圍的目的。

now = Time.current
# => 2010 年 8 月 9 日星期一 23:20:05 UTC +00:00
now.all_week
# => 2010 年 8 月 9 日星期一 00:00:00 UTC +00:00.. 2010 年 8 月 15 日星期日 23:59:59 UTC +00:00
now.all_week(:sunday)
# => 2012 年 9 月 16 日星期日 00:00:00 UTC +00:00.. 2012 年 9 月 22 日星期六 23:59:59 UTC +00:00
now.all_month
# => 2010 年 8 月 1 日星期六 00:00:00 UTC +00:00.. 2010 年 8 月 31 日星期二 23:59:59 UTC +00:00
now.all_quarter
# => 2010 年 7 月 1 日星期四 00:00:00 UTC +00:00..星期四 2010 年 9 月 30 日 23:59:59 UTC +00:00
now.all_year
# => 2010 年 1 月 1 日星期五 00:00:00 UTC +00:00..星期五 2010 年 12 月 31 日 23:59:59 UTC +00:00

active_support/core_ext/date_and_time/calculations.rb 中定義。

17.1.3 prev_day, next_day

[prev_day][Time#prev_day] 和 [next_day][Time#next_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

active_support/core_ext/time/calculations.rb 中定義。

17.1.4 prev_month, next_month

[prev_month][Time#prev_month] 和 [next_month][Time#next_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

active_support/core_ext/time/calculations.rb 中定義。

17.1.5 prev_year, next_year

[prev_year][Time#prev_year] 和 [next_year][Time#next_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

active_support/core_ext/time/calculations.rb 中定義。

17.1.6 prev_quarter, next_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

active_support/core_ext/date_and_time/calculations.rb 中定義。

17.2 時間建構函式

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

Time.zone_default
# => #<ActiveSupport::TimeZone:0x7f73654d4f38 @utc_offset=nil, @name="Madrid", ...>
Time.current
# => 2010 年 8 月 6 日星期五 17:11:58 CEST +02:00

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

如果構建的時間超出了執行時平臺中Time支援的範圍,則丟棄usecs並返回一個DateTime物件。

17.2.1 持續時間

Duration 物件可以新增和減去時間物件:

now = Time.current
# => 2010 年 8 月 9 日星期一 23:20:05 UTC +00:00
now + 1.year
# => 2011 年 8 月 9 日星期二 23:21:11 UTC +00:00
now - 1.week
# => 2010 年 8 月 2 日星期一 23:21:11 UTC +00:00

它們轉換為對 sinceadvance 的呼叫。例如在這裡我們得到了日曆改革的正確跳轉:

Time.utc(1582, 10, 3) + 5.days
# => 10 月 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

輔助檔案寫入臨時檔案的標準目錄中,但您可以傳遞您選擇的目錄作為第二個引數。

active_support/core_ext/file/atomic.rb 中定義。

19 NameError 的擴充套件

Active Support 將 missing_name? 新增到 NameError,它測試是否由於作為引數傳遞的名稱而引發異常。

該名稱可以作為 symbol 或字串給出。 symbol 針對裸常量名稱進行測試,字串針對完全限定的常量名稱進行測試。

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

例如,當呼叫 ArticlesController 的 action 時,Rails 會樂觀地嘗試使用 ArticlesHelper。 helper module 不存在是可以的,因此如果引發該常量名稱的異常,則應將其靜音。但可能是由於實際未知常數,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

active_support/core_ext/name_error.rb 中定義。

20 LoadError 的擴充套件

Active Support 將 is_missing? 新增到 LoadError

給定路徑名 is_missing? 測試是否由於該特定檔案引發異常(“.rb”副檔名除外)。

例如,當呼叫 ArticlesController 的 action 時,Rails 嘗試載入 articles_helper.rb,但該檔案可能不存在。沒關係,helper module 不是強制性的,因此 Rails 消除了載入錯誤。但也可能是 helper module 確實存在,進而需要另一個缺失的庫。在這種情況下,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

active_support/core_ext/load_error.rb 中定義。

回饋

我們鼓勵您幫助提高本指南的品質。

如果您發現任何拼寫錯誤或資訊錯誤,請提供回饋。 要開始回饋,您可以閱讀我們的 回饋 部分。

您還可能會發現不完整的內容或不是最新的內容。 請務必為 main 新增任何遺漏的文件。假設是 非穩定版指南(edge guides) 請先驗證問題是否已經在主分支上解決。 請前往 Ruby on Rails 指南寫作準則 查看寫作風格和慣例。

如果由於某種原因您發現要修復的內容但無法自行修補,請您 提出 issue

關於 Ruby on Rails 的任何類型的討論歡迎提供任何文件至 rubyonrails-docs 討論區