@═╦╣╚╗ A mazing engineer


  23-Jul-2019 Leaky Constants


  It took me by surprise when I realized that any constant declared in a block body is defined as a top-level constant.
  Yes, you've heard it correctly.

  
    A = Class.new do
      B = 1
    end
    A::B # NameError: uninitialized constant A::B
    B # => 1
  
Same applies to module and class declarations inside blocks, e.g.:
    A = Class.new do
      module B
      end
      class C
      end
    end
    A::B # NameError: uninitialized constant A::B
    A::C # NameError: uninitialized constant A::C
  
To be precise, it's not defined on a top-level, but rather in the same scope that the block is being run:
    module M
      A = Class.new do
        B = 1
      end
    end
    M::A::B # NameError: uninitialized constant M::A::B
    M::B # => 1
  
No big deal, you think, it's kind of rare. Not really if you use RSpec. Every construct in RSpec DSL is a block. What you define in an example group, be it a method, or a variable, are defined locally to example groups, which are transformed to classes. Each time an example in this example group is being run, a new instance of that class is initialized, and it does not affect the other examples. Except for the constants. On one of the projects I was working on, we kept getting heisenbugs, that heavily depended on the order of spec execution. As long research has discovered, it was due to class name clashes. Instead of creating a temporary class, we kept reopening them, and modifying their behaviour. Worst thing one could do is to `prepend` something, what makes the class behaviour practically irrevocable. Another example is to inherit a "temporary" class from `described_class`, which is a time bomb for "superclass mismatch" error. One example can be:
    # spec/a_spec.rb
    RSpec.describe A do
      class DummyClass < described_class
      end

      it { ... }
    end

    # spec/b_spec.rb
    RSpec.describe A do
      class DummyClass
      end

      it { ... }
    end
  
Those two specs would normally run and won't affect each other. However, if you run them in reverse order, "superclass mismatch" will raise. The reason the errors were not appearing immediately is the size of that project's codebase, and the fact that the specs were run on CI in multiple parallel processes. The clashing classes were clashing to fail only occasionally. Worry not, we've got you covered. There's this new `LeakyConstantDeclaration` RuboCop-RSpec cop to catch those cases. Update your `rubocop-rspec` to version 1.34, or install it if you're not using it yet on your projects using RSpec. There's also a RSpec Style Guide Declaring Constants guideline with good and bad examples. Keep in mind, RuboCop and its extensions are not only to dictate layout rules that don't always align with your preferences, but also to keep you off shooting yourself in the foot. RuboCop encourages you to tweak its configuration to match your project style. And even if you have thousands of offenses, it is possible to fix them quite quickly. If you'd like to dig deeper, please find some interesting links below:  - Original RuboCop-RSpec issue  - Sacrificial Test Classes article  - Cop pull request and documentation improvement  - Guideline introduction You are welcome to participate in all this, and your feedback is always appreciated. Subscribe to `rspec-style-guide` with "Watch", check existing issues, and submit your own. Safe coding!