概要
原著者の許諾を得て翻訳・公開いたします。
- 英語記事: Removing magic with magic | Arkency Blog
- 原文公開日: 2018/06/21
- 著者: Rafał Łasocha
- サイト: Arkency Blog
Ruby: マジックを取り除くマジック(翻訳)
gemの中には、inherited_resourcesのように自動的に定義を提供することでコード量の削減に役に立つものがあります。しかし、中にはマジックを繰り出すgemもあるので、そうしたgemを削除したいと考えました。
この「マジック」はメソッドの定義、ここではコントローラのアクションを、gemが提供する関数を一切呼び出さずに定義することを指します。そうした暗黙の振る舞いがあると、レガシーコードと戦うのがつらくなります。こうした機能やモデルを削除してよいかどうかを調べるのは大変な作業です。同様に、機能を正しく追加するのも難しい作業です。私たちのプログラム内でのユースケースや依存関係を見落としがちだからです。見落としがあると悲惨なバグが発生してコードが止まってしまうかもしれません。
そういうわけで、私たちのアプリからこのinherited_resources gemを何としても削除したかったのです。明示的な書き方といえばこれしかありませんでした。
class Admin::BaseController < InheritedResources::Base
...
end
しかもBaseController
は管理画面のすべてのコントローラの親クラスなので、暗黙の動作がどのコントローラに影響しているかわかったものではありません。これをきれいに取り除くのは骨です。最終的に利用されている場所を特定できない状態で、どうすればこういうgemを安全に取り除けるのでしょうか。gemをえいやっと取り除いてproductionがメラメラと焼け落ちるのを眺めるという方法もないわけではありませんが、勇気はそういうことに使うものではありません。目には目を、マジックと戦うにはマジックです。
まさにこの瞬間、Rubyがオープンクラスであることのありがたみを実感できます。私は、InheritedResources
gemを動的にextendして、gemが使われた場所を通知する振る舞いを追加する、以下のコードを書きました。
module InheritedResourcesRemoval
InheritedResourcesUsed = Class.new(StandardError)
def index(options = {}, &block)
Honeybadger.notify(InheritedResourcesUsed.new("Inherited resources used at controller `#{params[:controller]}` and action `#{params[:action]}`"))
super(options, &block)
end
def show(options = {}, &block)
Honeybadger.notify(InheritedResourcesUsed.new("Inherited resources used at controller `#{params[:controller]}` and action `#{params[:action]}`"))
super(options, &block)
end
def new(options = {}, &block)
Honeybadger.notify(InheritedResourcesUsed.new("Inherited resources used at controller `#{params[:controller]}` and action `#{params[:action]}`"))
super(options, &block)
end
def edit(options = {}, &block)
Honeybadger.notify(InheritedResourcesUsed.new("Inherited resources used at controller `#{params[:controller]}` and action `#{params[:action]}`"))
super(options, &block)
end
def create(options = {}, &block)
Honeybadger.notify(InheritedResourcesUsed.new("Inherited resources used at controller `#{params[:controller]}` and action `#{params[:action]}`"))
super(options, &block)
end
def update(options = {}, &block)
Honeybadger.notify(InheritedResourcesUsed.new("Inherited resources used at controller `#{params[:controller]}` and action `#{params[:action]}`"))
super(options, &block)
end
def destroy(options = {}, &block)
Honeybadger.notify(InheritedResourcesUsed.new("Inherited resources used at controller `#{params[:controller]}` and action `#{params[:action]}`"))
super(options, &block)
end
end
後は、特定のモジュールをひとつオープンしてこのハックをprepend
するだけです。
module InheritedResources
module Actions
prepend(::InheritedResourcesRemoval)
end
end
ご覧のとおり、メソッドのどれかが利用されるたびにHoneybadgerに通知が飛び、gemが使われたコントローラとアクションを正確に知らせてくれます。parser gemで自動書き換えすることで必要なコードを追加する手もありますが、今回の場合gemを使っていたアクションは数個しかなかったので、そこまでする意味はありませんでした。私はコントローラのコードを数行手書きし、対応するビューを変更して目障りな@resource
変数名を使わないようにしただけで済みました。
このコードをproductionで数週間動かしてからコードを取り除き、めでたくgemを削除できたのでした😊。
詳しく知りたい方へ
本記事をお楽しみいただけましたら、ぜひ私たちのニュースレターの購読をお願いします。私たちが日々追求している、開発者を驚かさないメンテ可能なRailsアプリの構築方法を皆さんにお届けいたします。
以下の記事も参考にどうぞ。
- Rails: Event Storeライブラリの非推奨APIをparser gemで書き直す(翻訳)
-
Using Ruby parser and the AST tree to find deprecated syntax ---
grep
で歯が立たないときに