How to define constants with traits

When defining a trait using the Modularity gem Show archive.org snapshot , you must take extra steps to define constants to avoid caveats (like when defining subclasses through traits).

tl;dr

In traits, always define constants with explicit self.

If your trait defines a constant inside the as_trait block, it will be bound to the trait module, not the class including the trait.
While this may seem unproblematic at first glance, it becomes a problem when including traits in multiple classes, especially when using parameterized traits.

Example (bad)

Consider the following trait and class.

module ExampleTrait
  as_trait do |audience|
    HELLO = "hello #{audience}" # don't do this
  end
end
class Foo
  include ExampleTrait["world"]
end

No problem so far:

>> Foo::HELLO
=> "hello world"

We then include the trait in another class, with a different configuration.

class Bar
  include ExampleTrait["universe"]
end

Bar::HELLO is set to "hello universe":

>> Bar::HELLO
=> "hello universe"

But Foo::HELLO has also changed!

>> Foo::HELLO
=> "hello universe" # oh no

Solution

In the as_trait block, always define constants on self. self references the class the trait is evaluated on.

module ExampleTrait
  as_trait do |audience|
    self::HELLO = "hello #{audience}"
  end
end
class Foo
  include ExampleTrait["world"]
end
class Bar
  include ExampleTrait["universe"]
end

Now, every class has its own HELLO constant with its expected value.

>> Foo::HELLO
=> "hello world"
>> Bar::HELLO
=> "hello universe"

Side notes

  • Without self, you'll also see an already initialized constant warning printed to your terminal when including your trait into a second class.
  • Without self, you are actually defining ExampleTrait::HELLO.
  • If you want to define a constant that belongs to a trait module, you can do that, and there a valid reasons to do so. Just define it outside of the as_trait block.
Arne Hartherz About 13 years ago