Ruby Modules

Background

Ruby Modules are probably the most elusive and yet an extremely important & powerful features of Ruby. The reason I say elusive is because for most beginners, if the module code is spread across various files, sifting through them takes a while especially when learning from ground up and that in turn gives advantage to folks who already know the language in and out. Important and powerful because most libraries (gems) and packaged functionality has an entry point through a module.I wouldn’t be doing justice to the audience if I said “I know modules”. I am writing this post just to explain what I understood so far, but if I ever want to learn modules without leaving any stone un-turned, I would probably go to “pragmatic programmers guide to ruby”. Since we are using Ruby for “Test Automation” here, I don’t think we need to learn every little feature and quirks , which probably a Rails app developer or someone who wants to use ONLY Ruby programming language as their full stack would use.

What are Ruby Modules

Modules offer the following features:

  • A way to group methods, constants and classes together
  • provide namespaces – hence prevent name clashes. Extremely helpful when your program loads libraries (modules/classes) that are written by different authors
  • are like hashes – different in that a) access a value/ code via .key instead of [key] b) access constant via ::<constant_name>
  • can also be viewed as similar to storing different file names under separate directories in filesystem
  • When we import a module in a piece of code (context), generally there is only ONE module, unlike classes, where we can instantiate scores of them
  • multiple inheritance using mixins

How to access module code

  1. require ‘<file_name.rb>’ # file that contains module code (we can also just do require ‘<file_name>’ i..e .rb is not required)
  2. include ‘<module_name>’ # include is a keyword that instantiates module, from here we can access the members of module viz. classes, constants, methods

Examples

Example1

Let’s define a simple module with a method, constant and a class and access it.

Output

module_output1

Example 2

Let’s see how we can define our own version of “String” class , that is different from the system defined “String” class. We can do this by name spacing our version of String class inside a module as follows:

Output

module_output2

Example 3

Let’s see how namespacing a class and accessing it the right way. Let’s assume that the “Browser” class is defined in two libraries viz. PageObject and MyPageObject. If we instantiate the Browser class without qualifying it with a module namespace , then Ruby interpreter would default to the last included module. So to resolve any conflicts and confusions for accessing the Browser class, we explicitly access it using Module namespace [This is a real time example when different gems use the same class name i.e. Browser, so if you have required & included all of the gems, then it can get confusing to track the Browser class when we do Browser.new]

Output

module_output3

Example 4

Let’s say you defined the same module in two different ruby files. So the code that gets required last will be the one accessed. See the example below

Output

Observe that we required “./module_example3.rb” after “./module_example4.rb”, hence the code in module_example3.rb gets called

module_output4

On the other hand if we had the below code, then the code in module_example4.rb gets called

module_output5

Multiple Inheritance (or Mixin)

In languages like Java, C#, inheriting from multiple classes is just adding a list of classes while inheriting the behavior. Ruby , does not let that happen with classes. It is done through modules using the same concepts we learnt above i.e. require the file in which the module is defined and include the module. There is lot more to mixins in terms of how it plays out in Ruby world, but for now, will keep it to simple concepts that are important for test automation. Also you will notice that when you open up any gem code, the modules are spread across various files, however the main entry point file i.e. the <gem_name.rb> file contains the module that requires all other definitions of this module, hence when you generally type require “<gem_name>” while loading the library module, all of the code that the gem has gets loaded into the context as defined in <gem_name.rb> file.

Example 1

In the below example, we see how two modules A and B are included and the behavior is accessible in the Sample class i.e. methods in A&B become instance methods of Sample (so when we include a module in a class, the methods of module become instance methods of class, if there is a method name conflict, then the last included module’s method takes precedence). If we extend a class with module, then the modules method becomes classe’s class methods (See the below output)

Output

module_output6

Example 2

included is a callback method that Ruby provides whenever a module is included in another module/class. Where is this useful? You will find included used in many gems – it is a pattern to invoke a certain piece of code whenever the module gets included in another module/class. Let’s say you want to perform something with the receiver class whenever this module is included, then you can use this. Similar to .included, there is .extended method too for a module.

In the below example code, lets say we have a module “MyPageObject” and every time this module gets included in a receiver class, we want to extend the receiver class with another module “MyPageObject1” [extending a module means the module methods become class methods of the class]

Output

module_output7

 

 

Closing Thoughts

There is lot more to modules , mixins and how it plays out with Ruby classes, however that is beyond the scope of this post. I am learning every day new things as I write more code.