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 它
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 Ruby on Rails 應用程式中的 Active Support
除非 config.active_support.bare
為 true,否則 Ruby on Rails 應用程式會載入所有 Active Support。在這種情況下,應用程式只會載入框架本身為其自身需求精選的內容,並且仍然可以按照上一節所述的任何粒度層級自行精選。
2 所有物件的擴展
2.1 blank?
和 present?
以下值在 Rails 應用程式中被視為空白
nil
和false
,僅由空格組成的字串 (請參閱下面的註解),
空的陣列和雜湊,以及
任何其他回應
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!
unless self["Cache-Control"].present?
# ...
end
end
2.2 presence
如果 presence
方法的接收者是 present?
,則會傳回接收者,否則傳回 nil
。它對於像這樣的慣用語很有用
host = config[:host].presence || "localhost"
2.3 duplicable?
從 Ruby 2.5 開始,大多數物件都可以透過 dup
或 clone
複製
"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
任何類別都可以透過移除 dup
和 clone
或從它們引發例外來禁止複製。因此,只有 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
的情況下呼叫方法時,最簡單的方式是使用條件陳述式,但這樣會增加不必要的混亂。替代方案是使用 try
。try
類似於 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 有一些類別與 Date
或 Time
類似,並遵循此合約。
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 中的幾個類別會覆寫此方法。
例如,nil
、true
和 false
會回傳它們本身。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
方法會建構一個查詢字串,將指定的 key
與 to_param
的回傳值關聯。例如,使用以下 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")
# => "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 支援
相較於 json
gem 通常為 Ruby 物件提供的實作,Active Support 提供了更好的 to_json
實作。這是因為某些類別,例如 Hash
和 Process::Status
需要特殊處理才能提供適當的 JSON 表示法。
2.12 實例變數
Active Support 提供了幾個方法來簡化對實例變數的存取。
2.12.1 instance_values
方法 instance_values
會回傳一個雜湊,將不帶 "@" 的實例變數名稱對應到它們對應的值。Key 是字串
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_warnings
和 enable_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_reader
、attr_internal_writer
和 attr_internal_accessor
。它們的行為與 Ruby 內建的 attr_*
對應項類似,不同之處在於它們會以降低衝突可能性方式命名底層的實例變數。
巨集 attr_internal
是 attr_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
設定,但您可以傳遞任何帶有開頭 @
和某處的 %s
的類 sprintf
格式字串,其中將放置名稱。預設值為 "@_%s"
。
Rails 在一些地方使用內部屬性,例如在檢視中
module ActionView
class Base
attr_internal :captures
attr_internal :request, :layout
attr_internal :controller, :template
end
end
3.1.3 模組屬性
巨集 mattr_reader
、mattr_writer
和 mattr_accessor
與為類別定義的 cattr_*
巨集相同。實際上,cattr_*
巨集只是 mattr_*
巨集的別名。請查看類別屬性。
例如,Active Storage 的 logger API 是使用 mattr_accessor
產生的
module ActiveStorage
mattr_accessor :logger
end
3.2 父層 (Parents)
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 匿名 (Anonymous)
一個模組可能有名稱,也可能沒有名稱。
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 方法委派 (Method Delegation)
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 重新定義方法 (Redefining Methods)
在某些情況下,您需要使用 define_method
定義方法,但不知道是否已存在具有該名稱的方法。如果存在,則在啟用時會發出警告。這不是什麼大問題,但也並不乾淨。
方法 redefine_method
可防止這種潛在的警告,如果需要,會先移除現有的方法。
如果您需要自行定義替換方法(例如,因為您正在使用 delegate
),您也可以使用 silence_redefinition_of_method
。
4 Class
的擴充 (Extensions to Class
)
4.1 類別屬性 (Class Attributes)
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_reader
為 false
時,實例述詞會像讀取器方法一樣回傳 NoMethodError
。
如果您不想要實例述詞,請傳遞 instance_predicate: false
,它就不會被定義。
4.1.2 cattr_reader
、cattr_writer
和 cattr_accessor
巨集 cattr_reader
、cattr_writer
和 cattr_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
,而不是任何 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 子類別和後代 (Subclasses and Descendants)
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]
這些類別回傳的順序未指定。
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]
這些類別回傳的順序未指定。
5 String
的擴充 (Extensions to String
)
5.1 輸出安全性 (Output Safety)
5.1.1 動機 (Motivation)
將資料插入 HTML 範本需要格外小心。例如,您不能只是逐字將 @review.title
插入 HTML 頁面。首先,如果評論標題是「Flanagan & Matz rules!」,則輸出將不會格式正確,因為 & 符號必須逸出為 "&"。此外,根據應用程式的不同,這可能是一個很大的安全漏洞,因為使用者可以透過設定精心製作的評論標題來注入惡意 HTML。有關風險的更多資訊,請參閱安全性指南中關於跨網站指令碼的部分。
5.1.2 安全字串 (Safe Strings)
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 + "<" # => "<"
安全引數會直接附加。
"".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 轉換 (Transformation)
一般來說,除了上面說明的串連之外,任何可能會變更字串的方法都會給您一個不安全的字串。這些是 downcase
、gsub
、strip
、chomp
、underscore
等。
在像 gsub!
這樣就地轉換的情況下,接收者本身會變得不安全。
無論轉換是否實際變更了任何內容,安全性位元都會永遠遺失。
5.1.4 轉換和強制型別轉換 (Conversion and Coercion)
在安全字串上呼叫 to_s
會回傳安全字串,但使用 to_str
強制型別轉換會回傳不安全的字串。
5.1.5 複製 (Copying)
在安全字串上呼叫 dup
或 clone
會產生安全字串。
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: "…")
# => "Oh dear! Oh …"
請特別注意,截斷會將省略字串的長度納入考量。
傳遞一個 :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: "…")
# => "Oh dear! Oh dear!…"
傳遞一個 :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
會移除 heredoc 中的縮排。
例如在
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(position)
方法 at
會返回字串中位置 position
的字元。
"hello".at(0) # => "h"
"hello".at(4) # => "o"
"hello".at(-1) # => "o"
"hello".at(10) # => nil
5.11.2 from(position)
方法 from
會返回字串中從位置 position
開始的子字串。
"hello".from(0) # => "hello"
"hello".from(2) # => "llo"
"hello".from(-2) # => "lo"
"hello".from(10) # => nil
5.11.3 to(position)
方法 to
會返回字串中到位置 position
的子字串。
"hello".to(0) # => "h"
"hello".to(2) # => "hel"
"hello".to(-2) # => "hell"
"hello".to(10) # => "hello"
5.11.4 first(limit = 1)
方法 first
會返回包含字串前 limit
個字元的子字串。
如果 n
> 0,則呼叫 str.first(n)
等同於 str.to(n-1)
,且當 n
== 0 時返回空字串。
5.11.5 last(limit = 1)
方法 last
會返回包含字串最後 limit
個字元的子字串。
如果 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 使用此方法來計算計數器快取欄位的名稱。
# 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
會以可用於美觀 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"
5.12.10 tableize
方法 tableize
是 underscore
後面接著 pluralize
。
"Person".tableize # => "people"
"Invoice".tableize # => "invoices"
"InvoiceLine".tableize # => "invoice_lines"
作為經驗法則,在簡單的情況下,tableize
會返回對應於給定模型的資料表名稱。實際上 Active Record 中的實作並非直接使用 tableize
,因為它還會將類別名稱 demodulize,並檢查一些可能會影響返回字串的選項。
5.12.11 classify
方法 classify
是 tableize
的反向操作。它會為您提供對應於資料表名稱的類別名稱。
"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 在相同位置評估實際常數時所做的操作。
Mailer 測試案例使用 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
從類別名稱給出一個外鍵欄位名稱。為此,它會進行 demudulize、使用底線,並加上 "_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_one
和 has_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_date
、to_time
、to_datetime
方法 to_date
、 to_time
和 to_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 物件。
這些方法可以與 from_now
、ago
等組合使用,以進行精確的日期計算。例如
# 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 物件。
這些方法可以與 from_now
、ago
等組合使用,以進行精確的日期計算。例如
# 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 ] ]
方法 second
、third
、fourth
和 fifth
返回對應的元素,second_to_last
和 third_to_last
也是如此(first
和 last
是內建的)。感謝社會智慧和積極的建設性,forty_two
也可用。
%w(a b c d).third # => "c"
%w(a b c d).fifth # => nil
11.2 提取
方法 extract!
移除並返回區塊返回 true 值的元素。如果沒有給定區塊,則會返回 Enumerator。
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
此方法接收任意數量的 action 名稱,以及一個可選的選項雜湊作為最後一個參數。透過呼叫 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 鍵 |
---|---|
:two_words_connector |
support.array.two_words_connector |
:words_connector |
support.array.words_connector |
:last_word_connector |
support.array.last_word_connector |
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
,則會調用該方法。如果傳回的值為nil
,Kernel#Array
會繼續嘗試to_a
,但Array.wrap
會立即傳回一個以該參數作為其單一元素的陣列。 - 如果從
to_ary
傳回的值既不是nil
也不是Array
物件,Kernel#Array
會引發例外,而Array.wrap
不會,它只會傳回該值。 - 它不會對參數呼叫
to_a
,如果參數不回應to_ary
,它會返回一個以該參數作為其單一元素的陣列。
最後一點對於某些可列舉物件來說尤其值得比較
Array.wrap(foo: :bar) # => [{:foo=>:bar}]
Array(foo: :bar) # => [[:foo, :bar]]
還有一個相關的慣用語使用 splat 運算子
[*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>
為此,該方法會循環遍歷這些對,並建立依賴於值的節點。給定一個對 key
、value
如果
value
是一個雜湊,則會以key
作為:root
進行遞迴呼叫。如果
value
是一個陣列,則會以key
作為:root
,並以key
的單數形式作為:children
進行遞迴呼叫。如果
value
是一個可呼叫的物件,它必須預期一個或兩個參數。根據 arity,可呼叫物件會以options
雜湊作為第一個參數,以key
作為:root
,並以key
的單數形式作為第二個參數調用。其返回值會成為一個新的節點。如果
value
回應to_xml
,則會以key
作為:root
調用該方法。否則,會建立一個以
key
作為標記的節點,並以value
的字串表示形式作為文字節點。如果value
為nil
,則會添加一個 "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
可以合併兩個雜湊
{ a: 1, b: 1 }.merge(a: 0, c: 2)
# => {:a=>0, :b=>1, :c=>2}
Active Support 定義了一些可能很方便的合併雜湊的更多方法。
12.2.1 reverse_merge
和 reverse_merge!
如果發生衝突,則參數雜湊中的鍵在 merge
中獲勝。您可以使用以下慣用語以緊湊的方式支援帶有預設值的選項雜湊
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!
可能會更改呼叫者中的雜湊,這可能是一個好主意,也可能不是。
12.2.2 reverse_update
方法 reverse_update
是上面解釋的 reverse_merge!
的別名。
請注意,reverse_update
沒有驚嘆號。
12.2.3 deep_merge
和 deep_merge!
正如您在前面的範例中看到的那樣,如果在兩個雜湊中都找到了鍵,則參數中的值獲勝。
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 except!
方法 except!
與內建的 except
方法相同,但會就地移除鍵,並返回 self
。
{ a: 1, b: 2 }.except!(:a) # => {:b=>2}
{ a: 1, b: 2 }.except!(:c) # => {:a=>1, :b=>2}
如果接收者響應 convert_key
,則會對每個參數調用該方法。這使得 except!
(和 except
)可以與具有非強制存取的雜湊表很好地協同工作。
{ a: 1 }.with_indifferent_access.except!(:a) # => {}
{ a: 1 }.with_indifferent_access.except!("a") # => {}
12.4.2 stringify_keys
和 stringify_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_checkbox_tag(options = {}, checked_value = "1", unchecked_value = "0")
options = options.stringify_keys
options["type"] = "checkbox"
# ...
end
第二行可以安全地存取 "type" 鍵,並讓使用者傳遞 :type
或 "type"。
還有驚嘆號變體 stringify_keys!
,它會就地字串化鍵。
除此之外,可以使用 deep_stringify_keys
和 deep_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_keys
和 symbolize_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_textarea_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_keys
和 deep_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_options
和 to_options!
方法 to_options
和 to_options!
分別是 symbolize_keys
和 symbolize_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_values
和 deep_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.yesterday
和 Date.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_week
、end_of_week
方法 beginning_of_week
和 end_of_week
分別返回一週的開始和結束日期。假設一週從星期一開始,但是可以通過傳遞參數、設定執行緒本地 Date.beginning_of_week
或 config.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 monday
、sunday
方法 monday
和 sunday
分別返回前一個星期一和下一個星期日的日期。
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_week
、next_week
方法 next_week
接收一個帶有英文日期名稱的符號(預設為執行緒本地 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_week
或 config.beginning_of_week
時,next_week
和 prev_week
都會按預期工作。
15.1.2.4 beginning_of_month
、end_of_month
方法 beginning_of_month
和 end_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 quarter
、beginning_of_quarter
、end_of_quarter
方法 quarter
返回接收者日曆年的季度
d = Date.new(2010, 5, 9) # => Sun, 09 May 2010
d.quarter # => 2
方法 beginning_of_quarter
和 end_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_year
、end_of_year
方法 beginning_of_year
和 end_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_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)
的簡寫。
15.1.3.2 months_ago
、months_since
方法 months_ago
和 months_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_since
方法 weeks_ago
和 [weeks_since
][DateAndTime::Calculations#week_since] 的運作方式與週類似。
Date.new(2010, 5, 24).weeks_ago(1) # => Mon, 17 May 2010
Date.new(2010, 5, 24).weeks_since(2) # => Mon, 07 Jun 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
它們會轉換為對 since
或 advance
的呼叫。例如,在這裡我們可以在日曆改革中獲得正確的跳轉。
Date.new(1582, 10, 4) + 1.day
# => Fri, 15 Oct 1582
15.1.6 時間戳記
如果可能,以下方法會回傳一個 Time
物件,否則會回傳 DateTime
。如果已設定,它們會採用使用者時區。
15.1.6.1 beginning_of_day
、end_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_day
、midnight
、at_midnight
的別名。
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
的別名。
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_hour
、end_of_hour
、beginning_of_minute
和 end_of_minute
是針對 Time
和 DateTime
實作的,但 **不適用於** Date
,因為要求 Date
實例的小時或分鐘的開始或結束沒有意義。
15.1.6.4 ago
、since
方法 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
。
另一方面,advance
和 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
定義的。
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
它們會轉換為對 since
或 advance
的呼叫。例如,在這裡我們可以在日曆改革中獲得正確的跳轉。
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
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_day
、all_week
、all_month
、all_quarter
和 all_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_week
、all_month
、all_quarter
和 all_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_day
、next_day
prev_day
和 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
17.1.4 prev_month
、next_month
prev_month
和 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
17.1.5 prev_year
、next_year
prev_year
和 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 日,則會取得 2 月 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_quarter
、next_quarter
prev_quarter
和 next_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.current
為 Time.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
支援的範圍,則會捨棄 usecs,並改為傳回 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
它們會轉換為對 since
或 advance
的呼叫。例如,在這裡我們可以在日曆改革中獲得正確的跳轉。
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
,但該檔案可能不存在。這沒關係,Helper 模組並非強制性,因此 Rails 會將載入錯誤靜音。但有可能 Helper 模組確實存在,並且反過來需要另一個遺失的程式庫。在這種情況下,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