I Hope You Like This

This is where I talk about things I make. Mostly, that means game development and occasionally other programming things.

Yesterday I spent a significant amount of time working on a cleaner way to access some of the data  I had stored in some classes. Here’s a quick example:

class Character
  attr_reader :stats
  def initialize
     @stats ={:speed => 10,
              :power => 11,
              :bulk => 8 }
   end
end

This stats Hash would be a pretty standard way of storing all the stats, and because of the attr_reader, we have access to everything. Don’t get me wrong, this is perfectly acceptable code right here, but as I was writing, I found myself wanting to “simplify” a bit. I’ll show you:

#What I was writing
character_object.stats[:speed] = 12

#What I wanted to be writing
character_object.speed = 12

The thing is, what I wanted to be writing is entirely possible, for any item in the hash, with a little bit of metaprogramming.

method_missing

Whenever a method can’t be found in Ruby, another method named method_missing gets called. This means that if you define a method_missing method for your class, it’ll get called instead of the one it inherits. Here’s a proof of concept:

class Whatever
  def method_missing(id, *args, &block)
    puts "You tried to call #{id}. SORRY CAN'T DO THAT."
    super
  end
end

var = Whatever.new
var.matt              #=> "You tried to call matt. SORRY"

If you ran that code, you’d expect to see exactly what’s in the comment right before the real method_missing gets called. Notice how method_missing actually receives the id of the method called (just the name), all of the arguments (via *args), and then the block if there was one. Let’s use this to our advantage and write the getters for that hash.

class Character
  def initialize
     @stats ={:speed => 10,
              :power => 11,
              :bulk => 8 }
   end

  def method_missing(id, *args, &block)
    #The getters for the values in our hash.
    return @stats[id] if @stats.has_key?(id)

    super
  end

end

Now that we have that one extra line of code in method_missing, any value inside the hash is accessible by it’s name. Do note that I always call super at the end of method_missing. This is so that a No_Method_Error will occur should you call a method that’s not in the hash or already predefined.

These are what are called “Ghost Methods.” It should be noted that this is a rather slow process, so if what you’re doing is time-sensitive, this might not be the greatest of ideas. Instead you should check out the define_method call, and get a little creative with that.

Update: I forgot to mention that if you are going to write Ghost Methods using the method_missing technique, you’ll want to override the respond_to? method as well. Therespond_to?method allows for you to check if a method exists for that class, so the character class should now look like so:

class Character
  def initialize
     @stats ={:speed => 10,
              :power => 11,
              :bulk => 8 }
   end

  def method_missing(id, *args, &block)
    #The getters for the values in our hash.
    return @stats[id] if @stats.has_key?(id)

    super
  end

  def respond_to?(method_name)
    return true if @stats.has_key?(method_name)

    super
  end
end

1 year ago
  1. hopeyoulikethis posted this