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 analready initialized constant
warning printed to your terminal when including your trait into a second class. - Without
self
, you are actually definingExampleTrait::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.