Plugin System: Extending Core at Boot
Discourse plugins are not compiled into core — each plugin ships a plugin.rb that gets instance_eval'd against a Plugin::Instance at boot, giving it a DSL (register_asset, after_initialize, reloadable_patch, add_to_class, etc.) for wiring in assets, routes, and callbacks. The poll plugin shows the sharpest case of this: it uses reloadable_patch to Post.prepend(DiscoursePoll::PostExtension), permanently splicing has_many :polls and an after_save hook into the 'core' Post model. New engineers should internalize that grep-ing app/models/post.rb alone will miss behavior that actually originates from plugin code executed at boot.
plugin.rb is a script run against a Plugin::Instance, not a normal Ruby file
plugins/poll/plugin.rb · lines 1–24These aren't ordinary comments — Plugin::Metadata.parse_line reads '# name:', '# about:' etc. line-by-line before the plugin is ever activated, populating the metadata shown in the admin plugin list.
register_asset looks like a top-level function call, but because activate! runs plugin.rb via instance_eval, this is actually plugin_instance.register_asset(...) — it queues the file to be registered as a CSS asset later.
enabled_site_setting wires poll's on/off switch to the poll_enabled site setting; every enabled?-guarded method in Plugin::Instance checks this before running plugin logic.
after_initialize doesn't run its block immediately — it stores it, and notify_after_initialize calls every stored block only once all plugins have finished their top-level activation. This is where poll mounts its engine and monkey-patches Post.
# frozen_string_literal: true# name: poll# about: Official poll plugin for Discourse# version: 1.0# authors: Vikhyat Korrapati (vikhyat), Régis Hanol (zogstrip)# url: https://github.com/discourse/discourse/tree/main/plugins/pollregister_asset "stylesheets/common/poll.scss"register_asset "stylesheets/common/poll-ui-builder.scss"register_asset "stylesheets/desktop/poll-ui-builder.scss", :desktopregister_asset "stylesheets/common/poll-breakdown.scss"register_svg_icon "far-square-check"enabled_site_setting :poll_enabledmodule ::DiscoursePoll PLUGIN_NAME = "poll"endrequire_relative "lib/poll/engine"after_initialize do
reloadable_patch do Post.prepend(DiscoursePoll::PostExtension) User.prepend(DiscoursePoll::UserExtension) end
plugins/poll/plugin.rb · lines 46–49Check your understanding
Answered in place — nothing is graded, everything is explained. 0 / 2 passed
How does a bare top-level call like `register_asset "..."` in plugin.rb actually get executed?
Why does poll wrap `Post.prepend(DiscoursePoll::PostExtension)` inside `reloadable_patch` instead of calling prepend directly at the top level?