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
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 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 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.
In Rails notifications, I am subscribing to "process_action.action_controller", and would like to add more attributes to the payload. How can I do that?
I have tried using append_info_to_payload, but this seems to do nothing.
module AppendExceptionPayload
module ControllerRuntime
extend ActiveSupport::Concern
protected
def append_info_to_payload(payload)
super
payload[:happy] = "HAPPY"
end
end
end
The subscription and above code is in a Rails engine, so this is where I make the call to add it:
require 'append_exception_payload'
module Instrument
class Engine < ::Rails::Engine
ActiveSupport.on_load :action_controller do
include AppendExceptionPayload::ControllerRuntime
end
end
end
After putting up the bounty, I found a solution myself. Rails handles this really cleanly.
Basically, the append_info_to_payload method is meant exactly for this.
So to include session information and signed_in user information I added this to my application_controller.rb:
def append_info_to_payload(payload)
super
payload[:session] = request.session_options[:id] rescue ""
payload[:user_id] = session[:user_id] rescue "unknown"
end
So i jumped in and had a look at the api for the process_action method (private) and the append_info_to_payload instance method (public) and the proccess action method seems to call append_info_to_payload in its code like so:
ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
result = super
payload[:status] = response.status
append_info_to_payload(payload)
result
end
and append_info_to_payload works something like this
def append_info_to_payload(payload) #:nodoc:
payload[:view_runtime] = view_runtime
end
I can suggest trying payload[:view_runtime] instead of payload[:happy] or trying to use payload[:status]
Let me know how you get on and I will try help more, unfortunately there is really no documentation for this stuff.
I have an expensive (time-consuming) external request to another web service I need to make, and I'd like to cache it. So I attempted to use this idiom, by putting the following in the application controller:
def get_listings
cache(:get_listings!)
end
def get_listings!
return Hpricot.XML(open(xml_feed))
end
When I call get_listings! in my controller everything is cool, but when I call get_listings Rails complains that no block was given. And when I look up that method I see that it does indeed expect a block, and additionally it looks like that method is only for use in views? So I'm guessing that although it wasn't stated, that the example is just pseudocode.
So my question is, how do I cache something like this? I tried various other ways but couldn't figure it out. Thanks!
an in-code approach could look something like this:
def get_listings
#listings ||= get_listings!
end
def get_listings!
Hpricot.XML(open(xml_feed))
end
which will cache the result on a per-request basis (new controller instance per request), though you may like to look at the 'memoize' helpers as an api option.
If you want to share across requests don't save data on the class objects, as your app will not be threadsafe, unless you're good at concurrent programming & make sure the threads don't interfere with each other's data access to the shared variable.
The "rails way" to cache across requests is the Rails.cache store. Memcached gets used a lot, but you might find the file or memory stores fit your needs. It really depends on how you're deploying and whether you want to prioritise cache hits, response time, storage (RAM), or use a hosted solution e.g. a heroku addon.
As nruth suggests, Rails' built-in cache store is probably what you want.
Try:
def get_listings
Rails.cache.fetch(:listings) { get_listings! }
end
def get_listings!
Hpricot.XML(open(xml_feed))
end
fetch() retrieves the cached value for the specified key, or writes the result of the block to the cache if it doesn't exist.
By default, the Rails cache uses file store, but in a production environment, memcached is the preferred option.
See section 2 of http://guides.rubyonrails.org/caching_with_rails.html for more details.
You can use the cache_method gem:
gem install cache_method
require 'cache_method'
In your code:
def get_listings
Hpricot.XML(open(xml_feed))
end
cache_method :get_listings
You might notice I got rid of get_listings!. If you need a way to refresh the data manually, I suggest:
def refresh
clear_method_cache :get_listings
end
Here's another tidbit:
def get_listings
Hpricot.XML(open(xml_feed))
end
cache_method :get_listings, (60*60) # automatically expire cache after an hour
You can also use cachethod gem (https://github.com/reneklacan/cachethod)
gem 'cachethod'
Then it is deadly simple to cache method's result
class Dog
cache_method :some_method, expires_in: 1.minutes
def some_method arg1
..
end
end
It also supports argument level caching
There was suggested cache_method gem, though it's pretty heavy. If you need to call method without arguments, solution is very simple:
Object.class_eval do
def self.cache_method(method_name)
original_method_name = "_original_#{method_name}"
alias_method original_method_name, method_name
define_method method_name do
#cache ||= {}
#cache[method_name] = send original_method_name unless #cache.key?(method_name)
#cache[method_name]
end
end
end
then you can use it in any class:
def get_listings
Hpricot.XML(open(xml_feed))
end
cache_method :get_listings
Note - this will also cache nil, which is the only reason to use it instead of #cached_value ||=
Late to the party, but in case someone arrives here searching.
I use to carry this little module around from project to project, I find it convenient and extensible enough, without adding an extra gem. It uses the Rails.cache backend, so please use it only if you have one.
# lib/active_record/cache_method.rb
module ActiveRecord
module CacheMethod
extend ActiveSupport::Concern
module ClassMethods
# To be used with a block
def cache_method(args = {})
#caller = caller
caller_method_name = args.fetch(:method_name) { #caller[0][/`.*'/][1..-2] }
expires_in = args.fetch(:expires_in) { 24.hours }
cache_key = args.fetch(:cache_key) { "#{self.name.underscore}/methods/#{caller_method_name}" }
Rails.cache.fetch(cache_key, expires_in: expires_in) do
yield
end
end
end
# To be used with a block
def cache_method(args = {})
#caller = caller
caller_method_name = args.fetch(:method_name) { #caller[0][/`.*'/][1..-2] }
expires_in = args.fetch(:expires_in) { 24.hours }
cache_key = args.fetch(:cache_key) { "#{self.class.name.underscore}-#{id}-#{updated_at.to_i}/methods/#{caller_method_name}" }
Rails.cache.fetch(cache_key, expires_in: expires_in) do
yield
end
end
end
end
Then in an initializer:
# config/initializers/active_record.rb
require 'active_record/cache_method'
ActiveRecord::Base.send :include, ActiveRecord::CacheMethod
And then in a model:
# app/models/user.rb
class User < AR
def self.my_slow_class_method
cache_method do
# some slow things here
end
end
def this_is_also_slow(var)
custom_key_depending_on_var = ...
cache_method(key_name: custom_key_depending_on_var, expires_in: 10.seconds) do
# other slow things depending on var
end
end
end
At this point it only works with models, but can be easily generalized.
Other answers are excellent but if you want a simple hand-rolled approach you can do this. Define a method like the below one in your class...
def use_cache_if_available(method_name,&hard_way)
#cached_retvals ||= {} # or initialize in constructor
return #cached_retvals[method_name] if #cached_retvals.has_key?(method_name)
#cached_retvals[method_name] = hard_way.call
end
Thereafter, for each method you want to cache you can put wrap the method body in something like this...
def some_expensive_method(arg1, arg2, arg3)
use_cache_if_available(__method__) {
calculate_it_the_hard_way_here
}
end
One thing that this does better than the simplest method listed above is that it will cache a nil. It has the convenience that it doesn't require creating duplicate methods. Probably the gem approach is cleaner, though.
I'd like to suggest my own gem https://github.com/igorkasyanchuk/rails_cached_method
For example:
class A
def A.get_listings
....
end
end
Just call:
A.cached.get_listings