问题
I have a rails 4 app with middleware located at lib/some/middleware.rb which is currently injected into the stack though an initializer like so:
MyApp::Application.configure.do |config|
config.middleware.use 'Some::Middleware'
end
Unfortunately, any time I change something I need to restart the server. How can I reload it on each request in development mode? I've seen similar questions about reloading lib code with either autoloading or wrapping code in a to_prepare block but I'm unsure how that could be applied in this scenario.
Thanks, - FJM
Update #1
If I try to delete the middleware and then re-add it in a to_prepare block I get an error "Can't modify frozen array".
回答1:
I thought that at some point Rails was smart enough replacing middleware code at runtime, but I may be wrong.
Here is what I came up with, circumventing Ruby class loading craziness and leveraging Rails class reloading.
Add the middleware to the stack:
# config/environments/development.rb
[...]
config.middleware.use "SomeMiddleware", "some_additional_paramter"
Make use of auto-reloading, but make sure that the running rails instance and the already initialized middleware object keep "forgetting" about the actual code that is executed:
# app/middlewares/some_middleware.rb
class SomeMiddleware
def initialize(*args)
@args = args
end
def call(env)
"#{self.class}::Logic".constantize.new(*@args).call(env)
end
class Logic
def initialize(app, additional)
@app = app
@additional = additional
end
def call(env)
[magic]
@app.call(env)
end
end
end
Changes in Logic should be picked up by rails auto reloading on each request.
I think that this actually might become a useful gem!
回答2:
Building up on @phoet's answer we can actually wrap any middleware with this kind of lazy loading, which I found even more useful:
class ReloadableMiddleware
def initialize(app, middleware_module_name, *middleware_args)
@app = app
@name = middleware_module_name
@args = middleware_args
end
def call(env)
# Lazily initialize the middleware item and call it immediately
@name.constantize.new(@app, *@args).call(env)
end
end
It can be then hooked into the Rails config with any other middleware as its first argument, given as a string:
Rails.application.config.middleware.use ReloadableMiddleware, 'YourMiddleware'
Alternatively - I packaged it into a gem called reloadable_middleware
, which can be used like so:
Rails.application.config.middleware.use ReloadableMiddleware.wrap(YourMiddleware)
回答3:
Can you not simply use shotgun? If I understand your question you want to ensure the environment reloads on every change you make to your code. That is what shotgun will do.
来源:https://stackoverflow.com/questions/20874222/reloading-rails-middleware-without-restarting-the-server-in-development