0

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

4
  • 3
    @@-style class variables are trouble, they can behave in surprising ways, which is why they're flagged by default. I'd avoid these if you can. Commented Oct 16, 2023 at 21:24
  • 1
    You may want to define this as def self.do_thing instead of adding extend self, which does imply this can be used as a mixin as well. Commented Oct 16, 2023 at 21:25
  • Modules can have instance variables as they themselves are instances of Module. 🤯 Commented Oct 17, 2023 at 7:12
  • I suggest you remove the Rails tag as this is a pure-Ruby question. Commented Oct 18, 2023 at 8:52

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.