Search code examples
rubymethod-missing

Ruby nested const_missing method_missing stack too deep


I have the following class:

module APIWrapper
  include HTTParty
  BASE_URI = 'https://example.com/Api'

  def self.const_missing(const_name)
    anon_class = Class.new do
      def self.method_missing method_name, *params
        params = {
          'Target' => const_name.to_s,
          'Method' => method_name.to_s,
        }

        APIWrapper.call_get params
      end
    end
  end

  def self.call_get(params)
    get(APIWrapper::BASE_URI, {:query => params})
  end

  def self.call_post(params)
    post(APIWrapper::BASE_URI, params)
  end
end

I want to be able to make a call to my wrapper like this:

APIWrapper::User::getAll

I'm getting a stack level too deep error:

1) Error:
test_User_getAll(APITest):
SystemStackError: stack level too deep
api_test.rb:16

What am I doing wrong?


Solution

  • After using the keyword def, a new scope is created, so the issue here is that the const_name variable is no longer in scope inside the body of the method_missing method.

    You can keep the variable in scope by using blocks like so:

    def self.const_missing(const_name)                                                                                                                             
      anon_class = Class.new do                                                                                                                                    
        define_singleton_method(:method_missing) do |method_name, *params|                                                                                                 
          params = {                                                                                                                                             
            'Target' => const_name.to_s,                                                                                                                         
            'Method' => method_name.to_s,                                                                                                                        
          }                                                                                                                                                      
    
          APIWrapper.call_get params                                                                                                                                                                                                                                                                                   
        end                                                                                                                                                        
      end                                                                                                                                                          
    end                                                                                                                                                            
    

    You might want to also set the constant to the anonymous class you just created:

    anon_class = Class.new do
      ...
    end
    
    const_set const_name, anon_class