Discourse Plugins and Rails 6 config/initializers Question

Hey Discourse Plugin Gurus,

Just a quick question about Discourse plugins and Rails initializers.

If a Discourse plugin has a directory called “config” and a subdirectory under “config” called “initializers” does the Discourse Rails app read all the plugin initializer files under the “initializers” directory like Rails 6 does?

The reason I ask is that I’m in the middle of writing a “back office” Rails 6 application (Rails only, no EmberJS or other JS framework on top) from scratch for a client and I have a directory under initializers like this:

./config/initializers/client/

… and all the initializers unique to the client are in the “client” subdirectory.

Rails 6 reads all files under the standard initializers directory (even subdirectories); and so I was wondering if Discourse plugins, with a similar directory structure for initializers, will behave as Rails 6 does and read all the initializers in the plugin in a manner like this:

./plugins/my_plugin/config/initializers/myclient/
      client_initializer1.rb
      client_initializer2.rb
      client_initializer2.rb

… without registering these assets in the plugin.rb file?

Thanks!

PS: I looked in about 10 Discourse plugins on GitHub and none of the ones I looked at had initializers under the config directory. That’s why I decided to post the question (and my Rails dev environment is not set up for Discourse at the moment, it’s all setup for the client).

2 Likes

I also want to know the Discourse Plugin different than a common Rails Engine. I were just look into the source code but didn’t get much.

After skimming through this Ruby on Rails Guides: Configuring Rails Applications
I think plugin.rb is kind of the same thing. Most of the plugins that are decent size use it as an entry point to the logic rather than the logic itself.

Yeah, I read that Rails guide a lot lately. Basically, I’m “OK” with how Rails 6 config works (still learning, but getting more and more comfortable each day).

I am just wondering if we can use the same structure in a Discourse plugin (for initializers), if Rails will read the initializers in the subdirectories like they do in Rails 6; or do we have to register the plugin initializer directory as a configuration “asset” (for a lack of a better word) in plugin.rb.

I was reading up on Rails plugins yesterday and comparing a bit as well.

Discourse doesn’t read any ruby file other than plugin.rb as far as I know. It does read the js and config files. All the ruby files have to be required in plugin.rb.

3 Likes

I believe so too.

You load any additional Ruby files you need from within plugin.rb.

Here’s an example from the Follow plugin:

https://github.com/paviliondev/discourse-follow/blob/0290c428210199e35972e17746fefa8c67f24070/plugin.rb#L31

1 Like

That’s kinda my impression reading though a number of Discourse plugins on GitHub.

Seems Discourse only reads plugin.rb and we have to load all other Ruby files we might want, like initializers, in plugin.rb.

However, I was hoping I was wrong and there might be a “deeper” integration of Discourse plugins and Rails; as I have been getting very spoiled on how great Rails 6 is.

Thanks for confirming as well.

1 Like

I think there should be a way to require initializer code in plugin.rb too.
Did you try this in plugin.rb

Rails.application.config.before_initialize do
  # initialization code goes here
end

Yes, that is not a problem calling and loading the initializers in plugin.rb.

The reason for my question was because I am currently writing a fairly large Rails 6 app for a business, converting their legacy back office scripts (over a few decades) to a Rails app, and to add initializers, I just have a subdirectory under config/initializers (in Rails) and all my initializer files are included so nicely without any need to write any code to include these files in Rails.

Thanks for all your replies. Much appreciated!

1 Like

Yeah, I would love those rails perks being ported to discourse plugins. One of my personal favourite would be able to live reload the ruby files without restarting the server.

1 Like

This is what reloadable_patch is for. If you wrap ruby class changes in reloadable_patch, it should live reload!

1 Like

Can I wrap the whole plugin.rb file inside it for dev? More seriously, how far can we go with it?

Also, adding new files or changing any yml files needs a server restart right?

2 Likes

You shouldn’t have to do a reloadable_patch for everything. Many of the methods defined in instance.rb to make plugin development easy, use reloadable_patch internally like add_to_serializer.

https://github.com/discourse/discourse/blob/66402abe9a093d96e3e8e9e40e9b396d61707719/lib/plugin/instance.rb#L124

Ideally our plugin api is good enough that you shouldn’t have to do an insane amount of reloadable_patch’ing.

Yeah, this is true. I wonder if anything can be done about the yml file changes; that one annoys me personally.

3 Likes

True that. I didn’t know reloadable_patch was meant for supporting live reloading. I can think of many use cases apart from where discourse already uses that. i.e.

reloadable_patch do
 require 'x/y/z'
end

or monkey patches.

1 Like

I think removing or adding such a function would still be a problem coz reloadable_patch is called inside the function.

In any case, it helps a lot.

1 Like

I started looking into live-reloading yml changes, and it actually does work. Plugins may break this live-reloading if they are not properly using reloadable_patch, but once all your plugins can safely reload, so will your yml changes.

4 Likes

It would be super helpful though, if you could describe the exact steps to use reloadable patch.

Here’s what I tried without much luck(I’m missing something for sure)

after_initialize do
    add_to_serializer(:topic_view, :check, false) do
        puts 'nocheck'
    end
end

Once the server starts, and I change nocheck to check, it still prints nocheck after I reload the topic route.

after_initialize do
    reloadable_patch do |plugin|
        puts 'nocheck'
    end
end

Still no luck when I reload any page after changing the string under puts.

I guess I have mislead you in posts above. reloadable_patch is helpful for discourse development, but @david explained it’s use very well:


Anything inside the after_initialize block of plugin.rb is only loaded during application boot, and not on subsequent reloads.

So, assuming you want to add something to the user serializer. The normal behavior would be like:

At boot:

  • Discourse loads user_serializer.rb
  • Discourse loads plugin.rb , which has an override for user_serializer

At reload:

  • Discourse reloads user_serializer.rb
  • (the plugin.rb patch is not reloaded, the plugin override is lost)

With our reloadable_patch system:

At boot:

  • Discourse loads user_serializer.rb
  • Discourse loads plugin.rb and registers the reloadable_patch for the user_serializer
  • The reloadable patches are executed

At reload:

  • Discourse reloads user_serlializer.rb
  • The reloadable patches are executed
  • (yay, the plugin override is still working)
6 Likes

This is describing a core Rails property, which is not germane to Discourse.

In Rails, all initializers are loaded only when Rails starts up, so any plugin which executes code based on the initializer, like “after_initializer” will only execute on Rails start up or restart (please correct me if wrong).

https://guides.rubyonrails.org/v2.3/configuring.html

The reason I am familiar with this is that I am currently building a Rails application for a client and I have written a lot of initializers for various tasks (booleans, static arrays, etc). In each case, if we change any code in a Rails initializer, it is necessary to restart Rails (“Control c, rails s in dev”) to get the new values in the initializers into the Rails app.

Please correct me if I am wrong! Thanks.

Sometimes, I think it might be a bit confusing to some Discourse plugin developers, especially those developers who primarily work on Discourse plugins and not with Rails applications in general, when we write “Discourse does this” or “Discourse does that” when what we are really describing is a core Rails function or property not necessarily germane to Discourse, per se.

Rails 6 reads all files in the config/initializers directory (and all config/initializers subdirectories) only when Rails starts up. Likewise (not 100% sure about plugins) a Discourse plugin with code dependent on after_initialize will (kindly confirm anyone?) only be loaded by the Rails app when Rails starts up or restarts, because this is a fundamental property of the Rails boot process.

My guess is that everyone here already knows this who works on Rails. I only am starting to know these details (and feel I have a long way to go to be an expert); because I’m currently working on a Rails application every day (these days), which by-the-way, I now wish I would have starting working with Rails a decade ago because coming from a LAMP dev background, I find Rails so very much better (easy and fun to work with).

I have become a major Rails fan this year and I am very grateful to Discourse to starting me down that path.

Again, please correct me if I am wrong! Thanks. I’m trying to be more expert in Rails.

6 Likes

Yep! This is correct as is your understanding of Rails initializers.

I too am a huge Rails fan :slight_smile:

7 Likes