-1

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)
Anand
  • 3,690
  • 4
  • 33
  • 64
  • Can you clarify what you're doing in the examples? –  Jan 30 '17 at 11:41
  • As in are you trying `MyModule.foo(Baz)` or `MyModule.Baz`? –  Jan 30 '17 at 11:42
  • @cobaltsoda I want to support arbitrary tokens returning their string representation as part of a DSL I am building. In my example foo is a reserved keyword for the DSL, but bar and Baz are names that I want to pass in without passing in the string or symbol. – Anand Jan 30 '17 at 11:44
  • @cobaltsoda MyModule.foo(Baz) – Anand Jan 30 '17 at 11:46
  • 1
    Can you give working code, something that can be pasted directly in irb? And why does `Module` start with capital letter? – Marko Avlijaš Jan 30 '17 at 11:50
  • I can't get your `foo bar` to work. I assume it's ` MyModule.foo(MyModule::bar) `. I get an error: `NoMethodError: undefined method `bar' for MyModule:Module` – Marko Avlijaš Jan 30 '17 at 11:57
  • @MarkoAvlijaš I fixed Module to module. You should be able to paste each snippet to reproduce – Anand Jan 30 '17 at 11:58
  • Look at my previous comment. I can't get your `foo bar` to work meaning you need to edit your question so it's reproducible. Taking a break. – Marko Avlijaš Jan 30 '17 at 11:58
  • @MarkoAvlijaš I have added the full repro code at the end. – Anand Jan 30 '17 at 12:07
  • @MarkoAvlijaš - this does not work as expected in irb - but save it to a file and execute it from command line, and you get the results I mention. – Anand Jan 30 '17 at 12:16
  • I get `nil` as result of `p foo(bar)`, not `"bar"` as you wrote I should – Marko Avlijaš Jan 30 '17 at 12:19
  • @MarkoAvlijaš You get `nil` because the `foo` method already does a `puts`, and returns `nil`. – Anand Jan 30 '17 at 12:21
  • Thank you. Answer coming soon. – Marko Avlijaš Jan 30 '17 at 12:22
  • I can reproduce the output on Ruby 2.1 –  Jan 30 '17 at 12:22
  • @cobaltsoda Are u able to repro it inside irb? irb crashes with `identify_identifier: can't convert NilClass to Array (NilClass#to_ary gives String) (TypeError)` - I have ruby 2.3 – Anand Jan 30 '17 at 12:30
  • Everyone who downvoted, this is a good question now when it's reproducible. – Marko Avlijaš Jan 30 '17 at 13:18

1 Answers1

2

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]
Marko Avlijaš
  • 1,579
  • 13
  • 27
  • The `Object.const_missing` response worked for me, with some modifications. I don't understand (3) in your explanation above. Why would `MyModule.Baz` trigger method_missing? `Baz` starts with an upper case letter, so wouldn't it directly look for `const_missing`? – Anand Jan 30 '17 at 20:12