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:
include MyModule[:some, :args]
include MyModule.new(:some, :args)
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:
class SomeClass include Dry::Monads[:result] end
What does it do? It accepts list of symbolized module names and includes them. If you need to include into your class
Dry::Monads::List, etc, it is as simple as:
class SomeClass include Dry::Monads[:result, :list] end
And it will be equal to:
class SomeClass include Dry::Monads::Result include Dry::Monads::List end
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
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
module SomeModule def self.define(method_name, result) Module.new do define_method method_name do puts result end end.freeze end end class SomeClass include SomeModule.define(:hello, :world) end
It is synthetic example to get a gist of it. But it allows to call
> SomeClass.new.hello world
Second option: Class Module
Another solution is to define Class as subclass of Module, and pass required arguments to
Lets take our previous synthetic example and implement it using Class inherited from Module:
class ModuleClass < Module def initialize(method_name, result) define_method method_name do puts result end end def self.call(method_name, result) new(method_name, result) end end
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:
class SomeClass include ModuleClass.new(:hello, 'world') include ModuleClass.call(:foo, 'bar') end
This pattern allows you to include your module class in several ways, try it and find what works best for you and your team.
> SomeClass.new.hello world > SomeClass.new.foo bar
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
My thoughts are with
ModuleClass solution it will be easier to debug and to write complex stuff for your needs.
What do you think?