Rails: Have initializer load before application.rb - ruby-on-rails

I have an initializer named _settings.rb that looks like this
class Settings < Settingslogic
source "#{Rails.root}/config/application.yml"
namespace Rails.env
end
My application.yml defines a value for a custom setting I call environhost.
I call it using:
Settings.environhost
This works fine, EXCEPT for when I try to call the value in my /app/config/application.rb
config.action_controller.asset_host = Settings.environhost
For this, I get an uninitialized constant.
Is there anyway I can put a pointer in my application.rb to load _settings.rb before
config.action_controller.asset_host = Settings.environhost
is loaded? What's the best way to do this?
http://guides.rubyonrails.org/initialization.html

Rails own configuration will be always loaded before any custom things, that's for sure. Otherwise can you imagine what a mess :)
The solution is not to try to load before Rails configuration. Instead, hook into initializer to add your own logic to override Rails default.
Railtie is the place you can do that without sweat. Here you can access config method shared in all initializers including Rails.
module MySettings
def self.environhost
"foobar"
end
class MySettingsRailtie < Rails::Railtie
config.action_controller.asset_host = MySettings.environhost
end
end
Side note: In most cases you should be fine to set assets host as mu_is_too_short commented. If you need anything other than that, you can use custom intializer by Railtie.

Related

How can I extend gem class in Rails 6/Zeitwerk without breaking code reloading?

How do I extend a class that is defined by a gem when I'm using rails 6 / zeitwerk?
I've tried doing it in an initializer using require to load up the class first.
I've tried doing it in an initializer and just referencing the class to let autoloading load it up first.
But both of those approaches break auto-reloading in development mode.
I've tried putting it in lib/ or app/, but that doesn't work because then the class never gets loaded from the gem, since my new file is higher up in the load order.
There is a similar question here, but that one specifically asks how to do this in an initializer. I don't care if it's done in an initializer or not, I just want to figure out how to do it some way.
What is the standard way of doing something like this?
I do have one nasty hack that seems to be working, but I don't like it (update: this doesn't work either. reloading is still broken):
the_gem_root = $LOAD_PATH.grep(/the_gem/).grep(/models/).first
require("#{the_gem_root}/the_gem/some_model")
class SomeModel
def my_extension
...
end
end
I know is late, but this was a real pain and someone could find it helpful, in this example I'll be using a modules folder located on app that will contain custom modules and monkey patches for various gems.
# config/application.rb
...
module MyApp
class Application < Rails::Application
config.load_defaults(6.0)
overrides = "#{Rails.root}/app/overrides"
Rails.autoloaders.main.ignore(overrides)
config.to_prepare do
Dir.glob("#{overrides}/**/*_override.rb").each do |override|
load override
end
end
end
end
Apparently this pattern is called the Override pattern, it will prevent the autoload of your overrides by zeitwerk and each file would be loaded manually at the end of the load.
This pattern is also documented in the Ruby on Rails guide: https://edgeguides.rubyonrails.org/engines.html#overriding-models-and-controllers

Accessing Rails engine's URL helpers in initializer

I'm trying to access the url helpers in my engine to set up rack-cors. Right now, I've hard-coded the strings for one of the urls in the rack-cors middleware configuration. I have read the order in which Rails initializers are run, and at this point in the load order I should have the engine routes available to me. I thought I would have them available at the event add_routing_paths, but I couldn't find the routes after digging around using pry. Another statement that leads me to think I'm doing this incorrectly is that the docs say: "Some parts of your application, notably routing, are not yet set up at the point where the after_initialize block is called." According to this list:
require "config/boot.rb" to setup load paths
require railties and engines
Define Rails.application as "class MyApp::Application < Rails::Application"
Run config.before_configuration callbacks
Load config/environments/ENV.rb
Run config.before_initialize callbacks
Run Railtie#initializer defined by railties, engines and application.
One by one, each engine sets up its load paths, routes and runs its config/initializers/* files.
Custom Railtie#initializers added by railties, engines and applications are executed
Build the middleware stack and run to_prepare callbacks
Run config.before_eager_load and eager_load! if eager_load is true
Run config.after_initialize callbacks
I'm trying to hook into (7), but perhaps routes aren't available until (11)?
module Zillow
class Engine < ::Rails::Engine
isolate_namespace Zillow
# Rails.application.routes.url_helpers
initializer "zillow.cors", after: :set_routes_reloader do |app|
require 'pry'; binding.pry
app.config.app_middleware.insert_before 0, Rack::Cors do
allow do
origins 'localhost:3000'
resource '/zillow/search_results', methods: :get
end
end
end
end
end
Here is the output of my routes
zillow /zillow Zillow::Engine
Routes for Zillow::Engine:
api_deep_comps GET /api/deep_comps(.:format) zillow/api#deep_comps
api_zestimate GET /api/zestimate(.:format) zillow/api#zestimate
api_search_results GET /api/search_results(.:format) zillow/api#search_results
api_updated_property_details GET /api/updated_property_details(.:format) zillow/api#updated_property_details
You can fire your own event when routes are loaded and then subscribe to that event during initialization to get routes data. For that:
Add this to the end of config/routes.rb file (outside of routes.draw block)
ActiveSupport::Notifications.instrument 'routes_loaded.application'
Subscribe to this event in initialization code and use URL helpers!
ActiveSupport::Notifications.subscribe 'routes_loaded.application' do
Rails.logger.info Rails.application.routes.url_helpers.home_path
end
For more information see:
Subscribing to an event
Creating custom events
Load it just after initialize.
I needed to use root_url in my initializer but it wasn't properly initialized. I tried using the Pub / Sub method that Anton Styagun suggests but even when that's called the URL Helpers were not properly initialized.
Instead, I opted to load my initializer file manually just after the Rails app itself has initialized. This takes a few steps:
Move your initializer from config/initializers/ to lib/.
Require your file in environment.rb just after your application initializes. Ours looks something like this:
CNTRAL::Application.initialize!
require "my_initializer" # Omit the `lib/` because that's the load path it looks in.
Now, inside of my_initializer.rb I was able to call root_url by doing this:
Rails.application.routes.url_helpers.root_url
NOTE: It's important not to just use include Rails.application.routes.url_helpers. It raised an exception on me.
I also added a comment in both the initializer and the environment.rb of where this was loaded/required so it's more obvious to myself in the future and other engineers.
Alternative Method
You can also use the following method and it works well:
In your application.rb file, add:
config.after_initialize do
Rails.application.reload_routes! # Necessary to load the Routes.
require "my_initializer"
end
After looking into this further and reading the rack-cors example done in Rails3, it may not be possible to retrieve the route helpers inside the initializer at any point in time.

Adding to Rails.application.config before initializers run

I want to inject a custom property (hash map) into my Rails.application.config. It seems that the way to do it is simply to assign a new variable in environment.rb:
Rails.application.config.feature_map = { :email => true }
I need to access this map through various places in my application, like the user model, controllers, and rake tasks.
Other gems, like devise, also need access to this. The problem is that adding it to environment.rb seems to be too early in the application life-cycle.
I have code in initializers/devise.rb like this:
if Rails.application.config.feature_map[:email] = true
The server complains that this field doesn't exist.
I also use it to add additional validation in my user model:
if Rails.application.config.feature_map.enabled?(:username)
validates_length_of :username, :in => 3..50
I also get a runtime error here about undefined feature Rails.application.config.feature_map
Where can I move this so that I can access it as early as in initializers and in my model class? I tried moving it into a new initializers/feature_map.rb file, but that didn't work either.
Put it in config/application.rb:
module MyRailsApp
class Application < Rails::Application
config.feature_map = ActiveSupport::OrderedOptions.new
config.feature_map.email = true
end
end
Anything you set in there will be default for all environments, but can be overridden per environment in config/environments/*.rb.
Gems like Figaro and .env will help you load up your config even before the loading of initializer.rb
Unless there is a strong reason that you wouldn't wanna use environment variables, I would recommend using either of those gems since they are the recommended way of adding your custom configs.
Edit: See Jimmy Cuadra's answer above, which I ended up going with.
I found an alternative solution: this answer to manipulate the order of initializers.
I can rename my initializer to 00_feature_map.rb and it loads first.

Rails doesn't load my module from lib

I have a bunch of custom classes in my Rails 3.2 app in lib folder: i.e. extending ActiveRecord, etc. It all works fine.
However I'm trying to add a couple of custom methods to FileUtils, i.e.
module FileUtils
def last_modified_file(path='.')
# blah ...
end
end
I put it in lib/file_utils.rb
In my application.rb I have
config.autoload_paths += %W(#{config.root}/lib)
My other custom classed are loaded but not the module.
I read (Best way to load module/class from lib folder in Rails 3? ) that I'm supposed to define a class inside module in order for Rails to pick it up and according to FileUtils.class - it should be Object < BasicObject.
So I tried
module FileUtils
class Object
def last_modified_file(path='.')
# blah ...
end
end
end
But that doesn't work either.
However when I fire up irb and just paste my code which effectivly puts my new code inside object and reinclude my module - it works fine.
Whaat amd I missing here?
Your patch is never going to be loaded because autoload is only invoked when Rails can't find a constant. Since the FileUtils constant already exists, the autoloader is never called, and your file is never loaded.
Simply require it from an initializer.
require File.join(Rails.root, "lib/file_utils.rb")

Constants set in environment.rb disappear in development mode

Someone who understands how rails caching works can really help me out here. Here's the code, nested inside of the Rails::Initializer.run block:
config.after_initialize do
SomeClass.const_set 'SOME_CONST', 'SOME_VAL'
end
Now if I run script/server and make a request, everything is dandy. However, on the second request to my Rails app, all goes to hell with an unitialized constant error. In production mode, I can make the second request successfully, meaning the constant is still there.
I've fixed the problem by changing the above to:
config.after_initialize do
require 'some_class' # in RAILS_ROOT/lib/some_class.rb
SomeClass.const_set 'SOME_CONST', 'SOME_VAL'
end
But now that means whenever I make a change to some_class.rb, I have to restart the server. Is there any way to set constants in an environment file and have them work correctly in development mode? Why does the constant exist on the first request, but not the following request?
UPDATE: Since environment.rb is only read when the Rails app is booted and I want both my lib files and models to be reloaded on each request, I was forced to move the constants into the some_class.rb file as follows:
if Rails.env.development?
const_set 'SOME_CONST', 'SOME_DEVELOPMENT_VAL'
end
And in environments/production.rb, I have the old const_set code.
UPDATE: An even better method using config.to_prepare is detailed below.
It only works on the first request in development mode because the classes are reloaded on each request. So on the first request the constant is set in the initializer, and all is good. Then on the next request, it reloads the class WITHOUT rerunning the bit from your initializer, so the constant isn't set from there on out.
It works in production mode because the classes aren't reloaded for each request, so you don't lose that bit of class state each time.
So you might want to set the constant either in the model, or in a config.to_prepare instead config.after_initialize. to_prepare is called before each request.
In the model:
class SomeClass < ActiveRecord::Base
MY_CONST = "whatever"
# You can access MY_CONST directly, but I tend to wrap them in a class
# method because literal constants often get refactored into the database.
def self.my_const
MY_CONST
end
end
In the config:
# This will run before every single request. You probably only want this in
# the development config.
config.to_prepare do
SomeClass.const_set 'SOME_CONST', 'SOME_VAL'
end
Production mode preloads all of the classes, whereas in development mode classes are loaded as needed, after the config files are read. Manually requiringing them in your configs forces the classes to be read before/during the config stage.

Resources