Skip to content
Andy Maleh edited this page Apr 9, 2015 · 4 revisions

SuperModule   SuperModule v1.1.1 [2015-04-09]

Gem Version Build Status Coverage Status Code Climate

Calling Ruby's Module#include to mix in a module does not bring in class methods by default. This can come as quite a surprise whenever a developer attempts to include class methods via a module. Fortunately, Ruby does offer a solution in the form of implementing the hook method Module.included(base) following a certain boilerplate code idiom. However, this solution can hinder code maintainability and productivity flow in a big production-environment project that takes advantage of many mixins to model the business domain via composable object traits.

ActiveSupport::Concern is a popular Rails library that attempts to ease some of the boilerplate pain by offering a DSL layer on top of Module.included(base). Unfortunately, while it improves the readability of the code needed to include class methods, it supports the same boilerplate idiom, thus feeling no more than putting a band-aid on the problem.

Fortunately, SuperModule comes to the rescue. Including SuperModule at the top of a Ruby module's body automatically ensures inclusion of class methods whenever a developer mixes it in via Module#include.

Introductory Comparison

To introduce SuperModule, here is a comparison of three different approaches for writing a UserIdentifiable module.

module UserIdentifiable
  include ActiveModel::Model

  def self.included(base_klass)
    base_klass.extend(ClassMethods)
    base.class_eval do
      belongs_to :user
      validates :user_id, presence: true
    end
  end
  
  module ClassMethods
    def most_active_user
      User.find_by_id(select('count(id) as head_count, user_id').group('user_id').order('count(id) desc').first.user_id)
    end
  end
  
  def slug
    "#{self.class.name}_#{user_id}"
  end
end

This is a lot to think about and process for simply wanting inclusion of class method definitions (like most_active_user) and class method invocations (like belongs_to and validates). The unnecessary complexity gets in the way of problem-solving; slows down productivity with repetitive boiler-plate code; and breaks expectations set in other similar object-oriented languages, discouraging companies from including Ruby in a polyglot stack, such as Groupon's Rails/JVM/Node.js stack and SoundCloud's JRuby/Scala/Clojure stack.

module UserIdentifiable
  extend ActiveSupport::Concern
  include ActiveModel::Model

  included do
    belongs_to :user
    validates :user_id, presence: true
  end
  
  module ClassMethods
    def most_active_user
      User.find_by_id(select('count(id) as head_count, user_id').group('user_id').order('count(id) desc').first.user_id)
    end
  end
  
  def slug
    "#{self.class.name}_#{user_id}"
  end
end

A step forward that addresses the boiler-plate repetitive code concern, but is otherwise really just lipstick on a pig. To explain more, developer problem solving and creativity flow is still disrupted by having to think about the lower-level mechanism of running code on inclusion (using included) and structuring class methods in an extra sub-module (ClassMethods) instead of simply declaring class methods like they normally would in Ruby and staying focused on the task at hand.

module UserIdentifiable
  include SuperModule
  include ActiveModel::Model
  
  belongs_to :user
  validates :user_id, presence: true

  def self.most_active_user
    User.find_by_id(select('count(id) as head_count, user_id').group('user_id').order('count(id) desc').first.user_id)
  end

  def slug
    "#{self.class.name}_#{user_id}"
  end
end

With include SuperModule declared on top, developers can directly add class method invocations and definitions inside the module's body, and SuperModule takes care of automatically mixing them into classes that include the module.

As a result, SuperModule collapses the difference between extending a super class and including a super module, thus encouraging developers to write simpler code while making better Object-Oriented Design decisions.

In other words, SuperModule furthers Ruby's goal of making programmers happy.

Feedback and Contribution

SuperModule is written in a very clean and maintainable test-first approach, so you are welcome to read through the code on GitHub for more in-depth details: https://github.com/AndyObtiva/super_module

The library is quite new and can use all the feedback and help it can get. So, please do not hesitate to add comments if you have any, and please fork the project on GitHub in order to make contributions via Pull Requests.