kinoppyd.dev

blog

products

accounts & contact

Mobbの機能拡張を実現するhelpersとregister

posted at 2018-12-05 01:29:42 +0900 by kinoppyd

この記事は Mobb/Repp Advent Calendar の五日目です

Mobbの機能拡張

これまで見てきたように、MobbにはSinatraとほぼ同じ仕組みが多数存在します。そして今日紹介するhelpers/registerメソッドも、Sinatra由来の機能です。これら2つのメソッドは、Sinatraと全く同じコードが書かれているので、挙動も全く同じです。

helpers

helpersメソッドは、トップレベルモード(require ‘mobb’をしてそのままロジックを書き始めるケース)では暗黙的に作成されるMobbのアプリケーションクラスを、モジュラーモード(require ‘mobb/base’ して自分でクラスを定義するケース)ではhelpersが呼び出されたクラスをそれぞれコンテキストとして、渡されたブロックをclass_evalで実行します。ブロックではなくモジュールを渡した場合は、そのモジュールがincludeされます。

require 'mobb'

def hoge
  'hoge'
end

on 'hello' do
  hoge
end
require 'mobb/base'

class Bot < Mobb::Base
  helpers do
    def hoge
      'hoge'
    end
  end

  on 'hello' do
    hoge
  end
end

Bot.run!

モジュラーモードでは、そもそもhelpersの中で定義しようがクラスの中で定義しようが、クラスのインスタンスメソッドとして定義されるのであまり違いはありません。ですが、トップレベルモードの場合、Rubyのmainクラスで定義したメソッドはObjectのプライベートメソッドとして定義されるので、いろいろなものを汚染しかねません。そのため、暗黙的に作られるMobbアプリケーションのクラスに、helpersを使って直接定義を行います。

https://docs.ruby-lang.org/ja/latest/class/main.html

やっていることは結局の所include呼び出しとあまり変わりませんが、ブロックを渡せるところが軽量でいいと思います。

helpersメソッドの中身は、Mobb::Baseを継承したアプリケーションを拡張しています。

register

helpersがclass_evalを行うのに対して、registerはclassにextendをおこないます。もしブロックが渡された場合は、そのブロックの内容で即時的にModuleオブジェクトを作成し、それをextendします。

includeとextendの違いはいくつかありますが、その一つにincludeはクラスを拡張するのに対し、extendはクラスオブジェクトを拡張する、つまりクラスメソッドを定義するという点が挙げられます。

たとえば、次のコードは実際にhelloを受け取ったときに失敗します。

require 'mobb/base'

class Bot < Mobb::Base
 extends do
    def hoge
      'hoge'
    end
  end

  on 'hello' do
    hoge # これは失敗する
  end
end

Bot.run!

なぜならば、helloのブロックが実行されるのは、Botインスタンスのコンテキストですが、registerメソッドが作成するのはBotのクラスメソッドだからです。インスタンスからは、クラスメソッドを参照することは通常できません。正しく動かす場合には、settingsを使ってアプリケーションクラスに対するアクセスが必要です。

require 'mobb/base'

class Bot < Mobb::Base
  register do
    def hoge
      'hoge'
    end
  end

  on 'hello' do
    settings.hoge # これは成功する
  end
end

Bot.run!

また、helpersと大きく違うのは、registerはextendを実行したあとのフックを持っています。

require 'mobb/base'

class Bot < Mobb::Base
  register do
    def self.registered(klass)
      puts "extended to #{klass}"
    end

    def hoge
      'hoge'
    end
  end

  on 'hello' do
    settings.hoge
  end
end

Bot.run!

# 実行すると、次の出力が得られる
# ruby app.rb
# extended to Bot
# == Mobb (v0.4.0) is in da house with Shell. Make some noise!

registerを使用すると、Botクラスの各メソッドにアクセスが可能です。つまり、Mobb::Baseクラスを直接機能拡張することが可能なのです。

helpers or register ?

さて、helpersとextendsですが、面白いことにこの2つのメソッドはそれぞれ互いを互いの中で呼ぶことが出来ます。例えば、このような使い方です。

require 'mobb/base'

class Bot < Mobb::Base
  register do
    def self.registered(klass)
      klass.helpers do
        def hoge
          'hoge'
        end
      end
    end

  end

  on 'hello' do
    hoge
  end
end

Bot.run!

registerの中からhelpersを使うには、registeredの引数に渡されたアプリケーションクラスを経由して行います。

require 'mobb/base'

class Bot < Mobb::Base
  helpers do
    register do
      def hoge
        'hoge'
      end
    end
  end

  on 'hello' do
    settings.hoge
  end
end

Bot.run!

helpersの中からregisterを使うのは、わかりやすいですね。コンテキストが特に変わっていないからです。

このように、helpers と register は、使い方によっては別にどちらを使っても良いように思いますが、それぞれ記述の容易さなどから次のように使い分けられます。

helpersは、Mobbアプリケーションの実行時に使いたい機能、すなわち on メソッドや cron メソッドのブロックの中から使いたいヘルパーメソッドを定義するのに使用されます。ユーザーは、自身のビジネスロジックを記述するために、helpersを利用することになると思います。

それに対してregisterは、アプリケーションの起動時、すなわち構築のときに使われるMobbそのものを拡張するケースに用いられます。

Mobbアプリケーションを書くユーザーが最も利用するのは、helpersです。むしろ、registerを利用するケースは殆ど無いでしょう。ですが、Mobbアプリケーションの拡張を書く必要があり、それを再利用するケースでは、registerを使用します。例えば、MobbからActiveRecordを利用するmobb-activerecordというgemは、registerを使った拡張という形で書かれています。

わりと似たような機能ですが、やっていることは歴然と違いそれぞれ用途はっきりとわかれているので、みなさんも自分の必要に応じたほうを利用してください。