Acitiveサポートの拡張

Active Supportは、Ruby言語拡張、ユーティリティ、その他横断的に関わりがあるものの提供を請け負うRuby on Railsのコンポーネントです。

これは、Railsアプリケーションの開発者とRuby on Rails自体の開発者の両方に対して、言語レベルで基板部分をより豊かにしてくれます。

このガイドを読むことで、次の事が学べるはずです。

  • Core拡張とは何か
  • どのようにして全ての拡張を読み込むのか
  • 必要とする拡張だけをcherry-pick(狙ったものだけを取得)する方法
  • Active Supportが提供する拡張とは何か

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.baretrueで無い限り、 全てのRuby on Railsアプリケーションを読み込みます。 このケースでは、アプリケーションはフレームワーク自身が必要とするものだけを選択して読み込み、 前のセクションで説明したように、依然として任意の粒度で選択することも出来ます。

2. 全てのObjectの拡張

2.1 blank?とpresent?

下記の値は、Railsアプリケーション内ではblankとみなされます。

  • nilfalse
  • 空白(whitespace)のみで構成された文字列(詳細は後述)
  • 空の配列とハッシュ
  • その他empty?の応答に、emptyを返すオブジェクト

文字列の判定には、Unicode対応の文字クラス[:space:]が使用されます。 そのため、例えば、U+2029(段落区切り)は空白であると判定されます。

数値について述べられていないことに注意してください。 特に00.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は、常に同じインスタンスを参照します。

Math.cos(0)の結果は、1.0です。

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

nilfalsetrue、symbols、numbers、class、moduleオブジェクトを除く、 全てのオブジェクトに定義されます。

一部のクラスでは、dupcloneを削除、または例外を発生させることで複製を許可しないようにしています。 そのため、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で、 @loggernilかどうかを確認するのに、これを使用しています。 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_paramto_sを呼ぶだけです。

7.to_param # => "7"

to_paramの戻り値は、エスケープすべきではありません。

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

Rails内の幾つかのクラスは、このメソッドを上書きします。

例えば、niltruefalseはそれら自身を返します。 Array#to_paramは、要素上でto_paramを呼び出し、その結果を"/"で繋げます。

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

特に、 Railsのルーティングシステムは、: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_warningsenable_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

getpostなどのメソッドを委任して動作させます。

: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_readerattr_internal_writerattr_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_readermattr_writermattr_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

もしモジュールが匿名、またはトップ階層に属するのであれば、parentObjectを返します。

この場合、parent_namenilを返す事に注意してください。

これは、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_nameObjectを返すことに注意してください。

これは、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_getconst_setは、 そのもの自身の定数名を受け取ります。 Active SupportはこのAPIを拡張し、相対的な修飾名を渡すことを可能にします。

新しいメソッドは、qualified_const_defined?qualified_const_getqualified_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_writerfalseに設定することで防ぐ事が可能です。

module ActiveRecord
  class Base
    class_attribute :table_name_prefix, instance_writer: false
    self.table_name_prefix = ""
  end
end

モデルの属性の設定からmass-assignmentを防ぐ方法として、このオプションは便利かもしれません。

readerインスタンメソッドの生成は、:instance_readerfalseに設定することで防ぐ事が可能です。

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_readercattr_writercattr_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_readerfalseに設定することで防ぎ、 writerインスタンスメソッドの生成は、:instance_writerfalseに設定することで防ぎます。 両方のメソッドの生成は、:instance_accessorfalseに設定することで防ぎます。 全てのケースで、値は必ず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_accessorfalseにして、 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!"の場合、 アンパーサンドは"&amp;"にエスケープする必要があるため、出力は正しい形式で出力されません。 更に言えば、アプリケーションに依存した大きなセキュリティーホールがあると、 ユーザーが細工したレビュータイトルを設定することで、悪意のあるHTMLを注入することが出来てしまいます。 これらのリスクについて更に情報が必要であれば、 セキュリティガイドのクラスサイトスクリプティングのセクションを確認してください。

5.1.2 安全な文字列

Active Supportは、(HTMLの)安全な文字列の概念を持ちます。 安全な文字列は、HTMLに挿入可能であるものとして印付けられたものです。 エスケープされているかどうかに関係なく、それは信頼できるものです。

文字列は、デフォルトでは安全ではないとみなされます。

"".html_safe? # => false

html_safeメソッドを使用することで、安全な文字列だという事を獲得することが出来ます。

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

html_safeはエスケープを行うわけではなく、あくまで安全である事を主張するだけであることを理解しておく事は非常に重要です。

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

特定の問題無いとする文字列上でhtml_safeを呼び出しの保証をするのは、あなた次第です。

もし安全な文字列上でconcat<<、または+のいずれかを使用すると、 その結果は安全な文字列になります。 安全ではない引数は、エスケープされます。

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

安全な引数は、直接追加されます。

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

これらのメソッドは、通常のビューで使用されるべきではありません。 安全ではない値は自動的にエスケープされます。

<%= @review.title %> <%# 必要であればエスケープされます %>

そのまま挿入するには、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)文字列を提供するかもしれません。 これには、downcasegsubstripchompunderscore等があります。

こういった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: '&hellip;')
# => "Oh dear! Oh &hellip;"

ただし、省略記号文字列の長さが、切り詰める際の長さに考慮されることに注意してください。

:separatorを渡すことで、文字列を自然な形で切り取る事が出来ます。

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

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

:separatorオプションを正規表現にすることも可能です。

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

これらの例では、"dear"が最初の切り取り対象になりますが、:separatorによってそれが阻止されています。

これは、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 == 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(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などの言語で、規則に従ったメソッド名を算出するのに便利かもしれません。

camelizeunderscoreの正反対のものと考えるかもしれませんが、 "SSLError".underscore.camelizeは"SslError"となり、元の状態を保持できないケースもあります。 このようなケースをサポートするために、Active Supportはconfig/initializers/inflections.rbで、 特定の頭文字を指定することを可能にしてくれます。

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

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

camelizecamelcaseにエイリアスされています。

これは、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

underscorecamelizeの正反対のものと考えるかもしれませんが、 "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"

titleizetitlecaseにエイリアスされています。

これは、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

もし文字列が認識できない定数、またはその内容が有効な定数名では無いと評価される場合、 constantizeNameErrorを発生させます。

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メソッドは、クラス名から外部キーカラム名を与えてくれます。 それを行うために、demodulizeunderscoreを行い、"_id"を追加します。

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

もし、"_id"にアンダースコアが不要であれば、引数にfalseを渡します。

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

関連付けはこれを使用して外部キーを推測します。 例えば、has_onehas_manyはこれを使用します。

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

これは、active_support/core_ext/string/inflections.rb内に定義されています。

5.10 変換

5.10.1 to_date、to_time、to_datetime

to_dateto_timeto_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_nowago等を使用する際に、 日付計算を正確に行うために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

これらのメソッドは、上記の使用例として正確な計算を提供する際に、 もしmonthsyears等の結果が使用する前に変換された場合、 それが実際のものでは無い事に注意する必要があります。(翻訳に自信なし)

# 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)           # => []

secondthirdfourthfifthメソッドは、 それに相当する要素を返します。(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>

これを行うために、メソッドはこのペアをループし、値に依存したノードを構築します。 keyvalueのペアには次のようなものを与えることが出来ます。

  • もしvalueがハッシュの場合、key:rootとして再帰的に呼び出されます。
  • もしvalueが配列の場合、key:rootとして再帰的に呼び出し、 その子要素のkeykeyの単数形の: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_keyssymbolize_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_keysdeep_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_keysdeep_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_keysdeep_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_optionsto_options!のメソッドは、 symbolize_keyssymbolize_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を確認してみてください。

参照:なぜ1582年10月4日の次は15日になっているのですか。 - Yahoo!知恵袋

15.1.1 Date.current

Active Supportは、現在のタイムゾーンでの本日の日付けであるDate.currentを定義しています。 これは、もしユーザーのタイムゾーンが定義されていれば、それを信用することを除いて、 Date.todayに似ています。 また、Date.yesterdayDate.tomorrowも定義されており、 そのインスタンスはpast?today?future?が元になっており、 それらの全てはDate.currentに関連しています。

ユーザーのタイムゾーンを信用するメソッドを使用した日付け比較をする場合、 Date.todayではなく、Date.currentを使用している事を確認してください。 デフォルトのDate.todayを使用すると、ユーザーのタイムゾーンがシステムのタイムゾーンと比較して、 未来になるケースがあるかもしれません。 これは、Date.todayDate.yesterdayと同じ日付けになるかもしれない事を意味します。

15.1.2 名前付けされた日付け
15.1.2.1 prev_year, next_year

Ruby 1.9で、prev_yearnext_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_monthnext_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_monthnext_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_weekend_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_weekat_beginning_of_weekに、 end_of_weekat_end_of_weekにエイリアスされています。

15.1.2.5 monday, sunday

mondaysundayメソッドは、 前の月曜日、次の日曜日の日付けをそれぞれ返します。

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

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

d = Date.new(2012, 9, 16)    # => Sun, 16 Sep 2012
d.sunday                     # => Sun, 16 Sep 2012
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_weekprev_weekの両方とも、それをあてにして動作します。

15.1.2.7 beginning_of_month, end_of_month

beginning_of_monthend_of_monthメソッドは、 それぞれ月の始めと終わりの日付けを返します。

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

beginning_of_monthat_beginning_of_monthに、 end_of_monthat_end_of_monthにエイリアスされています。

15.1.2.8 beginning_of_quarter, end_of_quarter

beginning_of_quarterend_of_quarterメソッドは、 レシーバーのカレンダーの四半期の始めと終わりの日付けを返します。

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

beginning_of_quarterat_beginning_of_quarterに、 end_of_quarterat_end_of_quarterにエイリアスされています。

15.1.2.9 beginning_of_year, end_of_year

beginning_of_yearend_of_yearメソッドは、年の始めと終わりの日付けを返します。

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

beginning_of_yearat_beginning_of_yearに、 end_of_yearat_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_agomonths_sinceメソッドは、 月に対して同様に動作します。

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

存在しない日付けの場合は、それに応じて月の最後の日付けを返します。

Date.new(2010, 4, 30).months_ago(2)    # => Sun, 28 Feb 2010
Date.new(2009, 12, 31).months_since(2) # => Sun, 28 Feb 2010
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の呼び出しに転換しています。 例えば、下記はカレンダーの改革による正しい飛び移りを取得しています。

参照:なぜ1582年10月4日の次は15日になっているのですか。 - Yahoo!知恵袋

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_dayat_beginning_of_daymidnightmidnightにエイリアスされています。

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_hourend_of_hourbeginning_of_minuteend_of_minuteは、 Dateでは無くTimeDateTimeのためのものであり、 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)

一方で、advancechangeも定義されており、以降のドキュメントにあるように、 より多くのオプションがサポートされています。

以降のメソッドは、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.yesterdayDateTime.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への呼び出しに転換しています。 例えば、下記はカレンダーの改革による正しい飛び移りを取得しています。

参照:なぜ1582年10月4日の次は15日になっているのですか。 - Yahoo!知恵袋

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またはagoTimeで表現できない時間に移った場合、 代わりにDateTimeオブジェクトが返されます。
17.1.1 Time.current

Active Supportは、カレントのタイムゾーンでの今日を示すTime.currentを定義します。 これは定義されていた場合ユーザーのタイムゾーンを信用することを除いて、Time.nowと似ています。 Time.yesterdayTime.tomorrowも定義されており、 インスタンスはpast?today?future?を元に、 Time.currentの相対になっています。

ユーザーのタイムゾーンを信用するメソッドを使用して時間比較を行う際は、 Time.nowではなく、Time.currentを使用している事を確かめてください。 デフォルトのTime.todayを使用すると、ユーザーのタイムゾーンがシステムのタイムゾーンと比較して、 未来になるケースがあるかもしれません。 これは、Time.nowTime.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_weekall_monthall_quarterall_yearも、 時間範囲の生成の用途を満たす方法を提供してくれます。

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

17.2 Timeコンストラクタ

Active Supportはタイムゾーンが定義されていれば、Time.nowのフォールバックとして、 Time.zone.nowTime.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への呼び出しに転換しています。 例えば、下記はカレンダーの改革による正しい飛び移りを取得しています。

参照:なぜ1582年10月4日の次は15日になっているのですか。 - Yahoo!知恵袋

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に指定されたログレベルより低いログは、そのブロック内で沈黙します。 ログレベルの順は、debuginfoerrorfatalになります。

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は楽観的にPostsHelperの使用を試みます。 ヘルパーモジュールが存在すれば問題ありませんが、 もしその定数数名で例外が発生したのであれば、それは沈黙させる必要があります。 ただし、実際に未知の定数であるためにposts_helper.rbNameErrorを発生させるケースがあるかもしれません。 このケースでは、例外を発生させるべきです。 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はLoadErroris_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内に定義されています。

 Back to top

© 2010 - 2017 STUDIO KINGDOM