rubymetaprogrammingmethod-missing

Method_missing works, but const_missing does not work


I have a simple ruby function:

module MyModule
    def foo(param)
      puts param
    end
end

I want to be able to make calls with undefined tokens like bar and Baz, as follows:

foo bar
foo Baz

Note that I need to be able to pass any token name that begins with an a letter (lower or upper case).

I can handle the first case (lower case beginning name, bar) by adding a method_missing:

module MyModule
  def method_missing(meth, *args, &blk)
    meth.to_s
  end
end

But the second line (with Baz) gives me an uninitialized constant error, so I tried adding a similar const_missing:

module MyModule
  def const_missing(const_name)
    const_name.to_s
  end
end

But I still get the uninitialized constant error with Baz - how do I trap missing names beginning with upper case and return the string?

Update: Here is the full code to repro the scenario:

module MyModule
  def foo(param)
    puts param
  end

  def method_missing(meth, *args, &blk)
    meth.to_s
  end

  def const_missing(const_name)
    const_name.to_s
  end
end

include MyModule
foo "bar" #=> bar
foo bar #=> bar
foo Baz #=> uninitialized constant Baz (NameError)

Solution

  • Solution

    If you want to be able to write just Baz then you need to define Object.const_missing and make it call MyModule.const_missing.

    def Object.const_missing(const_name)
      MyModule.const_missing(const_name)
    end
    

    Explanation

    You are getting an error because there is no constant Baz in current scope which is main ruby's object Object.

    This is better, more revealing test code:

    include MyModule
    p foo(bar)
    p bar
    p MyModule.Baz
    p Baz
    

    Output is this:

    bar # 1
    nil # from puts
    "bar" # 2
    "Baz" # 3
    test.rb:19:in `<main>': uninitialized constant Baz (NameError) # 4
    
    1. foo bar calls method foo, method missing is never called.
    2. bar searches for method in current object then calls method_missing on Object which is found in it's included MyModule
    3. MyModule.Baz searches for constant in module MyModule and then calls const_missing.
    4. Baz searches for constant_missing in current module and it calls const_missing which is Object.const_missing, Object's class instance method.

    When ruby looks up methods it starts with object methods and only when it can't define them it looks into it's modules, then parent class, then parent classes modules etc. It is not possible to override objects/classes method from it's module.

    Object (ruby's main object which includes Kernel) has defined it's const_missing method, but not method_missing.

    search = "method_"
    Object.public_methods.select { |s| s.to_s.include?(search) }
    => [:method_defined?, :public_method_defined?, 
    :private_method_defined?, :protected_method_defined?]
    

    Notice no method missing. Searching for const_ does find const_missing

    search = "const_"
    => "const_"
    irb(main):011:0> Object.public_methods.select { |s| s.to_s.include?(search) }
    => [:const_get, :const_defined?, :const_set, :const_missing]