Pregunta

I have this module:

module Api
  module ObjectMapper
    def self.const_missing const_name

      anon_class = Class.new do
        def self.foo
          puts self.class.name
        end
      end

      const_set const_name, anon_class
    end
  end
end

I want to be able to define an anonymous class at runtime with a method foo that can be called with Api::ObjectMapper::User::foo. That function should print User to the screen. Everything that I have tried has resulted in some sort of error or the function prints Class to the screen.

How do I fix my class and method definitions so that self.class.name resolves correctly?

¿Fue útil?

Solución

The name of a class is just the first constant that refers to it. The only problem with your original code is that you are using self.class.name instead of self.name:

module Api
  module ObjectMapper
    def self.const_missing const_name
      self.const_set const_name, Class.new{
        def self.foo
          name.split('::').last
        end
      }
    end
  end
end

p Api::ObjectMapper::User,      #=> Api::ObjectMapper::User
  Api::ObjectMapper::User.name, #=> "Api::ObjectMapper::User"
  Api::ObjectMapper::User.foo   #=> "User"

When you define a class method (singleton method on a class) the self is the class. The self.class is therefore always Class, whose name is "Class".


Original answer, which returns "User" instead of "Api::ObjectMapper::User"

One way (no better than your closure-based solution) is to use an instance variable on the class:

module Api
  module ObjectMapper
    def self.const_missing const_name
      anon_class = Class.new do
        def self.foo
          puts @real_name
        end
      end
      anon_class.instance_variable_set(:@real_name,const_name)
      const_set const_name, anon_class
    end
  end
end

Api::ObjectMapper::User.foo
#=> User

A couple alternative, tighter syntaxes:

def self.const_missing const_name
  const_set const_name, Class.new{
    def self.foo; puts @real_name end
  }.tap{ |c| c.instance_eval{ @real_name = const_name } }
end

def self.const_missing const_name
  const_set const_name, Class.new{
    def self.foo; puts @real_name end
  }.instance_eval{ @real_name = const_name; self }
end

Otros consejos

I found a solution. Since you are defining the anonymous class from within the scope of the const_missing method, you have const_name available to you which is the name of the class you're defining. I'm not sure if that's the best solution, but it does work. You have to redefine your class like this:

  anon_class = Class.new do
    define_singleton_method(:foo) do
      puts const_name
    end
  end

You can also define your foo method as class method

def self.const_missing const_name
  anon_class = Class.new do
    def self.foo
      puts self.class.name
    end
  end
end
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top