Is it possible to pass arguments to Ruby Modules?
Overview of simple and easy to use solutions to include Module into your Class with some parameters or arguments.
There is a hidden (or not obvious) power inside Ruby Modules, and I’ll try to show you some magic.
Did you know, that you can pass arguments while doing include MyModule
, and even include same module several times with different parameters?
Some gems I know (dry-monads for example) uses following technique:
|
|
Other gems (like concord or procto) doing it another way:
|
|
Both of this parameterized module inclusion patterns has its pros and cons. Such hacks can increase complexity if done wrong, but also helps implement amazing custom-multi-module inclusion.
First option: def self.[]
Lets start with technique used by dry-monads
. Reading the docs you can notice interesting pattern of including Monad related modules:
|
|
What does it do? It accepts list of symbolized module names and includes them. If you need to include into your class Dry::Monads::Result
, Dry::Monads::List
, etc, it is as simple as:
|
|
And it will be equal to:
|
|
To understand how this works we need to look into the code
|
|
Apart from gem specific actions, it is very simple. You need to define some method (#[]
for example, but it can be anything, #call
may suit you better), and this method should return anonymous module (look at ::Module.new {}
).
This anonymous module can include necessary modules or define some methods (welcome to monkey patching).
Lets implement our own parameterized module. It should define method with method_name
and output result
.
|
|
It is synthetic example to get a gist of it. But it allows to call #hello
on SomeClass
instance:
|
|
Second option: Class Module
Another solution is to define Class as subclass of Module, and pass required arguments to initialize
.
Lets take our previous synthetic example and implement it using Class inherited from Module:
|
|
You can use an initializer, but you also may use any class method like #call
in example. This is how we include this type of Class Modules:
|
|
This pattern allows you to include your module class in several ways, try it and find what works best for you and your team.
|
|
What’s the difference (pros and cons)?
If you need to dynamically include several other modules, like Dry Monads do, using anonymous modules via defining module singleton is a great solution. But if you need to do more complex stuff, like storing defined methods or callbacks registry, etc, Class is a friend to stick with.
If you look into ancestors of SomeClass
, you’ll notice that anonymous module has its name for a reason: it is anonymous, and in SomeClass
ancestors is listed as #Module:<0x00007ff431916ec8>
. On the other hand, ModuleClass
has it’s name and is listed in ancestors as ModuleClass:<0x00007ff431915b68>
.
My thoughts are with ModuleClass
solution it will be easier to debug and to write complex stuff for your needs.
What do you think?