reload rails stack in development for requests through custom middleware

937 views Asked by At

I am using a custom Rack middleware in my Rails 3.1 app that wraps around a vanilla Rails controller:

in routes.rb

stacked_router = 
  Example::Middleware::StackedRouter.new(ProductsController.action(:show))

match '/:id', :to => stacked_router

in example/middleware/stacked_router.rb

class Example::Middleware::StackedRouter
  def initialize(app)
    @app = app
  end

  def call(env)
    # ... do stuff before forwarding the request, then
    @app.call(env) # forward the request
  end
end

This works fine.

However there is a catch: When I now change code in ProductsController, the changes are not picked up automatically. I have to restart the app manually (touch tmp/restart.txt)

What's the way to tell the Rails stack that it needs to reload this piece of middleware whenever code is changed?

3

There are 3 answers

0
James On

It looks like Rails is loading the copy of ProductsController at the moment the server is started into your middleware, and keeping that cached copy. I am not 100% sure about this, but what happens when you try to load the middleware with a proc? e.g.

stacked_router = Proc.new {
  Example::Middleware::StackedRouter.new(ProductsController.action(:show))
}

That should hopefully make it load a new middleware (and a new ProductsController) on each request. It probably will be unhelpful in production. I don't have similar code in front of me, so I can't test this.

2
carpamon On

If you are using Pow you can use the powder gem to easily restart Pow without the need to execute touch tmp/restart.txt command. See https://github.com/Rodreegez/powder.

0
Woahdae On

The tool you want is autoload. When Rails reloads in dev, it iterates through autoloaded and explicitly defined 'unloadable' constants and removes them (the watch stack manages this).

In your plugin's main lib directory, ex. lib/stacked_router.rb, if you declared:

autoload Example::Middleware::StackedRouter, 'lib/middleware/stacked_router'

I think you should get the behavior you're looking for.

Note that if you don't like autoload (people argue it interferes with the ability to monkeypatch), you can mark a constant (unloadable, then require the file with load (just using require won't pick up the file the second time). or + load. This would also load the class every request in prod, so maybe conditionally load or require depending on env? Autoload is probably the better choice IMO.

Note that I found the actual Rails code (not Rails documentation) to be the best reference for this.