Loading 'Plugins' in Ruby
Formatting

Indenting

The standard ruby indent is 2 spaces.  For example:

def puts(string)
  if @@logger.nil?
    super(string)
  else
    @@logger.info string
  end
end


Design

Class Variables

I would not use class variables to set the logging.  As you
discovered, they have surprising
behavior.

Instead, I would just use a class instance variable that belonged
just to the Plugin class and was not automatically shared with its
descendants.

module Plugin
  class &lt;&lt; self
    attr_accessor :logger
  end
end


If that's all you do, then the descendants will have to do this to access the logger:

Plugin.logger.info 'Logging some info'


That's awful, so we'll add an instance method that defers to the class method:

module Plugin

  class &lt;&lt; self
    attr_accessor :logger
  end

  def logger
    self.class.logger
  end

end


Now instances of Plugin can log easily:

logger.info "Logging some info"


Ruby tricks

Use #inspect to quote strings

Instead of this,

@logger.info "Loading \"#{class_name}\" from \"#{full_path}\""


Use #inspect to do the quoting for you:

@logger.info "Loading #{class_name.inspect} from #{full_path.inspect}"


Remove eval

You don't need eval here:

unless eval("defined? #{class_name}")


instead:

unless defined?(class_name)


ancestors.include?

Instead of this:

if plugin_class.ancestors.include? Plugin


do this:

if plugin_class &lt; Plugin


See this SO answer, and the rdoc for Module#&lt;

instance_method

Instead of this:

if plugin_class.allocate.method(:initialize).arity &gt; 0


this:

if plugin_class.instance_method(:initialize).arity &gt; 0


This avoids the funny business of allocating one.

exceptions

The code wishes, when a plugin cannot be added, to log it and continue
adding more plugins.  That works well, but the way it's done can be
simplified using exceptions.

Instead of this:

plugins.each do |plugin|
  ...
        unless eval("defined? #{class_name}")
            @logger.error "Class \"#{class_name}\" not found!"
            next
        end

  ...
end


consider this:

class PluginError &lt; StandardError ; end


plugins.each do |plugin|
  begin
    ...
    unless eval("defined? #{class_name}")
      raise PluginError, "Class \"#{class_name}\" not found!"
    end
    ...
  rescue PluginError =&gt; e
    @logger.error e.to_s
  end
end


This gets rid of all of those "next" calls, simplifying the error handling.