RubyonRailsで開発していると様々なgemを組み合わせてプロダクトを作っていきます。
しかし、Railsのレールに乗って開発しているときはいいんですが、ちょっと変わったことや自分達の独自実装を加えたいときに利用しているgemをカスタマイズしたいことがたくさん出ていきます。
ただ、gemをfolkしてカスタマイズしたりすると将来のアップデート時に辛くなることが目に見えているので今回はそれをせずにgemをカスタマイズしていく方法を紹介します。
カスタマイズしたいClass, Module等をコピーする。
一番簡単な方法はgemの中にあるカスタマイズしたいClassやModuleをコピーして再定義してしまうことです。
今回はdoorkeeperというgemのDoorkeeper::Request::Passwordというクラスをカスタマイズしていきます。ちなみにバージョンは1.4を使っています。
Githubのdoorkeeperのレポジトリで該当クラスが定義されているファイルがlib/doorkeeper/request/password.rbにあるので、自分のrailsアプリケーションでも同一ディレクトリにファイルを作って内容をコピーして、カスタマイズしていきます。
$ cd rails-app
$ mkdir -p lib/doorkeeper/request/
$ vi lib/doorkeeper/request/password.rb
module Doorkeeper
module Request
class Password
def self.build(server)
new(server.credentials, server.resource_owner, server)
end
attr_accessor :credentials, :resource_owner, :server
def initialize(credentials, resource_owner, server)
@credentials, @resource_owner, @server = credentials, resource_owner, server
end
def request
@request ||= OAuth::PasswordAccessTokenRequest.new(Doorkeeper.configuration, credentials, resource_owner, server.parameters)
end
def authorize
logger.debug('authorizing') # カスマイズしてみる
request.authorize
end
end
end
end
ただ、この方法だとカスタマイズしたい箇所はメソッド一つなのに、そのファイルにあるすべての内容をコピーしないといけないのであまり良いとは言えません。
オープンクラスを使う
オープンクラスとは、すでに定義されているクラスを再定義することでメソッドの追加、上書きなどが簡単に出来る機能です。
以下にオープンクラスの機能を使ってNumericクラスを拡張する例を載せておきます。
# クラスの拡張前
1.count_up
=> NoMethodError: undefined method 'count_up' for 1:Fixnum
# count_upというメソッドを追加する
class Numeric
def count_up
self + 1
end
end
# 追加したcount_upが使える
1.count_up
=> 2
10.count_up
=> 11
これならば既存のクラスの他の機能には干渉せずに、カスタマイズしたいメソッドの追加や拡張のみを行えます。
(このようにオリジナルのコードを変更せずに拡張、変更することをモンキーパッチと言います。)
この機能を使って、先ほどと同じようなカスタマイズをしていきます。
Railsではデフォルトでconfig/initializers以下の.rbファイルが起動時に自動で読み込まれるので、ここにmonkey_patchesディレクトリを作成し、monkey_patchesディレクトリ以下に拡張用のrubyファイルを作成します。
$ mkdir lib/initializers/monkey_patches
$ vi lib/initializers/monkey_patches/doorkeeper.rb
module Doorkeeper
module Request
class Password
def authorize
logger.debug('authorizing') # カスマイズしてみる
request.authorize
end
end
end
end
これでカスタマイズしたいメソッドのみを簡単に拡張することが出来ました。
Refinementsを使う。
先ほどオープンクラスで拡張したクラスですが、ひとつ問題があるのはその影響範囲です。
たとえば複数人でのチーム開発を行っているときにみんなが使っているgemの動作をオープンクラスで勝手に変更してしまうと、アプリケーション全体でそのメソッドの動作が変わってしまうのでいままで動いていたのもが動かなくなってしまったりしてトラブルの元になりかねません。
そこでRefinementsというruby2.0で実験導入、ruby2.1で正式採用された機能を使うとクラスの拡張範囲を限定することが出来ます。
では、先ほどのモンキーパッチをRefinementsを使ったコードに変更します。
module DoorkeeperRequestCustomPassword
refine Doorkeeper::Request::Password do
def authorize
logger.debug('authorizing') # カスマイズしてみる
request.authorize
end
end
end
そして、この拡張を適応したい箇所に using モジュール名 と記述するとそのファイル上でのみ拡張が利用できるようになります。
using DoorkeeperRequestCustomPassword # 拡張の使用を宣言する
module Doorkeeper
class CustomTokensController < Doorkeeper::TokensController
def create
response = strategy.authorize
self.headers.merge! response.headers
self.response_body = response.body
self.status = response.status
rescue Errors::DoorkeeperError => e
handle_token_exception e
end
...
end
end
終わりに
この方法を知るまではgem内のコードを確認するときにコードを読みながらイメージで動きを追っていたのですが、これらの方法で簡単にログ出力を差し込むことが出来るのでデバックも簡単になります。
結構、その恩恵がかなりでかい!!