Module: ThecoreBackendCommons::DefaultModuleRegistry
- Defined in:
- lib/thecore_backend_commons/default_module_registry.rb
Overview
Shared registry other gems use to register "default" modules that get
included automatically into every ApplicationRecord subclass, as it
is defined -- not via a post-boot scan of ApplicationRecord.subclasses.
This exists so multiple gems (model_driven_api, thecore_ui_rails_admin,
...) can each contribute default model behavior (API serialization shape,
RailsAdmin config, ...) through ONE shared ApplicationRecord.inherited
hook instead of each gem independently overriding inherited itself.
See ADR 0001/0002 (vendor/external/thecore/docs/adr/ in the host app).
Usage
ThecoreBackendCommons::DefaultModuleRegistry.register(
MyDefaultModule,
applies_to: ->(klass) { klass.table_exists? rescue false }
)
applies_to defaults to "every subclass" (->(_klass) { true }) when
omitted. Registered modules are included, in registration order, into
every ApplicationRecord subclass for which applies_to returns true,
at the moment the subclass is defined -- see InheritedHook below.
MyDefaultModule should normally be an ActiveSupport::Concern with an
included do ... end block, so its default methods/callbacks/DSL calls
land as own behavior on each model class (see ADR 0001's consequences
section -- model_driven_api's introspection depends on this).
Idempotency
- Calling
.registertwice with the same module object is a no-op the second time -- it will not be applied twice to any subclass. .apply_tonever re-includes a module a class already has in its ancestor chain (e.g. an STI subclass that already inherited it from its base class), so STI subclasses are never double-included..apply_tonever applies anything to an abstract class (klass.abstract_class?) -- most importantlyApplicationRecorditself (primary_abstract_class), which never receives default modules through this mechanism sinceApplicationRecord.inheritedonly fires for classes that inherit fromApplicationRecord, never forApplicationRecord's own definition.
Defined Under Namespace
Modules: InheritedHook
Class Method Summary collapse
-
.apply_to(klass) ⇒ Object
Applies every registered module whose
applies_topredicate matchesklass, in registration order. -
.install!(base_class) ⇒ Object
Installs
InheritedHookonto +base_class+'s singleton class so every subsequently-defined subclass triggers.apply_to. -
.register(mod, applies_to: ->(_klass) { true }) ⇒ Object
Registers
modso it getsincluded into every matchingApplicationRecordsubclass defined from now on. -
.registered?(mod) ⇒ Boolean
True if
modhas already been registered. -
.reset! ⇒ Object
Test helper: clears all registrations.
Class Method Details
.apply_to(klass) ⇒ Object
Applies every registered module whose applies_to predicate matches
klass, in registration order. No-op for abstract classes. Never
re-includes a module klass already has in its ancestor chain.
Called automatically by InheritedHook -- only call directly from
application/test code that needs to (re-)apply defaults to a class
defined before the registry/hook was set up.
86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/thecore_backend_commons/default_module_registry.rb', line 86 def apply_to(klass) return klass if klass.abstract_class? entries.each do |entry| next unless entry.applies_to.call(klass) next if klass.include?(entry.mod) klass.include(entry.mod) end klass end |
.install!(base_class) ⇒ Object
Installs InheritedHook onto +base_class+'s singleton class so every
subsequently-defined subclass triggers .apply_to. Idempotent --
safe to call on every to_prepare cycle in development.
Must run before any subclass of base_class is defined (i.e. before
eager loading), which is why the host initializer installs it from
config.to_prepare rather than config.after_initialize -- Rails
runs to_prepare callbacks before eager_load!, while
after_initialize runs after it (see
Rails::Application::Finisher). Installing from after_initialize
would miss every subclass already loaded by eager loading in
production.
111 112 113 114 115 116 |
# File 'lib/thecore_backend_commons/default_module_registry.rb', line 111 def install!(base_class) return base_class if base_class.singleton_class.ancestors.include?(InheritedHook) base_class.singleton_class.prepend(InheritedHook) base_class end |
.register(mod, applies_to: ->(_klass) { true }) ⇒ Object
Registers mod so it gets included into every matching
ApplicationRecord subclass defined from now on. Returns mod.
applies_to is a 1-arity callable invoked with the candidate class;
only classes for which it returns truthy receive mod.
Registering the same module object again is a no-op -- the original
registration (and its original applies_to) is kept.
69 70 71 72 |
# File 'lib/thecore_backend_commons/default_module_registry.rb', line 69 def register(mod, applies_to: ->(_klass) { true }) entries << Entry.new(mod, applies_to) unless registered?(mod) mod end |
.registered?(mod) ⇒ Boolean
True if mod has already been registered.
75 76 77 |
# File 'lib/thecore_backend_commons/default_module_registry.rb', line 75 def registered?(mod) entries.any? { |entry| entry.mod.equal?(mod) } end |
.reset! ⇒ Object
Test helper: clears all registrations. Not intended for production
use -- does not uninstall InheritedHook from any class it was
already installed on, and does not un-include modules already
included into existing classes.
122 123 124 |
# File 'lib/thecore_backend_commons/default_module_registry.rb', line 122 def reset! @entries = [] end |