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(翻訳)

デザインも頼めるシステム開発会社をお探しならBPS株式会社までどうぞ 開発エンジニア積極採用中です! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

hachi8833

Twitter: @hachi8833、GitHub: @hachi8833 コボラー、ITコンサル、ローカライズ業界、Rails開発を経てTechRachoの編集・記事作成を担当。 これまでにRuby on Rails チュートリアル第2版の監修および半分程度を翻訳、Railsガイドの初期翻訳ではほぼすべてを翻訳。その後も折に触れて更新翻訳中。 かと思うと、正規表現の粋を尽くした日本語エラーチェックサービス enno.jpを運営。 実は最近Go言語が好きで、Goで書かれたRubyライクなGoby言語のメンテナーでもある。 仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。

hachi8833の書いた記事

夏のTechRachoフェア2019

週刊Railsウォッチ

インフラ

ActiveSupport探訪シリーズ