Rails のコンローラのフィルタのようなものを実装してみる
Rails のコントローラで利用できるフィルターのような機能を Rails ではないアプリに組み込みたく、実装してみることにした。
ちなみに、Rails のコントローラ内で利用できるフィルタには、以下のものがある。
before_filter: アクションの前に実行after_filter: アクションの後に実行around_filter: アクションの前後で実行
あまりよい例を出せないのだが、現在以下のクラスが既に実装されているものとする。
1 2 3 4 5 6 7 8 9 |
class Greeting def initialize(out = STDOUT) @out = out end def hello @out.puts 'hello!' end end |
Rails のフィルターのように上記の hello メソッドの前後で実行するための機能をClass Macroで追加できるようにしたい。
実装イメージとしては、以下のような感じ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class Greeting ... before_filter :before_greeting after_filter :after_greeting ... private def before_greeting @out.puts 'Ya!' end def after_greeting @out.puts 'bye!' end end Greeting.new.hello # >> Ya! # 「hello!」の前に表示される # >> hello! # >> bye! # 「hello!」の後に表示される |
通したいテストは以下のような感じ。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
require 'test/unit' class GreetingTest < Test::Unit::TestCase def setup @out = StringIO.new @greeting = Greeting.new(@out) end def test_running_filters @greeting.hello assert_equal ['Ya!', 'hello!', 'bye!'], @out.string.split("\n") end end |
やることは、2つ。
- 既に実装されている
Greeting#helloメソッドには手を入れないで機能を追加する。 - 前後に呼び出す機能をコールバックとして定義し、実行できるようにする。
全て1から実装するのはシンドイので。。なにかと便利な ActiveSupport を使う。
ActiveSupport には、ActiveSupport::Callbacks というモジュールがあり、Rails のコントローラのフィルタでもこのモジュールが利用されている。拝借する。
まずは、前後に挟む機能を定義するためのフィルタの準備。モジュールで準備する。
1 2 3 4 5 6 7 8 9 10 11 12 |
require 'rubygems' require 'active_support' module Filters def self.included(base) base.class_eval do include ActiveSupport::Callbacks define_callbacks :before_filter, :after_filter end end end |
このモジュールは、利用するクラスでインクルードされると、
ActiveSupport::Callbacksモジュールをインクルードする。ActiveSupport::Callbacksモジュールで定義されているdefine_callbacksメソッドで:before_filter、:after_filterをコールバックメソッドとして定義する。
ということを行う。
これで、Greeting クラスで Filters モジュールをインクルードすれば、before_filter、after_filter でコールバックメソッドが定義できるようになる。
後は、Greeting クラスの中で、それぞれのコールバックされるメソッドを記述し、コールバックメソッドの呼び出しを行なわないといけない。
Open Class で Greeting クラスを再定義し、Around Alias で既にある hello メソッドに機能を追加する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
class Greeting include Filters before_filter :before_greeting after_filter :after_greeting def hello_with_filters run_callbacks(:before_filter) hello_without_filters run_callbacks(:after_filter) end alias_method :hello_without_filters, :hello alias_method :hello, :hello_with_filters private def before_greeting @out.puts 'Ya!' end def after_greeting @out.puts 'bye!' end end |
最終的に1つのファイルに以下のように記述している。
filter_example1.rb:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# 既存のクラス
class Greeting
def initialize(out = STDOUT)
@out = out
end
def hello
@out.puts 'hello!'
end
end
require 'rubygems'
require 'active_support'
module Filters
def self.included(base)
base.class_eval do
include ActiveSupport::Callbacks
define_callbacks :before_filter, :after_filter
end
end
end
# Greetin クラスを再定義
class Greeting
include Filters
before_filter :before_greeting
after_filter :after_greeting
def hello_with_filters
run_callbacks(:before_filter)
hello_without_filters
run_callbacks(:after_filter)
end
# |
テストを実行。
1 2 3 4 5 6 7 |
$ ruby filter_example1.rb Loaded suite filter_example1 Started . Finished in 0.000389 seconds. 1 tests, 1 assertions, 0 failures, 0 errors |
OK だ。
このくらいのことであれば、AspectR を使うこともできるが、より自由な組み込みができそう。