はじめに
Object#try
に代わる選択肢として下の記事で紹介されていた、ActiveSupport#delegate
について調べた内容をまとめます。
定義
前提
- UserモデルとProfileモデルが
has_one
で関連付けられている(userごとにprofileが一つ存在する) - リンクカラム(
user_id
)はProfileテーブルに存在する -
user.pofile.name
のようにProfileテーブルのユーザー名を取得したい
class User < ApplicationRecord
has_one :profile
end
# delegateマクロを使わない場合、nameメソッドを追加するとuser.nameでアクセスする
def name
profile.name
end
定義方法
- 上記をdelegateを使って下記のように書き換える
class User < ApplicationRecord
has_one :profile
# delegateマクロを使うとnameメソッドを下記のように書き換えができ、user.nameでアクセスする
delegate :name, to: :profile
end
補足
:allow_nil
オプション
- 対象がnilの場合、
NoMethodError
が発生する -
allow_nil: true
を渡すと例外の代わりにnilが返る
delegate :name, to: :profile, allow_nil: true
prefix
オブション
- 生成されたメソッドの名前にプレフィックスを追加する
# profile_nameが生成される。上記の例ではuser.profile_nameでアクセスする
delegate :name, to: :profile, prefix: true
# avatar_nameが生成される。上記の例ではuser.avatar_nameでアクセスする
delegate: :name, to: :profile, prefix: avatar
:private
オプション
- デフォルトではpublicメソッドなので
private: true
を渡してメソッドのスコープを変更する
delegate :name, to: :profile, private: true
複数のメソッドがまとめて指定できる
delegate :name, :age, :address, :twitter, to: :profile
delegate_missing_to
- オブジェクト内にある呼び出し可能なもの(インスタンス変数、メソッド、定数など)を対象に、スコープがpublicなものを委譲する
class User < ApplicationRecord
has_one :profile
delegate_missing_to :profile
end
# user.name、user.ageなどでアクセスできるようになる
おわりに
-
allow_nil: true
とするとNoMethodErrorの例外が発生せず、Object#tryと結果が同じになります。しかし、より癒着が弱く、コードが明確という点から、ActiveSupport#delegateの方が適切と言えるようです。 -
user.profile.name
やuser.profile.try(:name)
はオブジェクト間の癒着が強く好ましくないので、delegateマクロを使わない場合は下記のように書き換える方が正しいようです。
class User
def profile_name
profile.try(:name)
end
end
class User
def profile_name
return nil if profile.nil? # nilになるのが正しい場合はガード節で早期にnilを返す
profile.name
end
end
参考