delegateとは?
delegateとは「移譲する、委任する」という意味です。簡単にいうと"give"だと考えて良いでしょう。
delegateは、railsのメソッドの1つで、オブジェクトの特定のメソッド呼び出しを別のオブジェクトに委譲するために使用されます。
これにより、
- コードの重複を避けることができる
- クラス間の依存関係を減らすことができる
このあたりは後ほど説明するとして、まずは書き方です。
delegate :method_name, to: :associated_object
ポイントは、以下のように書くと、
foobar.hogeと書かないといけないところをhogeだけ書けば良くなるという点です。
to:で書いた部分を省略できると覚えておきましょう。右側は省略!!
delegate :hoge, to: :foobar
コードの見通しも良くなったり、書く手間が省けるので良いですね。
@instance.foobar.hoge と書いていたのを、 @instance.hogeで呼び出せるようになります。
「foobarにhogeを移譲するが、foobarは書かなくてもよい」ということです。
つまり、foobar.hogeとするんですが、foobarは不要になります。
アソシエーション先がnilの場合
この場合は、optionにallow_nil: trueをつけると良いです。
例えば、UserとPostが一対多で関連づいているとします。
そして、以下のように書きます。
class Post < ApplicationRecord
belongs_to :user
delegate :name, to: :user
end
この場合、以下のように投稿に関連づいているユーザー名を取得できます。
post.name
# post.user.name
しかし、仮にその値がnilだとしましょう。(テーブル設計が正しければそんなことは起こらないはずですが、仮に...)
すると、NoMethodErrorが発生してしまいます。
そこでallow_nilオプションをつければ、これを回避できます。
class Post < ApplicationRecord
belongs_to :user
delegate :name, to: :user, allow_nil: true
end
実際に、実行してみるとNoMethodErrorではなく、nilが返ってきます。
ぼっち演算子と同じですね。
post.name
=> nil
solidusで応用の確認
solidusというECサイトを構築するためのオープンソースフレームワークがあります。
某プログラミングスクールで教材として使用されていることでも有名ですね。
その中の記述に以下の部分があります。難しそうですが、1つずつ紐解いていけば大丈夫です。
has_one :master,
-> { where(is_master: true).with_deleted },
inverse_of: :product,
class_name: 'Spree::Variant',
autosave: true
...
def find_or_build_master
master || build_master
end
...
MASTER_ATTRIBUTES = [
:cost_currency,
:cost_price,
:depth,
:height,
:price,
:sku,
:weight,
:width,
]
MASTER_ATTRIBUTES.each do |attr|
->delegate :"#{attr}", :"#{attr}=", to: :find_or_build_master
end
->delegate :amount_in,
:display_amount,
:display_price,
:has_default_price?,
:images,
:price_for,
:price_in,
:rebuild_vat_prices=,
to: :find_or_build_master
alias_method :master_images, :images
この中に、今回のdelegateを使用している箇所があります。
def find_or_build_master
master || build_master
end
...
#①
MASTER_ATTRIBUTES.each do |attr|
->delegate :"#{attr}", :"#{attr}=", to: :find_or_build_master
end
#②
->delegate :amount_in,
:display_amount,
:display_price,
:has_default_price?,
:images,
:price_for,
:price_in,
:rebuild_vat_prices=,
to: :find_or_build_master
①の説明
①ですが、展開すると以下のような形になります。
delegate :cost_currency, :cost_currency=, to: :find_or_build_master
delegate :cost_price, :cost_price=, to: :find_or_build_master
delegate :depth, :depth=, to: :find_or_build_master
delegate :height, :height=, to: :find_or_build_master
delegate :price, :price=, to: :find_or_build_master
delegate :sku, :sku=, to: :find_or_build_master
delegate :weight, :weight=, to: :find_or_build_master
delegate :width, :width=, to: :find_or_build_master
これは少し応用の形になっていますが、find_or_build_masterオブジェクトに対して、2つのメソッド(ゲッターとセッター)が移譲されています。つまり、この2つを移譲することで、値を取得もできるし、更新もできるようになるわけです。実はメソッドは複数個取ることができるんですね...
したがって、
product.cost_currencyで値を取得もできるし、
product.cost_currency=(1000)で値を更新もできるということです。(product.cost_currency= 1000でも良いです。)
本来であれば、
product.find_or_build_master.cost_currency
と書く必要があるので、移譲することでコードがかなりスッキリしますね。
ちなみに、find_or_build_masterは上の方に定義されています。
masterもしくはbuild_masterです。masterがなければbuild_masterを探します。
これが何を表すかは、他も見ないと分からないので割愛しますが、delegateを理解するのが目的なので今回は必要ないです。
②の説明
②については、①が分かっていれば難しくないでしょう。
これも展開してみると、以下のようになります。最後だけセッターになっていますね。
delegate :amount_in, to: :find_or_build_master
delegate :display_amount, to: :find_or_build_master
delegate :display_price, to: :find_or_build_master
delegate :has_default_price?, to: :find_or_build_master
delegate :images, to: :find_or_build_master
delegate :price_for, to: :find_or_build_master
delegate :price_in, to: :find_or_build_master
delegate :rebuild_vat_prices=, to: :find_or_build_master
まとめ
- delegateはオブジェクトに対して、メソッドを移譲するメソッド
- toに与えたオブジェクトは省略できる。
- メソッドは複数個設定することができる。
一度理解してしまえば、そこまで難しくないですね。
ぜひ機会があれば積極的に使ってみましょう。