Techioz Blog

self を拡張する Ruby ユーティリティ モジュールのインスタンス変数?

概要

いくつかの単純な関数を備えたユーティリティモジュールを作成しています。インスタンス化する必要がないようにモジュールを自己拡張したいのです。同僚と私は、これらのタイプのモジュールにデータを保存するさまざまな方法について話し合っていたのですが、彼が提供した一例は、インスタンス変数を使用した次のようなものでした。

module ExampleModule
  extend self

  @my_instance_variable = "Hello from module"

  def do_thing
    puts "**** #{@my_instance_variable}"
    @my_instance_variable
  end
end

これは私たちが使用することに決めたアプローチではありませんが、それが機能する理由や最良の代替案をより深く理解したいと思いました。そこで、いくつか質問します。

なぜこれが機能するのでしょうか?インスタンスなしで呼び出されたモジュール メソッドはどのようにしてインスタンス変数にアクセスできるのでしょうか?この動作について説明している Ruby のドキュメント/記事を教えていただけますか?

これが適切なパターンであるかどうか、または適切な場合について考えますか? (このようなモジュールにインスタンス変数があるのは直観に反しているように思えるので、私はファンではありません)

ここでは代わりにクラス変数を使用する必要がありますか?私たちのrubocopは@@fooクラス変数にコード臭としてフラグを立てます

解決策

Rubyではモジュールにインスタンス変数を持たせることができます。これは、モジュールの一種であるクラスにも当てはまります。キーワードの代わりに Module.new メソッドを使用すると、これがさらに明確になります。

module Foo
  @bar = "Hello World"
  def self.bar
    @bar
  end
end

# is pretty much equivilent to
Foo = Module.new do
  @bar = "Hello World"
  def self.bar
    @bar
  end
end

上記の例と実際のコードの唯一の違いは、extende self を使用していることです。これにより、モジュールを含むクラスだけでなく、モジュール上でも do_thing を呼び出すことができます。

私はこれの大ファンではありません。個人的な意見としては、def self.method_name を使用してメソッドを明示的に定義するほうが良いでしょう。これにより、意図がより明確になり、静的分析で検出できるようになります。

モジュール インスタンス変数の非常に一般的な使用法の 1 つは、構成パターンです。

module MyGem
  class << self
    attr_accessor :configuration
  end

  def self.configure
    self.configuration ||= Configuration.new
    yield(configuration)
  end

  class Configuration
    attr_accessor :mailer_sender

    def initialize
      @mailer_sender = '[email protected]'
    end
  end
end

ここでは、Gem 固有の設定のセットが @configuration に保存されています。次に、アプリの起動時に読み込まれる初期化ファイルにこれを設定します。

MyGem.configure do |config|
  config.mailer_sender = '[email protected]'
end

これにより、グローバルに依存せずにアプリ全体の設定が可能になります。

Ruby を初めて使用する場合、モジュール/クラスのインスタンス変数は混乱するかもしれませんが、Ruby ではすべてがオブジェクトであり、インスタンス変数はクラスの定義されたプロパティではないことを覚えておいてください。これらは、モジュール、クラス、またはクラスのインスタンスである可能性のあるもののインスタンスに語彙的にスコープされる単なる変数です。

Ruby のクラス変数には予期しない動作がたくさんあるため、通常は避けるのが最善です。たとえば、クラスとそのすべてのサブクラス間で共有されます。