Callbacks when autoloading constants

Hi all

I work on some fairly large ruby on rails applications and boot up time in development always starts to become an issue, using a preloader like zeus or spring helps but reducing the first boot time is always useful. One of the big slowdowns for booting these large apps usually turns out to be initializers touching constants which then triggers an autoload. There is a popular pattern used where the class definition and configuration are split up into two, you then end up with a file in config/initializers which will run something like: MyClass.configure, which then triggers MyClass to be defined and slows down the boot process.

It would be nice instead to be able to run the configuration code when the constant gets autoloaded, something like this:

ActiveSupport::Dependencies.on_load(‘MyClass’) do

MyClass.configure do |config|

config here

end

end

That way we still have the benefit of splitting up the class definition and the configuration, but we aren’t eagerly loading the class in the initializer, only later when it’s needed.

I’ve written a small patch which implements this behaviour: Add hooks when autoloading constants · mariovisic/rails@b5ecbf3 · GitHub

As you can see the logic is only a few lines.

Does anyone have any thoughts on if this patch would be useful to others?

Cheers

Mario

I would find this useful. I wanted something like this just recently.

Brian

Let me understand the use case.

Autoloading constants in initializers in general is not a good idea because the initializer only runs when the application boots.

If MyClass.configure stores anything in the class object autoloaded at boot time, in development mode[*] when code changes all autoloaded constants are wiped. In particular next time you hit MyClass the constant is going to store a different class object that has no relationship with the other one.

Why is the application autoloading in initializers?

Xavier

[*] Technically depends on the configuration.

Xavier, yes, you’re right, usually config.to_prepare would be wrapped around the MyClass.configure to ensure it is run again when the constant is removed and autoloading brings in a new one.

Here’s an actual example from an application i’m working on now. We have a class which handles outages, think feature flips but for services going down. There’s a model which records the list of outages OutageSet. in an initializer we have a list of outages which are setup, so like this:

config/initializers/outages.rb

OutageSet.configure do |config|

config.outage :search, { some: ‘options’ }

end

Many gems follow this same pattern, but the we don’t run into the same issues as the constants are usually eagerly loaded. It only become a problem when the constants in our initialiazer are using autoloading. I think autoloading is GOOD here, as it reduces application boot time, but it would be nice to be able to ensure the configuration runs as soon as the constant is available rather than negating the benefits we get from autoloading.

Also, yes, the code could be re-written so that internally OutageSet knows about how to load its configuration, but I think the seperation of class and configuration is a very good pattern, which many gems already employ, it just needs a small addition to get it working well with autoloading.

I’ve been thinking about this and I am not totally convinced by the configuration use case. Let me explain.

A callback would technically allow that kind of class configuration idiom, but let’s for a moment imagine the initializer (writing on an iPad):

config/initializers/invoice_configuration.rb

ActiveSupport::Dependencies.on_autoload(‘Invoice’) do

Invoice.configure do

self.generator = MagicPDF

end

end

Looks too convoluted to me for the trivial task that configuring a class should be. Also it looks kinda unbalanced to me, there’s that framework level instruction that to me looks out of place intuitively.

Think about the maintainer, what on earth is this code? The code comment to explain that idiom would be longer than the code itself. Smellish.

Also, the maintainer would wonder, therefore this class is only configured if autoloaded? What if eager loaded? More stuff to explain.

In Rails you typically configure stuff in two ways: config files, or class level. That is, it is not idiomatic in Rails to split User like

config/initializers/user_configuration.rb

User.configure do

has_many :posts

end

Rather, you just do it at the class level

class User < ActiveRecord::Base

has_many :posts

end

which happens to play well with autoloading.

Gems do not have to deal with autoloading, they can choose other patterns, but in Rails we have this constraint, and have to choose idioms that look good and work well.

I encounter loading problems when developing a gem, which adds cache_at method to active_model.

In the following example, the cache_at method needs to add after_commit callback to Profile, but Profile may has not been loaded yet.

class User < ActiveRecord::Base has_one : profile cache_at :profile end

It will be useful if there is a way to know when the constant gets autoloaded.

ActiveSupport::Dependencies.onload(‘Profile’) do

after_commit ->{ clean_the_cache }

end

Mario Visic於 2016年1月23日星期六 UTC+8上午2時10分32秒寫道: