Dennis
Dennis

Reputation: 1975

Internal plugin system and Modules in Ruby (Rails used as a framework)

I want to create a base lass and then load all sub classes (plugins) and process all of them via each loop. This is working example:

require 'set'

class Plugin
  # Keep the plugin list inside a set so we don't double-load plugins
  @plugins = Set.new

  def self.plugins
    @plugins
  end

  def self.register_plugins
    # Iterate over each symbol in the object space
    Object.constants.each do |klass|
      # Get the constant from the Kernel using the symbol
      const = Kernel.const_get(klass)
      # Check if the plugin has a super class and if the type is Plugin
      if const.respond_to?(:superclass) and const.superclass == Plugin
        @plugins << const
      end
    end
  end
end

class DogPlugin < Plugin

  def self.handle_command(cmd)
    p "Command received #{cmd}"
  end

end
class CatPlugin < Plugin

  def self.handle_command(cmd)
    p "Command received #{cmd}"
  end

end

Plugin.register_plugins

# Test that we can send a message to each plugin
Plugin.plugins.each do |plugin|
  plugin.handle_command('test')
end

This code sample works perfectly. Output is:

"Command received test"
"Command received test"
=> #<Set: {DogPlugin, CatPlugin}>

However, In my rails application, my custom implementations are in modules. Lets say I have an A module. In this case it doesn't work.

require 'set'

module A
  class Plugin
    @plugins = Set.new

    class << self
      attr_reader :plugins
    end

    def self.register_plugins
      # Iterate over each symbol in the object space
      Object.constants.each do |klass|
        # Get the constant from the Kernel using the symbol
        const = Kernel.const_get(klass)
        # Check if the plugin has a super class and if the type is Plugin
        if const.respond_to?(:superclass) && (const.superclass == A::Plugin)
          @plugins << const
        end
      end
    end
  end
end

module A
  class MyAction < Plugin
    def self.handle_command(cmd)
      puts "Command received #{cmd}"
    end
   end
end

A::Plugin.register_plugins
A::Plugin.plugins.each do |plugin|
  plugin.handle_command('test')
end

Set is empty and nothing get executed. Why?

See live sample here: https://repl.it/repls/EllipticalDamagedCategory

There are other types of plugin samples on the net but they have to initialized one-by-one. This sample code loads all plugins and execute same method in all of them. I need this functionality with modules.

Upvotes: 1

Views: 74

Answers (1)

Chen Kinnrot
Chen Kinnrot

Reputation: 21015

require 'set'

module A
  class Plugin
    @plugins = Set.new

    def self.plugins
      @plugins
    end

    def self.register_plugins
      # Iterate over each symbol in the object space
      ::A.constants.each do |klass|
        # Get the constant from the Kernel using the symbol
        const = A.const_get(klass)
        puts const
        # Check if the plugin has a super class and if the type is Plugin

        if const.respond_to?(:superclass) && (const.superclass == Plugin)
          @plugins << const
        end
      end
    end
  end
end

module A
  class MyAction < Plugin
    def self.handle_command(cmd)
      puts "Command received #{cmd}"
    end
   end
end

A::Plugin.register_plugins
A::Plugin.plugins.each do |plugin|
  plugin.handle_command('test')
end

Upvotes: 1

Related Questions