changing ruby modules without restarting rails application - ruby-on-rails

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'

Related

Cache an http call results in rails

I am new to rails and get stuck on this problem.
The thing I am trying to do is:
I need to call service A to retrieve an idA and then I use idA to perform other actions. my actions in the controller is something like
class SomeController < ApplicationController
def someAction
idA = getIdAfromServiceA(config)
doSomethingElseToServiceB(idA)
end
end
Since the result from serviceA only depends on config, once the config is loaded, idA should not change. Therefore I want to cache idA.
I tried to use instance variable to cache it, no luck("getIdAfromServiceA is called is printed" on every request)
class SomeController
def getIdAfromServiceA(config)
#IdA ||= getIdAfromServiceAviaHTTP(config)
end
private
def getIdAfromServiceAviaHTTP(config)
puts "getIdAfromServiceAviaHTTP is called"
#make some http call
end
end
I also tried to put it in application.rb to cache it on start up. it shows error: undefined method 'getIdAfromServiceAviaHTTP' for SomeHelper:Module (NoMethodError)
module MyProject
class Application < Rails::Application
config.load_defaults 5.1
require_relative 'relativePathToSomeHelperModule'
config.idA = SomeHelper.getIdAfromServiceAviaHTTP(config)
end
end
So my question is, what's a good way to achieve this ? I've been googling for a while but end up in vain. Could you help me with it ? Thanks !
Create a ruby file in under /config/initializers/ and put that code there.
Option 1:
# config/initializers/ida_from_api.rb
IDA = SomeHelper.getIdAfromServiceAviaHTTP(config)
You can then use IDA through out the application.
Option 2:
# config/initializers/ida_from_api.rb
Rails.configuration.idA = SomeHelper.getIdAfromServiceA(config)
You can then use Rails.configuration.idA through out the application. Check Custom configuration for more details.
FYI - files under initializers loaded at the time of application startup.

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)

Reloading rails middleware without restarting the server in development

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.

Including methods to a controller from a plugin

Using Rails 2.3.11, I'm creating a plugin for Redmine that add methods to ApplicationController.
I've created the following module, in the plugin :
module ApplicationControllerPatch
def self.included(base) # :nodoc:
base.class_eval do
rescue_from AnException, :with => :rescue_method
def rescue_method(exception)
...
end
end
end
end
Now, if I include this module directly into the application_controller.rb file, like this:
class ApplicationController < ActionController::Base
include ApplicationControllerPatch
...
end
Everything works just fine, however I would like to avoid editing the core source by including this module from the plugin itself.
So far, if I do:
ApplicationController.send(:include, ApplicationControllerPatch)
directly from this module file (located in the plugin folder). This will load properly for the request and then it gets overwritten by the controller (I guess).
What's is the way to accomplish this ?
A common pattern is to use Dispatcher.to_prepare inside your plugin's init.rb. This is required because in development mode (or generally if config.cache_classes = false) Rails reloads all classes right before each request to pick up changes without the need to completely restart the application server each time.
This however means the you have to apply your patch again after the class got reloaded as Rails can't know which modules got injected later on. Using Dispatcher.to_prepare you can achieve exactly that. The code defined in the block is executed once in production mode and before each request in development mode which makes it the premier place to monkey patch core classes.
The upside of this approach is that you can have your plugins self-contained and do not need to change anything in the surrounding application.
Put this inside your init.rb, e.g. vendor/plugins/my_plugin/init.rb
require 'redmine'
# Patches to the Redmine core.
require 'dispatcher'
Dispatcher.to_prepare do
ApplicationController.send(:include, MyPlugin::ApplicationControllerPatch) unless ApplicationController.include?(RedmineSpentTimeColumn::Patches::IssuePatch)
end
Redmine::Plugin.register :my_plugin do
name 'My Plugin'
[...]
end
Your patch should always be namespaced inside a module named after your plugin to not run into issues with multiple plugins defining the same module names. Then put the patch into lib/my_plugin/application_controller_patch.rb. That way, it will be picked up automatically by the Rails Autoloader.
Put this into vendor/plugins/my_plugin/lib/my_plugin/application_controller_patch.rb
module MyPlugin
module ApplicationControllerPatch
def self.included(base) # :nodoc:
base.class_eval do
rescue_from AnException, :with => :rescue_method
def rescue_method(exception)
[...]
end
end
end
end
end
This kind of problem occurs only in dev because the classes are reloaded but not gems.
So add your send method in a config.to_prepare block within config/environments/development.rb
Read Rails doc concerning initialization process for further details.

Rails extending models without modifying the base file

I've got two Rails 2.3 applications, we'll call them admin and frontend. In admin I have all my models specified in app/models. In frontend I have those models symlinked. I would like to add frontend specific methods to a model that only show up for the frontend application, and not the admin app.
At first I tried just adding config.autoload_paths += "#{RAILS_ROOT}/app/augments/address.rb" with:
class Address
def hello
"hello world"
end
end
But that just wasn't loaded. Calls to Address.first.hello would be met with undefined method 'hello'.
If I require a file that does this:
Address.class_eval do
def hello
"hello world"
end
end
It is loaded once, and for the first hit in development it works, but all subsequent reloads it fails. This is due to config.cache_classes = false in development.
A semi-working solution is to run that from ApplicationController:
class ApplicationController < ActionController::Base
Address.class_eval do
def hello
"hello world"
end
end
end
Which does reload and works every time in dev andprod, but doesn't work for script/runner or script/console. (If this is the only solution I'm sure we could extract that out into a module and include ModelExtensions in ApplicationController.)
Is there something I can add to environment.rb or an initializer that will get reloaded every time in development?
To extend your class you should use module and include it in your model. Something like this:
module Address
def hello
"hello world"
end
end
This is an old but always interesing article on that argument: http://weblog.jamisbuck.org/2007/1/17/concerns-in-activerecord
To include the module only in frontend you should check if the module exists with:
Class A
include Address if defined? Address
end

Resources