Callbacks when autoloading constants

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
6 messages Options
Reply | Threaded
Open this post in threaded view
|

Callbacks when autoloading constants

Mario Visic
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: https://github.com/mariovisic/rails/commit/b5ecbf36fa087c59e716e1e85eb05a5bcb658e29
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

--
You received this message because you are subscribed to the Google Groups "Ruby on Rails: Core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/rubyonrails-core.
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: Callbacks when autoloading constants

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

Brian


On Fri, Jan 22, 2016 at 1:22 AM, Mario Visic <[hidden email]> wrote:
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: https://github.com/mariovisic/rails/commit/b5ecbf36fa087c59e716e1e85eb05a5bcb658e29
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

--
You received this message because you are subscribed to the Google Groups "Ruby on Rails: Core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/rubyonrails-core.
For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "Ruby on Rails: Core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/rubyonrails-core.
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: Callbacks when autoloading constants

Xavier Noria
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.

--
You received this message because you are subscribed to the Google Groups "Ruby on Rails: Core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/rubyonrails-core.
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: Callbacks when autoloading constants

Mario Visic
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.


On Saturday, January 23, 2016 at 6:49:17 AM UTC+11, Xavier Noria wrote:
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.

--
You received this message because you are subscribed to the Google Groups "Ruby on Rails: Core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/rubyonrails-core.
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: Callbacks when autoloading constants

Xavier Noria
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.



--
Sent from Gmail Mobile

--
You received this message because you are subscribed to the Google Groups "Ruby on Rails: Core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/rubyonrails-core.
For more options, visit https://groups.google.com/d/optout.
Reply | Threaded
Open this post in threaded view
|

Re: Callbacks when autoloading constants

Khiav REOY
In reply to this post by Mario Visic
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秒寫道:
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: <a href="https://github.com/mariovisic/rails/commit/b5ecbf36fa087c59e716e1e85eb05a5bcb658e29" target="_blank" rel="nofollow" onmousedown="this.href=&#39;https://www.google.com/url?q\x3dhttps%3A%2F%2Fgithub.com%2Fmariovisic%2Frails%2Fcommit%2Fb5ecbf36fa087c59e716e1e85eb05a5bcb658e29\x26sa\x3dD\x26sntz\x3d1\x26usg\x3dAFQjCNFH4QGeXNaZ5ZrIgGTDja4epXr0wg&#39;;return true;" onclick="this.href=&#39;https://www.google.com/url?q\x3dhttps%3A%2F%2Fgithub.com%2Fmariovisic%2Frails%2Fcommit%2Fb5ecbf36fa087c59e716e1e85eb05a5bcb658e29\x26sa\x3dD\x26sntz\x3d1\x26usg\x3dAFQjCNFH4QGeXNaZ5ZrIgGTDja4epXr0wg&#39;;return true;">https://github.com/mariovisic/rails/commit/b5ecbf36fa087c59e716e1e85eb05a5bcb658e29
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

--
You received this message because you are subscribed to the Google Groups "Ruby on Rails: Core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
To post to this group, send email to [hidden email].
Visit this group at https://groups.google.com/group/rubyonrails-core.
For more options, visit https://groups.google.com/d/optout.