Domanda

I would like to access a class' name in its superclass MySuperclass' self.inherited method. It works fine for concrete classes as defined by class Foo < MySuperclass; end but it fails when using anonymous classes. I tend to avoid creating (class-)constants in tests; I would like it to work with anonymous classes.

Given the following code:

class MySuperclass
  def self.inherited(subclass)
    super
    # work with subclass' name
  end
end

klass = Class.new(MySuperclass) do
  def self.name
    'FooBar'
  end
end

klass#name will still be nil when MySuperclass.inherited is called as that will be before Class.new yields to its block and defines its methods.

I understand a class gets its name when it's assigned to a constant, but is there a way to set Class#name "early" without creating a constant?

I prepared a more verbose code example with failing tests to illustrate what's expected.

È stato utile?

Soluzione

Probably #yield has taken place after the ::inherited is called, I saw the similar behaviour with class definition. However, you can avoid it by using ::klass singleton method instead of ::inherited callback.

def self.klass
   @klass ||= (self.name || self.to_s).gsub(/Builder\z/, '')
end

Altri suggerimenti

I am trying to understand the benefit of being able to refer to an anonymous class by a name you have assigned to it after it has been created. I thought I might be able to move the conversation along by providing some code that you could look at and then tell us what you'd like to do differently:

class MySuperclass
  def self.inherited(subclass)
    # Create a class method for the subclass
    subclass.instance_eval do
      def sub_class() puts "sub_class here" end
    end
    # Create an instance method for the subclass
    subclass.class_eval do
      def sub_instance() puts "sub_instance here" end
    end  
  end
end

klass = Class.new(MySuperclass) do
  def self.name=(name)
    @name = Object.const_set(name, self)
  end
  def self.name
    @name
  end
end

klass.sub_class        #=> "sub_class here"
klass.new.sub_instance #=> "sub_instance here"

klass.name = 'Fido'    #=> "Fido"
kn = klass.name        #=> Fido

kn.sub_class           #=> "sub_class here"
kn.new.sub_instance    #=> "sub_instance here"

klass.name = 'Woof'    #=> "Woof"
kn = klass.name        #=> Fido (cannot change)

There is no way in pure Ruby to set a class name without assigning it to a constant.

If you're using MRI and want to write yourself a very small C extension, it would look something like this:

VALUE
force_class_name (VALUE klass, VALUE symbol_name)
{
    rb_name_class(klass, SYM2ID(symbol_name));
    return klass;
}

void
Init_my_extension ()
{
    rb_define_method(rb_cClass, "force_class_name", force_class_name, 1);
}

This is a very heavy approach to the problem. Even if it works it won't be guaranteed to work across various versions of ruby, since it relies on the non-API C function rb_name_class. I'm also not sure what the behavior will be once Ruby gets around to running its own class-naming hooks afterward.

The code snippet for your use case would look like this:

require 'my_extension'

class MySuperclass
  def self.inherited(subclass)
    super
    subclass.force_class_name(:FooBar)
    # work with subclass' name
  end
end
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top