みなさん、こんにちは。 ウェブ・サーバーサイドを担当しています、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エンジニア/フロントエンドエンジニアを大募集しています! みなさまのご応募お待ちしております!