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

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.
Hi,
I didn’t test your code, but if you look at the class-method self.add_research_to_queue:
you are using there the associated objects (city and research). How I see it, it is wrong! Or do I see something wrong?
I don’t know whether it’s just me oor if perhaps everyone else experiencing problems with your site.
It appears like some oof the written text in your posts are running off
the screen. Can someone else please provide feedback and let me know
iff this is happening to them as well? This may be a problem with my webb browser because I’ve had this happen previously.
Many thanks