Dynamically calling ruby methods in modules

When I am creating Watir tests, I write ruby methods to define user tasks, for example, adding a book to a cart becomes def add_book. I then group these ruby methods into ruby modules divided logically by the area of the application I am writing tests for. For example, I would have a ‘Customer’ module and an ‘Admin’ module for the Depot app. The benefit of using modules is you can avoid namespace conflicts as essentially each method is defined by its module’s prefix. This means that you can happily have def Customer.log_on and def Admin.log_on without any conflict or confusion.

As I have mentioned before, I like defining tests outside my code. These tests ultimately need to execute an associatted ruby method (stored in a module) by passing some data in (and getting an outcome and some output back). One way of calling these tests defined external to our code is to have a massive case statement that determines what calls what. This isn’t ideal as it is a maintenance burden, and really it isn’t needed.

In ruby it’s straightforward to dynamically load ruby modules, and then dynamically call individual methods.

require 'temp'

module_name = "Temp"
method_name = "hello_world"

required_module = Kernel.const_get(module_name)
required_method = required_module.method(method_name)
required_method.call('Alister')

This is all well and good if Temp.helloworld() exists, but if it doesn’t, our code throws exceptions:


`const_get': uninitialized constant Kernel::Temp (NameError)

or

`method': undefined method `hello_world' for class `Module' (NameError)

One way to avoid these exceptions is to wrap the code with a rescue clause, but I realised there are some easy ways to check if both modules and methods exist before loading them.

require 'temp'

module_name = "Temp"
method_name = "hello_world"

if Object.const_defined?(module_name)
  required_module = Kernel.const_get(module_name)
  if required_module.respond_to?(method_name) then
    required_method = required_module.method(method_name)
    required_method.call('Alister')
  else
    puts "Invalid method '#{method_name}' for module '#{module_name}'"
  end  
else
 puts "Invalid module '#{module_name}'"
end

This ensures that the code continues to execute if the module or method name is specified incorrectly, which is sometimes the case if its specified in a spreadsheet, and especially if someone else has designed the spreadsheet.

Once we are happy about dynamically finding methods in modules, the next step is to make sure that each method is called with the correct number of parameters. This property of a method is called the arity.

The great thing about ruby and arity is that you simply determine the number of parameters and then pass in a correct sized array, using a *, and the receiving method will automatically unpack the array into the parameters specified.

puts required_method.arity()
required_method.call(*parameters)

The flexibility that ruby offers is amazing. I have tried to accomplish this same concept in VBScript but I couldn’t work out how. That’s why I am glad Watir uses ruby, it ultimately means my automated test framework is more efficient and maintainable.

Author: Alister Scott

Alister is an Excellence Wrangler for Automattic.

2 thoughts on “Dynamically calling ruby methods in modules”

Comments are closed.