I want to run a piece of code in rails app before every request. Also, it should run before even reaching application_controller.rb.
I know that we can put such stuff in config/initializers or application.rb. But, I want to run this before every request.
Sounds like a job for Rack middleware. You can checkout the Rails on Rack Guide and the RailsCast for details.
So put something like the following in lib:
#lib/my_app_middleware.rb
class MyAppMiddleware
def initialize(app)
#app = app
end
def call(env)
# place the code that you want executed on every request here
end
end
And the following in config/application.rb to enable the middleware
config.middleware.use MyAppMiddleware
Check that its inserted ok:
rake middleware
Thats it!
You'll want to write some Rack Middleware. It's easy to do, here's a simple example which gets the subdomain for the purpose of multi tenant scoping:
class ClientSetup
def initialize(app)
#app = app
end
def call(env)
#request = Rack::Request.new(env)
Multitenant.current_tenant = Tenant.find_by_subdomain!(get_subdomain)
#app.call(env)
end
private
def get_subdomain
host = #request.host
return nil unless !(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
subdomain = host.split('.')[0..-3].first
return subdomain unless subdomain == "www"
return host.split('.')[0..-3][1]
end
end
There's loads more examples around. You then need to add this class to your middleware stack with:
config.middleware.use 'ClientSetup'
in your application.rb.
It's usually a subclass of ApplicationController that gets called when routing dispatches to one of your actions. That being said, if you really want to execute code before the controller is even called (before the before_filters ... etc) then you can modify the chain of middlewares in Rails like so:
config.middleware.insert_after(Rails::Rack::Logger, MyCustomMiddlewareClass)
You can read here for more info: http://guides.rubyonrails.org/rails_on_rack.html#action-dispatcher-middleware-stack.
The example above may change depending on what you are trying to do.
Related
We have a use case for mounting a mock engine to process sessions when developing locally in which a custom session middleware calls the mock engine via Net::http request when a request comes through.
When there is code change, the reloader is triggered and here calls the ActiveSupport::Dependencies to start unloading. Then the request is pass down to our custom session middleware and the http request is fired.
However since the http request calls to a mountable engine, it goes thought the same middlewares again and the reloader unloads all the dependencies again which cause the first reload to timeout. So the goal is to be able to skip the reload for the second request.
I added the follow code to ActionDispatch::Reloader here and it does exactly what I wanted.
class Reloader < Executor
def initialize(app, executor)
super(app, executor)
end
def call(env)
request = ActionDispatch::Request.new(env)
return #app.call(env) if skip_request?(request)
super(env)
end
def skip_request?(request)
request.path_info.start_with?('/session')
end
end
Then I want to make this cleaner figured to pull that out completely to a module and just do a swap like this from an initializer
app.config.middleware.swap(::ActionDispatch::Reloader, MyModule::CustomReloaderMiddleware)
Here is the module
require 'action_dispatch'
module MyModule
class CustomReloaderMiddleware < ActionDispatch::Executor
def initialize(app, executor)
#app, #executor = app, executor
end
def call(env)
request = ActionDispatch::Request.new(env)
return #app.call(env) if skip_request?(request)
super(env)
end
def skip_request?(request)
request.path_info.start_with?('/session')
end
end
end
But I ran into a couple issues.
Uncaught exception: wrong number of arguments (given 1, expected 2) from for the initialize in MyModule, when I starts the server. Then I tried the following
#1
def initialize(app, executor = nil)
#app, #executor = app, executor
end
#2
def initialize(app, executor = nil)
#app, #executor = app, ActiveSupport::Reloader
end
Both of them starts the service correctly and I see the request going through this middleware but it does not reload the code.. So I wondered what is the correct way of swapping ActionDispatch::Reloader with a custom reloader ?
You need to pass your middleware's additional argument to the swap call:
app.config.middleware.swap(::ActionDispatch::Reloader, MyModule::CustomReloaderMiddleware, app.reloader)
That's the same argument given when ActionDispatch::Reloader is first added -- it's the application's reloader, which is a more specifically configured subclass of AS::Reloader (so you were on the right track).
I have read this question in some length, particularly this answer. This may be the same, too. I'm of the opinion that they are for an older version of rack+rails than I am using now.
I have a rack middleware:
config.middleware.insert_before(Rack::Runtime, Rack::Rewrite) do
r301 %r{^/reviews/company/(\d+)}, lambda { |match, _rack_env|
company = Company.find_by_id(match[1])
case company.reviews.count
when 0
"/company-reviews"
when 1..3
"/#{company.slug}/reviews/"
# set no_index = true
else
"/#{company.slug}/reviews/"
# set no_index = false
end
}
end
Within those non-zero clauses, I would like to set a no_index variable to be available in the controller.
module ApplicationHelper
def application_meta_tags
#application_meta_tags.merge(
'no-index' => no_index_from_rack
)
end
end
Inside of the lambda in rack, I can do
request = Rack::Request.new(env)
request.session['no-index']=true
but it doesn't not appear in the controller scope.
request.session.keys
# [
# [0] "session_id",
# [1] "_csrf_token"
# ]
Since similar-looking answers have not worked, I wonder is this due to
I didn't implement them correctly
They were done inside the lambda scope
something else...
I am open to altogether-different strategies to pass data between rack and rails.
Update
I am currently using 'ENV' and/or Rails.configuration but this is not session-based, and I must un-set the variable after every use. Even then, I suspect that a race condition may nip me.
Is this a place I can set headers that will be later available to Rails? I'm trying to understand what is the right concept for passing data between these apps / contexts.
You definitely should not use ENV or Rails.configuration because they are global variables, and as you know global variables are evil. And as you said you will have race conditions.
If there is no reason to store the no_index boolean in the session, you should directly use the env variable :
Your middleware :
class Middleware
def initialize(app)
#app = app
end
def call(env)
env['app.no_index'] = true
#app.call(env)
end
end
Your controller/view :
class Controller
def new
env['app.no_index'] # is true
end
end
I have some logic that is going to manipulate data before starting a job queue. However, inside the controller and also in the rails console I cannot seem to access the classes. Example:
In app/services/hobo_service.rb I have
class HoboService
def initialize
#api = Hobos::Api.new
end
def run
hobo
end
private
attr_reader :api
def hobo
api.hobo
end
end
However, if in my relevent controller I put
...
def create
#name = HoboService.new.run
end
...
Raises an exception saying the object cannot be found.
It seems as if all in the app directory should be in the pipeline and available. What am I missing here? Haven't been on Rails since 3.2 until recently.
I'm not sure why a subdirectory of app would be ignored, but let's try the simple solution- what happens when you add this to the Application class in your application.rb?
config.autoload_paths += %W(#{config.root}/app/services)
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".
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!
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)
In Rails 6 with the new default Zeitwork code loader, this works for me:
# at the top of config/application.rb, after Bundler.require
# Load the middleware. It will later be hot-reloaded in config.to_prepare
Dir["./app/middleware/*.rb"].each do |middleware|
load middleware
end
Below it in the section that configures your class Application, add hot-reloading in config.to_prepare:
middleware = "#{Rails.root}/app/middleware"
Rails.autoloaders.main.ignore(middleware)
# Run before every request in development mode, or before the first request in production
config.to_prepare do
Dir.glob("#{middleware}/*.rb").each do |middleware|
load middleware
end
end
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.
I'm trying to compare the start time of a response to various places inside the code. Does rails have any magic that gives you response start time, or would I have to write my own middleware?
I think the easiest way is to use custome middleware
Railscast : http://asciicasts.com/episodes/151-rack-middleware
Not tested example
class TimestampMiddleware
def initialize(app)
#app = app
end
def call(env)
env[:timestamp] = Time.now
#app.call(env)
end
end
Register middleware
Rails::Initializer.run do |config|
config.middleware.use "TimestampMiddleware"
end