Reputation:
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
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