Code Spelunking: Radiant extension deactivate

A long time ago when Radiant was just a young CMS, it allowed you to activate and deactivate extensions with the click of a button.

This was simply an awesome feature. But there was a problem: it didn't work.

Radiant couldn't really know how you built your extension, and you can do whatever you want in there, so deactivating an extension might not actually deactivate certain parts. I've long wanted to take the time to look into Mixology to see how this extension deactivation feature might be able to make it back in, but the problem will still exist that you can build an extension however you want. Even if Mixology provides a way, it would only work with extensions built in a particular way; it can't solve everything.

Even so, the feature was removed because: it doesn't really matter, and nobody who worked on the core cared enough to put time in to solve a problem around what seemed to be an edge use case. How often do you need to turn off an extension, and does it really need to happen when the application is running? It would be nice, for example, to have a client upgrade or downgrade their account with the click of a button, which would remove or add features. I'm working on several ideas for this, but that'll be a discussion for later.

So what's with the deactivate method in your extensions?

It might as well look like this:

def deactivate
      # Ha ha! Fooled you!
    end

Mostly, because of the generated code in many extensions you'll just have something that says:

def deactivate
      admin.tabs.remove "My Extension"
    end

That deactivate method is there because it was formerly used. It's like finding a dinosaur bone in your back yard, but everyone has a dinosaur bone in their back yard so it's not special.

Is this deactivate method ever used? Lets look:

The Radiant::ExtensionLoader has a method that looks like this:

def deactivate_extensions
      extensions.each &:deactivate
    end

Clearly, this will call the deactivate method on each extension. And the Radiant::ExtensionLoader::DependenciesObserver has this method:

def before_clear(*args)
      ExtensionLoader.deactivate_extensions
    end

Aha! So extensions are deactivated before they are cleared... And they are cleared... um... never? A new DependenciesObserver is created but where is the @observer object used?

This is where we start digging into Rails. Radiant bundles Rails, so we can stay within the source and find that action_controller/dispatcher.rb clears the dependencies. ActionController clears the dependencies and the instance of Radiant::ExtensionLoader::DependenciesObserver has the message before_clear sent to it, which calls each extension to run the deactivate method.

Does this mean that you can use the deactivate method in your extension? Yup. Put whatever you want in there. But still... it won't do anything, or at least not much. Yes, the method is called (go ahead and drop a puts 'hello from the deactivate method' in there and watch your logs) but only in Rails development mode. In production, the dependencies are not cleared, your app just runs.

You can still use it, you could even build a controller for your extension to run the deactivate method and do something with it, but you're on your own as far as the Radiant core is concerned.

So why keep that method in there at all? We even have specs built around it to ensure that it works properly. Sure, this means a bit of code complexity when the feature isn't used, but it's minimal and perhaps deactivate will come back in some way.

At a code sprint weekend over a year ago we discussed this method briefly and decided to leave it in because... well, maybe it will be of use in the future. I'm sure that it will be removed at some point if it is never used but for now keeping it in means that it can more easily come back and extensions using it will merely do things like remove some admin tab only to add it back in with the activate method and only in development mode.

If you've been wondering about that method, well, there you are.