こんにちは、鈴木です。
Rails が提供する API の特徴として、引数の指定が柔軟である点が挙げられると思います。
柔軟な引数の指定
例えば、一括代入を許可する属性を指定する attr_accessible メソッドは、以下のように色々な呼び出し方をすることができます。
1 2 3 4 5 6 7 8 |
# 一つだけ指定するパターン. attr_accessible :name # 複数指定するパターン. attr_accessible :name, :email # 複数指定して, さらにハッシュでオプションを指定するパターン. attr_accessible :name, :email, :status, :as => :admin |
引数は一つ指定しても複数指定しても良く、:as => :admin のようにオプションを指定することもできます。
attr_accessible 以外にもルーティング定義で使用する resources や、エラーハンドリングを行なう rescue_from など、多くのメソッドが引数を柔軟に指定することができます。
引数を柔軟に指定することができるメソッドの定義を確認すると、以下のように可変長引数を取るようになっています。
1 2 3 |
def attr_accessible(*args) def resources(*resources, &block) def rescue_from(*klasses, &block) |
使う側からすると引数指定が柔軟であると便利ですが、そのようなメソッドを書いた人は可変長引数を適切に処理するコードをメソッドの先頭で一生懸命書いているのでしょうか。
Array#extract_options!
Array#extract_options! を使用すると attr_accessible などのように柔軟な引数指定ができるメソッドを簡単に書くことができます。
Array#extract_options! は ActiveSupport で以下のように定義されています(activesupport-3.2.9 で確認)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class Hash def extractable_options? instance_of?(Hash) end end class Array def extract_options! if last.is_a?(Hash) && last.extractable_options? pop else {} end end end |
中身は単純で、配列の最後の要素が Hash かつ extractable_options? が true なら、配列の最後の要素(Hash)を取り出す、そうでなければ空の Hash を返します。
つまり、Array#extract_options! は可変長引数からハッシュで指定されたオプションを取り出すためのメソッドです。
動作を確認するために、以下のメソッドを定義し、色々なパターンで引数を渡してみます。
1 2 3 4 5 |
def func(*args) options = args.extract_options! p args p options end |
まずは引数を指定しない場合です。
1 2 3 |
> func [] {} |
引数を一つだけ指定します。
1 2 3 |
> func('Hello') ["Hello"] {} |
引数を複数指定すると、以下のようになります。
1 2 3 |
> func(:one, :two, :three) [:one, :two, :three] {} |
今度はハッシュでオプションも指定してみます。
1 2 3 |
> func(:one, :two, :three, :a => true, :b => true) [:one, :two, :three] {:a=>true, :b=>true} |
ハッシュのオプションだけ指定すると次のようになります。
1 2 3 |
> func(:a => true, :b => true) [] {:a=>true, :b=>true} |
Array#extract_options! を使用すると、「引数はいくつでも指定可能(可変長)、オプションがある時はハッシュで指定する」というパターンのメソッドを簡単に書くことができます。
Array#extract_options! の使用例
具体的な使用例ということで、URL パラメータの一部を遷移先に引き継ぐでご紹介した、take_params を再掲します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
class ApplicationController < ActionController::Base # # take_params が params から取り出すキーのデフォルト. # def default_take_param_keys %w(page per_page) end # # 遷移先に引き継ぐパラメータ. # # ==== 詳細 # 以下のようにリンク時のパラメータとして使用されることを想定している. # # link_to('ユーザ一覧', users_path(take_params)) # link_to('ユーザ詳細', user_path(@user, take_params)) # # ==== 引数 # 呼び出し形式は以下の通り. # # (1) take_params # params から default_take_param_keys が返す名前のパラメータを取り出す. # # (2) take_params(:key1, :key2) # params から :key1, :key2 を取り出す. # # (3) take_params(:additional_key1 => 1, :additional_key2 => 2) # params から default_take_param_keys が返す名前のパラメータを取り出す. # その結果に {:additional_key1 => 1, :additional_key2 => 2} をマージする. # # (4) take_params(:key1, :key2, :additional_key1 => 1, :additional_key2 => 2) # params から :key1, :key2 を取り出す. # その結果に {:additional_key1 => 1, :additional_key2 => 2} をマージする. # # ==== 戻り値 # 遷移先に引き継ぐパラメータを保持する Hash. # def take_params(*param_keys) overwrites = param_keys.extract_options! param_keys = default_take_param_keys if param_keys.blank? params.dup.extract!(*param_keys).update(overwrites) end helper_method :take_params end |
まとめ
Array#extract_options! を使用すると柔軟な引数指定が可能なメソッドを簡単に定義できます。
ただし、柔軟な引数指定ができることと引き替えに、メソッドが可変長引数を取るようになっている点は要注意です。
「def func(*args)」というコードから読み取れることは、あまり多くありません。
メソッドを使う人が困らないように、
- ドキュメンテーションコメントをしっかり記述する。
- 「def func(values, options={})」 で十分な場合に、わざわざ可変長引数にしない。
といったところがポイントだと思います。