Controller Plugins in Rails

I want to write a Rails plugin, which adds a controller to a rails app. It seems like it should be easy, plugins are fairly flexible after all. After a couple of experiments, I concluded that you need at least the following defined in your plugin:

lib/foo_controller.rb
1
2
3
4
5
  class FooController < ActionController::Base
    def hello
      render :text => 'hello world'
    end
  end
lib/foo_helper.rb
1
2
  module FooHelper
  end

But there’s still a serious problem. It only works on the first request. After much mucking around in routing.rb, I’ve found out why. It turns out that when a request is finished, dispatcher.rb clears out all the controllers, so that routing.rb will reload them on the next request (only in development mode). The problem is, that the controller in the plugin doesn’t live in one of the “trusted” paths1, so can’t be reloaded. So, on the second and subsequent requests, you get a routing failure.

There’s a quick and easy way around this.

lib/foo_controller.rb
1
2
3
4
5
6
7
  class FooController < ActionController::Base
    class << self
      def reloadable?
        false
      end
    end
  end

But that means that you have to restart the web server each time that you change your plugin, which certainly isn’t ideal. At this point, I started thinking about monkey-patching how the reloading works, but a gag reflex caused me to take a step back and think differently.

One minor tip: running rake rails:freeze:gems makes it much easier to dig into the rails source code. Mostly because it all becomes immediately accessible from within textmate’s “Go To File” command.

Looking around the web, I can’t find much information about controllers in plugins. One answer appears to be the rails engines plugins. But it appears to be more heavyweight than I’d like.

However, nuby on rails mentions an interesting alternative.

You can’t directly write a controller plugin, but you can write a generator that copies a controller to your app/controllers directory.

The more that I think about this, the more it makes sense to me. It ensures that magic URL namespaces don’t just “appear” in your application. It also provides a point for overriding aspects of the actions I wish to provide. In addition to all that, it’s just plain better separated so easier to test.

And I haven’t even begin to think about how the views are going to work…

1 app/, lib/ and components/ according to safe_load_paths.

Comments are Closed