Class: LcpRuby::Engine
- Inherits:
-
Rails::Engine
- Object
- Rails::Engine
- LcpRuby::Engine
- Defined in:
- lib/lcp_ruby/engine.rb
Constant Summary collapse
- BIND_TO_APPLICATOR_MAP =
{ "ransack" => ModelFactory::RansackApplicator, "scopes" => ModelFactory::ScopeApplicator, "validations" => ModelFactory::ValidationApplicator, "virtual_columns" => ModelFactory::AggregateApplicator, "transforms" => ModelFactory::TransformApplicator, "computed_fields" => ModelFactory::ComputedApplicator, "custom_fields" => CustomFields::Applicator, "workflow" => ModelFactory::WorkflowApplicator, "userstamps" => ModelFactory::UserstampsApplicator, "soft_delete" => ModelFactory::SoftDeleteApplicator, "auditing" => ModelFactory::AuditingApplicator, "tree" => ModelFactory::TreeApplicator, "sequences" => ModelFactory::SequenceApplicator, "positioning" => ModelFactory::PositioningApplicator, "attachments" => ModelFactory::AttachmentApplicator, "enums" => ModelFactory::EnumApplicator, "service_accessors" => ModelFactory::ServiceAccessorApplicator, "defaults" => ModelFactory::DefaultApplicator, "inherited_parent_validator" => ModelFactory::InheritedParentValidatorApplicator }.freeze
- RELOAD_MUTEX =
Serializes ‘reload!` against itself in dev/test so a YAML edit that triggers autoreload while another thread is mid-`reset!` does not crash the in-flight request with `RegistryError`. Production deploys are expected to drain traffic before reload (rolling restart, blue-green) and never hit this; the mutex is a no-op there but costs nothing on the read path which never enters `reload!`.
Mutex.new
Class Method Summary collapse
-
.apply_bind_to_managed!(model_def) ⇒ Object
Re-apply ‘lcp_managed:` schema (additive columns) and AR macros for a bind_to: host class.
-
.ensure_active_record_loaded! ⇒ Object
Drains the ‘on_load(:active_record)` queue at the caller’s expense rather than during ‘load_metadata!`.
- .load_metadata! ⇒ Object
-
.register_engine_action_keys! ⇒ Object
Re-applies engine-internal ‘Actions::ActionRegistry.register` calls.
-
.register_oidc_bearer_resolver_if_enabled! ⇒ Object
Boot-time gate for the OIDC bearer resolver.
- .reload! ⇒ Object
Class Method Details
.apply_bind_to_managed!(model_def) ⇒ Object
Re-apply ‘lcp_managed:` schema (additive columns) and AR macros for a bind_to: host class. Idempotent. Used by `lcp_ruby:ensure_tables` to recover after `db:schema:load` has recreated the host table without the managed columns (those columns are LCP-runtime-added and never present in `schema.rb`).
Safe to call repeatedly: ‘SchemaManager.apply_managed!` skips existing columns; `AssociationApplicator` short-circuits on `already_managed?` for previously-installed macros.
387 388 389 390 391 392 393 394 395 |
# File 'lib/lcp_ruby/engine.rb', line 387 def apply_bind_to_managed!(model_def) return unless model_def.bind_to? host_class = LcpRuby.registry.model_for(model_def.name) return unless host_class apply_bind_to_managed_schema(host_class, model_def) apply_bind_to_managed_associations(host_class, model_def) end |
.ensure_active_record_loaded! ⇒ Object
Drains the ‘on_load(:active_record)` queue at the caller’s expense rather than during ‘load_metadata!`. The engine’s ‘lcp_ruby.load_metadata` initializer registers a callback there; in non-eager-load contexts (rake, `bin/rails runner`) AR::Base is loaded lazily, so the callback fires synchronously the first time `build_model` references it — re-entering `load_metadata!`. Rake tasks that call `load_metadata!` directly should call this first to make the on_load fire deterministically. The `@metadata_loading` guard inside `load_metadata!` is the in-engine backstop.
186 187 188 |
# File 'lib/lcp_ruby/engine.rb', line 186 def self.ensure_active_record_loaded! ActiveRecord::Base end |
.load_metadata! ⇒ Object
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 |
# File 'lib/lcp_ruby/engine.rb', line 267 def return if @metadata_loaded # Re-entrance guard: model building below references ActiveRecord::Base, # which fires the `on_load(:active_record)` hook registered by the # `lcp_ruby.load_metadata` initializer. In dev/test rake contexts the # boot-time AR load is deferred, so that hook fires for the first time # *during* this method — re-entering it. Without this flag the second # entry would run `loader.load_all` on the already-populated, memoized # Loader and raise "Duplicate presenter/model ...". @metadata_loaded # is set true only at the end, so it cannot serve as the re-entry # guard on its own. return if @metadata_loading @metadata_loading = true # Re-register engine-internal action keys and (re-)evaluate the # OIDC bearer gate on every load. Both registries are cleared by # `LcpRuby.reset!` (called from `Engine.reload!`), so the bare # initializer-once registrations are gone by the time we get here # on a reload. Idempotent — safe at first boot too. register_engine_action_keys! register_oidc_bearer_resolver_if_enabled! Types::BuiltInTypes.register_all! Services::BuiltInTransforms.register_all! Services::BuiltInDefaults.register_all! Services::BuiltInAccessors.register_all! Services::Registry.discover!(Rails.root.join("app").to_s) Display::RendererRegistry.register_built_ins! Display::RendererRegistry.discover!(Rails.root.join("app").to_s) ViewSlots::Registry.register_built_ins! ConditionServiceRegistry.register_built_ins! loader = LcpRuby.loader loader.load_all # Build models in topological order (parents before children) for STI sorted_model_defs = Metadata::ModelInheritanceResolver.sort_definitions(loader.model_definitions) sorted_model_defs.each do |model_def| build_model(model_def, loader) end CustomFields::Setup.apply!(loader) RecordAliases::Setup.apply!(loader) Roles::Setup.apply!(loader) Permissions::Setup.apply!(loader) Groups::Setup.apply!(loader) Auditing::Setup.apply!(loader) Workflow::Setup.apply!(loader) BackgroundJobs::Setup.apply!(loader) SavedFilters::Setup.apply!(loader) Export::Setup.apply!(loader) Import::Setup.apply!(loader) Pages::Setup.apply!(loader) loader.merge_db_pages! DataSource::Setup.apply!(loader) # Late-bound host extensions (e.g. `LcpRuby.on_models_loaded { ... }` # registering custom AR scopes referenced by permission YAML) fire # *before* the runtime invariant validator, so AUTH-001's # `klass.respond_to?(method)` check sees them. LcpRuby._drain_models_loaded_callbacks!(loader) Authorization::RuntimeInvariantValidator.new(loader).run! Metrics::Setup.apply!(loader) LcpRuby.check_services! LcpRuby.check_action_text_compat! @metadata_loaded = true LcpRuby.instance_variable_set(:@booted, true) ActiveSupport::Notifications.instrument("boot.lcp_ruby", instrumentation_payload(loader)) ensure @metadata_loading = false end |
.register_engine_action_keys! ⇒ Object
Re-applies engine-internal ‘Actions::ActionRegistry.register` calls. Called from the `lcp_ruby.api_tokens` initializer at boot AND from the start of every `Engine.load_metadata!` — `LcpRuby.reset!` clears `Actions::ActionRegistry`, so the bare initializer-once registration would lose these keys on `Engine.reload!`. `register` overwrites by name, so re-firing at every load is idempotent. Engine-internal `on_model_ready` registrations live in the initializer body directly — they survive reload via `@configuration` preservation in `LcpRuby.reset!` and would accumulate if re-fired here.
170 171 172 173 174 175 |
# File 'lib/lcp_ruby/engine.rb', line 170 def self.register_engine_action_keys! LcpRuby::Actions::ActionRegistry.register( "api_token/revoke", LcpRuby::Actions::ApiTokens::Revoke ) end |
.register_oidc_bearer_resolver_if_enabled! ⇒ Object
Boot-time gate for the OIDC bearer resolver. Extracted as a class method so it can be unit-tested without booting Rails — see spec/lib/lcp_ruby/engine_oidc_bearer_spec.rb. Idempotent: re-registering by name overwrites in the registry.
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 |
# File 'lib/lcp_ruby/engine.rb', line 194 def self.register_oidc_bearer_resolver_if_enabled! return false unless LcpRuby::Authentication::ProviderRegistry.configured? return false unless LcpRuby::Authentication::ProviderRegistry.oidc_enabled? return false if LcpRuby::Authentication::ProviderRegistry.oidc_providers.none? { |p| p.audience.to_s.length.positive? } LcpRuby::Authentication::OidcBearerResolver.register! true rescue LcpRuby::Authentication::ConfigurationError => e # Symmetric to OmniAuthBuilder.install_middleware!: in production, fail # loud; in dev/test, log + skip so `rake lcp_ruby:validate` can run # and surface the problem. raise if defined?(Rails) && Rails.respond_to?(:env) && Rails.env.production? Rails.logger.warn( "[lcp_ruby] OIDC bearer resolver NOT registered — auth.yml is invalid: #{e.}" ) if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger false end |
.reload! ⇒ Object
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 |
# File 'lib/lcp_ruby/engine.rb', line 343 def reload! RELOAD_MUTEX.synchronize do @metadata_loaded = false # `reset_for_reload!` (NOT `reset!`) — preserves # `@configuration` and `@models_loaded_callbacks` so host # settings and host-registered callbacks from # `config/initializers/lcp_ruby.rb` survive the reload. # See docs/design/boot_reload_lifecycle.md § Q7. LcpRuby.reset_for_reload! begin rescue Authorization::InvariantError, MetadataError # Rollback half-loaded state so the next request re-boots # cleanly rather than serving from a partially-populated # registry. Rescue scope is intentionally narrow — host-side # StandardError from a custom `on_models_loaded` callback or # unexpected platform bugs propagate without rollback so the # host's restart path handles them. See # docs/design/authorization_hardening.md § "Reload-failure # rollback". @metadata_loaded = false LcpRuby.reset_for_reload! raise end # Reload-only signal so host apps can subscribe to "metadata # was reloaded" without also being notified on initial boot. # `boot.lcp_ruby` fires from inside `load_metadata!` and so # also fires here — that is the general "metadata is now # loaded" signal. `reload.lcp_ruby` is the more specific "and # it was a reload, not an initial boot" signal. ActiveSupport::Notifications.instrument("reload.lcp_ruby", instrumentation_payload(LcpRuby.loader)) end end |