There have been (at least a few times) that during the course of developing a personal project, that I’ve considered using a plugin style system to solve a particular problem. However, I have never designed such a system and almost always find that what I wanted to do can be solved in another way that is far simpler. A few days ago I discovered a real use case for such a pattern and finally bit the bullet and dived into discovering how I could go about implementing such a pattern. Because the project was in Ruby, I hoped that the solution would be simpler than if I tried to implement it in a stricter language.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
The above code is the guts of the plugin system I’ve been developing. Each plugin system will need an appropriately named plugin configuration object to store information about the loaded plugins and also serve as the object that is modified with the contents of our plugins.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
|
This is the guts of the plugin handler and acts as our interface to calling loaded plugins. The “process” method is used to route the request to the loaded plugin or gracefully fall back onto the default handler which can do whatever we want. In this case I simply have it raising an error that what we’re trying to do is not implemented.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
This is the implementation of a simple plugin that will, when loaded, insert itself as a module in PluginConfiguration and then register as a callable method in the loaded_plugin hash. The biggest issue here is to make sure we won’t step on any other loaded plugins.
The idea behind this system is that we use mixins to dynamically increase the functionality of the PluginConfiguration class, and then we register that functionality into the hash so that it can be dynamically called. This way we have runtime dynamic methods without having to define them in the code. My use case that led to this design was a dynamic parser that can be configured to parse a source differently based on the expected content. For example an RSS feed can have a special RSS parser, and Atom feed can have an Atom parser, raw HTML can have it’s own parser, and any other source we can dream of can have a parser. As I work, I can write the parsers independently without having to update the plugin manager with the list of loaded parsers.
I’m sure there are plenty of other ways to do this in Ruby, and my way is probably not the best. But it is working wonderfully for what I need it to do.