开发者

Help a ruby noob understand class inheritance

I'm trying to learn ruby by building a basic Campfire bot to screw around with at work. I've gotten pretty far (it works!) and learned a lot (it works!), but now I'm trying to make it a bit more complex by separating the actions to be performed out into their own classes, so that they can be easier to write / fix when broken. If you're interested in seeing all the (probably crappy) code, it's all up on GitHub. But for the sake of this question, I'll narrow the scope a bit.

Ideally, I would like to be able to create plugins easily, name them the same as the class name, and drop them into an "actions" directory in the root of the project, where they will be instantiated at runtime. I want the plugins themselves to be as simple as possible to write, so I want them all to inherit some basic methods and properties from an action class.

Here is action.rb as it currently exists:

module CampfireBot
  class Action
    @handlers = {}

    def initialize(room)
      @room = room
    end


    class << self
      attr_reader :handlers
      attr_reader :room

      def hear(pattern, &action)
        Action.handlers[pattern] = action
      end
    end
  end
end

Where @room is the room object, and @handlers is a hash of patterns and blocks. I kind of don't understand why I have to do that class << self call, but that's the only way I could get the child plugin classes to see that hear method.

I then attempt to create a simple plugin like so (named Foo.rb):

class Foo < CampfireBot::Action

    hear /foo/i do
        @room.speak "bar"
    end
end

I then have my plugins instantiated inside bot.rb like so:

def load_handlers(room)
  actions = Dir.entries("#{BOT_ROOT}/actions").delete_if {|action| /^\./.match(action)}
  action_classes = []
  # load the source
  actions.each do |action|
    load "#{BOT_ROOT}/actions/#{action}"
    action_classes.push(action.chomp(".rb"))
  end

  # and instantiate
  action_classes.each do |action_class|
    Kernel.const_get(action_class).new(room)
  end

  @handlers =  Action.handlers

end

The blocks are then called inside room.rb when the pattern is matched by the following:

handlers.each do |pattern, acti开发者_如何学Goon|
  if pattern.match(msg)
    action.call($~)
  end
end

If I do puts @room inside the initialization of Action, I see the room object printed out in the console. And if I do puts "foo" inside Foo.rb's hear method, I see foo printed out on the console (so, the pattern match is working). But, I can't read that @room object from the parent class (it comes out as a nil object). So obviously I'm missing something about how this is supposed to be working.

Furthermore, if I do something to make the plugin a bit cleaner (for larger functions) and rewrite it like so:

class Foo < CampfireBot::Action

    hear /foo/i do
        say_bar
    end

    def say_bar
        @room.speak "bar"
    end
end

I get NoMethodError: undefined method 'say_bar' for Foo:Class.


The definition of hear can be pulled out of the class << self block and changed to:

 def self.hear(pattern, &action)
   Action.handlers[pattern] = action
 end

to yield the exact same result. That also immediately explains the problem. hear Is a class method. say_bar is an instance method. You can't call an instance method from a class method, because there simply isn't an instance of the class available.

To understand the class << self bit, you'll have to do your own reading and experiments: I won't try to improve on what has already been said. I'll only say that within the class << self .. end block, self refers to the eigenclass or metaclass of the CampfireBot::Action class. This is the instance of the Class class that holds the definition of the CampfireBot::Action class.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜