Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Rails tips: Policy Objectパターンでリファクタリング(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。

Rails tips: Policy Objectパターンでリファクタリング(翻訳)

Policy Objectは操作の分離に用いられる素朴なRubyオブジェクトのことです。個人的にこのパターンが好きですが、オブジェクトにPolicy Objectとしてふさわしい名前をつけるために従っておくべきルールがいくつかあります。

Policy Objectの規則

  1. メソッド名の末尾は常に?にする
  2. メソッドはtruefalseのいずれかだけを返す
  3. 渡された属性は変更しない
  4. コードはシンプルな読み出しロジックだけを行う: データベース呼び出しなどは行わない

デモ

サンプルのクラスを作ってみましょう。後ほどPolicy Objectをここに実装します。

class UserService
  def initialize(user)
    @user = user
  end

  def name
    if user.full_name.blank? && user.email.present?
      user.email
    else
      user.full_name
    end
  end

  def account_name
    if user.sign_in_count > 0 && user.role == "admin"
      "Administrator"
    else
      "User"
    end
  end

  private
  attr_reader :user
end

このクラスは「読み取り」と「チェック」という2つの操作に簡単に分けられます。チェック部分はそのままPolicy Objectとして完璧なコードです。Userオブジェクトに対して操作を行っているので、2つのメソッドのためのPolicy Objectを1つ作成できます。

class UserPolicy
  def initialize(user)
    @user = user
  end

  def administrator_account_name?
    user.sign_in_count > 0 && user.role == "admin"
  end

  def use_email_as_name?
    user.full_name.blank? && user.email.present?
  end

  private
  attr_reader :user
end

UserServiceサービス(Service Object)にUserPolicyを実装すると、コードがとてもスッキリしました。

class UserService
  def initialize(user)
    @user = user
  end

  def name
    user_policy.use_email_as_name? ? user.email : user.full_name
  end

  def account_name
    user_policy.administrator_account_name? ? "Administrator" : "User"
  end

  private
  attr_reader :user

  def user_policy
    @_user_policy ||= UserPolicy.new(user)
  end
end

読み取り部分からポリシーのロジックが分離され、UserPolicyをシンプルな方法でスタブできるようになったのでUserServiceクラスがテストしやすくなりました。Rails tips: ロジックをわかりやすい変数に置き換えるリファクタリング(翻訳)【要リンク変更】をこのPolicy Objectに適用することもできます。ロジックを変数に移す代わりに、意味のある名前を持つもっと小さなメソッドにロジックを直接移してみましょう。

class UserPolicy
  def initialize(user)
    @user = user
  end

  def administrator_account_name?
    user_signed_in? && user_is_administrator?
  end

  def use_email_as_name?
    user_does_not_have_full_name? && user_has_email?
  end

  private
  attr_reader :user

  def user_does_not_have_full_name?
    user.full_name.blank?
  end

  def user_has_email?
    user.email.present?
  end

  def user_signed_in?
    user.sign_in_count > 0
  end

  def user_is_administrator?
    user.role == "admin"
  end
end

クラスのコード量は増えたものの、コードが自ら語るようになりました。

Railsでお困りの方にお知らせ

知りたいことがありましたら、twitter または連絡用フォームにてお気軽にお問い合わせください。

RSpec & TDDの電子書籍を無料でダウンロード

もっと稼ぎたい方や会社をさらに発展させたい方へ: テスティングのスキルの重要性にお気づきでしょうか?テストを正しく書き始めることが、唯一のファーストステップです。無料でダウンロードいただける私の書籍『RSpec & Test Driven Developmentの無料ebook』をどうぞお役立てください。

関連記事

肥大化したActiveRecordモデルをリファクタリングする7つの方法(翻訳)

Railsで重要なパターンpart 1: Service Object(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。