l0b0
l0b0

Reputation: 58778

Inherit a new class method at runtime

In Ruby, <ClassName>.constants is useful to inspect classes:

> Numeric.constants(false)
=> [:KILOBYTE, :MEGABYTE, :GIGABYTE, :TERABYTE, :PETABYTE, :EXABYTE]
> Object.constants(false)
=> [:Object, :Module, ...]

This is defined in Module:

> Object.method(:constants)
=> #<Method: Class(Module)#constants>

I'd like to add another method to print a hash with all the constants and their values. Here's the result so far:

def Module.constants_hash(inherit=true)
    self.constants(inherit).inject({}) do |hash, constant|
        hash[constant] = self::const_get(constant)
        hash
    end
end

This works for Module (although it has no constants, so the result is just an empty hash), but it's not inherited:

> Object.constants_hash(false)
NoMethodError: undefined method `constants_hash' for Object:Class
from (pry):117:in `<main>'

I can of course change the class name in the code to e.g. Object to make the call work, but is it possible to make all the dependent modules inherit the new method as well? In other words, is it possible to add a method at runtime which is then inherited by classes which require the modified class?

I'd rather not overload the original Module.constants, to avoid changing the API.

Upvotes: 2

Views: 544

Answers (2)

Aliaksei Kliuchnikau
Aliaksei Kliuchnikau

Reputation: 13719

Object is an instance of type Class. Class class is inherited from Module. In order for Object.constants_hash to work, you need to define instance method of Class or Module classes:

class Module
  def constants_hash(inherit=true)
      self.constants(inherit).inject({}) do |hash, constant|
          hash[constant] = self::const_get(constant)
          hash
      end
  end
end

In you code you just add constants_hash to a singleton class of Module instance (of type Class), this is why you don't get expected result.

Upvotes: 4

tadman
tadman

Reputation: 211560

You might have better luck by declaring this method in a module, then mixing it in to each context as required:

module ConstantsHash
  def constants_hash(inherit = true)
    Hash[
      self.constants(inherit).collect do |constant|
        [ constant, const_get(constant) ]
      end
    ]
  end
end

Module.extend(ConstantsHash)
Object.extend(ConstantsHash)

puts Object.constants_hash.inspect

As a note, using Hash[] instead of inject({ }) does seem to work better in cases like this.

Upvotes: 0

Related Questions