Acitiveサポートの拡張
Active Supportは、Ruby言語拡張、ユーティリティ、その他横断的に関わりがあるものの提供を請け負うRuby on Railsのコンポーネントです。
これは、Railsアプリケーションの開発者とRuby on Rails自体の開発者の両方に対して、言語レベルで基板部分をより豊かにしてくれます。
このガイドを読むことで、次の事が学べるはずです。
- Core拡張とは何か
- どのようにして全ての拡張を読み込むのか
- 必要とする拡張だけをcherry-pick(狙ったものだけを取得)する方法
- Active Supportが提供する拡張とは何か
- 1. Core拡張の読み込み方法
- 2. 全てのObjectの拡張
- 3. Moduleの拡張
- 4. Classの拡張
- 5. Stringの拡張
- 6. Numericの拡張
- 7. Integerの拡張
- 8. BigDecimalの拡張
- 9. Enumerableの拡張
- 10. Arrayの拡張
- 11. Hashの拡張
- 12. Regexpの拡張
- 13. Rangeの拡張
- 14. Procの拡張
- 15. Dateの拡張
- 16. DateTimeの拡張
- 17. Timeの拡張
- 18. Fileの拡張
- 19. Marshalの拡張
- 20. Loggerの拡張
- 21. NameErrorの拡張
- 22. LoadErrorの拡張
1. Core拡張の読み込み方法
1.1 スタンドアローンのActive Support
ほぼ何もしていない状態であれば、デフォルトではActive Supportは何も読み込みません。 必要なものだけを読み込めるように小さな断片に細分化され、 全部であっても1度に関連する拡張を読み込む便利なエントリーポイントを持ちます。
このように、単にrequireを使用して指定します。
require 'active_support'
objects do not even respond to blank?. その定義の読み込み方を確認してみましょう。
1.1.1 定義のCherry-picking(狙いすました選択)
最も簡単なblank?
の取得方法は、それが定義されたファイルをcherry-pick(選択)することです。
このガイドでは、Core拡張として定義されている各単一のメソッドについて、
そのメソッドがどこで定義されているかに注目するように促します。
このblank?
のケースでは、次の点に注目します。
active_support/core_ext/object/blank.rb
内に定義
これは、下記の単一の呼び出しで十分という事になります。
require 'active_support/core_ext/object/blank'
Active Supportは、もし依存関係があれば厳密に必要とされるファイルを読み込み、慎重に内容が変更されます。
1.1.12 グループ化されたCore拡張の読み込み
次の段階では、単純に全てのObject
への拡張を読み込んでみます。
大雑把に説明すると、SomeClass
の拡張は、active_support/core_ext/some_class
を1度に読み込むことで、
利用可能になります。
そのため、全てのObject
への拡張を読み込むには(blank?を含む
)、次のようにします。
require 'active_support/core_ext/object'
1.1.3 全てのCore拡張の読み込み
全てのCore拡張を読み込む事が出来るファイルがあれば、その方が好まれるかもしれません。
require 'active_support/core_ext'
1.1.4 全てのActive Supportの読み込み
最後に、全てのActive Supportを利用可能にしたければ、次のように指定します。
require 'active_support/all'
これは実際にActive Support全体をメモリ内に置いているわけではなく、幾つかのものがオートロードを通して設定され、 使用するものだけを読み込みます。
1.2 Ruby on Rails内のActive Support
config.active_support.bare
がtrue
で無い限り、
全てのRuby on Railsアプリケーションを読み込みます。
このケースでは、アプリケーションはフレームワーク自身が必要とするものだけを選択して読み込み、
前のセクションで説明したように、依然として任意の粒度で選択することも出来ます。
2. 全てのObjectの拡張
2.1 blank?とpresent?
下記の値は、Railsアプリケーション内ではblankとみなされます。
-
nil
とfalse
- 空白(whitespace)のみで構成された文字列(詳細は後述)
- 空の配列とハッシュ
-
その他
empty?
の応答に、emptyを返すオブジェクト
文字列の判定には、Unicode対応の文字クラス[:space:]
が使用されます。
そのため、例えば、U+2029(段落区切り)は空白であると判定されます。
数値について述べられていないことに注意してください。
特に0
と0.0
は、blankでは無い事に注意してください。
例えば、ActionDispatch::Session::AbstractStore
で、
下記のメソッドがセッションキーが提供されているかを判定するのにblank?
を使用します。
def ensure_session_key!
if @key.blank?
raise ArgumentError, 'A key is required...'
end
end
present?
メソッドは、!blank?
と同義です。
下記は、ActionDispatch::Http::Cache::Response
での例になります。
def set_conditional_cache_control!
return if self["Cache-Control"].present?
...
end
active_support/core_ext/object/blank.rb
内に定義されています。
2.2 presence
presence
メソッドは、present?
がtrueであればその中身を返し、そうでなければnilになります。
これは下記のように慣用句(イディオム)的な書き方をする際に便利です。
host = config[:host].presence || 'localhost'
active_support/core_ext/object/blank.rb
内に定義されています。
2.3 duplicable?
Rubyの基板となる幾つかのオブジェクトは、シングルトンです。 例えば、プログラムサイクルでの整数1は、常に同じインスタンスを参照します。
1.object_id # => 3
Math.cos(0).to_i.object_id # => 3
したがって、これらのオブジェクトにはdup
またはclone
を通して重複させる術がありません。
true.dup # => TypeError: can't dup TrueClass
シングルトンでは無い幾つかの数値にも、重複させる事が出来ないものがあります。
0.0.clone # => allocator undefined for Float
(2**1024).clone # => allocator undefined for Bignum
Active Supportは、プログラミング的にこの事についてオブジェクトに問い合わせるduplicable?
を提供します。
"foo".duplicable? # => true
"".duplicable? # => true
0.0.duplicable? # => false
false.duplicable? # => false
nil
、false
、true
、symbols、numbers、class、moduleオブジェクトを除く、
全てのオブジェクトに定義されます。
一部のクラスでは、dup
とclone
を削除、または例外を発生させることで複製を許可しないようにしています。
そのため、rescue
のみが、与えられた任意のオブジェクトが複製可能か否かを伝える事を可能にします。
duplicable?
は上記のハードコードされたものに依存しますが、rescue
よりもはるかに高速です。
あなたのケースで、ハードコードされたリストで十分であることが分かっていれば、それのみを使用してください。
active_support/core_ext/object/duplicable.rb
内に定義されています。
2.4 deep_dup
deep_dup
メソッドは、与えられたオブジェクトのディープ(deep)コピーを返します。
通常、他のオブジェクトを含むオブジェクトをdup
する際に、
Rubyはその含まれるオブジェクトはdup
せず、オブジェクトの浅いコピーを作成します。
もし、下記のような文字列の配列を持っている場合、
array = ['string']
duplicate = array.dup
duplicate.push 'another-string'
# 要素は複製された方だけに追加されます。
array #=> ['string']
duplicate #=> ['string', 'another-string']
duplicate.first.gsub!('string', 'foo')
# 最初の要素は複製されていないので、両方の配列内で変更されます。
array #=> ['foo']
duplicate #=> ['foo', 'another-string']
ご覧のとおり、配列インスタンスを複製後、
別のオブジェクトを取得したので元を変更すること無く、修正することが出来ました。
しかし、これは配列内の要素には適用されません。
dup
はディープコピーをしないため、配列内の文字列は同じオブジェクトのままになります。
オブジェクトのディープコピーが必要なのであれば、deep_dup
を使用する必要があります。
下記はその例になります。
array = ['string']
duplicate = array.deep_dup
duplicate.first.gsub!('string', 'foo')
array #=> ['string']
duplicate #=> ['foo']
もしオブジェクトが複製不可であれば、deep_dup
はそのオブジェクト自体を返します。
number = 1
duplicate = number.deep_dup
number.object_id == duplicate.object_id # => true
active_support/core_ext/object/deep_dup.rb
内に定義されています。
2.5 try
もしnilでは無い場合にのみ、オブジェクトのメソッドを呼びたい場合、
もっともシンプルな方法でそれを達成するにも、雑多な条件文を指定しなければいけません。
この代わりに、try
を使用することが出来ます。
try
は、nilが送られた際にnilを返す事を除いて、
Object#send
のように動作します。
下記はこの例になります。
# try無し
unless @number.nil?
@number.next
end
# 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}" }
active_support/core_ext/object/try.rb
に定義されています。
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
active_support/core_ext/kernel/singleton_class.rb
内に定義されています。
2.7 acts_like?(duck)
acts_like?
は、あるクラスが別のあるクラスのように動作するかを、
String
の定義で同じインターフェースが提供されているクラスであるかを基に検証する方法を提供します。(翻訳に自信なし)
def acts_like_string?
end
上記はマーカーのみで、そのボディまたは戻り値に関連性はありません。(翻訳に自信なし) そのため、クライアントコードはこの方法で、 安全にダックタイプ を問い合わせることが出来ます。(翻訳に自信なし)
some_klass.acts_like?(:string)
RailsはDate
またはTime
のように振る舞うクラスを持ちます。
active_support/core_ext/object/acts_like.rb
内に定義されています。
2.8 to_param
Rails内の全てのオブジェクトは、クエリー文字列、またはURLの断片として表すことが出来る何らかの値を返すto_param
メソッドに応答します。
デフォルトでは、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のルーティングシステムは、:id
プレースホルダのために値を取得するのに、
モデル上でto_param
を呼び出します。
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]
の値になるからです。
これは、active_support/core_ext/object/to_param.rb
内に定義されます。
2.9 to_query
ハッシュを除くエスケープされていないkey
を与えると、このメソッドはkeyをto_param
が返すものにマッピングし、
クエリー文字列の一部として構成します。例えば、下記のように定義されているとして、
class User
def to_param
"#{id}-#{name.parameterize}"
end
end
次の結果を得ることが出来ます。
current_user.to_query('user') # => user=357-john-smith
キーと値のどちらでも、この値は必要があればエスケープを行います。
account.to_query('company[name]')
# => "company%5Bname%5D=Johnson+%26+Johnson"
そのため、この出力はクエリー文字列を用意する際に使用されます。
配列は各要素をキーをkey[]
として、各要素にto_query
を適用し、
それを"&"で連結した結果を返します。
[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
メソッドは、キーのための任意の名前空間を受け付けます。
{id: 89, name: "John Smith"}.to_query('user')
# => "user%5Bid%5D=89&user%5Bname%5D=John+Smith"
これは、active_support/core_ext/object/to_query.rb
内に定義されています。
2.10 with_options
with_options
メソッドは、メソッド呼び出し一連の共通オプションを除去する方法を提供してくれます。
デフォルトのオプションハッシュが与えられると、with_options
はproxyオブジェクトをブロックへyieldします。
ブロック内のproxy上でのメソッド呼び出しは、オプションをマージしてレシーバーに転送されます。
例えば下記の重複している記述を、
class Account < ActiveRecord::Base
has_many :customers, dependent: :destroy
has_many :products, dependent: :destroy
has_many :invoices, dependent: :destroy
has_many :expenses, dependent: :destroy
end
次のようにして取り除く事が出来ます。
class Account < ActiveRecord::Base
with_options dependent: :destroy do |assoc|
assoc.has_many :customers
assoc.has_many :products
assoc.has_many :invoices
assoc.has_many :expenses
end
end
この書き方はコードの読み手に対しても、グループ化されていることを伝える事ができるかもしれません。 例えば、あなたはニュースレターをユーザー毎の言語で送信したいと考えているとすると、 メーラー処理の何処かで、次のようにしてロケール(地域・言語)依存のグループ化を行う事が出来ます。
I18n.with_options locale: user.locale, scope: "newsletter" do |i18n|
subject i18n.t :subject
body i18n.t :body, user_name: user.name
end
with_options
はそのレシーバー呼び出しを転送するため、それを入れ子にする事が可能です。
各入れ子階層は、それら自身に追加され継承されたデフォルトをマージします。
これは、active_support/core_ext/object/with_options.rb
内に定義されています。
2.11 JSONサポート
Active Supportは、Rubyオブジェクトのために提供される任意のjsonのGemよりも、to_json
のより良い実装を提供します。
これはHashとOrderedHashのような一部のクラスでは、適切なJSON表記を提供するための特別な処理が必要になるためです。
Active Supportはまた、Process::Status
クラスのために、as_json
実装も提供します。
これは、active_support/core_ext/object/to_json.rb
に定義されています。
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}
これは、active_support/core_ext/object/instance_variables.rb
内に定義されています。
2.12.2 instance_variable_names
instance_variable_names
メソッドは配列を返します。
名前には、"@"が含まれます。
class C
def initialize(x, y)
@x, @y = x, y
end
end
C.new(0, 1).instance_variable_names # => ["@x", "@y"]
これは、active_support/core_ext/object/instance_variables.rb
内に定義されています。
2.13 警告、ストリーム(標準出力、標準エラー出力)、例外の沈黙化
silence_warnings
とenable_warnings
メソッドは、
それぞれのブロック中とそれ以降の$VERBOSE
値を変更します。
silence_warnings { Object.const_set "RAILS_DEFAULT_LOGGER", logger }
silence_stream
を使用して、ブロック実行中のストリームを沈黙化させることが出来ます。
silence_stream(STDOUT) do
# ここでは標準出力(STDOUT)は沈黙します。
end
quietly
メソッドは、サブプロセスの場合も含め、標準出力と標準エラー出力を沈黙化したい共通のケースで使用されます。
quietly { system 'bundle install' }
例えば、railtieのテストスイートの一部で、ステータスの進展と混在して出力されるコマンドメッセージを防ぐために使用されます。
例外も、suppress
を使用することで沈黙させることが可能です。
このメソッドは、任意の数の例外クラスを受け取ります。
もし、ブロックの実行中に例外が発生し、引数のいずれかのkind_of?
に該当すれば、
suppress
はそれを補足し、沈黙を返します。(翻訳に自信なし)
そうでなければ、例外は再度発生させられます。
# もしユーザーがロックされていたら、増加分を破棄し何もしません。
suppress(ActiveRecord::StaleObjectError) do
current_user.increment! :visits
end
これは、active_support/core_ext/kernel/reporting.rb
で定義されています。
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
これは、active_support/core_ext/object/inclusion.rb
内に定義されています。
3. Moduleの拡張
3.1 alias_method_chain
素のRubyを使用してメソッドを別のメソッドでラップすることを、エイリアス(チェーニング?)(alias chaining)と呼びます。
例えば、機能テスト内で実際のリクエストとしてパラメータを文字列にしたいところを、
利便性を考慮して数値またはその他の種類の値のままにしておきたいとします。
test/test_helper.rb
内のActionController::TestCase#process
を使用して、
これを行うことが可能です。
ActionController::TestCase.class_eval do
# 元のプロセスメソッドへの参照を保存
alias_method :original_process, :process
# ここでプロセスを再定義し、original_processへ委任します。
def process(action, params=nil, session=nil, flash=nil, http_method='GET')
params = Hash[*params.map {|k, v| [k, v.to_s]}.flatten]
original_process(action, params, session, flash, http_method)
end
end
get
、post
などのメソッドを委任して動作させます。
:original_process
が取得されている可能性があるため、このテクニックはリスクを伴います。
ラベルの選択で衝突を避けられるように、チェーニングが何なのか分かるような特徴付けを行います。
ActionController::TestCase.class_eval do
def process_with_stringified_params(...)
params = Hash[*params.map {|k, v| [k, v.to_s]}.flatten]
process_without_stringified_params(action, params, session, flash, http_method)
end
alias_method :process_without_stringified_params, :process
alias_method :process, :process_with_stringified_params
end
alias_method_chain
は、このパターンのショートカットを提供します。
ActionController::TestCase.class_eval do
def process_with_stringified_params(...)
params = Hash[*params.map {|k, v| [k, v.to_s]}.flatten]
process_without_stringified_params(action, params, session, flash, http_method)
end
alias_method_chain :process, :stringified_params
end
Railsは全てのコードベース上で、alias_method_chain
を使用します。
例えば、ActiveRecord::Base#save
へは、
隔てられた検証モジュールで専用化を行ってメソッドをラッピングすることで、検証が追加されます。(翻訳に自信なし)
これは、active_support/core_ext/module/aliasing.rb
内に定義されています。
3.2 属性
3.2.1 alias_attribute
Modelの属性は、リーダー(reader)、ライター(writer)、述語(predicate)を持ちます。 3つのメソッドに応じたそれぞれの定義を持つモデル属性に対して、1度でエリアスをすることが可能です。 その他へメソッドへのエイリアスのように、新しい名前を1つ目の引数に、古い名前を2つ目の引数に指定します。 (覚えやすいように、割り当てを行うような順序でそれをします。)(翻訳に自信なし)
class User < ActiveRecord::Base
# emailカラムを"login"として参照することを可能にし、
# 認証コード用に意味のあるものにしてくれます。
alias_attribute :login, :email
end
これは、active_support/core_ext/module/aliasing.rb
に定義されています。
3.2.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
はライブラリのpublicインターフェースに属さず、
developmentでのみ使用されるというケースになる可能性があります。(翻訳に自信なし)
クライアントコード(client code)は、サブクラスと自身の:log_level
と意図せず衝突する可能性があります。(翻訳に自信なし)
attr_internal
のおかげで、衝突することはありません。(翻訳に自信なし)
デフォルトで内部インスタンス変数は、上記の例の@_log_level
のようにアンダースコアの後に続いて名前付けされます。
これはModule.attr_internal_naming_format
を通して設定可能ですが、
@
から始まり、名前として置き換えられる%s
を持つ、
sprintf
ライクなフォーマット文字列に渡すことが出来ます。
デフォルトは、"@_%s"
です。
Railsは内部属性をいくつかの場所で使用しており、 例えばビューはその1つです。
module ActionView
class Base
attr_internal :captures
attr_internal :request, :layout
attr_internal :controller, :template
end
end
これは、active_support/core_ext/module/attr_internal.rb
に定義されています。
3.2.3 Module Attributes
mattr_reader
、mattr_writer
、mattr_accessor
のマクロは、
クラスのために定義されるcattr_*
に似ています。
クラス属性を確認してみてください。
例えば、依存性の仕組みにこれらが使用されます。
module ActiveSupport
module Dependencies
mattr_accessor :warnings_on_first_load
mattr_accessor :history
mattr_accessor :loaded
mattr_accessor :mechanism
mattr_accessor :load_paths
mattr_accessor :load_once_paths
mattr_accessor :autoloaded_constants
mattr_accessor :explicitly_unloadable_constants
mattr_accessor :logger
mattr_accessor :log_activity
mattr_accessor :constant_watch_stack
mattr_accessor :constant_watch_stack_mutex
end
end
これは、active_support/core_ext/module/attribute_accessors.rb
に定義されています。
3.3 Parents
3.3.1 parent
入れ子の名前付けされたモジュール上でのparent
メソッドは、それに対応する定数を含むモジュールを返します。
module X
module Y
module Z
end
end
end
M = X::Y::Z
X::Y::Z.parent # => X::Y
M.parent # => X::Y
もしモジュールが匿名、またはトップ階層に属するのであれば、parent
はObject
を返します。
この場合、parent_name
がnil
を返す事に注意してください。
これは、active_support/core_ext/module/introspection.rb
に定義されています。
3.3.2 parent_name
入れ子の名前付けされたモジュール上でのparent_name
メソッドは、
対応する定数が含まれるモジュールの完全修飾名を返します。
module X
module Y
module Z
end
end
end
M = X::Y::Z
X::Y::Z.parent_name # => "X::Y"
M.parent_name # => "X::Y"
トップ階層または匿名モジュールのparent_name
は、nil
を返します。
このケースでは、parent_name
はObject
を返すことに注意してください。
これは、active_support/core_ext/module/introspection.rb
内に定義されています。
3.3.3 parents
parents
メソッドは、parent
メソッドをレシーバー上で呼び出し、
Object
に達するまで遡ります。
このチェーンにより最下層からトップまでの配列を返します。
module X
module Y
module Z
end
end
end
M = X::Y::Z
X::Y::Z.parents # => [X::Y, X, Object]
M.parents # => [X::Y, X, Object]
これは、active_support/core_ext/module/introspection.rb
内に定義されています。
3.4 定数
local_constants
メソッドは、レシーバーモジュール内で定義されている複数の定数の名前を返します。
module X
X1 = 1
X2 = 2
module Y
Y1 = :y1
X1 = :overrides_X1_above
end
end
X.local_constants # => [:X1, :X2, :Y]
X::Y.local_constants # => [:Y1, :X1]
名前はシンボルとして返されます。
(非推奨のメソッドlocal_constant_names
は、文字列を返します。)
これは、active_support/core_ext/module/introspection.rb
内に定義されています。
3.4.1 修飾定数名
標準メソッドのconst_defined?
、const_get
、const_set
は、
そのもの自身の定数名を受け取ります。
Active SupportはこのAPIを拡張し、相対的な修飾名を渡すことを可能にします。
新しいメソッドは、qualified_const_defined?
、qualified_const_get
、qualified_const_set
です。
これらの引数は、それらのレシーバーの相対の修飾定数名とみなされます。
Object.qualified_const_defined?("Math::PI") # => true
Object.qualified_const_get("Math::PI") # => 3.141592653589793
Object.qualified_const_set("Math::Phi", 1.618034) # => 1.618034
引数はそのもの自身の定数名かもしれません。
Math.qualified_const_get("E") # => 2.718281828459045
これらのメソッドは、それぞれ対になる組み込みのメソッドと似ています。
特に、qualified_constant_defined?
は任意の第2引数を受け入れ、
述語(predicate)が祖先内を見るかどうかを指定することを可能にしてくれます。(翻訳に自信なし)
パスを巡る間、このフラグは式内の各定数で考慮されます。(翻訳に自信なし)
下記の例が与えられた場合、
module M
X = 1
end
module N
class C
include M
end
end
qualified_const_defined?
は次のように振る舞います。
N.qualified_const_defined?("C::X", false) # => false
N.qualified_const_defined?("C::X", true) # => true
N.qualified_const_defined?("C::X") # => true
最後の行は、2つ目の引数はconst_defined?
内のものとして、デフォルトのtrueが設定されます。
組み込みメソッドへの一貫性を保つために、相対パスのみが受けれられます。
::Math::PI
のような絶対装飾定数名は、NameError
を引き起こします。
これは、active_support/core_ext/module/qualified_const.rb
内に定義されています。
3.5 Reachable
もし、対応する定数内であれば、名前付けされたモジュールは到達可能(reachable)です。 これは、定数を通してモジュールオブジェクトに到達可能であることを意味します。
module M
end
M.reachable? # => true
ただし、定数とモジュールを切り離すことで、モジュールオブジェクトへの到達を不可にすることが出来ます。
module M
end
orphan = Object.send(:remove_const, :M)
# ここでは、モジュールオブジェクトは孤立(orphan)していますが、
# 名付けはまだ有効です。
orphan.name # => "M"
# まだ存在しないため、定数Mを通して、
# これに到達することは出来ません。
orphan.reachable? # => false
# "M"と呼ばれるモジュールを再び定義しましょう。
module M
end
# ここでM定数は再び存在することになり、
# "M"と呼ばれるモジュールオブジェクト"M"を格納しますが、
# それは新しいインスタンスです。
orphan.reachable? # => false
これは、active_support/core_ext/module/reachable.rb
内に定義されています。
3.6 匿名(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.reachable? # => false
m.anonymous? # => false
もっとも、匿名モジュールは定義によって到達不可になりますが。
これは、active_support/core_ext/module/anonymous.rb
内に定義されています。
3.7 メソッド委任
delegate
マクロは、簡単にメソッドのフォワードする方法を提供してくれます。
ユーザーはログイン情報をUser
モデルに持ちますが、
名前とその他の情報はProfile
モデルに分離されているアプリケーションを想像してみてください。
class User < ActiveRecord::Base
has_one :profile
end
設定で、あなたはユーザー名user.profile.name
をProfileを通して取得しますが、
更に下記の直接的な属性にすることで、更に手軽にアクセスすることを可能にします。
class User < ActiveRecord::Base
has_one :profile
def name
profile.name
end
end
delegate
があなたのために、何をしてくれるかを見てましょう。
class User < ActiveRecord::Base
has_one :profile
delegate :name, to: :profile
end
記述が短く、意図がより鮮明になっています。
そのメソッドは、対象内でpublicでなければいけません。
delegate
マクロは幾つかのメソッドを受け入れます。
delegate :name, :age, :address, :twitter, to: :profile
文字列に補完された際に、:to
オプションは委任されたメソッドのオブジェクトとして評価される式になる必要があります。
一般的に、文字列かシンボルが指定されます。
これらの式はレシーバー(受け取り側)のコンテキスト内で評価されます。
# Rails定数へ委任
delegate :logger, to: :Rails
# レシーバーのクラスへ委任
delegate :table_name, to: :class
もし、:prefix
オプションがtrueの場合、
普遍的な指定は出来なくなります。詳細は後述します。
デフォルトでは、もし、委任がNoMethodError
を発生し対象がnilの場合、例外が増幅してしまいます。
nil
を返される代わりに、:allow_nil
オプションを使用して問い合わせることが可能です。
delegate :name, to: :profile, allow_nil: true
:allow_nil
を使用してuser.name
を呼び出しを行うと、
もしユーザーがProfileを持たなければnil
を返します。
:prefix
オプションは、生成されたメソッドの名前に接頭辞を追加します。
これは、より適した名前取得を手軽にしてくれるかもしれません。
delegate :street, to: :address, prefix: true
この例では、street
ではなく、address_street
が生成されます。
このケースでは、生成されたメソッドの名前は対象のオブジェクトと対象のメソッド名から構成されるため、
:to
オプションはメソッド名にする必要があります。
接頭辞を変えるために、次のように指定することも可能です。
delegate :size, to: :attachment, prefix: :avatar
この例では、マクロはsize
では無く、avatar_size
を生成します。
これは、active_support/core_ext/module/delegation.rb
内に定義されています。
3.8 メソッドの再定義
define_method
を使用してメソッドを定義する必要があるが、
その名前が既に存在しているかどうか分からないといったケースが存在します。
もし、それを行い、それらが有効であれば、警告が発せられます。
対した影響はありませんが、きれいなやり方ではありません。
redefine_method
メソッドは、必要であれば既存のメソッドを削除して、
このような潜在的な警告を防ぎます。
Railsは関連のAPIを生成する際に、インスタンスのために幾つかの場所でこれを使用します。
redefine_method("#{reflection.name}=") do |new_value|
association = association_instance_get(reflection.name)
if association.nil? || association.target != new_value
association = association_proxy_class.new(self, reflection)
end
association.replace(new_value)
association_instance_set(reflection.name, new_value.nil? ? nil : association)
end
これは、active_support/core_ext/module/remove_method.rb
内に定義されています。
4. Classの拡張
4.1 クラス属性
4.1.1 class_attribute
class_attribute
メソッドは、1つまたは複数の継承先の下の階層で上書きされる継承可能なクラス属性を宣言します。
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, Aの値
a2.x # => 2, 上書きされた値
writerインスタンメソッドの生成は、:instance_writer
をfalse
に設定することで防ぐ事が可能です。
module ActiveRecord
class Base
class_attribute :table_name_prefix, instance_writer: false
self.table_name_prefix = ""
end
end
モデルの属性の設定からmass-assignmentを防ぐ方法として、このオプションは便利かもしれません。
readerインスタンメソッドの生成は、:instance_reader
をfalse
に設定することで防ぐ事が可能です。
class A
class_attribute :x, instance_reader: false
end
A.new.x = 1 # NoMethodError
class_attribute
はまた、readerが返すインスタンスを二重否定するインスタンス述語を定義します。
(翻訳に自信なし)(訳注: 二重否定とは、!!
と思われる)
上記の例であれば、x?
です。
:instance_reader
がfalseの場合、
インスタンス述語はreaderメソッドのように、NoMethodError
を返します。
もしインスタンス述語が不要であれば、instance_predicate: false
を渡し、
宣言させないようにします。
これは、active_support/core_ext/class/attribute.rb
内に定義されています。
4.1.2 cattr_reader、cattr_writer、cattr_accessor
cattr_reader
、cattr_writer
、cattr_accessor
は、
それぞれのattr_*
と似ていますが、これはクラス用です。
これらはもし存在しなければクラス変数をnil
で初期化し、
それにアクセスするための、それぞれに対するクラスメソッドを生成します。
class MysqlAdapter < AbstractAdapter
# @@emulate_booleansにアクセスするクラスメソッドを生成
cattr_accessor :emulate_booleans
self.emulate_booleans = true
end
インスタンスメソッドが同様に利便性のために生成され、
クラス属性へのプロキシになります。
そのため、インスタンスはクラス属性の変更が可能ですが、
class_attribute
(前述したものを参照)を使用して上書きすることは出来ません。
例えば、次の場合、
module ActionView
class Base
cattr_accessor :field_error_proc
@@field_error_proc = Proc.new{ ... }
end
end
ビュー内でfield_error_proc
にアクセスする事が可能です。
readerインスタンスメソッドの生成は、:instance_reader
をfalse
に設定することで防ぎ、
writerインスタンスメソッドの生成は、:instance_writer
をfalse
に設定することで防ぎます。
両方のメソッドの生成は、:instance_accessor
をfalse
に設定することで防ぎます。
全てのケースで、値は必ずfalse
です。
module A
class B
# first_nameインスタンスreaderは、生成されません。
cattr_accessor :first_name, instance_reader: false
# last_name=インスタンスwriterは、生成されません。
cattr_accessor :last_name, instance_writer: false
# surnameインスタンスreader、またはsurname=インスタンスwriterは
# 生成されません。
cattr_accessor :surname, instance_accessor: false
end
end
モデルの属性の設定から、:instance_accessor
をfalse
にして、
mass-assignmentを防ぐ方法として便利かもしれません。
これは、active_support/core_ext/class/attribute_accessors.rb
内に定義されています。
4.2 サブクラスと子孫
4.2.1 subclasses
subclasses
メソッドは、レシーバーのサブクラスを返します。
class C; end
C.subclasses # => []
class B < C; end
C.subclasses # => [B]
class A < B; end
C.subclasses # => [B]
class D < C; end
C.subclasses # => [B, D]
返されるこれらのクラスの並び順は、不特定です。
これは、active_support/core_ext/class/subclasses.rb
内に定義されています。
4.2.2 descendants
descendants
(子孫)メソッドは、レシーバーの<
以降の全てのクラスを返します。
class C; end
C.descendants # => []
class B < C; end
C.descendants # => [B]
class A < B; end
C.descendants # => [B, A]
class D < C; end
C.descendants # => [B, A, D]
返されるこれらのクラスの並び順は、不特定です。
これは、active_support/core_ext/class/subclasses.rb
内に定義されています。
5. Stringの拡張
5.1 安全な出力
5.1.1 自発性
HTMLテンプレートにデータを挿入する場合、より慎重になる必要があります。
例えば、あなたは@review.title
をそのままHTMLページに差し込む事は出来ません。
一例を挙げると、もしレビュータイトルが"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 %> <%# 必要であればエスケープされます %>
そのまま挿入するには、html_safe
を呼び出すより、raw
ヘルパーを使用します。
<%= raw @cms.current_template %> <%# inserts @cms.current_template as is %>
または同義の\u003C%==
を使用します。
<%== @cms.current_template %> <%# inserts @cms.current_template as is %>
raw
ヘルパーはhtml_safe
を呼び出しています。
def raw(stringish)
stringish.to_s.html_safe
end
これは、active_support/core_ext/string/output_safety.rb
内に定義されています。
5.1.3 変形
これまで説明してきたものを除いて、幾つかのメソッドは文字列を変更し、あなたに対して安全ではない(unsafe)文字列を提供するかもしれません。
これには、downcase
、gsub
、strip
、chomp
、underscore
等があります。
こういったgsub!
のような変形は、レシーバー自体を安全ではないもの(unsafe)にしてしまいます。
実際の変形がどんなものであったかに関係なく、安全を表すマーク付けは失われます。
5.1.4 変換と強制
安全な文字列上でto_s
を呼び出すと安全な文字列を返しますが、
to_str
を使用して変換すると安全では無い(unsafe)文字列になります。
5.1.5 コピー
安全な文字列上でのdup
またはclone
は、安全な文字列を生成します。
5.2 squish
squish
メソッドは、先頭と末尾の空白を除去し、
その他の各空白部分は単一のスペースに置き換えます。
" \n foo\n\r \t bar \n".squish # => "foo bar"
破壊的なメソッドのバージョンである、String#squish!
も存在します。
これはモンゴルの母音分離(?)のU+180E(mongolian vowel separator)のような、ASCIIとUnicodeの空白の両方を扱う事に注意してください。
これは、active_support/core_ext/string/filters.rb
内で定義されています。
5.3 truncate
truncate
メソッドは、そのレシーバーを与えられた数で切り詰めらた(truncated)複製を返します。
"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
によってそれが阻止されています。
これは、active_support/core_ext/string/filters.rb
内に定義されています。
5.4 inquiry
inquiry
メソッドは、等価確認をスマートに行えるように、
文字列をStringInquirer
オブジェクトに変換してくれます。
"production".inquiry.production? # => true
"active".inquiry.inactive? # => false
5.5 starts_with?とends_with?
Active Supportは、String#start_with?
とString#end_with?
の、
メソッド名の動詞が3人称のエイリアスを定義します。
"foo".starts_with?("f") # => true
"foo".ends_with?("o") # => true
これは、active_support/core_ext/string/starts_ends_with.rb
内に定義されています。
5.6 strip_heredoc
strip_heredoc
メソッドは、ヒアドキュメントのギザギザを取り除きます。
例えば、
if options[:usage]
puts <<-USAGE.strip_heredoc
This command does such and such.
Supported options are:
-h This message
...
USAGE
end
上記はユーザーに、左マージンに沿って一直線にそった使用方法のメッセージを表示します。
厳密に言えば、これは文字列中から最もインデントの少ない行を見つけ、 先頭の空白を削除して調整します。
これは、active_support/core_ext/string/strip.rb
内に定義されています。
5.7 indent
レシーバー内の行をインデントします。
<<EOS.indent(2)
def some_method
some_code
end
EOS
# =>
def some_method
some_code
end
2つ目の引数indent_string
は、インデントに使用する文字を指定します。
デフォルトはnilで、これはメソッドに最初のインデント行から推測するように伝え、
もし何も見つからなければ空白を代用します。
" foo".indent(2) # => " foo"
"foo\n\t bar".indent(2) # => "\t\tfoo\n\t\t\t\tbar"
"foo".indent(2, " ") # => "\t\tfoo"
indent_string
は通常は1つのスペースまたはタブですが、
場合によっては何らかの文字列かもしれません。
3つ目の引数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.8 アクセス
5.8.1 at(position)
文字列のposition
の位置の文字を返します。
"hello".at(0) # => "h"
"hello".at(4) # => "o"
"hello".at(-1) # => "o"
"hello".at(10) # => nil
これは、active_support/core_ext/string/access.rb
内に定義されています。
5.8.2 from(position)
文字列のposition
位置から開始する部分文字列を返します。
"hello".from(0) # => "hello"
"hello".from(2) # => "llo"
"hello".from(-2) # => "lo"
"hello".from(10) # => "" if < 1.9, nil in 1.9
これは、active_support/core_ext/string/access.rb
内に定義されています。
5.8.3 to(position)
文字列のposition
位置に至るまでの部分文字列を返します。
"hello".to(0) # => "h"
"hello".to(2) # => "hel"
"hello".to(-2) # => "hell"
"hello".to(10) # => "hello"
これは、active_support/core_ext/string/access.rb
内に定義されています。
5.8.4 first(limit = 1)
str.first(n)
の呼び出しは、もしn
> 0であれば、str.to(n-1)
と同義で、
n == 0
であれば空文字列を返します。
これは、active_support/core_ext/string/access.rb
内に定義されています。
5.9 変化
5.9.1 pluralize
pluralize
メソッドは、そのレシーバーの複数形を返します。
"table".pluralize # => "tables"
"ruby".pluralize # => "rubies"
"equipment".pluralize # => "equipment"
この例で分かるように、Active Supportは不規則な複数系と不可算名詞を認識しています。
組み込みの規則は、config/initializers/inflections.rb
で拡張することが可能です。
このファイルはrails
コマンドによって生成され、コメント内で指示を持ちます。
またpluralize
は、任意のcount
引数を受け取る事が出来ます。
もし、count
の値は、複数形が返されます。
"dude".pluralize(0) # => "dudes"
"dude".pluralize(1) # => "dude"
"dude".pluralize(2) # => "dudes"
Active Recordはモデルに相当するデフォルトのテーブル名を算出するのに、このメソッドを使用します。
# active_record/model_schema.rb
def undecorated_table_name(class_name = base_class.name)
table_name = class_name.to_s.demodulize.underscore
pluralize_table_names ? table_name.pluralize : table_name
end
これは、active_support/core_ext/string/inflections.rb
内に定義されています。
5.9.2 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
これは、active_support/core_ext/string/inflections.rb
内に定義されています。
5.9.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
にエイリアスされています。
これは、active_support/core_ext/string/inflections.rb
内に定義されています。
5.9.4 underscore
underscore
メソッドは、キャメルケースがパスにする動作の順序を逆にしたものです。(翻訳に自信なし)
"Product".underscore # => "product"
"AdminUser".underscore # => "admin_user"
また、"::"を"/"に戻す変換も行い、
"Backoffice::Session".underscore # => "backoffice/session"
先頭の小文字を認識しています。
"visualEffect".underscore # => "visual_effect"
ただし、underscore
は引数を受け取りません。
Railsクラスとモジュールの自動読み込みは、underscore
を使用して、
欠けている定数を定義するファイルの相対パス(拡張子抜きの)を推測します。
# active_support/dependencies.rb
def load_missing_constant(from_mod, const_name)
...
qualified_name = qualified_name_for from_mod, const_name
path_suffix = qualified_name.underscore
...
end
underscore
はcamelize
の正反対のものと考えるかもしれませんが、
"SSLError".underscore.camelize
は"SslError"となり、元の状態を保持できないケースもあります。
これは、active_support/core_ext/string/inflections.rb
内に定義されています。
5.9.5 titleize
titleize
メソッドは、レシーバーの単語をキャピタライズ(先頭の文字を大文字に)します。
"alice in wonderland".titleize # => "Alice In Wonderland"
"fermat's enigma".titleize # => "Fermat's Enigma"
titleize
はtitlecase
にエイリアスされています。
これは、active_support/core_ext/string/inflections.rb
内に定義されています。
5.9.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
これは、active_support/core_ext/string/inflections.rb
内に定義されています。
5.9.7 demodulize
装飾定数名の文字列を与えられると、demodulize
は一番右端の部分の実際の定数名の部分を返します。
"Product".demodulize # => "Product"
"Backoffice::UsersController".demodulize # => "UsersController"
"Admin::Hotel::ReservationUtils".demodulize # => "ReservationUtils"
例えばActive Recordは、カウンターキャッシュカラムの名前の算出にこのメソッドを使用します。
# active_record/reflection.rb
def counter_cache_column
if options[:counter_cache] == true
"#{active_record.name.demodulize.underscore.pluralize}_count"
elsif options[:counter_cache]
options[:counter_cache]
end
end
これは、active_support/core_ext/string/inflections.rb
内に定義されています。
5.9.8 deconstantize
式を参照する装飾定数の文字列を与えられると、deconstantize
は一番右端の部分を取り除き、
大抵の場合は定数のコンテナの名前だけを残します。
"Product".deconstantize # => ""
"Backoffice::UsersController".deconstantize # => "Backoffice"
"Admin::Hotel::ReservationUtils".deconstantize # => "Admin::Hotel"
例としてActive Supportは、Module#qualified_const_set:
内でこのメソッドを使用します。
def qualified_const_set(path, value)
QualifiedConstUtils.raise_if_absolute(path)
const_name = path.demodulize
mod_name = path.deconstantize
mod = mod_name.empty? ? self : qualified_const_get(mod_name)
mod.const_set(const_name, value)
end
これは、active_support/core_ext/string/inflections.rb
内に定義されています。
5.9.9 parameterize
parameterize
メソッドは、URLで使用できるようにするために、そのレシーバーを正規化します。
"John Smith".parameterize # => "john-smith"
"Kurt Gödel".parameterize # => "kurt-godel"
実際に結果文字列は、ActiveSupport::Multibyte::Chars
のインスタンス内にラップされます。(翻訳に自信なし)
これは、active_support/core_ext/string/inflections.rb
内に定義されています。
5.9.10 tableize
tableize
メソッドは、pluralize
に続いてunderscore
が行われます。
"Person".tableize # => "people"
"Invoice".tableize # => "invoices"
"InvoiceLine".tableize # => "invoice_lines"
tableize
は単純なケースであれば、与えられたモデルからそれに相当するテーブル名が返されます。
ただし、実際のActive Record内の実装は、tableize
をストレートに使用したものではありません。
demodulizeなクラス名の考慮とオプションをチェックも行い、それは返される文字列の結果に影響を与えます。
これは、active_support/core_ext/string/inflections.rb
内に定義されています。
5.9.11 classify
classify
メソッドは、tableize
の正反対のものです。
これはテーブル名から、それに相当するクラス名を取得します。
"people".classify # => "Person"
"invoices".classify # => "Invoice"
"invoice_lines".classify # => "InvoiceLine"
このメソッドは、装飾テーブル名も認識します。
"highrise_production.companies".classify # => "Company"
classify
は、文字列としてクラス名を返すことに注意してください。
実際のクラスオブジェクトは、次に説明するconstantize
を実行することで取得することが出来ます。
これは、active_support/core_ext/string/inflections.rb
内に定義されています。
5.9.12 constantize
constantize
メソッドは、式を参照するレシーバーの定数を解決します。
"Fixnum".constantize # => Fixnum
module M
X = 1
end
"M::X".constantize # => 1
もし文字列が認識できない定数、またはその内容が有効な定数名では無いと評価される場合、
constantize
はNameError
を発生させます。
constantize
による定数名の解釈は、
::
に続くもので無くても、常に最上位の階層のオブジェクトから始まります。
X = :in_Object
module M
X = :in_M
X # => :in_M
"::X".constantize # => :in_Object
"X".constantize # => :in_Object
#(!)モジュール内にXがあるにも関わらず、モジュール外のものを評価
end
そのため、一般的に同じ場所であってもRubyで評価される事が、 実際に評価されるものとは異なります。
メーラーのテストケースで、テストクラスの名前からメーラーをテストされるメーラーを取得するのに、
constantize
を使用します。
# action_mailer/test_case.rb
def determine_default_mailer(name)
name.sub(/Test$/, '').constantize
rescue NameError => e
raise NonInferrableMailerError.new(name)
end
これは、active_support/core_ext/string/inflections.rb
内に定義されています。
5.9.13 humanize
humanize
メソッドは、画面に表示しても問題のないような属性名を与えてくれます。
それを行うために、アンダースコアを空白に置換し、"_id"接尾辞を削除し、最初の単語のキャピタライズを行います。
"name".humanize # => "Name"
"author_id".humanize # => "Author"
"comments_count".humanize # => "Comments count"
full_messages
ヘルパーメソッドは、
含まれている属性名のフォールバックとしてhumanize
を使用します。
def full_messages
full_messages = []
each do |attribute, messages|
...
attr_name = attribute.to_s.gsub('.', '_').humanize
attr_name = @base.class.human_attribute_name(attribute, default: attr_name)
...
end
full_messages
end
これは、active_support/core_ext/string/inflections.rb
内に定義されています。
5.9.14 foreign_key
foreign_key
メソッドは、クラス名から外部キーカラム名を与えてくれます。
それを行うために、demodulize
とunderscore
を行い、"_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
これは、active_support/core_ext/string/inflections.rb
内に定義されています。
5.10 変換
5.10.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 # => Tue Jul 27 23:37:00 UTC 2010
"2010-07-27 23:37:00".to_datetime # => Tue, 27 Jul 2010 23:37:00 +0000
to_time
は、あなたが指定したいタイムゾーンを指し示すための、
任意の引数:utc
または:local
を受け取ります。
デフォルトは、:utf
です。
更に詳細を知りたければ、Date._parse
のドキュメントを参照してください。
3つとも、ブランクのレシーバーであればnilを返します。
これは、active_support/core_ext/string/conversions.rb
内に定義されています。
6. Numericの拡張
6.1 Bytes
全ての数値(nuber)は、これらのメソッドに応答します。
bytes
kilobytes
megabytes
gigabytes
terabytes
petabytes
exabytes
これらは、1024の変換要素を使用して、相当するバイト数を返します。
2.kilobytes # => 2048
3.megabytes # => 3145728
3.5.gigabytes # => 3758096384
-4.exabytes # => -4611686018427387904
単数形であってもエイリアスされているため、次のようにすることが可能です。
1.megabyte # => 1048576
これは、active_support/core_ext/numeric/bytes.rb
内に定義されています。
6.2 Time
45.minutes + 2.hours + 4.years
のようにして、時間の計算と宣言の使用を有効にします。
これらのメソッドは、Timeオブジェクトから加算・減算はもちろんのこと、from_now
、ago
等を使用する際に、
日付計算を正確に行うためにTime#advance
を使用します。
例えば、
# Time.current.advance(months: 1)と同義
1.month.from_now
# Time.current.advance(years: 2)と同義
2.years.from_now
# Time.current.advance(months: 4, years: 5)と同義
(4.months + 5.years).from_now
これらのメソッドは、上記の使用例として正確な計算を提供する際に、
もしmonths
、years
等の結果が使用する前に変換された場合、
それが実際のものでは無い事に注意する必要があります。(翻訳に自信なし)
# 30.days.to_i.from_nowと同義
1.month.to_i.from_now
# 365.25.days.to_f.from_nowと同義
1.year.to_f.from_now
そのような場合は、RubyのコアのDateとTimeが、 正確な日付と時間の計算アルゴリズムに使用されるべきです。
これは、active_support/core_ext/numeric/time.rb
内に定義されています。
6.3 フォーマット
様々な方法で、数値のフォーマットを行うことが出来ます。
下記は、電話番号の数値を表現する文字列を生成しています。
5551234.to_s(:phone)
# => 555-1234
1235551234.to_s(:phone)
# => 123-555-1234
1235551234.to_s(:phone, area_code: true)
# => (123) 555-1234
1235551234.to_s(:phone, delimiter: " ")
# => 123 555 1234
1235551234.to_s(:phone, area_code: true, extension: 555)
# => (123) 555-1234 x 555
1235551234.to_s(:phone, country_code: 1)
# => +1-123-555-1234
下記は、通貨の数値を表現する文字列を生成しています。
1234567890.50.to_s(:currency) # => $1,234,567,890.50
1234567890.506.to_s(:currency) # => $1,234,567,890.51
1234567890.506.to_s(:currency, precision: 3) # => $1,234,567,890.506
下記は、パーセントとして数値を表現する文字列を生成しています。
100.to_s(:percentage)
# => 100.000%
100.to_s(:percentage, precision: 0)
# => 100%
1000.to_s(:percentage, delimiter: '.', separator: ',')
# => 1.000,000%
302.24398923423.to_s(:percentage, precision: 5)
# => 302.24399%
下記は、カンマ区切りの数値を表現する文字列を生成しています。
12345678.to_s(:delimited) # => 12,345,678
12345678.05.to_s(:delimited) # => 12,345,678.05
12345678.to_s(:delimited, delimiter: ".") # => 12.345.678
12345678.to_s(:delimited, delimiter: ",") # => 12,345,678
12345678.05.to_s(:delimited, separator: " ") # => 12,345,678 05
下記は、正確に丸められた数値を表現する文字列を生成しています。
111.2345.to_s(:rounded) # => 111.235
111.2345.to_s(:rounded, precision: 2) # => 111.23
13.to_s(:rounded, precision: 5) # => 13.00000
389.32314.to_s(:rounded, precision: 0) # => 389
111.2345.to_s(:rounded, significant: true) # => 111
下記は、人にとって読みやすいバイト数を表現する文字列を生成しています。
123.to_s(:human_size) # => 123 Bytes
1234.to_s(:human_size) # => 1.21 KB
12345.to_s(:human_size) # => 12.1 KB
1234567.to_s(:human_size) # => 1.18 MB
1234567890.to_s(:human_size) # => 1.15 GB
1234567890123.to_s(:human_size) # => 1.12 TB
下記は、数値を人にとって読みやすいように単語付きで表現する文字列を生成しています。
123.to_s(:human) # => "123"
1234.to_s(:human) # => "1.23 Thousand"
12345.to_s(:human) # => "12.3 Thousand"
1234567.to_s(:human) # => "1.23 Million"
1234567890.to_s(:human) # => "1.23 Billion"
1234567890123.to_s(:human) # => "1.23 Trillion"
1234567890123456.to_s(:human) # => "1.23 Quadrillion"
これは、active_support/core_ext/numeric/formatting.rb
内に定義されています。
7. Integerの拡張
7.1 multiple_of?
multiple_of?
は、数値が引数の倍数であるかを調べます。
2.multiple_of?(1) # => true
1.multiple_of?(2) # => false
これは、active_support/core_ext/integer/multiple.rb
内に定義されています。
7.2 ordinal
ordinal
メソッドは、レシーバーの数値に合った序数の接尾辞をを返します。
1.ordinal # => "st"
2.ordinal # => "nd"
53.ordinal # => "rd"
2009.ordinal # => "th"
-21.ordinal # => "st"
-134.ordinal # => "th"
これは、active_support/core_ext/integer/inflections.rb
内に定義されています。
7.3 ordinalize
ordinalize
メソッドは、レシーバーの数値に相当する序数の文字列を返します。
また、ordinal
メソッドは接尾辞の文字列だけを返します。
1.ordinalize # => "1st"
2.ordinalize # => "2nd"
53.ordinalize # => "53rd"
2009.ordinalize # => "2009th"
-21.ordinalize # => "-21st"
-134.ordinalize # => "-134th"
これは、active_support/core_ext/integer/inflections.rb
内に定義されています。
8. BigDecimalの拡張
8.1 to_s
to_s
メソッドは、to_formatted_s
にエイリアスされています。
これは浮動小数点表記で、BigDecimalの値を手軽に表示する方法を提供します。
BigDecimal.new(5.00, 6).to_s # => "5.0"
8.2 to_formatted_s
to_formatted_s
メソッドは、"F"のデフォルト指定を提供します。
これは、単なるto_formatted_s
またはto_s
の呼び出しの結果が、
エンジニアリングな表記の代わりに浮動小数点表現になることを意味します。
BigDecimal.new(5.00, 6).to_formatted_s # => "5.0"
また、シンボルによる指定もサポートされています。
BigDecimal.new(5.00, 6).to_formatted_s(:db) # => "5.0"
エンジニアリングな表記も依然としてサポートされます。
BigDecimal.new(5.00, 6).to_formatted_s("e") # => "0.5E1"
9. Enumerableの拡張
9.1 sum
sum
メソッドは、列挙型の要素を加算し合計を算出します。
[1, 2, 3].sum # => 6
(1..100).sum # => 5050
要素は+
に応答するものとしてみなされます。(翻訳に自信なし)
[[1, 2], [2, 3], [3, 4]].sum # => [1, 2, 2, 3, 3, 4]
%w(foo bar baz).sum # => "foobarbaz"
{a: 1, b: 2, c: 3}.sum # => [:b, 2, :c, 3, :a, 1]
デフォルトでは空のコレクションの合計はゼロですが、これはカスタマイズ可能です。
[].sum # => 0
[].sum(1) # => 1
もしブロックが与えられると、sum
はコレクションの要素をyieldして、
その返された値の合計値を算出するイテレータになります。
(1..5).sum {|n| n * 2 } # => 30
[2, 4, 6, 8, 10].sum # => 30
空のレシーバーの合計は、下記の形式で同様にカスタマイズすることが可能です。
[].sum(1) {|n| n**3} # => 1
これは、active_support/core_ext/enumerable.rb
内に定義されています。
9.2 index_by
index_by
メソッドは、キーによってインデックス化された列挙型の要素を使用してハッシュを生成します。
コレクションを通してイテレートし、ブロックに各要素を渡します。 ブロックによって返された値によって、要素はキーにされます。
invoices.index_by(&:number)
# => {'2009-032' => <Invoice ...>, '2009-008' => <Invoice ...>, ...}
キーは通常は一意にすべきです。 もしブロックが同じ値を異なる要素に返した場合、そのキーのコレクションは構築されません。 最後の項目が優先されます。(翻訳に自信なし)
これは、active_support/core_ext/enumerable.rb
内に定義されています。
9.3 many?
many?
メソッドは、collection.size > 1
の略記です。
<% if pages.many? %>
<%= pagination_links %>
<% end %>
もし任意のブロックが与えられると、many?
はtrueを返す要素のみを取り込みます。
@see_more = videos.many? {|video| video.category == params[:category]}
これは、active_support/core_ext/enumerable.rb
内に定義されています。
9.4 exclude?
exclude?
は与えられたオブジェクトがコレクションに属しているか否かを確認します。
これは組み込みのinclude?
の否定版です。
to_visit << node if visited.exclude?(node)
これは、active_support/core_ext/enumerable.rb
内に定義されています。
10. Arrayの拡張
10.1 アクセス
Active Supportは、簡単に配列にアクセス出来るように配列のAPIを増やします。
例えば、to
は渡されたインデックスに至るまでの要素の部分配列を返します。
%w(a b c d).to(2) # => %w(a b c)
[].to(7) # => []
同様にfrom
は末尾から渡されたインデックスを数え、そこから最後までの部分配列を返します。
もし、配列の長さよりインデックスのほうが大きければ、空の配列が返されます。
%w(a b c d).from(2) # => %w(c d)
%w(a b c d).from(10) # => []
[].from(0) # => []
second
、third
、fourth
、fifth
メソッドは、
それに相当する要素を返します。(first
は組み込み)
集合知と建設的な意見のおかげで、forty_two
も利用することが可能です。
%w(a b c d).third # => c
%w(a b c d).fifth # => nil
これは、active_support/core_ext/array/access.rb
内に定義されています。
10.2 要素を追加
10.2.1 prepend
このメソッドは、Array#unshift
のエイリアスです。
%w(a b c d).prepend('e') # => %w(e a b c d)
[].prepend(10) # => [10]
これは、active_support/core_ext/array/prepend_and_append.rb
内に定義されています。
10.2.2 append
このメソッドは、Array#<<
のエイリアスです。
%w(a b c d).append('e') # => %w(a b c d e)
[].append([1,2]) # => [[1,2]]
これは、active_support/core_ext/array/prepend_and_append.rb
内に定義されています。
10.3 オプションの抽出
メソッド呼び出しの最後の引数がハッシュの場合、&block
引数を除いて、
Rubyは角括弧を省略することを許可します。
User.exists?(email: params[:email])
引数が多すぎる場所での引数の位置取りを避けるために、Rails内では多くのシンタックスシュガーが使用されていおり、 名前付きパラメータをエミュレートするインタフェースの代わりに提供されます。(翻訳に自信なし) 特にオプション(任意指定の設定値など)用に最後にハッシュを使用するのは、非常に多用される慣習となっています。
もしメソッドが可変の数で引数が渡される前で、その宣言に*
を使用する場合、
引数の最後に渡されるオプションのハッシュは、引数の配列の1つの項目となり、
そのルールに取り込まれることになります。
そのようなケースでは、extract_options!
を使用して認識して扱えるようなオプションのハッシュを与える事が可能です。
このメソッドは配列の最後の要素の型を調べます。
もし、それがハッシュであれば、それをpop
して返し、
そうでなければ空のハッシュを返します。
例としてcaches_action
コントローラーのマクロの定義を確認してみましょう。
def caches_action(*actions)
return unless cache_configured?
options = actions.extract_options!
...
end
このメソッドは任意の数のアクション名と、最後の引数として任意のオプションのハッシュを受け取ります。
extract_options!
を呼び出すことで、シンプル且つ分かり易い方法で、
オプションのハッシュを取得して、それをアクションの集まりから削除することが出来ます。
これは、active_support/core_ext/array/extract_options.rb
内に定義されています。
10.4 変換
10.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"
このメソッドは3つのオプションを受け取ります。
-
:two_words_connector
- 長さが2の配列に使用されるものです。デフォルトは" and "です。 -
:words_connector
- 3つまたはそれ以上の配列で、最後の2つを除く要素を繋げる際に使用されるものです。 デフォルトは、", "です。 -
: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
|
:connector
と:skip_last_comma
オプションは、非推奨です。
これは、active_support/core_ext/array/conversions.rb
内に定義されています。
10.4.2 to_formatted_s
to_formatted_s
メソッドは、デフォルトでto_s
のように振る舞います。
もし配列がid
に応答する項目を含む場合、引数としてシンボルである:db
が渡されたかもしれません。(翻訳に自信なし)
これは、一般的にはActive Recordオブジェクトのコレクションで使用されます。
返される文字列は下記の通りです。
[].to_formatted_s(:db) # => "null"
[user].to_formatted_s(:db) # => "8456"
invoice.lines.to_formatted_s(:db) # => "23,567,556,12"
この例で取得している数値は、それぞれid
を呼び出して取得したものと仮定しています。
これは、active_support/core_ext/array/conversions.rb
内に定義されています。
10.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
を各項目に順番に送り、結果をrootノード下に集めています。
全ての項目はto_xml
に応答する必要があり、そうでなければ例外が発生します。
デフォルトでは、root要素の名前は最初の項目のクラス名をunderscore化とdasherize化した複数形で、
要素の残りの部分を型に沿って(is_a?
でチェックし)提供します。
それらはハッシュではありません。(翻訳に自信なし)
上記の例では、"contributors"がそれに該当します。
もし最初の型に沿っていない要素が存在する場合は、rootノードは"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>
また、レシーバーがハッシュの配列の場合も、root要素はデフォルトで"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>
もしコレクションが空の場合、root要素はデフォルトに依って"nil-classes"となります。
これはハマりやすいポイントで、例えば上記のcontributorsのリストのroot要素は、コレクションがからの場合、
"nil-classes"となり"contributors"にはなりません。
:root
オプションを使用して、root要素を確定させることが可能です。
子要素の名前は、デフォルトでは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>
これは、active_support/core_ext/array/conversions.rb
内に定義されています。
10.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
に応答する場合に、メソッドが実行されます。Kernel#Array
は返された値がnilであってもto_a
の実行を試みますが、Array.wrap
は即座にnilを返します。 -
もし
to_ary
から返された値がnilでもArrayオブジェクトでも無い場合、Kernel#Array
が例外を発生させる一方で、Array.wrap
はそれを行わずに、その値を返します。 -
引数上で
to_a
を呼び出さない特別なケースとして、nilは空の配列を返します。
最後の点は、その他の列挙型と比較しても特に価値のある機能です。
Array.wrap(foo: :bar) # => [{:foo=>:bar}]
Array(foo: :bar) # => [[:foo, :bar]]
関連したイディオムに、splat演算子を使用したものもあります。
[*object]
Ruby 1.8はnilは[nil]
を返し、そうでなければArray(object)
を呼び出します。
(Please if you know the exact behavior in 1.9 contact fxn.)
そのため、このケースではnil
のための挙動が異なり、
上記で説明したKernel#Array
を使用したこの違いが、残りのオブジェクトに適用します。(翻訳に自信なし)
これは、active_support/core_ext/array/wrap.rb
内に定義されています。
10.6 複製
Array.deep_dup
メソッドは自身と再帰に含まれる全てのオブジェクトを、
Active SupportのObject#deep_dup
メソッドを使用して複製します。
これは、deep_dup
メソッドを各オブジェクト内部へ送るArray#map
のように動作します。
array = [1, [2, 3]]
dup = array.deep_dup
dup[1][2] = 4
array[1][2] == nil # => true
これは、active_support/core_ext/array/deep_dup.rb
内に定義されています。
10.7 グループ化
10.7.1 in_groups_of(number, fill_with = nil)
in_groups_of
メソッドは特定のサイズの連続したグループに配列を分断します。
これはグループ化した配列を返します。
[1, 2, 3].in_groups_of(2) # => [[1, 2], [3, nil]]
またブロックに渡せば、それらを順にyieldします。
<% 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を使用してそのサイズを満たしています。
2つ目の任意の引数を使用して、この埋める値を変更することが可能です。
[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
自体を埋める値として使用することは出来ません。
これは、active_support/core_ext/array/grouping.rb
内に定義されています。
10.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]]
また、ブロックを渡すことで、それらを順にyieldします。
%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を使用しているのが確認出来ます。
グループはこれら余りの要素が1つでも存在する場合、右端のものを取得します。(翻訳に自信なし)
そしてこれらを持つグループは、常に最後になります。
2つ目の任意の引数を使用して、この埋める値を変更することが可能です。
%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
を渡すことで、最後のグループを満たす必要が無いことを伝える事が出来ます。
これは、active_support/core_ext/array/grouping.rb
内に定義されています。
10.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"]]
上記例で確認できる通り、連続したレパレーターは結果として空の配列になります。
これは、active_support/core_ext/array/grouping.rb
内に定義されています。
11. Hashの拡張
11.1 変換
11.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
はkey
の単数形の:children
となります。 -
もし値が呼び出し可能なオブジェクトの場合、1つまたは2つの引数を受け入れなくてはなりません。
引数の数に依存して、呼び出し可能なオブジェクトは1つ目の引数を
:root
のキーとして、 2つ目の引数をキーを単数形にしたものとして実行されます。(翻訳に自信なし) その戻り値は新しいノードになります。 -
もし値が
to_xml
に応答するものであれば、keyを:root
としてそれを実行します。 -
ここまでのどれにも該当しなければ、タグとして
key
を使用し、テキストノードとして値を文字列で表記するノードを生成します。 もし値がnilなのであれば、属性"nil"に"true"が設定されたものが追加されます。:skip_types
オプションが存在しそれがtrueで無い限り、 下記のマッピングに沿って属性"type"が追加されます。
XML_TYPE_NAMES = {
"Symbol" => "symbol",
"Fixnum" => "integer",
"Bignum" => "integer",
"BigDecimal" => "decimal",
"Float" => "float",
"TrueClass" => "boolean",
"FalseClass" => "boolean",
"Date" => "date",
"DateTime" => "datetime",
"Time" => "datetime"
}
デフォルトではrootノードは"hash"ですが、:root
オプションを通して設定を変更することが出来ます。
デフォルトのXMLビルダーは、Builder::XmlMarkup
の新しいインスタンスです。
:builder
オプションを使用して、自身のビルダーを設定することが可能です。
メソッドはまた、:dasherize
とそれと類似したオプションも受け入れ、ビルダーに橋渡しします。
これは、active_support/core_ext/hash/conversions.rb
内に定義されています。
11.2 マージ
Rubyは2つのハッシュをマージする組み込みのHash#merge
メソッドを持っています。
{a: 1, b: 1}.merge(a: 0, c: 2)
# => {:a=>0, :b=>1, :c=>2}
Active Supportには、他に別の方法でハッシュをマージする方法が定義されていて便利です。
11.2.1 reverse_mergeとreverse_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!
は呼び出し元のハッシュを、
それが良い悪いに関わらず変更してしまうことに注意してください。
これは、active_support/core_ext/hash/reverse_merge.rb
内に定義されています。
11.2.2 reverse_update
reverse_update
メソッドは、上記で説明したreverse_merge!
へのエイリアスです。
reverse_update
には、感嘆符が付かない事に注意してください。
これは、active_support/core_ext/hash/reverse_merge.rb
内に定義されています。
11.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!
メソッドは、呼び出し元そのもののディープマージを行います。
これは、active_support/core_ext/hash/deep_merge.rb
内に定義されています。
11.3 深い(deep)複製
Hash.deep_dup
はそのハッシュ自身と内部の再帰的な全てのキーと値を、
Active SupportのObject#deep_dup
メソッドを使用して複製します。
これは、内部のそれぞれのペアにdeep_dup
を実行して、
Enumerator#each_with_object
のように動作します。
hash = { a: 1, b: { c: 2, d: [3, 4] } }
dup = hash.deep_dup
dup[:b][:e] = 5
dup[:b][:d] << 5
hash[:b][:e] == nil # => true
hash[:b][:d] == [3, 4] # => true
これは、active_support/core_ext/hash/deep_dup.rb
内に定義されています。
11.4 差分
diff
メソッドは、下記のロジックに従いレシーバーとその引数の差分を表すハッシュを返します。
- 両方のハッシュに存在するキーと値のペアは、差分ハッシュには属しません。
- もし両方のハッシュが同じキーで異なる値のペアを持つ場合、レシーバーのペアが採用されます。
- その他のものは、マージされます。
{a: 1}.diff(a: 1)
# => {}, first rule
{a: 1}.diff(a: 2)
# => {:a=>1}, second rule
{a: 1}.diff(b: 2)
# => {:a=>1, :b=>2}, third rule
{a: 1, b: 2, c: 3}.diff(b: 1, c: 3, d: 4)
# => {:a=>1, :b=>2, :d=>4}, all rules
{}.diff({}) # => {}
{a: 1}.diff({}) # => {:a=>1}
{}.diff(a: 1) # => {:a=>1}
この差分ハッシュの重要な点は、再度diffを適用することによって元のハッシュを取得できるということです。
hash.diff(hash2).diff(hash2) == hash
ハッシュの差分処理は、例えば予期されるオプションハッシュに関連付けられたエラーメッセージに便利かもしれません。(翻訳に自信なし)
これは、active_support/core_ext/hash/diff.rb
内に定義されています。
11.5 キーによる処理
11.5.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!
も存在し、レシーバーのキーを削除します。
これはactive_support/core_ext/hash/except.rb
内に定義されています。
11.5.2 transform_keysとtransform_keys!
transform_keys
メソッドはブロックを受け取り、
レシーバー内の各キーに対してブロックの処理を適用したハッシュを返します。
{nil => nil, 1 => 1, a: :a}.transform_keys{ |key| key.to_s.upcase }
# => {"" => nil, "A" => :a, "1" => 1}
キーの衝突が発生するケースでは、未定義になります。
{"a" => 1, a: 2}.transform_keys{ |key| key.to_s.upcase }
# => {"A" => 2}, 必ずしもこの結果になると保証することは出来ません。(翻訳に自信なし)
このメソッドは例えば特定の変換を構築する際に便利かもしれません。
一例として、stringify_keys
とsymbolize_keys
は、
transform_keys
を使用してキーの変換を行っています。
def stringify_keys
transform_keys{ |key| key.to_s }
end
...
def symbolize_keys
transform_keys{ |key| key.to_sym rescue key }
end
感嘆符付きのtransform_keys!
も存在し、
レシーバーのキーにブロックの処理を適用します。
その他にdeep_transform_keys
とdeep_transform_keys!
が存在し、
与えられたハッシュ内の全ての入れ子のものも含めてブロックを処理を適用します。
下記はこのメソッドの一例になります。
{nil => nil, 1 => 1, nested: {a: 3, 5 => 5}}.deep_transform_keys{ |key| key.to_s.upcase }
# => {""=>nil, "1"=>1, "NESTED"=>{"A"=>3, "5"=>5}}
これは、active_support/core_ext/hash/keys.rb
内に定義されています。
11.5.3 stringify_keysとstringify_keys!
stringify_keys
メソッドは、レシーバーのキーを文字列に変えたハッシュを返します。
これを行うために、内部でto_s
が適用されています。
{nil => nil, 1 => 1, a: :a}.stringify_keys
# => {"" => nil, "a" => :a, "1" => 1}
キーの衝突が発生するケースでは、未定義になります。
{"a" => 1, a: 2}.stringify_keys
# => {"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
2つ目の行で安全に"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}}
これは、active_support/core_ext/hash/keys.rb
内に定義されています。
11.5.4 symbolize_keysとsymbolize_keys!
symbolize_keys
レシーバーのキーをシンボル化が可能であればシンボルに変えてハッシュを返します。
これを行うために、内部でto_sym
が適用されています。
{nil => nil, 1 => 1, "a" => "a"}.symbolize_keys
# => {1=>1, nil=>nil, :a=>"a"}
上記の例で、1つだけキーがシンボル化されていることに注意してください。
キーの衝突が発生するケースでは、未定義になります。
{"a" => 1, a: 2}.symbolize_keys
# => {:a=>2}, 必ずしもこの結果になると保証することは出来ません。(翻訳に自信なし)
このメソッドは、例えばオプションとしてシンボルと文字列の両方を容易に受け入れるようにする場合に便利かもしれません。
一例として、下記はActionController::UrlRewriter
の定義です。
def rewrite_path(options)
options = options.symbolize_keys
options.update(options[:params].symbolize_keys) if options[:params]
...
end
2つ目の行で安全に":params"キーにアクセスする事ができ、
ユーザーが:params
または"params"
のどちらでも渡せるようにしています。
また、感嘆符付きの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}}
これは、active_support/core_ext/hash/keys.rb
内に定義されています。
11.5.5 to_optionsとto_options!
to_options
とto_options!
のメソッドは、
symbolize_keys
とsymbolize_keys!
のそれぞれのエイリアスになります。
これは、active_support/core_ext/hash/keys.rb
内に定義されています。
11.5.6 assert_valid_keys
assert_valid_keys
メソッドは任意の数の引数を受け取り、
それをホワイトリストとしてレシーバーの持つキーにそれ以外のものが無いかチェックします。
もしあれば、ArgumentError
が発生します。
{a: 1}.assert_valid_keys(:a) # パス
{a: 1}.assert_valid_keys("a") # ArgumentError
例えば、Active Recordは関連付けを構築する際に、素性の分からないオプション受け入れません。
これは、assert_valid_keys
を通して制御する処理が実装されています。
これは、active_support/core_ext/hash/keys.rb
内に定義されています。
11.6 スライス
Rubyは組み込みで文字列と配列をスライスして取得するメソッドをサポートしています。 Active Supportではスライスをハッシュに対しても拡張しています。
{a: 1, b: 2, c: 3}.slice(:a, :c)
# => {:c=>3, :a=>1}
{a: 1, b: 2, c: 3}.slice(:b, :X)
# => {:b=>2} # 存在しないキーは無視されます
もしレシーバーがconvert_key
キーに応答するのであれば、
キーは正規化されます。
{a: 1, b: 2}.with_indifferent_access.slice("a")
# => {:a=>1}
スライスはキーのホワイトリストを使用して、オプションハッシュをサニタイズするのに便利かもしれません。
また、slice!
も存在し、その場でスライスが実行されて取り除かれたものが返されます。
hash = {a: 1, b: 2}
rest = hash.slice!(:a) # => {:b=>2}
hash # => {:a=>1}
これは、active_support/core_ext/hash/slice.rb
内に定義されています。
11.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
これは、active_support/core_ext/hash/slice.rb
内に定義されています。
11.8 無頓着なアクセス
with_indifferent_access
メソッドは、
ActiveSupport::HashWithIndifferentAccess
を返します。
{a: 1}.with_indifferent_access["a"] # => 1
これは、active_support/core_ext/hash/indifferent_access.rb
内に定義されています。
12. Regexpの拡張
12.1 multiline?
multiline?
メソッドは、正規表現に/m
フラグが設定されているか、
ドットが改行にマッチするかを調べます。
%r{.}.multiline? # => false
%r{.}m.multiline? # => true
Regexp.new('.').multiline? # => false
Regexp.new('.', Regexp::MULTILINE).multiline? # => true
Railsはこのメソッドをルーティングコード内でも、1箇所このメソッドを使用しています。 複数行の正規表現はルート(経路)の必要条件の指定で禁止されており、このフラグは制約の強制を容易にしてくれます。(翻訳に自信なし)
def assign_route_options(segments, defaults, requirements)
...
if requirement.multiline?
raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}"
end
...
end
これは、active_support/core_ext/regexp.rb
内に定義されています。
13. Rangeの拡張
13.1 to_s
Active SupportはRange#to_s
メソッドを拡張し、任意のフォーマット引数を理解するようになります。
これによって唯一サポートされたデフォルトではないフォーマットは、:db
です。
(Date.today..Date.tomorrow).to_s
# => "2009-10-25..2009-10-26"
(Date.today..Date.tomorrow).to_s(:db)
# => "BETWEEN '2009-10-25' AND '2009-10-26'"
この例から、:db
フォーマットがSQLのBETWEEN
を生成していることが分かります。
状況に応じてActive Recordが使用されています。
これは、active_support/core_ext/range/conversions.rb
内に定義されています。
13.2 include?
Range#include?
メソッドとRange#===
は、
ある値が指定されたインスタンス(端から端までの間)に含まれるかを調べます。
(2..3).include?(Math::E) # => true
Active Supportはこれらのメソッドを拡張しているため、引数は順に別の範囲になります。 このケースは、レシーバーそれぞれ自身の引数の範囲をテストしています。
(1..10).include?(3..7) # => true
(1..10).include?(0..7) # => false
(1..10).include?(3..11) # => false
(1...9).include?(3..9) # => false
(1..10) === (3..7) # => true
(1..10) === (0..7) # => false
(1..10) === (3..11) # => false
(1...9) === (3..9) # => false
これは、active_support/core_ext/range/include_range.rb
内に定義されています。
13.3 overlaps?
Range#overlaps?
メソッドは、2つの与えられた範囲が交わっていないかを調べます。
(1..10).overlaps?(7..11) # => true
(1..10).overlaps?(0..7) # => true
(1..10).overlaps?(11..27) # => false
これは、active_support/core_ext/range/overlaps.rb
内に定義されています。
14. Procの拡張
14.1 bind
ご存知のようにRubyにはUnboundMethod
クラスがあり、
そのインスタンスはレシーバの無いメソッドです。(翻訳に自信なし)
このModule#instance_method
メソッドは、
例えば下記のようにアンバインド(unbound)されたメソッドを返します。
Hash.instance_method(:delete) # => #<UnboundMethod: Hash#delete>
そのままではアンバインドされたメソッドは呼び出す事が出来ないため、
始めにbind
を使用してオブジェクトにバインドする必要があります。
clear = Hash.instance_method(:clear)
clear.bind({a: 1}).call # => {}
Active Supportは、この用途に似たProc#bind
を定義しています。
Proc.new { size }.bind([]).call # => 0
上記の通り呼び出し可能で引数にバインドされており、戻り値はまさにMethod
になっています。
これを行うために、実際にはProc#bind
は裏でメソッドを生成しています。
もしかしたら、__bind_1256598120_237302
のような代わった名前のメソッドをスタックトレース内で見たことがあるかもしれません。
それはまさにこの処理によるものです。
例えば、Action Packはrescue_from
内でこのトリックを使用し、
メソッド名と、また例外をrescueするコールバックとして与えるprocも受け取ります。
いずれのケースでも呼び出す必要があり、そのためバインドされたメソッドは呼び出し元でシンプルになるように、
handler_for_rescue
によって返されます。(翻訳に自信なし)
def handler_for_rescue(exception)
_, rescuer = Array(rescue_handlers).reverse.detect do |klass_name, handler|
...
end
case rescuer
when Symbol
method(rescuer)
when Proc
rescuer.bind(self)
end
end
これは、active_support/core_ext/proc.rb
内に定義されています。
15. Dateの拡張
15.1 計算
ここで紹介するメソッドは全てactive_support/core_ext/date/calculations.rb
内に定義されています。
以下の計算メソッドでは、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?
、future?
が元になっており、
それらの全てはDate.current
に関連しています。
ユーザーのタイムゾーンを信用するメソッドを使用した日付け比較をする場合、
Date.today
ではなく、Date.current
を使用している事を確認してください。
デフォルトのDate.today
を使用すると、ユーザーのタイムゾーンがシステムのタイムゾーンと比較して、
未来になるケースがあるかもしれません。
これは、Date.today
がDate.yesterday
と同じ日付けになるかもしれない事を意味します。
15.1.2 名前付けされた日付け
15.1.2.1 prev_year, next_year
Ruby 1.9で、prev_year
とnext_year
は、
翌年と前年の同じ月と日の日付けを返します。
d = Date.new(2010, 5, 8) # => Sat, 08 May 2010
d.prev_year # => Fri, 08 May 2009
d.next_year # => Sun, 08 May 2011
もし、日付けが閏年の2月29日であれば、これらのメソッドは28日を取得します。
d = Date.new(2000, 2, 29) # => Tue, 29 Feb 2000
d.prev_year # => Sun, 28 Feb 1999
d.next_year # => Wed, 28 Feb 2001
prev_year
は、last_year
にエイリアスされています。
15.1.2.2 prev_month, next_month
Ruby 1.9で、prev_month
とnext_month
は、
翌月と前月の同じ月と日の日付けを返します。
d = Date.new(2010, 5, 8) # => Sat, 08 May 2010
d.prev_month # => Thu, 08 Apr 2010
d.next_month # => Tue, 08 Jun 2010
日付けが存在しない場合は、その月に応じた最後の日付けが返されます。
Date.new(2000, 5, 31).prev_month # => Sun, 30 Apr 2000
Date.new(2000, 3, 31).prev_month # => Tue, 29 Feb 2000
Date.new(2000, 5, 31).next_month # => Fri, 30 Jun 2000
Date.new(2000, 1, 31).next_month # => Tue, 29 Feb 2000
prev_month
は、last_month
にエイリアスされています。
15.1.2.3 prev_quarter, next_quarter
prev_month
、next_month
と同様です。
次の四半期と前の四半期の、同じ日付けを返します。
t = Time.local(2010, 5, 8) # => Sat, 08 May 2010
t.prev_quarter # => Mon, 08 Feb 2010
t.next_quarter # => Sun, 08 Aug 2010
日付けが存在しない場合は、その月に応じた最後の日付けが返されます。
Time.local(2000, 7, 31).prev_quarter # => Sun, 30 Apr 2000
Time.local(2000, 5, 31).prev_quarter # => Tue, 29 Feb 2000
Time.local(2000, 10, 31).prev_quarter # => Mon, 30 Oct 2000
Time.local(2000, 11, 31).next_quarter # => Wed, 28 Feb 2001
prev_quarter
は、last_quarter
にエイリアスされています。
15.1.2.4 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.5 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.6 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.7 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.8 beginning_of_quarter, end_of_quarter
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.9 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
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
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
The most generic way to jump to other days is 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
date = Date.new(2010, 6, 6)
date.advance(years: 1, weeks: 2) # => Mon, 20 Jun 2011
date.advance(months: 2, days: -2) # => Wed, 04 Aug 2010
この例の後者では、値にマイナスが指定されている事に注意してください。
計算を実行するために、メソッドは最初に年を、次に月を、次に週を、最後に日を増加します。 この順序は月の最終日に対して、重要になります。 例として2010年の2月の最後の日で、 1月分と1日分先に進めたいとします。
advance
メソッドは先に1月分進め、次に1日分を進めるため、
結果は下記のようになります。
Date.new(2010, 2, 28).advance(months: 1, days: 1)
# => Sun, 29 Mar 2010
一方、もし次のように別の方法で同じような事を行うと、異なる結果を得ることが出来ます。
Date.new(2010, 2, 28).advance(days: 1).advance(months: 1)
# => Thu, 01 Apr 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 期間
日付けの期間は加算、減算が可能です。
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
、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
は、
Date
では無く、Time
とDateTime
のためのものであり、
Date
インスタンス上では時間、分の始まり・終わりを要求する事は意味をなしません。
15.1.6.4 ago, since
ago
メソッドは引数として秒数を受け取り、深夜12:00からその秒数分前のタイムスタンプを返します。
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
15.1.7 Other Time Computations
15.2 Conversions
16. DateTimeの拡張
DateTime
はDSTルールを把握していないため、
メソッドの幾つかはDST変更が起こった際にエッジケースを持つことになります。(翻訳に自信なし)
例えば、seconds_since_midnight
は、そのような日付では実際の量を返さないかもしれません。
16.1 計算
以下の全てのメソッドは、active_support/core_ext/date_time/calculations.rb
内に定義されています。
DateTime
クラスはDate
のサブクラスであるため、
active_support/core_ext/date/calculations.rb
に読み込むことで、
常にDateTimeを返すものを除き、それらのメソッドとエイリアスを継承します。
yesterday
tomorrow
beginning_of_week (at_beginning_of_week)
end_of_week (at_end_of_week)
monday
sunday
weeks_ago
prev_week (last_week)
next_week
months_ago
months_since
beginning_of_month (at_beginning_of_month)
end_of_month (at_end_of_month)
prev_month (last_month)
next_month
beginning_of_quarter (at_beginning_of_quarter)
end_of_quarter (at_end_of_quarter)
beginning_of_year (at_beginning_of_year)
end_of_year (at_end_of_year)
years_ago
years_since
prev_year (last_year)
next_year
以下のメソッドは再実装がされているため、active_support/core_ext/date/calculations.rb
を読み込む必要はありません。
beginning_of_day (midnight, at_midnight, at_beginning_of_day)
end_of_day
ago
since (in)
一方で、advance
とchange
も定義されており、以降のドキュメントにあるように、
より多くのオプションがサポートされています。
以降のメソッドは、DateTime
インスタンスのみで使用することを意味するものとして、
active_support/core_ext/date_time/calculations.rb
内でのみ実装されています。
beginning_of_hour (at_beginning_of_hour)
end_of_hour
16.1.1 名前付けされたDateTime
16.1.1.1 DateTime.current
Active SupportはDateTime.current
を、
ユーザーのタイムゾーンが定義されていた場合にそれを信用するという点を除いて、
Time.now.to_datetime
のように定義しています。
DateTime.yesterday
とDateTime.tomorrow
も定義されており、
そのインスタンスはDateTime.current
に関連するpast?
とfuture?
を元にしています。
16.1.2 その他の拡張
16.1.2.1 seconds_since_midnight
seconds_since_midnight
メソッドは、深夜12時からの秒数を返します。
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
もしhour
がゼロであれば、分と秒も同様にゼロになります。(それらの値が指定されていなければ)
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 期間
期間は日付けから加算・減算される事が可能です。
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 計算
以下の全てのメソッドは、active_support/core_ext/time/calculations.rb
内に定義されています。
Active SupportはTime
を追加し、メソッドの多くはDateTime
で利用可能です。
past?
today?
future?
yesterday
tomorrow
seconds_since_midnight
change
advance
ago
since (in)
beginning_of_day (midnight, at_midnight, at_beginning_of_day)
end_of_day
beginning_of_hour (at_beginning_of_hour)
end_of_hour
beginning_of_week (at_beginning_of_week)
end_of_week (at_end_of_week)
monday
sunday
weeks_ago
prev_week (last_week)
next_week
months_ago
months_since
beginning_of_month (at_beginning_of_month)
end_of_month (at_end_of_month)
prev_month (last_month)
next_month
beginning_of_quarter (at_beginning_of_quarter)
end_of_quarter (at_end_of_quarter)
beginning_of_year (at_beginning_of_year)
end_of_year (at_end_of_year)
years_ago
years_since
prev_year (last_year)
next_year
これらは、類似しています。 前述のドキュメントを参照し、以降で説明する違いについて認識しておいてください。
-
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
-
もし
since
またはago
がTime
で表現できない時間に移った場合、 代わりにDateTime
オブジェクトが返されます。
17.1.1 Time.current
Active Supportは、カレントのタイムゾーンでの今日を示すTime.current
を定義します。
これは定義されていた場合ユーザーのタイムゾーンを信用することを除いて、Time.now
と似ています。
Time.yesterday
とTime.tomorrow
も定義されており、
インスタンスはpast?
、today?
、future?
を元に、
Time.current
の相対になっています。
ユーザーのタイムゾーンを信用するメソッドを使用して時間比較を行う際は、
Time.now
ではなく、Time.current
を使用している事を確かめてください。
デフォルトのTime.today
を使用すると、ユーザーのタイムゾーンがシステムのタイムゾーンと比較して、
未来になるケースがあるかもしれません。
これは、Time.now
がTime.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.2 Timeコンストラクタ
Active Supportはタイムゾーンが定義されていれば、Time.now
のフォールバックとして、
Time.zone.now
にTime.current
を定義します。(翻訳に自信なし)
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 期間
期間は時間オブジェクトから加算・減算される事が可能です。
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
のクラスメソッドを使用して、
書きかけの内容を見られることを防ぎながら、ファイルへ書き込む事が可能です。
引数としてファイル名を渡し、メソッドが書き込むためにファイルハンドルをyieldします。
ブロックが終わると、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
がファイルのオーナーと権限を変更できないケースもあり、
このエラーはキャッチされ、処理を行うために必要なファイルへのアクセスを確保するため、
ユーザー/ファイルシステム正当性を無視します。(翻訳に自信なし)
chmod操作のためatomic_write
の実行は、もし対象のファイルがACLの設定を持つ場合、
このACLは再処理/修正が行われます。
atomic_write
を使用して、付加を行うことは出来ないことに注意してください。
予備のファイルは一時ファイルための標準のディレクトリに書き込まれますが、 第2引数として自分で選択したディレクトリを指定することが可能です。
これは、active_support/core_ext/file/atomic.rb
内に定義されています。
19. Marshalの拡張
19.1 load
Active Supportはload
へ、定数のオートローディングのサポートを追加します。
例えば、ファイルキャッシュの格納は、このようにデシリアライズします。
File.open(file_name) { |f| Marshal.load(f) }
もし、キャッシュされたデータがその時点では不明な定数を参照する場合、 オートローディングがトリガされ、それが成功した場合、デシリアライズは透過的にリトライされます。(翻訳に自信なし)
もし引数がIOの場合、それはリトライ出来るように巻き戻りに応答する必要があります。 通常のファイルは巻き戻りに応答します。
これは、active_support/core_ext/marshal.rb
内に定義されています。
20. Loggerの拡張
20.1 around_[level]
2つの引数before_message
メッセージとafter_message
をメッセージを取得し、
Logger
インスタンス上で現在の[level]
のメソッドで各beforeとafterのメッセージは呼び出され、
処理の順番は、before_message
のメッセージ、特定のメッセージ(訳注:ブロック内の処理)、after_message
のメッセージ、
となります。
logger = Logger.new("log/development.log")
logger.around_info("before", "after") { |logger| logger.info("during") }
20.2 silence
silence
に指定されたログレベルより低いログは、そのブロック内で沈黙します。
ログレベルの順は、debug
、info
、error
、fatal
になります。
logger = Logger.new("log/development.log")
logger.silence(Logger::INFO) do
logger.debug("In space, no one can hear you scream.")
logger.info("Scream all you want, small mailman!")
end
20.3 datetime_format=
ロガー(Logger
)に関連付けられたフォーマッタークラスによる日付フォーマット出力を変更します。
もしフォーマッタークラスがdatetime_format
メソッドを持たない場合、これは無視されます。
class Logger::FormatWithTime < Logger::Formatter
cattr_accessor(:datetime_format) { "%Y%m%d%H%m%S" }
def self.call(severity, timestamp, progname, msg)
"#{timestamp.strftime(datetime_format)} -- #{String === msg ? msg : msg.inspect}
"
end
end
logger = Logger.new("log/development.log")
logger.formatter = Logger::FormatWithTime
logger.info("<- is the current time")
これは、active_support/core_ext/logger.rb
内に定義されています。
21. NameErrorの拡張
Active Supportは、名前を引数として渡して例外が発生したかどうかを確認するためのmissing_name?
を、
NameError
に追加します。
名前はシンボルまたは文字列で渡されます。 シンボルは装飾の無い定数名に対して、文字列は完全修飾定数名に対してテストされます。
シンボルは、:"ActiveRecord::Base"
として完全修飾名を表現することが可能なので、
技術的にそうでなければならない事から、便宜上シンボルとして動作するように定義されます。
例えば、PostsController
のアクションが呼ばされた際に、Railsは楽観的にposts_helper.rb
が
NameError
を発生させるケースがあるかもしれません。
このケースでは、例外を発生させるべきです。
missing_name?
メソッドは、両方のケースを見分ける方法を提供します。
def default_helper_module!
module_name = name.sub(/Controller$/, '')
module_path = module_name.underscore
helper module_path
rescue MissingSourceFile => e
raise e unless e.is_missing? "#{module_path}_helper"
rescue NameError => e
raise e unless e.missing_name? "#{module_name}Helper"
end
これは、active_support/core_ext/name_error.rb
内に定義されています。
22. LoadErrorの拡張
Active SupportはLoadError
にis_missing?
を追加し、
また後方互換性のためにMissingSourceFile
定数もクラスに割り当てます。
パス名をis_missing?
に与えて、特定のファイルで例外が発生したか否かを確認します(".rb"の拡張子は除く)。
例えば、PostsController
アクションが呼び出されると、
Railsは存在しないかもしれないposts_helper.rb
ファイルの読み込みを試みますが、
問題はありません。
ヘルパーモジュールは必須とされているわけでは無いので、Railsは読み込みエラーを沈黙させます。
ただし、ヘルパーモジュールが存在し、次に必須となる別のライブラリが存在しないケースがあるかもしれません。
そのようなケースでは、Railsは例外を再発生させる必要があります。
is_missing?
メソッドは、両方のケースを見分ける方法を提供します。
def default_helper_module!
module_name = name.sub(/Controller$/, '')
module_path = module_name.underscore
helper module_path
rescue MissingSourceFile => e
raise e unless e.is_missing? "helpers/#{module_path}_helper"
rescue NameError => e
raise e unless e.missing_name? "#{module_name}Helper"
end
これは、active_support/core_ext/load_error.rb
内に定義されています。
© 2010 - 2017 STUDIO KINGDOM