Railsで利用するgem達を俺色に染めてやる！

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
````

````ruby: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クラスを拡張する例を載せておきます。

````ruby
# クラスの拡張前
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
````

````ruby: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を使ったコードに変更します。

````ruby:lib/initializers/monkey_patches/doorkeeper.rb

module DoorkeeperRequestCustomPassword
  refine Doorkeeper::Request::Password do
    def authorize
      logger.debug('authorizing') # カスマイズしてみる
      request.authorize
    end
  end
end

````

そして、この拡張を適応したい箇所に **using モジュール名** と記述するとそのファイル上でのみ拡張が利用できるようになります。

````ruby:app/controllers/doorkeeper/custom_tokens_controller
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内のコードを確認するときにコードを読みながらイメージで動きを追っていたのですが、これらの方法で簡単にログ出力を差し込むことが出来るのでデバックも簡単になります。

結構、その恩恵がかなりでかい！！