Instance variables in ruby utility module that extends self?

88 views Asked by At

I'm making a utility module that have some simple functions - I want the module to extend self so it does not need to be instantiated. A coworker and I were discussing various ways to store data in these types of modules, and one example he provided was this, using an instance variable:

module ExampleModule
  extend self

  @my_instance_variable = "Hello from module"

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

This isn't an approach we decided to use, but I wanted to get a better understanding of why it works / best alternatives. So, a few questions:

Why does this work? How can a module method called without an instance access instance variables? Could you point me to any ruby docs / articles discussing this behavior?

Thoughts on if / when this is an appropriate pattern? (i'm not a fan since it seems counterintuitive for a module like this to have instance variables)

Should a class variable be used here instead? Our rubocop flags @@foo class variables as a code smell

1

There are 1 answers

0
max On

In Ruby modules can have instance variables. This goes for classes as well as they are a kind of module. If you use the Module.new method instead of the keyword this becomes even more apparent:

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

The only real difference between the example above and your code is that you're using extend self - which causes do_thing to be callable on the module and not just classes which include the module.

I'm not a huge fan of this and IMHO it would be better to just define the methods explicitly with def self.method_name which is clearer about intent and can be picked up by static analysis.

Thoughts on if / when this is an appropriate pattern? (i'm not a fan since it seems counterintuitive for a module like this to have instance variables)

One very common use for module instance variables is the configuration pattern:

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

Here a set of gem specific settings are stored in @configuration. You then set this in a initializer file which is loaded when booting the app:

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

This allows app wide settings without relying on globals.

Module/class instance variables can be confusing if you're new to Ruby but remember that in Ruby everything is an object and that instance variables are not defined properties of a class. They are just variables which are lexically scoped to an instance of something which can be a module, class or an instance of a class.

Should a class variable be used here instead? Our rubocop flags @@foo class variables as a code smell

Class variables in Ruby have a lot of unexpected behaviors and for that reason are usually best avoided. For example they are shared between a class and all of it's subclasses.