1 如何載入核心擴充
1.1 獨立的 Active Support
為了讓預設的佔用空間最小,Active Support 預設載入最少的依賴項。它被切成小塊,以便只載入所需的擴充。它也有一些方便的進入點,可以一次載入相關的擴充,甚至全部載入。
因此,在進行像這樣的簡單 require 之後
require "active_support"
僅載入 Active Support 架構所需的擴充套件。
1.1.1 選取定義
此範例顯示如何載入 Hash#with_indifferent_access
。此擴充套件可將 Hash
轉換為 ActiveSupport::HashWithIndifferentAccess
,允許使用字串或符號存取金鑰。
{ a: 1 }.with_indifferent_access["a"] # => 1
對於每個定義為核心擴充套件的方法,本指南都有註解說明該方法的定義位置。在 with_indifferent_access
的情況下,註解寫道
這表示您可以像這樣需要它
require "active_support"
require "active_support/core_ext/hash/indifferent_access"
Active Support 已經過仔細修改,因此選取檔案時僅會載入嚴格需要的相依性(如果有)。
1.1.2 載入群組核心擴充套件
下一層級只需載入 Hash
的所有擴充套件。根據經驗法則,SomeClass
的擴充套件可透過載入 active_support/core_ext/some_class
一次取得。
因此,要載入 Hash
的所有擴充套件(包括 with_indifferent_access
)
require "active_support"
require "active_support/core_ext/hash"
1.1.3 載入所有核心擴充套件
您可能只想載入所有核心擴充套件,有一個檔案可以做到這件事
require "active_support"
require "active_support/core_ext"
1.1.4 載入所有 Active Support
最後,如果您想要使用所有 Active Support,只需執行
require "active_support/all"
這甚至不會將整個 Active Support 預先置於記憶體中,確實有些東西是透過 autoload
設定的,因此只有在使用時才會載入。
1.2 Ruby on Rails 應用程式中的 Active Support
除非 config.active_support.bare
為 true,否則 Ruby on Rails 應用程式會載入所有 Active Support。在這種情況下,應用程式將僅載入架構本身為其自身需求而選取的內容,並且仍可以根據前一節所述以任何層級選取。
2 所有物件的擴充套件
2.1 blank?
和 present?
在 Rails 應用程式中,下列值會被視為空白
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!
return if self["Cache-Control"].present?
# ...
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 和值進行必要的轉譯
account.to_query('company[name]')
# => "company%5Bname%5D=Johnson+%26+Johnson"
因此其輸出可直接用於查詢字串中。
陣列會傳回將 to_query
套用至每個元素的結果,其中 key[]
為 key,並使用「&」將結果串接起來
[3.4, -45.6].to_query('sample')
# => "sample%5B%5D=3.4&sample%5B%5D=-45.6"
雜湊也會回應 to_query
,但簽章不同。如果未傳遞參數,呼叫會產生一個已排序的 key/value 指派系列,並對其值呼叫 to_query(key)
。然後,它會使用「&」將結果串接起來
{ c: 3, b: 2, a: 1 }.to_query # => "a=1&b=2&c=3"
方法 Hash#to_query
接受 key 的選用命名空間
{ id: 89, name: "John Smith" }.to_query('user')
# => "user%5Bid%5D=89&user%5Bname%5D=John+Smith"
2.10 with_options
方法 with_options
提供一個方法,可將一系列方法呼叫中的常見選項分解出來。
給定一個預設選項雜湊,with_options
會產生一個代理物件給區塊。在區塊中,呼叫代理物件的方法會將其選項合併後轉發給接收器。例如,您可以用
class Account < ApplicationRecord
has_many :customers, dependent: :destroy
has_many :products, dependent: :destroy
has_many :invoices, dependent: :destroy
has_many :expenses, dependent: :destroy
end
這種方式
class Account < ApplicationRecord
with_options dependent: :destroy do |assoc|
assoc.has_many :customers
assoc.has_many :products
assoc.has_many :invoices
assoc.has_many :expenses
end
end
這種慣用語法也可以傳達給讀者分組。例如,假設您要寄送一封取決於使用者的語言的電子報。在郵件程式中,您可以像這樣將依據地區而定的部分分組
I18n.with_options locale: user.locale, scope: "newsletter" do |i18n|
subject i18n.t :subject
body i18n.t :body, user_name: user.name
end
由於 with_options
會將呼叫轉發給其接收器,因此它們可以巢狀。每個巢狀層級除了自己的預設值之外,還會合併繼承的預設值。
2.11 JSON 支援
Active Support 提供了比 json
寶石通常為 Ruby 物件提供的 to_json
更佳的實作。這是因為某些類別,例如 Hash
和 Process::Status
需要特殊處理才能提供適當的 JSON 表示。
2.12 執行個體變數
Active Support 提供了多種方法來簡化執行個體變數的存取。
2.12.1 instance_values
方法 instance_values
會傳回一個雜湊,將沒有「@」符號的執行個體變數名稱對應到其對應值。金鑰是字串
class C
def initialize(x, y)
@x, @y = x, y
end
end
C.new(0, 1).instance_values # => {"x" => 0, "y" => 1}
2.12.2 instance_variable_names
方法 instance_variable_names
會傳回一個陣列。每個名稱都包含「@」符號。
class C
def initialize(x, y)
@x, @y = x, y
end
end
C.new(0, 1).instance_variable_names # => ["@x", "@y"]
2.13 靜音警告和例外
方法 silence_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
進行設定,您可以傳遞任何類似 sprintf
的格式字串,其中包含開頭的 @
和某處的 %s
,名稱將置於此處。預設值為 "@_%s"
。
Rails 在幾個地方使用內部屬性,例如檢視
module ActionView
class Base
attr_internal :captures
attr_internal :request, :layout
attr_internal :controller, :template
end
end
3.1.3 模組屬性
巨集 mattr_reader
、mattr_writer
和 mattr_accessor
與為類別定義的 cattr_*
巨集相同。事實上,cattr_*
巨集只是 mattr_*
巨集的別名。請查看 類別屬性。
例如,Active Storage 的記錄器的 API 是使用 mattr_accessor
產生的
module ActiveStorage
mattr_accessor :logger
end
3.2 父類別
3.2.1 module_parent
巢狀命名模組上的 module_parent
方法傳回包含其對應常數的模組
module X
module Y
module Z
end
end
end
M = X::Y::Z
X::Y::Z.module_parent # => X::Y
M.module_parent # => X::Y
如果模組是匿名的或屬於頂層,module_parent
會傳回 Object
。
請注意,在這種情況下,module_parent_name
會傳回 nil
。
3.2.2 module_parent_name
巢狀命名模組上的 module_parent_name
方法傳回包含其對應常數的模組的完整限定名稱
module X
module Y
module Z
end
end
end
M = X::Y::Z
X::Y::Z.module_parent_name # => "X::Y"
M.module_parent_name # => "X::Y"
對於頂層或匿名模組,module_parent_name
會傳回 nil
。
請注意,在這種情況下,module_parent
會傳回 Object
。
3.2.3 module_parents
方法 module_parents
對接收者呼叫 module_parent
,並向上呼叫直到到達 Object
。鏈會以由下至上的順序傳回陣列中
module X
module Y
module Z
end
end
end
M = X::Y::Z
X::Y::Z.module_parents # => [X::Y, X, Object]
M.module_parents # => [X::Y, X, Object]
3.3 匿名
模組可能有或沒有名稱
module M
end
M.name # => "M"
N = Module.new
N.name # => "N"
Module.new.name # => nil
您可以使用謂詞 anonymous?
檢查模組是否有名稱
module M
end
M.anonymous? # => false
Module.new.anonymous? # => true
請注意,無法到達並不表示是匿名的
module M
end
m = Object.send(:remove_const, :M)
m.anonymous? # => false
儘管匿名模組根據定義是無法到達的。
3.4 方法委派
3.4.1 delegate
巨集 delegate
提供了一個轉發方法的簡易方式。
想像在某個應用程式中,使用者的登入資訊在 User
模型中,但姓名和其他資料在一個獨立的 Profile
模型中
class User < ApplicationRecord
has_one :profile
end
有了這個設定,你可以透過使用者的個人資料取得使用者的姓名,user.profile.name
,但直接存取此類屬性可能會更方便
class User < ApplicationRecord
has_one :profile
def name
profile.name
end
end
這就是 delegate
為你做的事
class User < ApplicationRecord
has_one :profile
delegate :name, to: :profile
end
它更簡短,而且意圖更明顯。
方法在目標中必須是公開的。
delegate
巨集接受多種方法
delegate :name, :age, :address, :twitter, to: :profile
當內插到字串中時,:to
選項應成為一個表達式,用於評估方法委派到的物件。通常是字串或符號。此類表達式會在接收者的內容中評估
# delegates to the Rails constant
delegate :logger, to: :Rails
# delegates to the receiver's class
delegate :table_name, to: :class
如果 :prefix
選項為 true
,這會較不通用,請見下方。
預設情況下,如果委派引發 NoMethodError
且目標為 nil
,例外狀況會傳播。你可以使用 :allow_nil
選項要求改為傳回 nil
delegate :name, to: :profile, allow_nil: true
使用 :allow_nil
時,如果使用者沒有個人資料,呼叫 user.name
會傳回 nil
。
選項 :prefix
會在產生的方法名稱中加上一個前綴。這可能很方便,例如用於取得更好的名稱
delegate :street, to: :address, prefix: true
前一個範例會產生 address_street
而不是 street
。
由於在這個案例中,產生的方法名稱是由目標物件和目標方法名稱組成,因此 :to
選項必須是一個方法名稱。
也可以設定自訂的前綴
delegate :size, to: :attachment, prefix: :avatar
在之前的範例中,巨集會產生 avatar_size
而不是 size
。
選項 :private
會變更方法範圍
delegate :date_of_birth, to: :profile, private: true
委派的方法預設為公開。傳遞 private: true
來變更。
3.4.2 delegate_missing_to
想像一下,您想要將 User
物件中所有遺失的內容委派給 Profile
物件。巨集 delegate_missing_to
讓您可以輕鬆實作這一點
class User < ApplicationRecord
has_one :profile
delegate_missing_to :profile
end
目標可以是物件中任何可呼叫的項目,例如實例變數、方法、常數等。只有目標的公開方法會被委派。
3.5 重新定義方法
在某些情況下,您需要使用 define_method
定義方法,但不知道是否已經存在具有該名稱的方法。如果存在,如果已啟用,則會發出警告。這不是什麼大問題,但也不乾淨。
方法 redefine_method
可防止此類潛在警告,並在需要時移除現有方法。
如果您需要自己定義替換方法(例如,因為您正在使用 delegate
),您也可以使用 silence_redefinition_of_method
。
4 Class
的延伸
4.1 類別屬性
4.1.1 class_attribute
方法 class_attribute
宣告一個或多個可繼承的類別屬性,可以在階層中的任何層級覆寫。
class A
class_attribute :x
end
class B < A; end
class C < B; end
A.x = :a
B.x # => :a
C.x # => :a
B.x = :b
A.x # => :a
C.x # => :b
C.x = :c
A.x # => :a
B.x # => :b
例如,ActionMailer::Base
定義
class_attribute :default_params
self.default_params = {
mime_version: "1.0",
charset: "UTF-8",
content_type: "text/plain",
parts_order: [ "text/plain", "text/enriched", "text/html" ]
}.freeze
也可以在實例層級存取和覆寫。
A.x = 1
a1 = A.new
a2 = A.new
a2.x = 2
a1.x # => 1, comes from A
a2.x # => 2, overridden in a2
可以透過將選項 :instance_writer
設為 false
來防止產生寫入實例方法。
module ActiveRecord
class Base
class_attribute :table_name_prefix, instance_writer: false, default: "my"
end
end
模型可能會發現此選項很有用,可以防止大量指定設定屬性。
可以透過將選項 :instance_reader
設為 false
來防止產生讀取實例方法。
class A
class_attribute :x, instance_reader: false
end
A.new.x = 1
A.new.x # NoMethodError
為了方便,class_attribute
也定義一個實例謂詞,它是否定實例讀取器回傳值的兩次否定。在上述範例中,它會稱為 x?
。
當 :instance_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`,而不是任何假值。
module A
class B
# No first_name instance reader is generated.
cattr_accessor :first_name, instance_reader: false
# No last_name= instance writer is generated.
cattr_accessor :last_name, instance_writer: false
# No surname instance reader or surname= writer is generated.
cattr_accessor :surname, instance_accessor: false
end
end
模型可能會發現將 `:instance_accessor` 設定為 `false` 有用,因為這可以防止大量指派設定屬性。
4.2 子類別和後代
4.2.1 `subclasses`
subclasses
方法會傳回接收者的子類別
class C; end
C.subclasses # => []
class B < C; end
C.subclasses # => [B]
class A < B; end
C.subclasses # => [B]
class D < C; end
C.subclasses # => [B, D]
這些類別的傳回順序未指定。
定義在 `active_support/core_ext/class/subclasses.rb` 中。
4.2.2 `descendants`
descendants
方法會傳回所有小於其接收者的類別
class C; end
C.descendants # => []
class B < C; end
C.descendants # => [B]
class A < B; end
C.descendants # => [B, A]
class D < C; end
C.descendants # => [B, A, D]
這些類別的傳回順序未指定。
定義在 `active_support/core_ext/class/subclasses.rb` 中。
5 擴充 `String`
5.1 輸出安全性
5.1.1 動機
將資料插入 HTML 範本需要特別小心。例如,您不能直接將 `@review.title` 插值到 HTML 頁面中。原因之一是,如果評論標題為「Flanagan & Matz rules!」,輸出將不會格式良好,因為必須將 & 字元轉譯為「&」。更重要的是,這可能會造成重大的安全漏洞,具體取決於應用程式,因為使用者可以透過設定精心設計的評論標題來注入惡意 HTML。請參閱 安全性指南 中關於跨網站指令碼的區段,以進一步了解風險。
5.1.2 安全字串
Active Support 有 (html) 安全 字串的概念。安全字串標記為可插入 HTML。不論是否已跳脫,它都是可信賴的。
預設情況下,字串被視為 不安全
"".html_safe? # => false
你可以使用 html_safe
方法從給定的字串取得安全字串
s = "".html_safe
s.html_safe? # => true
了解 html_safe
不會執行任何跳脫非常重要,它只是一個聲明
s = "<script>...</script>".html_safe
s.html_safe? # => true
s # => "<script>...</script>"
你應負責確保在特定字串上呼叫 html_safe
是沒問題的。
如果你附加到安全字串,不論是使用 concat
/<<
原地附加,還是使用 +
,結果都是安全字串。不安全的引數會被跳脫
"".html_safe + "<" # => "<"
安全引數會直接附加
"".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 轉換
根據經驗法則,除了如上所述的串接之外,任何可能變更字串的方法都會給你一個不安全的字串。這些方法包括 downcase
、gsub
、strip
、chomp
、underscore
等。
對於 gsub!
等原地轉換,接收者本身會變得不安全。
安全位元會永遠遺失,不論轉換是否實際變更某些內容。
5.1.4 轉換和強制轉型
在安全字串上呼叫 to_s
會傳回安全字串,但使用 to_str
強制轉型會傳回不安全的字串。
5.1.5 複製
呼叫安全字串上的 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
方法會移除 heredocs 中的縮排。
例如:
if options[:usage]
puts <<-USAGE.strip_heredoc
This command does such and such.
Supported options are:
-h This message
...
USAGE
end
使用者會看到使用訊息與左邊界對齊。
技術上來說,它會尋找整個字串中最少縮排的行,並移除該行開頭的空白。
5.10 indent
indent
方法會縮排接收者的行
<<EOS.indent(2)
def some_method
some_code
end
EOS
# =>
def some_method
some_code
end
第二個參數 indent_string
指定要使用的縮排字串。預設為 nil
,這會讓方法查看第一個縮排行並做出明智的猜測,如果沒有縮排行則改用空白。
" foo".indent(2) # => " foo"
"foo\n\t\tbar".indent(2) # => "\t\tfoo\n\t\t\t\tbar"
"foo".indent(2, "\t") # => "\t\tfoo"
雖然 indent_string
通常是一個空白或一個 tab,但它可以是任何字串。
第三個參數 indent_empty_lines
是個旗標,表示是否要縮排空行。預設為 false。
"foo\n\nbar".indent(2) # => " foo\n\n bar"
"foo\n\nbar".indent(2, nil, true) # => " foo\n \n bar"
indent!
方法會就地縮排。
5.11 存取
5.11.1 at(位置)
at
方法傳回字串中位於 位置
的字元
"hello".at(0) # => "h"
"hello".at(4) # => "o"
"hello".at(-1) # => "o"
"hello".at(10) # => nil
5.11.2 from(位置)
from
方法傳回從 位置
開始的字串子字串
"hello".from(0) # => "hello"
"hello".from(2) # => "llo"
"hello".from(-2) # => "lo"
"hello".from(10) # => nil
5.11.3 to(位置)
to
方法傳回字串子字串到 位置
為止
"hello".to(0) # => "h"
"hello".to(2) # => "hel"
"hello".to(-2) # => "hell"
"hello".to(10) # => "hello"
5.11.4 first(限制 = 1)
first
方法傳回包含字串開頭 限制
個字元的子字串。
如果 n
> 0,呼叫 str.first(n)
等同於 str.to(n-1)
,而當 n
== 0 時則傳回空字串。
5.11.5 last(限制 = 1)
last
方法傳回包含字串結尾 限制
個字元的子字串。
如果 n
> 0,呼叫 str.last(n)
等同於 str.from(-n)
,而當 n
== 0 時則傳回空字串。
5.12 詞形變化
5.12.1 pluralize
pluralize
方法傳回其接收者的複數型態
"table".pluralize # => "tables"
"ruby".pluralize # => "rubies"
"equipment".pluralize # => "equipment"
如前一個範例所示,Active Support 認識一些不規則複數和不可數名詞。內建規則可以在 config/initializers/inflections.rb
中擴充。這個檔案預設由 rails new
指令產生,並在註解中提供說明。
pluralize
也能接受一個選用的 count
參數。如果 count == 1
會傳回單數型態。對於 count
的任何其他值,都會傳回複數型態
"dude".pluralize(0) # => "dudes"
"dude".pluralize(1) # => "dude"
"dude".pluralize(2) # => "dudes"
Active Record 使用此方法來計算對應於模型的預設資料表名稱
# active_record/model_schema.rb
def undecorated_table_name(model_name)
table_name = model_name.to_s.demodulize.underscore
pluralize_table_names ? table_name.pluralize : table_name
end
5.12.2 singularize
singularize
方法是 pluralize
的反向
"tables".singularize # => "table"
"rubies".singularize # => "ruby"
"equipment".singularize # => "equipment"
關聯使用此方法來計算對應的預設關聯類別的名稱
# active_record/reflection.rb
def derive_class_name
class_name = name.to_s.camelize
class_name = class_name.singularize if collection?
class_name
end
5.12.3 camelize
方法 camelize
以駝峰式大小寫回傳其接收器
"product".camelize # => "Product"
"admin_user".camelize # => "AdminUser"
根據經驗法則,您可以將此方法視為將路徑轉換為 Ruby 類別或模組名稱的方法,其中斜線分隔命名空間
"backoffice/session".camelize # => "Backoffice::Session"
例如,Action Pack 使用此方法來載入提供特定階段儲存的類別
# action_controller/metal/session_management.rb
def session_store=(store)
@@session_store = store.is_a?(Symbol) ?
ActionDispatch::Session.const_get(store.to_s.camelize) :
store
end
camelize
接受一個選用參數,它可以是 :upper
(預設值)或 :lower
。後者會將第一個字母轉換為小寫
"visual_effect".camelize(:lower) # => "visualEffect"
這可能有助於計算遵循該慣例的語言(例如 JavaScript)中的方法名稱。
根據經驗法則,您可以將 camelize
視為 underscore
的反向,儘管在某些情況下並非如此:"SSLError".underscore.camelize
會回傳 "SslError"
。為了支援此類情況,Active Support 允許您在 config/initializers/inflections.rb
中指定縮寫
ActiveSupport::Inflector.inflections do |inflect|
inflect.acronym 'SSL'
end
"SSLError".underscore.camelize # => "SSLError"
camelize
別名為 camelcase
。
5.12.4 underscore
方法 underscore
反其道而行,從駝峰式大小寫轉換為路徑
"Product".underscore # => "product"
"AdminUser".underscore # => "admin_user"
同時將「::」轉換回「/」
"Backoffice::Session".underscore # => "backoffice/session"
並理解以小寫開頭的字串
"visualEffect".underscore # => "visual_effect"
不過,underscore
不接受參數。
Rails 使用 underscore
來取得控制器類別的小寫名稱
# actionpack/lib/abstract_controller/base.rb
def controller_path
@controller_path ||= name.delete_suffix("Controller").underscore
end
例如,該值是您在 params[:controller]
中取得的值。
根據經驗法則,您可以將 underscore
視為 camelize
的反向,儘管在某些情況下並非如此。例如,"SSLError".underscore.camelize
會回傳 "SslError"
。
5.12.5 titleize
方法 titleize
將接收者中的字詞大寫
"alice in wonderland".titleize # => "Alice In Wonderland"
"fermat's enigma".titleize # => "Fermat's Enigma"
titleize
別名為 titlecase
。
5.12.6 dasherize
方法 dasherize
將接收者中的底線取代為破折號
"name".dasherize # => "name"
"contact_data".dasherize # => "contact-data"
模型的 XML 序列器使用此方法將節點名稱破折號化
# active_model/serializers/xml.rb
def reformat_name(name)
name = name.camelize if camelize?
dasherize? ? name.dasherize : name
end
5.12.7 demodulize
給定一個包含限定常數名稱的字串,demodulize
會傳回該常數名稱,也就是字串最右邊的部分
"Product".demodulize # => "Product"
"Backoffice::UsersController".demodulize # => "UsersController"
"Admin::Hotel::ReservationUtils".demodulize # => "ReservationUtils"
"::Inflections".demodulize # => "Inflections"
"".demodulize # => ""
例如,Active Record 使用此方法來計算 counter 快取欄位的名稱
# active_record/reflection.rb
def counter_cache_column
if options[:counter_cache] == true
"#{active_record.name.demodulize.underscore.pluralize}_count"
elsif options[:counter_cache]
options[:counter_cache]
end
end
5.12.8 deconstantize
給定一個包含限定常數參考表達式的字串,deconstantize
會移除最右邊的區塊,通常會留下常數容器的名稱
"Product".deconstantize # => ""
"Backoffice::UsersController".deconstantize # => "Backoffice"
"Admin::Hotel::ReservationUtils".deconstantize # => "Admin::Hotel"
5.12.9 parameterize
方法 parameterize
會以可用於美觀網址的方式正規化其接收者。
"John Smith".parameterize # => "john-smith"
"Kurt Gödel".parameterize # => "kurt-godel"
若要保留字串的大小寫,請將 preserve_case
參數設為 true。預設情況下,preserve_case
設為 false。
"John Smith".parameterize(preserve_case: true) # => "John-Smith"
"Kurt Gödel".parameterize(preserve_case: true) # => "Kurt-Godel"
若要使用自訂分隔符號,請覆寫 separator
參數。
"John Smith".parameterize(separator: "_") # => "john_smith"
"Kurt Gödel".parameterize(separator: "_") # => "kurt_godel"
5.12.10 tableize
方法 tableize
是 underscore
接著 pluralize
。
"Person".tableize # => "people"
"Invoice".tableize # => "invoices"
"InvoiceLine".tableize # => "invoice_lines"
根據經驗法則,tableize
會傳回對應於給定模型的資料表名稱,適用於簡單的情況。Active Record 中的實際實作並非直接 tableize
,因為它還會取消模組化類別名稱,並檢查可能影響傳回字串的幾個選項。
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 在同一個位置會執行的動作,如果評估一個真正的常數。
郵件測試案例會使用 constantize
從測試類別的名稱取得正在測試的郵件
# action_mailer/test_case.rb
def determine_default_mailer(name)
name.delete_suffix("Test").constantize
rescue NameError => e
raise NonInferrableMailerError.new(name)
end
5.12.13 humanize
方法 humanize
會調整屬性名稱,以顯示給最終使用者。
具體來說,它會執行以下轉換
- 將人類的變格規則套用至參數。
- 刪除前導底線(如果有)。
- 移除「_id」字尾(如果有)。
- 將底線替換為空格(如果有)。
- 將所有字詞轉為小寫,縮寫詞除外。
- 將第一個字詞大寫。
可以透過將 :capitalize
選項設定為 false 來關閉第一個字詞的大寫(預設為 true)。
"name".humanize # => "Name"
"author_id".humanize # => "Author"
"author_id".humanize(capitalize: false) # => "author"
"comments_count".humanize # => "Comments count"
"_id".humanize # => "Id"
如果「SSL」被定義為縮寫詞
'ssl_error'.humanize # => "SSL error"
輔助方法 full_messages
會使用 humanize
作為備用方案,以包含屬性名稱
def full_messages
map { |attribute, message| full_message(attribute, message) }
end
def full_message
# ...
attr_name = attribute.to_s.tr('.', '_').humanize
attr_name = @base.class.human_attribute_name(attribute, default: attr_name)
# ...
end
5.12.14 foreign_key
方法 foreign_key
從類別名稱給出一個外來鍵欄位名稱。為此,它會取消模組化、加上底線,並加上 "_id"
"User".foreign_key # => "user_id"
"InvoiceLine".foreign_key # => "invoice_line_id"
"Admin::Session".foreign_key # => "session_id"
如果你不想要 "_id" 中的底線,請傳遞一個 false 參數
"User".foreign_key(false) # => "userid"
關聯使用這個方法來推斷外來鍵,例如 has_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 物件或從 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 物件或從 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 值的元素。如果未提供區塊,則傳回列舉器。
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
odd_numbers = numbers.extract! { |number| number.odd? } # => [1, 3, 5, 7, 9]
numbers # => [0, 2, 4, 6, 8]
11.3 選項萃取
當方法呼叫中的最後一個引數是雜湊時,除了 &block
引數外,Ruby 允許您省略括號
User.exists?(email: params[:email])
這種語法糖在 Rails 中大量使用,以避免位置引數過多,而是提供模擬命名參數的介面。特別是使用尾隨雜湊來表示選項是非常慣用的。
如果一個方法預期變數數量的引數,但在其宣告中使用 *
,那麼此類選項雜湊最終會成為引數陣列的項目,而在其中它會失去其角色。
在這些情況下,你可以使用 extract_options!
為選項雜湊提供區別對待。此方法會檢查陣列最後一個項目的類型。如果它是雜湊,它會將其彈出並傳回,否則它會傳回一個空的雜湊。
讓我們以 caches_action
控制器巨集的定義為例
def caches_action(*actions)
return unless cache_configured?
options = actions.extract_options!
# ...
end
此方法接收任意數量的動作名稱,以及一個選項雜湊作為最後一個引數。透過呼叫 extract_options!
,你可以取得選項雜湊,並以簡單且明確的方式從 actions
中移除它。
11.4 轉換
11.4.1 to_sentence
方法 to_sentence
會將陣列轉換成一個包含列舉其項目的句子的字串
%w().to_sentence # => ""
%w(Earth).to_sentence # => "Earth"
%w(Earth Wind).to_sentence # => "Earth and Wind"
%w(Earth Wind Fire).to_sentence # => "Earth, Wind, and Fire"
此方法接受三個選項
:two_words_connector
:用於長度為 2 的陣列。預設為「 and 」。:words_connector
:用於串接具有 3 個或更多元素的陣列的元素,最後兩個元素除外。預設為「, 」。:last_word_connector
:用於串接具有 3 個或更多元素的陣列的最後幾個項目。預設為「, and 」。
這些選項的預設值可以在地化,其金鑰為
選項 | I18n 金鑰 |
---|---|
:兩個字連接詞 |
support.array.兩個字連接詞 |
:字詞連接詞 |
support.array.字詞連接詞 |
:最後一個字詞連接詞 |
support.array.最後一個字詞連接詞 |
11.4.2 to_fs
方法 to_fs
預設會像 to_s
一樣運作。
不過,如果陣列包含會回應 id
的項目,則可以將符號 :db
傳遞為引數。這通常用於 Active Record 物件的集合。傳回的字串為
[].to_fs(:db) # => "null"
[user].to_fs(:db) # => "8456"
invoice.lines.to_fs(:db) # => "23,567,556,12"
上面範例中的整數假設來自對 id
的各別呼叫。
11.4.3 to_xml
方法 to_xml
會傳回一個包含其接收者的 XML 表示形式的字串
Contributor.limit(2).order(:rank).to_xml
# =>
# <?xml version="1.0" encoding="UTF-8"?>
# <contributors type="array">
# <contributor>
# <id type="integer">4356</id>
# <name>Jeremy Kemper</name>
# <rank type="integer">1</rank>
# <url-id>jeremy-kemper</url-id>
# </contributor>
# <contributor>
# <id type="integer">4404</id>
# <name>David Heinemeier Hansson</name>
# <rank type="integer">2</rank>
# <url-id>david-heinemeier-hansson</url-id>
# </contributor>
# </contributors>
為此,它會依序將 to_xml
傳送給每個項目,並在根節點下收集結果。所有項目都必須會回應 to_xml
,否則會引發例外狀況。
預設情況下,根元素的名稱是第一個項目的類別名稱的底線和破折號複數形式,前提是其他元素屬於該類型(使用 is_a?
檢查),而且它們不是雜湊。在上面的範例中,那是「contributors」。
如果任何元素不屬於第一個元素的類型,則根節點會變成「objects」
[Contributor.first, Commit.first].to_xml
# =>
# <?xml version="1.0" encoding="UTF-8"?>
# <objects type="array">
# <object>
# <id type="integer">4583</id>
# <name>Aaron Batalion</name>
# <rank type="integer">53</rank>
# <url-id>aaron-batalion</url-id>
# </object>
# <object>
# <author>Joshua Peek</author>
# <authored-timestamp type="datetime">2009-09-02T16:44:36Z</authored-timestamp>
# <branch>origin/master</branch>
# <committed-timestamp type="datetime">2009-09-02T16:44:36Z</committed-timestamp>
# <committer>Joshua Peek</committer>
# <git-show nil="true"></git-show>
# <id type="integer">190316</id>
# <imported-from-svn type="boolean">false</imported-from-svn>
# <message>Kill AMo observing wrap_with_notifications since ARes was only using it</message>
# <sha1>723a47bfb3708f968821bc969a9a3fc873a3ed58</sha1>
# </object>
# </objects>
如果接收者是雜湊的陣列,則根元素預設也是「objects」
[{ a: 1, b: 2 }, { c: 3 }].to_xml
# =>
# <?xml version="1.0" encoding="UTF-8"?>
# <objects type="array">
# <object>
# <b type="integer">2</b>
# <a type="integer">1</a>
# </object>
# <object>
# <c type="integer">3</c>
# </object>
# </objects>
如果集合是空的,則根元素預設為「nil-classes」。這是一個陷阱,例如,如果集合是空的,則上面貢獻者清單的根元素不會是「contributors」,而是「nil-classes」。你可以使用 :root
選項來確保根元素一致。
子節點的名稱預設為根節點的名稱單數化。在以上的範例中,我們看過「contributor」和「object」。選項 :children
可讓您設定這些節點名稱。
預設的 XML 建構器是 Builder::XmlMarkup
的新實體。您可以透過 :builder
選項設定您自己的建構器。此方法也接受 :dasherize
和相關選項,它們會轉送至建構器
Contributor.limit(2).order(:rank).to_xml(skip_types: true)
# =>
# <?xml version="1.0" encoding="UTF-8"?>
# <contributors>
# <contributor>
# <id>4356</id>
# <name>Jeremy Kemper</name>
# <rank>1</rank>
# <url-id>jeremy-kemper</url-id>
# </contributor>
# <contributor>
# <id>4404</id>
# <name>David Heinemeier Hansson</name>
# <rank>2</rank>
# <url-id>david-heinemeier-hansson</url-id>
# </contributor>
# </contributors>
11.5 封裝
方法 Array.wrap
會將其引數封裝在陣列中,除非它已經是陣列(或類似陣列)。
具體來說
- 如果引數是
nil
,則會傳回一個空陣列。 - 否則,如果引數回應
to_ary
,則會呼叫它,如果to_ary
的值不是nil
,則會傳回它。 - 否則,會傳回一個陣列,其中引數為其單一元素。
Array.wrap(nil) # => []
Array.wrap([1, 2, 3]) # => [1, 2, 3]
Array.wrap(0) # => [0]
此方法的目的類似於 Kernel#Array
,但有一些差異
- 如果引數回應
to_ary
,則會呼叫此方法。如果傳回的值是nil
,Kernel#Array
會繼續嘗試to_a
,但Array.wrap
會立即傳回一個陣列,其中引數為其單一元素。 - 如果
to_ary
傳回的值既不是nil
也不是Array
物件,Kernel#Array
會引發例外,而Array.wrap
則不會,它只會傳回該值。 - 如果引數未回應
to_ary
,它不會在引數上呼叫to_a
,它會傳回一個陣列,其中引數為其單一元素。
最後一點對於某些可列舉值特別值得比較
Array.wrap(foo: :bar) # => [{:foo=>:bar}]
Array(foo: :bar) # => [[:foo, :bar]]
還有一個相關的慣用語法使用展開運算子
[*object]
11.6 複製
方法 Array#deep_dup
會遞迴複製自身和內部所有物件,使用 Active Support 方法 Object#deep_dup
。它運作方式類似於 Array#map
,將 deep_dup
方法傳送給內部的每個物件。
array = [1, [2, 3]]
dup = array.deep_dup
dup[1][2] = 4
array[1][2] == nil # => true
11.7 分組
11.7.1 in_groups_of(number, fill_with = nil)
方法 in_groups_of
會將陣列分割成特定大小的連續群組。它會傳回一個包含群組的陣列
[1, 2, 3].in_groups_of(2) # => [[1, 2], [3, nil]]
或是在傳遞區塊時逐一產生
<% sample.in_groups_of(3) do |a, b, c| %>
<tr>
<td><%= a %></td>
<td><%= b %></td>
<td><%= c %></td>
</tr>
<% end %>
第一個範例顯示 in_groups_of
如何使用盡可能多的 nil
元素填滿最後一個群組,以符合要求的大小。您可以使用第二個選用參數變更這個填補值
[1, 2, 3].in_groups_of(2, 0) # => [[1, 2], [3, 0]]
而且您可以透過傳遞 false
來告知方法不要填滿最後一個群組
[1, 2, 3].in_groups_of(2, false) # => [[1, 2], [3]]
因此 false
無法用作填補值。
11.7.2 in_groups(number, fill_with = nil)
方法 in_groups
會將陣列分割成特定數量的群組。該方法會傳回一個包含群組的陣列
%w(1 2 3 4 5 6 7).in_groups(3)
# => [["1", "2", "3"], ["4", "5", nil], ["6", "7", nil]]
或是在傳遞區塊時逐一產生
%w(1 2 3 4 5 6 7).in_groups(3) { |group| p group }
["1", "2", "3"]
["4", "5", nil]
["6", "7", nil]
上述範例顯示 in_groups
會視需要使用尾隨 nil
元素填滿一些群組。一個群組最多只能取得這些額外元素中的其中一個,也就是最右邊的一個(如果有)。而且擁有這些元素的群組永遠都是最後面的那些。
您可以使用第二個選用參數變更這個填補值
%w(1 2 3 4 5 6 7).in_groups(3, "0")
# => [["1", "2", "3"], ["4", "5", "0"], ["6", "7", "0"]]
而且您可以透過傳遞 false
來告知方法不要填滿較小的群組
%w(1 2 3 4 5 6 7).in_groups(3, false)
# => [["1", "2", "3"], ["4", "5"], ["6", "7"]]
因此 false
無法用作填補值。
11.7.3 split(value = nil)
方法 split
會以分隔符號將陣列分割,並傳回分割後的區塊。
如果傳入區塊,分隔符號會是陣列中讓區塊傳回 true 的元素
(-5..5).to_a.split { |i| i.multiple_of?(4) }
# => [[-5], [-3, -2, -1], [1, 2, 3], [5]]
否則,接收到的引數值(預設為 nil
)會是分隔符號
[0, 1, -5, 1, 1, "foo", "bar"].split(1)
# => [[0], [-5], [], ["foo", "bar"]]
請注意在先前的範例中,連續的分隔符號會產生空陣列。
12 Hash
的擴充
12.1 轉換
12.1.1 to_xml
方法 to_xml
會傳回一個字串,其中包含接收者的 XML 表示。
{ foo: 1, bar: 2 }.to_xml
# =>
# <?xml version="1.0" encoding="UTF-8"?>
# <hash>
# <foo type="integer">1</foo>
# <bar type="integer">2</bar>
# </hash>
為此,此方法會迴圈處理成對,並根據值建立節點。給定成對的 key
和 value
如果
value
是雜湊,則會使用key
作為:root
進行遞迴呼叫。如果
value
是陣列,則會使用key
作為:root
,並將key
單數化為:children
進行遞迴呼叫。如果
value
是可呼叫物件,則它必須預期一個或兩個引數。根據引數個數,可呼叫物件會以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
,可合併兩個 hash
{ a: 1, b: 1 }.merge(a: 0, c: 2)
# => {:a=>0, :b=>1, :c=>2}
Active Support 定義了更多合併 hash 的方式,可能會很方便。
12.2.1 reverse_merge
和 reverse_merge!
如果發生衝突,merge
中引數 hash 中的鍵會獲勝。你可以使用此慣用語以簡潔的方式支援具有預設值的選項 hash
options = { length: 30, omission: "..." }.merge(options)
如果你偏好此替代標記法,Active Support 定義了 reverse_merge
options = options.reverse_merge(length: 30, omission: "...")
以及會就地執行合併的驚嘆號版本 reverse_merge!
options.reverse_merge!(length: 30, omission: "...")
請注意,reverse_merge!
可能會變更呼叫者中的 hash,這可能不是好主意。
12.2.2 reverse_update
方法 reverse_update
是 reverse_merge!
的別名,如上所述。
請注意,reverse_update
沒有驚嘆號。
12.2.3 deep_merge
和 deep_merge!
如前一個範例所示,如果兩個 hash 中都找到某個鍵,引數中的那個鍵的值會獲勝。
Active Support 定義 Hash#deep_merge
。在深度合併中,如果在兩個雜湊中都找到一個鍵,並且它們的值又分別是雜湊,那麼它們的 合併 就會成為結果雜湊中的值
{ a: { b: 1 } }.deep_merge(a: { c: 2 })
# => {:a=>{:b=>1, :c=>2}}
方法 deep_merge!
就地執行深度合併。
12.3 深度複製
方法 Hash#deep_dup
使用 Active Support 方法 Object#deep_dup
遞迴複製自身以及所有鍵和值。它就像 Enumerator#each_with_object
,會將 deep_dup
方法傳送給內部的每對。
hash = { a: 1, b: { c: 2, d: [3, 4] } }
dup = hash.deep_dup
dup[:b][:e] = 5
dup[:b][:d] << 5
hash[:b][:e] == nil # => true
hash[:b][:d] == [3, 4] # => true
12.4 使用鍵
12.4.1 except
和 except!
方法 except
會傳回一個雜湊,其中已移除引數清單中的鍵(如果存在)
{ a: 1, b: 2 }.except(:a) # => {:b=>2}
如果接收器回應 convert_key
,則會對每個引數呼叫該方法。這讓 except
能夠與具有無差別存取權限的雜湊順利互動
{ a: 1 }.with_indifferent_access.except(:a) # => {}
{ a: 1 }.with_indifferent_access.except("a") # => {}
還有驚嘆號變體 except!
,它會就地移除鍵。
12.4.2 stringify_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_check_box_tag(options = {}, checked_value = "1", unchecked_value = "0")
options = options.stringify_keys
options["type"] = "checkbox"
# ...
end
第二行可以安全地存取「type」鍵,並讓使用者傳遞 :type
或「type」。
還有驚嘆號變體 stringify_keys!
,它會就地將鍵字串化。
此外,可以使用 deep_stringify_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_text_area_tag(name, value = nil, options = {})
options = options.symbolize_keys
options[:input] ||= "trix_input_#{ActionText::TagHelper.id += 1}"
# ...
end
第三行可以安全地存取 :input
鍵,並讓使用者傳遞 :input
或「input」。
還有一個驚嘆號變體 symbolize_keys!
,它會就地符號化鍵。
此外,可以使用 deep_symbolize_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_ago
類似地適用於星期
Date.new(2010, 5, 24).weeks_ago(1) # => Mon, 17 May 2010
Date.new(2010, 5, 24).weeks_ago(2) # => Mon, 10 May 2010
15.1.3.4 advance
跳到其他日期最通用的方法是 advance
。此方法接收一個雜湊,其鍵為 :years
、:months
、:weeks
、:days
,並傳回一個日期,其進位與目前的鍵所指示的一樣多
date = Date.new(2010, 6, 6)
date.advance(years: 1, weeks: 2) # => Mon, 20 Jun 2011
date.advance(months: 2, days: -2) # => Wed, 04 Aug 2010
請注意,在先前的範例中,增量可能是負數。
15.1.4 變更組成部分
方法 change
允許您取得一個新的日期,其與接收者相同,但年份、月份或日期除外
Date.new(2010, 12, 23).change(year: 2011, month: 11)
# => Wed, 23 Nov 2011
此方法不允許不存在的日期,如果變更無效,則會引發 ArgumentError
Date.new(2010, 1, 31).change(month: 2)
# => ArgumentError: invalid date
15.1.5 持續時間
Duration
物件可以加到日期或從日期中減去
d = Date.current
# => Mon, 09 Aug 2010
d + 1.year
# => Tue, 09 Aug 2011
d - 3.hours
# => Sun, 08 Aug 2010 21:00:00 UTC +00:00
它們轉換為對 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 日,您將取得 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
所支援的範圍,將會捨棄微秒並傳回 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
,但該檔案可能不存在。這沒關係,輔助模組不是強制性的,所以 Rails 會忽略載入錯誤。但輔助模組確實存在,且反過來需要另一個不存在的函式庫時,Rails 必須重新引發例外。is_missing?
方法提供一種方法來區分這兩種情況
def default_helper_module!
module_name = name.delete_suffix("Controller")
module_path = module_name.underscore
helper module_path
rescue LoadError => e
raise e unless e.is_missing? "helpers/#{module_path}_helper"
rescue NameError => e
raise e unless e.missing_name? "#{module_name}Helper"
end
21 Pathname 的擴充功能
21.1 existence
existence
方法如果命名檔案存在,則傳回接收者,否則傳回 nil
。它適用於像這樣的慣用語法
content = Pathname.new("file").existence&.read
回饋
我們鼓勵您協助提升本指南的品質。
如果您看到任何錯字或事實錯誤,請協助我們修正。首先,您可以閱讀我們的 文件貢獻 部分。
您也可能會發現不完整或過時的內容。請務必為 main 新增任何遺漏的文件。請務必先查看 Edge Guides,以驗證問題是否已在主分支中修正。查看 Ruby on Rails 指南指南,了解樣式和慣例。
如果您發現需要修正的事項,但無法自行修補,請 開啟問題。
最後但並非最不重要的一點,任何有關 Ruby on Rails 文件的討論都非常歡迎在 官方 Ruby on Rails 論壇 上進行。