Reloading rails middleware without restarting the server in development - ruby-on-rails

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.

Related

How can I avoid a constant when I want a global object in rails?

I am want to add S3 file storage to my rails 5 application. Since I am using heroku I used their tutorial which says to just create a constant named S3_BUCKET in your config/initializers/aws.rb and you can use that constant everywhere.
The heroku code looks like this:
#config/initializers/aws.rb
S3_BUCKET = Aws::S3::Resource.new.bucket(ENV['S3_BUCKET'])
The problem with this is that I have to override this constant for the specs to work.
I have this alternative solution (which is sadly not working):
#lib/aws_helpers/s3.rb
module AWSHelpers
module S3
class << self
attr_accessor :configuration
def configure
self.configuration ||= Configuration.new
yield(configuration)
end
def bucket
#bucket ||= Aws::S3::Resource.new.bucket(configuration.s3_bucket)
end
end
class Configuration
attr_accessor :s3_bucket, :aws_access_key_id, :aws_secret_access_key_id
end
end
end
#config/initializers/aws.rb
AWSHelpers::S3.configure do |config|
config.s3_bucket = ENV['S3_BUCKET']
config.aws_access_key_id = ENV['AWS_ACCESS_KEY_ID']
config.aws_secret_access_key_id = ENV['AWS_SECRET_ACCESS_KEY']
end
What I want to be able to do in a controller is:
AWSHelpers::S3.bucket.object(a_key)
Unfortunately this fails and tells me that bucket method can't return anything because the configuration is nil.
What I know:
the aws initializer gets executed and when I add puts in all the methods I can see the correct output when my server starts via rails s
the controller knows about the service module or it would not even get to the bucket method
the code works when I dump the content of config/initializers/aws.rb into the controller
I really would like to know why the code above is not working. It seems to set up everything correctly and then when I want to use it in the controller suddenly things are as if i'd never called configure.
I am also open to other suggestions on how to do this. Just using the constant is not an option because it has to be changed for the specs to work.
This code might look strange, but it's actually exactly what you'd want in this situation. Keep in mind this situation is a bit irregular, it's for configuring a plugin that has external API dependencies and associated keys that must be populated before the rest of the code works.
It's ugly from an implementation perspective, but from a usability perspective it's nice. You can just do AWSHelpers::S3.configure do |config| as shown in the initializer. All that code is to make those semantics work properly.
This is a cheap trick, but it works.
in config/application.rb:
module YourApp
class Application < Rails::Application
def s3_bucket
#s3_bucket ||= begin
# your stuff
end
end
end
end
Rails.application.s3_bucket.object(a_key)

Rails 4.2 - Made a 'services' directory for PORO helpers but console/controllers etc don't see it

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)

Rails run code before every request before application.rb

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.

Can I monkey-patch an existing ActiveRecord Observer?

I'm trying to monkey patch an existing ActiveRecord Observer; specifically, IssueObserver from the code base of Redmine 1.0.4.
In a plugin's init.rb, I have include the *patch.rb file:
require File.join(File.dirname(__FILE__), 'lib/issue_observer_patch.rb')
IssueObserver.send :include, IssueObserverPatch
This is done outside the Redmine::Plugin.register block.
The module does something like:
module IssueObserverPatch
def self.included(base)
base.send :alias_method_chain, :after_create, :audit
end
def after_create_with_audit(issue)
after_create_without_audit(issue)
issue.logger.info('***'*50)
# Insert a new Audit instance.
end
end
However, when I run the server (using script/server, which is using Mongrel), the patch seems to do nothing. The ***..*** string does not get logged when I create a new issue.
By changing the IssueObserver and including a sentence to log the after_create event, and then running the server and creating an issue; I can see this log, but not the log on after_create_with_audit method.
Is there any proper way to patch an ActiveRecord Observer?
It seems that Rails VM are loaded and unloaded on demand. So, changing the code on the init.rb to the following solves the problem:
config.to_prepare do
IssueObserver.send :include, IssueObserverPatch
end
This makes this block of code to be execute per VM on production and per request on development.

changing ruby modules without restarting rails application

In my rails application, i have modules which are required and included in the controllers.
The problem is: i have to restart the application every time i make any changes in these modules.
Any solutions?
Example
included module
#rails_application/lib/services/test.rb
module Services
module TestService
def start
'Service started successfully'
end
end
end
controller
#rails_application/app/controllers
class TestController < ApplicationController
require 'services/test.rb'
include Services::TestService
def index
render :text => start
end
end
In development, it should reload every you access.
In production mode, you can turn off cache by modifying
config/environments/production.rb
Change the following line to false.
config.cache_classes = false
And restart the application.
It reloads the changes without restarting the server.
Update
You might try load instead of require.
load 'services/test.rb'

Resources