🐘

【Rails】delegate メソッドの解説

2024/04/06に公開

概要

Rails の delegate メソッドについて解説します。
初見では「ん?」となるメソッドの1つですが、整理してみると簡単です。

delegate とは

delegate メソッドは Rails の ActiveSupport の機能の1つです。
https://railsguides.jp/active_support_core_extensions.html#メソッドの委譲

delegate には、「委任する」「委譲する」といった、委ねる的な意味があります。

例えば以下のコードがあるとします。

class User < ApplicationRecord
  has_one :profile, dependent: :destroy
end

class Profile < ApplicationRecord
  def aisatsu
    "私の名前は#{name}です。年齢は#{age}歳です。"
  end
end

このとき、 普通に User に紐づく Profile#aisatsu を実行しようとすると以下になります。

User.last.profile.aisatsu
=> "私の名前はTanakaです。年齢は20歳です。"

delegate 使うと、「aisatu メソッドの呼び出し元を、 profile に委譲」できます。

class User < ApplicationRecord
  has_one :profile, dependent: :destroy

  delegate :aisatsu, to: :profile
end

これで、あたかも User インスタンスから aisatu メソッドを呼び出しているかのように使うことができます。

User.last.aisatsu
=> "私の名前はTanakaです。年齢は20歳です。"

複数指定して委譲できます

class User < ApplicationRecord
  has_one :profile, dependent: :destroy

  delegate :aisatsu, :name, :age, to: :profile
end
User.last.aisatsu
=> "私の名前はTanakaです。年齢は20歳です。"

User.last.name
=> "Tanaka"

User.last.age
=> 20

prefix オプションを true にすると、委譲先の接頭辞をメソッド呼び出し時につける必要があります

class User < ApplicationRecord
  has_one :profile, dependent: :destroy

  delegate :aisatsu, to: :profile, prefix: true
end

今回の場合だと、profile が委譲先なので、profile_aisatsu メソッドになります。

User.last.profile_aisatsu
=> "私の名前はTanakaです。年齢は20歳です。"

接頭辞なしだと、undefined methodになります。

> User.last.aisatsu
/usr/local/bundle/gems/activemodel-7.0.8/lib/active_model/attribute_methods.rb:450:in `method_missing': undefined method `aisatsu' for #<User id: 1, created_at: "2024-04-06 01:29:44.679016000 +0000", updated_at: "2024-04-06 01:29:44.679016000 +0000"> (NoMethodError)

prefix の名前はカスタマイズすることもできます。

class User < ApplicationRecord
  has_one :profile, dependent: :destroy

  delegate :aisatsu, to: :profile, prefix: :hogehoge
end
User.first.hogehoge_aisatsu
=> "私の名前はTanakaです。年齢は20歳です。"

allow_nil オプションを true にすると、委譲先のインスタンスが存在しない場合、nil を返します

Uesr インスタンスに紐づく profile が存在しないとき、NoMethodError になります。

User.last.profile
=> nil
 User.last.aisatsu
/myapp/app/models/user.rb:14:in `rescue in aisatsu': User#aisatsu delegated to profile.aisatsu, but profile is nil: #<User id: 2, created_at: "2024-04-06 02:24:49.116208000 +0000", updated_at: "2024-04-06 02:24:49.116208000 +0000"> (Module::DelegationError)
/myapp/app/models/user.rb:14:in `aisatsu': undefined method `aisatsu' for nil:NilClass (NoMethodError)

# ボッチ演算子を使っても同じ
User.last&.aisatsu
/myapp/app/models/user.rb:14:in `rescue in aisatsu': User#aisatsu delegated to profile.aisatsu, but profile is nil: #<User id: 2, created_at: "2024-04-06 02:24:49.116208000 +0000", updated_at: "2024-04-06 02:24:49.116208000 +0000"> (Module::DelegationError)
/myapp/app/models/user.rb:14:in `aisatsu': undefined method `aisatsu' for nil:NilClass (NoMethodError)

allow_nil: true とすると、委譲先のインスタンスが存在しないときに nil を返します。

class User < ApplicationRecord
  has_one :profile, dependent: :destroy

  delegate :aisatsu, to: :profile, allow_nil: true
end
User.last.profile
=> nil
# 委譲先が存在しないと、NomethodError とならずに nil が返る。
User.last.aisatsu
=> nil

おまけ

今回は親→子への委譲について書きましたが、子→親への委譲もできます。

class User < ApplicationRecord
  def aisatsu
    'こんにちは'
  end
end

class Profile < ApplicationRecord
  belongs_to :user

  delegate :aisatsu, to: :user
end
Profile.last.aisatsu
=> "こんにちは"

Discussion