1
Posted September 2, 2012 by Spyros in Ruby on Rails
 
 

How to Create a Module in Rails and When to Do it

xen2-09puzzle1-300x225
xen2-09puzzle1-300x225

Apart from classes, modules are a very important part of casual ruby programming. The same is true for Ruby on Rails. If you know how to use them, modules can really make your programming better and DRYer. Simply put, a module is just a way to extend the functionality of a class, without using inheritance or being restricted by object oriented methods/techniques. Generally speaking, modules tend to present a very nice way to follow on some design patterns that are best be utilized without classes.

Load Modules in Rails

In Rails, modules are by convention placed in the /lib directory of your application. Although you can use other places to store them, you are highly advised to follow the standard norms and put your modules in /lib. Notice that modules in /lib are not automatically loaded. Instead, you will need to add this line in your config/application.rb file file config block :

config.autoload_paths += %W(#{config.root}/lib) 

Do that and restart your application web server and your /lib modules will be loaded. This also includes subfolders of the /lib folder.

When and Why Use a Module ?

A module should generally be used when you are creating methods that are used in more than one models. In order to keep it DRY, it is best to extract this code to a module and include it in the classes that use it. To illustrate, i would like to present you with an example module from one of my projects. In this one, i wanted to create two different types of queues. One was a building queue, the other was a research queue. The idea is that a queue has generally the same functionality in both cases. It needs to add, remove items and so on. However, there are some vital differences. One of them is that a building queue entry needs some more information compared to the research queue, like the state in which the building was before adding a queue entry for upgrading it. This code is actually part of a browser game application.

I had two models, namely QueuedResearch and QueuedBuilding. So, i went on with creating a module in /lib/game_engine/game_queue.rb. Let me present some parts of the module :

module GameEngine
	module GameQueue

	    # add class methods
	    def self.included(base)
	        base.extend(ClassMethods)
	    end

		def add_to_queue(item, queue_items, options)
			options[:end_time] = options[:start_time] + options[:duration]
			queue_items.create(options)
		end

		def remove_from_queue
			self.destroy
		end

		def prev(queue)
			job = queue.where('start_time < ?', self.start_time).order('start_time desc').limit(1)
			job.empty? ? nil : job.first
		end

		def next(queue)
			job = queue.where('start_time > ?', self.start_time).order('start_time asc').limit(1)
			job.empty? ? nil : job.first
		end

		def finished?
			self.end_time < Time.now
		end

		# Class Methods
		module ClassMethods

			def normalize_time_for_addition(queue, options)
				previous_job = queue.last(:order => "end_time desc", :limit => 1)
				options[:start_time] =  previous_job.end_time if not previous_job.nil?
				options
			end

		end

	end
end

I want you to notice that the methods in the module are not tied to a specific model, neither queuedBuilding or queuedResearch. It’s in fact a general way to handle such Queues, providing next, prev, delete, create and more methods. Also notice that i needed to extend the class that uses my module with a new class method, namely normalize_time_for_addition(). Normally, we tend to use the ‘include’ directive to include our module in the classes that use it. However, there is a catch. ‘Include’ is only used to add  instance methods to our class. When we need to add class methods, instead of ‘include’, we need to use ‘extend’. Since we should not do both, we just use include and dynamically add any class methods using base.extend().

The module is namespaced under GameEngine::GameQueue. It’s important to understand that the filenames of the modules and their containing folders are important. For instance, the GameEngine::GameQueue module must be found under ‘/lib/game_engine/game_queue.rb’. Finally, in order to actually include the module and execute its internal methods, we open up our QueuedBuilding Model and you can see how we put it to use :

 


class QueuedBuilding < ActiveRecord::Base
	include GameEngine::GameQueue

    belongs_to :city
	belongs_to :building

	def self.add_building_to_queue(city, building, new_level)
		queue = city.queued_buildings
		options = { :building_id => building.id, :new_level => new_level, :duration => building.time(new_level), :start_time => Time.now }
  	    options = QueuedBuilding.normalize_time_for_addition(queue, options)
  	    city.add_to_queue(building, queue,  options)
	end

end

I’ve excluded most of the QueuedBuilding functionality just to show you the concept more easily. We first include the GameQueue module and in the add_building_to_queue() class method, we first use the extended module class method normalize_time_for_addition(). After that, we use an instance method named add_to_queue() that is once more part of the module. Let’s now look how similar functionality is created in the QueuedResearch model :


class QueuedResearch < ActiveRecord::Base
	include GameEngine::GameQueue
	
	belongs_to :city
	belongs_to :research
	
	def self.add_research_to_queue(city, research)
		queue = city.queued_researches
		options = { :research_id => research.id, :duration => research.time, :start_time => Time.now }
  	    options = QueuedResearch.normalize_time_for_addition(queue, options)
  	    city.add_to_queue(research, queue,  options)
	end	
	
end

Hope that gives you some interesting insight on how and when to use modules. Please leave a comment if you find this useful or you want to ask something.


Spyros