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.
@@-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.def self.do_thinginstead of addingextend self, which does imply this can be used as a mixin as well.