Package a Discourse plugin as a gem

Pavilion has started to package some Discourse plugins as ruby gems, starting with our subscription client.

https://rubygems.org/gems/discourse_subscription_client

Our subscription client is still a separate plugin which now loads this gem, but it’s backend is now entirely packaged as a gem and the separate plugin will soon be deprecated entirely. This is a little primer on how to do the same with your plugins that are more suited to being gems.

How to work with gems

Firstly, you need to understand how to work with ruby gems. If you’ve never created a gem before I’d recommend creating your own standard gem in isolation before you tackle working with a Discourse plugin as a gem. I’d recommend this guide

https://bundler.io/guides/creating_gem.html

How plugins inject code into Discourse

The way a Discourse plugin gem injects code into Discourse is exactly the same way a standard plugin does. The only difference is the packaging. So to work with a plugin as a gem you’ll also need to understand how a standard plugin injects code into Discourse. There are essentially two things to understand here

  1. A Discourse plugin is a rails engine. You’re probably already aware of this, but you’ll need to really understand what this means. I’d recommend going through this guide on Rails engines. For example you’ll need to understand why a lot of code in a Discourse plugin’s plugin.rb file is wrapped in an after_initialize callback.

  2. How the Discourse initialization process works. There’s really just one file to read and understand here, namely the discourse/discourse/config/application.rb file. That is where most of the rails code is loaded, where all the plugin code is loaded and where plugins are initialized. Go through that file in some depth and understand where and how the plugin files are required and then initialized.

How a Discourse plugin as a gem works

To bring it all together you’ll then need to understand how the above two topics are synthesised in a Discourse plugin gem. In particular note the following:

  1. In a Discourse plugin gem the engine.rb file plays a similar role to the plugin.rb file, with a few configuration differences. Check out the subscription client gem engine.rb file and compare it with a standard plugin.rb file.

  2. In a Discourse plugin gem you need to mock discourse/discourse in your rspec tests in order to properly test the gem. You don’t need to mock the entire Discourse app, just the parts you’re testing against. You do this by creating a skeleton rails app with the specific Discourse classes and endpoints you need and loading it as an rspec support. See the subscription client gem’s mock Discourse app, and where it’s loaded in the spec rails_helper.rb.

How to load a local gem in a Discourse plugin

To load your local version of a gem in a Discourse plugin when working with that plugin and gem in development you need to do the following.

Symlink your gem folder to the relevant plugin’s gem folder. For example to work with my local version of the discourse_subscription_client gem in the discourse-subscription-client plugin I do the following

ln -s /Users/angus/discourse/gems/discourse_subscription_client /Users/angus/discourse/discourse/plugins/discourse-subscription-client/gems/3.2.1/gems

Then change symlinked gem folder in the plugin to use same name pattern as a standard gem folder, e.g.

discourse_subscription_client-0.1.0.pre11

Now when your plugin loads your Discourse plugin gem it will load your local version instead of the one on rubygems.

If you have any questions or get stuck, post here and I’ll help you out.

14 Likes

Seems this approach not possible to cover any Discourse theme stuff in plugin? Maybe a better title is “Package a Discourse specific Rails Engine as a gem”

1 Like

That’s correct, you need to handle the front end JS framework stuff separately

Whilst technically accurate, I wouldn’t agree that title would be better as that would put some of the right people off from reading the Topic: most people who write plugins might not identify them as “Discourse specific Rails Engines”

I actually more regularly separate my code into back end plugins and front end theme components now so I can iterate the front end stuff very quickly on deployments to staging and production environments.

3 Likes

I agree with that, although may this document possible only be useful for some advanced developers, it should cover all people.

And thanks for sharing this.

1 Like

Agree it’s an advanced topic.