Money Forward Developers Blog

株式会社マネーフォワード公式開発者向けブログです。技術や開発手法、イベント登壇などを発信します。サービスに関するご質問は、各サービス窓口までご連絡ください。

20230215130734

Railsでリファクタリングにオススメな gem 5選

みなさん、こんにちは。 ウェブ・サーバーサイドを担当しています、Railsエンジニアの黒田です。

マネーフォワードも早いもので、サービスインしてから2年以上が経過しました。 サービスをご愛顧してくださっている皆様には、心から感謝しております。  

さて、今回のエンジニアブログは「リファクタリング」についてです。

マネーフォワードのように、ユーザーファースト&デリバリー優先で爆速開発を進めていると、サービスとしてはイケてても、コード的にイケてるとは言い難い部分が発生してしまいがちです。

「思いやりのないコード」「可読性が悪いコード」「必要以上に複雑なコード」は、バグ発生率を高め、開発スピードを低下させ、何よりエンジニアの気分を憂鬱にさせてしまいます。。。

マネーフォワードでは継続的かつ積極的にリファクタリングの時間を創る取組みをしていますが、そのなかで今回はRailsのリファクタリングでとても便利に活躍してくれているGem 5選を紹介したいと思います。  

1. draper

まず1つ目のgemはdraperというgemです。

このgemは、肥大化するhelper、view層のリファクタリングに役に立ちます。 例えば、以下のようなhelperメソッドがあったとします。

app/helpers/users_helper.rb

def premium_price_message(user)
  if user.premium?
    "料金は#{numer_to_currency(user.premium_price)}です。"  
  else
    "無料です。"
  end
end

このhelperメソッドは引数にモデルオブジェクトを渡している、リファクタしたくなる例です。

モデルのインスタンスメソッドにすれば良い気もしますが、モデルにビュー周りのメソッドを挿したり、モデル内でヘルパーメソッドを呼ぶのも適切ではありません。 こういった、モデル層、ビュー層どっちつかずのメソッドをどうにかしたい時に活躍してくれるのがdraperです。

draperの Draper::Decorator 継承クラスは、モデルをラップし、helperメソッドを h. でコールできるようになっており、上記のヘルパーに定義していたメソッドは下記のように置き換えることができます。

app/decorators/user_decorator.rb

class UserDecorator < Draper::Decorator
  delegate_all
  
  def premium_price_message
    if premium?
      "料金は#{h.number_to_currencry(premium_price)}です。"   
    else
      "無料です"
    end
  end
end

Decoratorオブジェクトへの変換は以下のようにします。

app/controllers/user_controller.rb

def show
  @user = User.find(params[:id]).decorate  
end

元のモデルオブジェクトは、Decoratorオブジェクトでラップされており、変換後も既存のコードについては特に修正なく使用できるのがとても良いです。  

2. cells

こちらはRailsの部分テンプレートとロジックを分離してくれるgemです。

例えばユーザー通知(Notification)を様々な画面で表示するといったケースでは、ユーザー通知を表示する画面のコントローラーで、before_actionを使って、ユーザー通知データをインスタンス変数で準備するといったことを行います。

app/controllers/hoges_controller.rb

  before_action :set_notifications

  def set_notifications
    @notifications = Notification.all
  end

そしてビュー側で、部分テンプレートを呼び出すような実装にすることが多いと思います。

app/controllers/views/hoges/show.html.erb

= render 'notifications', notifications: @notifications

これでも問題ないと思うのですが、before_actionやインスタンス変数の数が増えてくると、だんだんと見通し悪くなってくるという問題があります。

こういう場合にcellsを利用して部分テンプレートをコントローラ部分も含めて分離すると便利です。 上記の例をcellsを使うと、まずビュー部分は以下のように置き換えられます。

app/controllers/views/hoges/show.html.haml

= render_cell :notificaton, :index

@notifications の受け渡しがなくなっています。 cellsでは、app/cells 配下にコントローラー相当のクラスとビューテンプレートを配置することになります。 以下のような感じです。

app/cells/notification_cell.rb

class NotificationCell < Cell::Rails
  def index
    @notifications = Notification.all
    
    render
  end
end

ビューテンプレートは、以下のような感じですね。

app/cells/notification/index.html.haml

%ul
  - @notifications.each do |notification|
    %li
      = notification.message

テンプレートの使い回しが手軽になります。  

3. enumerize

この gem で列挙型の定数を置き換えると便利です。

例えば、マネーフォワードでは以前、Userモデルの sex カラムについて、 「1:男性」「2:女性」の定義を下記のようにモデルに記述していました。

app/models/user.rb

class User < ActiveRecord::Base
  SEX = {
    man: 1,
    woman: 2
  }
end

これを、enumerizeを使って

app/models/user.rb

class User < ActiveRecord::Base
  extend Enumerize
  
  enumerize :sex, in: { man: 1, woman: 2 }, predicates: true, scope: true
end

上記でリプレースしました。enumerizeが素晴らしいのは、色々便利なメソッドが使えるようになることです。 (predicates: trueや scope: true といったオプションをつけると便利なメソッドが追加で生えます)

before, afterで比較すると以下のようになります。

before after
user.sex == User::SEX[:man] user.man?
User.where(sex: User::SEX[:man]) User.with_sex(:man)
- user.sex.text # man → 男 i18n変換

定数周りの悩みを大抵解消してくれます。  

4. ActiveHash

このgemは、ActiveModel等を使っていたモデルクラスで、ActiveRecord like な belongs_to とか has_many associationが使えるといいのになぁと思っていたモデルクラスのリプレースに最適です。

また、ActiveRecordを使っていたマスタテーブル等の静的データのモデルの置き換えにも良いです。

例えば、マネーフォワードでは都道府県マスタテーブルを以前は持っていたのですが、こちらをActiveHashに置き換えました。

ただし、ActiveRecordからの置き換えでは注意が必要で、has_many :through 等、複雑なassociationや join, include を使うケースでは対応できないので、置き換えるモデルは慎重に検証する必要ありです。  

5. squeel

最後に紹介するsqueelは、ActiveRecordのクエリが難解になったり、長くなって困った時に役立つgemです。

使い方はgithub参照ですが、難解なクエリが驚くほどすっきりします。

難点は、rubocopがsqueelのDSLを文法エラー判定しまくることです。 そのため、マネーフォワードではプロジェクト毎に採用、不採用が割れている gem でもあります。  

最後に

マネーフォワードでは、爆速開発もリファクタリングも大好き(自薦他薦問わず)な、Railsエンジニア/フロントエンドエンジニアを大募集しています! みなさまのご応募お待ちしております!

マネーフォワード採用サイト https://recruit.moneyforward.com/

Wantedly https://www.wantedly.com/companies/moneyforward