user945747
user945747

Reputation:

What's the convention for namespacing extensions to ruby gems / modules?

I can't for the life of me find any simple explanation to this.

I'm working on a project that has it's own custom code, but also has some extensions/overrides to the classes available though included gems.

What is the right folder structure and module naming patter to achieve this?

For example, here is how I have the code organized for an extension to the third party "parallel_tests" gem, the library I'mw working on is called "ChemistyKit":

lib/
  chemistrykit/
     parallel_tests/ # the gem name space i'm extending
       rspec/ 
         runner.rb

So I basically follow the same naming structure for the gem, below that parallel_tests folder.

Then in that runner.rb file I have something like:

module ChemistryKit
  module ParallelTests
    module RSpec
      class Runner < ParallelTests::Test::Runner

However using this causes errors. I think that it is because it never actually over rides the class in question because it is namespaced under ChemistryKit That's confusing though because in something like this extension of the RSpec HTML formatter, the formatter is used under the gem's top level namespace. Is that because in this case it's creating a similarly named but different class? The intention is not to override, but extend?

Upvotes: 1

Views: 465

Answers (1)

Stephenr
Stephenr

Reputation: 101

To completely replace the Runner class in RSpec you want to do the following

module RSpec
  module Core
    remove_const(:Runner)
    class Runner < ParallelTests::Test::Runner
      ...
    end
  end
end

The code inside of a module looks up constants using a path based on its name. So the Runner class you define in your example refers to the constant ChemistryKit::ParallelTests::RSpec::Runner.

You can reference the RSpec Runner from inside one of your modules by specifying that your RSpec module is in the global namespace like in the following example.

module ChemistryKit
  module ParallelTests
    module ::RSpec
      module Core
        class Runner < ParallelTests::Test::Runner

Now even if you do this there is one other issue with modifying the class in this way. Because you want to replace the Class you have to remove it first otherwise you will just be modifying the existing class(and you can't reopen a class if you specify a different superclass, the rspec runner is already a subclass of Object and you can't change that). That is what the remove_const call in my first example is for.

The extra modules however don't actually do anything in the above example except make things less clear. When modifying the existing gem's code it is fine to just have all of the code in a rspec subfolder(like you do) but with the exact same module names as the original.

Keep in mind that modifying classes in this way can fail really easily. Such as if the class if used when it is required, your replacement won't happen until afterwards which can cause unforeseen issues. This is why the formatter example you linked takes a different approach, the code does not monkeypatch RSpec but rather attaches its new class at runtime.

Upvotes: 1

Related Questions