Read more

How to define constants with traits

Arne Hartherz
February 01, 2011Software engineer at makandra GmbH

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.

Illustration online protection

Rails professionals since 2007

Our laser focus on a single technology has made us a leader in this space. Need help?

  • We build a solid first version of your product
  • We train your development team
  • We rescue your project in trouble
Read more Show archive.org snapshot

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
February 01, 2011Software engineer at makandra GmbH
Posted by Arne Hartherz to makandra dev (2011-02-01 10:12)