Some years ago while I was contributing to the awesome Open Source project Adhearsion, we decided to split the logic contained in 1.0 version into different modules that might be loaded by the developer when needed. Adhearsion 1.0 was tightly coupled with gems like activerecord and activesupport, which were not required for the framework basic functionality and did not provide any real value to most of the Adhearsion applications. Decoupling the logic allowed developers to include only the required dependencies in their applications.

As result of this exercise, different gems were developed to provide isolated functionalities, like adhearsion-activerecord builds the bridge to use ActiveRecord in an Adhearsion application.

Besides the primary goal of decoupling logic, developers should be able as well to create their own modules to extend the base functionality provided by Adhearsion. Those modules were called plugins.

While thinking about how to build this plugin functionality, I dug into different libraries trying to find a clean solution, and Rails came to the rescue. The developer that is using Rails can define her own plugins to extend the functionality of the framework or modify its initialization. These plugins are called Railties. Creating a Railtie is really simple:

  • inherit from Railtie class.
  • load your class during the Rails boot process.

But… how does Rails know that it should execute the code defined in the Railtie subclass while boosting itself? The trick is based on a cool feature from the Ruby language, which defines a hook in the parent class that is raised every time the class is inherited. Here you can see the snippet of code that builds the Railtie magic.

Eventually, for Adhearsion plugins I followed the same rule.

Find below two snippets of code that registers a list of subclasses in both ruby and python.

class Plugin

  class << self

    def inherited(base)
      registry << base
    end

    def registry
      @registry ||= []
    end

    def each(&block)
      registry.each do |member|
        block.call(member)
      end
    end

  end
end

class Foo < Plugin
end

Bar = Class.new Plugin

puts "Plugin subclasses: " + Plugin.each(&:to_s).join(', ')
class Registry(type):

    def __init__(cls, name, bases, dct):
        if not hasattr(cls, 'registry'):
            # Parent class
            cls.registry = []
        else:
            # Child class
            cls.registry.append(cls)
        super(Registry, cls).__init__(name, bases, dct)


class Plugin(object):
    __metaclass__ = Registry


class Foo(Plugin):
    pass

Bar = type('Bar', (Plugin,), {})

print "Plugin subclasses: " + ", ".join([item.__name__ for item in Plugin.registry])

The output of both scripts is:

Plugin subclasses: Foo, Bar

Happy coding! :kissing_cat:

« Home