brut 0.0.29 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +23 -2
- data/assets/LogoStop.pxd +0 -0
- data/assets/MetroLogo.graffle +0 -0
- data/assets/SocialImage.png +0 -0
- data/assets/SocialImage.pxd +0 -0
- data/brutrb.com/.vitepress/config.mjs +48 -9
- data/brutrb.com/.vitepress/theme/style.css +14 -35
- data/brutrb.com/adrs.md +15 -0
- data/brutrb.com/ai.md +10 -15
- data/brutrb.com/assets.md +2 -9
- data/brutrb.com/brut-js.md +12 -2
- data/brutrb.com/cli.md +9 -13
- data/brutrb.com/components.md +118 -96
- data/brutrb.com/configuration.md +3 -4
- data/brutrb.com/css.md +2 -2
- data/brutrb.com/custom-element-tests.md +3 -4
- data/brutrb.com/database-access.md +1 -1
- data/brutrb.com/database-schema.md +29 -41
- data/brutrb.com/dev-environment.md +13 -8
- data/brutrb.com/dir-structure.md +120 -0
- data/brutrb.com/doc-conventions.md +17 -15
- data/brutrb.com/dx +1 -0
- data/brutrb.com/end-to-end-tests.md +12 -10
- data/brutrb.com/features.md +373 -0
- data/brutrb.com/flash-and-session.md +115 -131
- data/brutrb.com/form-constraints.md +266 -0
- data/brutrb.com/forms.md +140 -765
- data/brutrb.com/getting-started.md +10 -11
- data/brutrb.com/handlers.md +119 -95
- data/brutrb.com/hooks.md +18 -20
- data/brutrb.com/i18n.md +6 -4
- data/brutrb.com/images/DevEnvironment.graffle +0 -0
- data/brutrb.com/images/DevEnvironment.png +0 -0
- data/brutrb.com/images/LogoStop.png +0 -0
- data/brutrb.com/index.md +0 -3
- data/brutrb.com/instrumentation.md +7 -10
- data/brutrb.com/javascript.md +14 -14
- data/brutrb.com/keyword-injection.md +72 -114
- data/brutrb.com/layouts.md +20 -52
- data/brutrb.com/lsp.md +1 -1
- data/brutrb.com/overview.md +30 -372
- data/brutrb.com/pages.md +119 -207
- data/brutrb.com/public/SocialImage.png +0 -0
- data/brutrb.com/public/favicon.ico +0 -0
- data/brutrb.com/recipes/alternate-layouts.md +32 -0
- data/brutrb.com/recipes/authentication.md +315 -6
- data/brutrb.com/recipes/blank-layouts.md +22 -0
- data/brutrb.com/recipes/custom-flash.md +51 -0
- data/brutrb.com/recipes/indexed-forms.md +149 -0
- data/brutrb.com/recipes/text-field-component.md +182 -0
- data/brutrb.com/roadmap.md +57 -0
- data/brutrb.com/routes.md +56 -82
- data/brutrb.com/security.md +0 -3
- data/brutrb.com/space-time-continuum.md +8 -12
- data/brutrb.com/tutorial.md +5 -1
- data/brutrb.com/why.md +19 -0
- data/docs/404.html +8 -3
- data/docs/SocialImage.png +0 -0
- data/docs/adrs.html +29 -0
- data/docs/ai.html +11 -6
- data/docs/api/Brut/BackEnd/SeedData.html +1 -1
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +1 -1
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +1 -1
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +1 -1
- data/docs/api/Brut/BackEnd/Sidekiq.html +1 -1
- data/docs/api/Brut/BackEnd/Validators/FormValidator.html +1 -1
- data/docs/api/Brut/BackEnd/Validators.html +1 -1
- data/docs/api/Brut/BackEnd.html +1 -1
- data/docs/api/Brut/CLI/App.html +1 -1
- data/docs/api/Brut/CLI/AppRunner.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Create.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Drop.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Migrate.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Seed.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Status.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB.html +1 -1
- data/docs/api/Brut/CLI/Apps/DeployBase/GitChecks.html +1 -1
- data/docs/api/Brut/CLI/Apps/DeployBase.html +1 -1
- data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy/Deploy.html +1 -1
- data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +3 -3
- data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +2 -2
- data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test/Audit.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test/E2e.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test/JS.html +6 -6
- data/docs/api/Brut/CLI/Apps/Test/Run.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test.html +2 -2
- data/docs/api/Brut/CLI/Apps.html +1 -1
- data/docs/api/Brut/CLI/Command.html +3 -3
- data/docs/api/Brut/CLI/Error.html +1 -1
- data/docs/api/Brut/CLI/ExecutionResults/Result.html +1 -1
- data/docs/api/Brut/CLI/ExecutionResults.html +1 -1
- data/docs/api/Brut/CLI/Executor.html +1 -1
- data/docs/api/Brut/CLI/InvalidOption.html +1 -1
- data/docs/api/Brut/CLI/Options.html +1 -1
- data/docs/api/Brut/CLI/Output.html +1 -1
- data/docs/api/Brut/CLI/SystemExecError.html +1 -1
- data/docs/api/Brut/CLI.html +1 -1
- data/docs/api/Brut/FactoryBot.html +1 -1
- data/docs/api/Brut/Framework/App.html +1 -1
- data/docs/api/Brut/Framework/Config.html +1 -1
- data/docs/api/Brut/Framework/Container.html +1 -1
- data/docs/api/Brut/Framework/Error.html +1 -1
- data/docs/api/Brut/Framework/Errors/AbstractMethod.html +1 -1
- data/docs/api/Brut/Framework/Errors/Bug.html +1 -1
- data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +1 -1
- data/docs/api/Brut/Framework/Errors/MissingParameter.html +1 -1
- data/docs/api/Brut/Framework/Errors/NoClassForPath.html +1 -1
- data/docs/api/Brut/Framework/Errors/NotFound.html +1 -1
- data/docs/api/Brut/Framework/Errors/NotImplemented.html +1 -1
- data/docs/api/Brut/Framework/Errors.html +1 -1
- data/docs/api/Brut/Framework/FussyTypeEnforcement.html +1 -1
- data/docs/api/Brut/Framework/MCP.html +1 -1
- data/docs/api/Brut/Framework/ProjectEnvironment.html +1 -1
- data/docs/api/Brut/Framework.html +1 -1
- data/docs/api/Brut/FrontEnd/AssetPathResolver.html +1 -1
- data/docs/api/Brut/FrontEnd/Component/Helpers.html +1 -1
- data/docs/api/Brut/FrontEnd/Component.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/FormTag.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Input.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +3 -3
- data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/TimeTag.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Traceparent.html +1 -1
- data/docs/api/Brut/FrontEnd/Components.html +1 -1
- data/docs/api/Brut/FrontEnd/Download.html +1 -1
- data/docs/api/Brut/FrontEnd/Flash.html +1 -1
- data/docs/api/Brut/FrontEnd/Form.html +9 -11
- data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/Input/Color.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/Input/TimeOfDay.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/Input.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +135 -20
- data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +135 -20
- data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms.html +1 -1
- data/docs/api/Brut/FrontEnd/GenericResponse.html +1 -1
- data/docs/api/Brut/FrontEnd/Handler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers.html +1 -1
- data/docs/api/Brut/FrontEnd/HandlingResults.html +1 -1
- data/docs/api/Brut/FrontEnd/HttpMethod.html +1 -1
- data/docs/api/Brut/FrontEnd/HttpStatus.html +1 -1
- data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +1 -1
- data/docs/api/Brut/FrontEnd/Layout.html +1 -1
- data/docs/api/Brut/FrontEnd/Middleware.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares.html +1 -1
- data/docs/api/Brut/FrontEnd/Page.html +1 -1
- data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +2 -2
- data/docs/api/Brut/FrontEnd/Pages.html +1 -1
- data/docs/api/Brut/FrontEnd/RequestContext.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHook.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/Route.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing.html +1 -1
- data/docs/api/Brut/FrontEnd/Session.html +1 -1
- data/docs/api/Brut/FrontEnd.html +1 -1
- data/docs/api/Brut/I18n/BaseMethods.html +1 -1
- data/docs/api/Brut/I18n/ForBackEnd.html +1 -1
- data/docs/api/Brut/I18n/ForCLI.html +1 -1
- data/docs/api/Brut/I18n/ForHTML.html +1 -1
- data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +1 -1
- data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +1 -1
- data/docs/api/Brut/I18n.html +1 -1
- data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +1 -1
- data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +1 -1
- data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +1 -1
- data/docs/api/Brut/Instrumentation/OpenTelemetry.html +1 -1
- data/docs/api/Brut/Instrumentation.html +1 -1
- data/docs/api/Brut/SinatraHelpers/ClassMethods.html +1 -1
- data/docs/api/Brut/SinatraHelpers.html +1 -1
- data/docs/api/Brut/SpecSupport/ClockSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/ComponentSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/E2ETestServer.html +1 -1
- data/docs/api/Brut/SpecSupport/E2eSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/EnhancedNode.html +1 -1
- data/docs/api/Brut/SpecSupport/FlashSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +1 -1
- data/docs/api/Brut/SpecSupport/GeneralSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/HandlerSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/BeABug.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/BePageFor.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/BeRoutingFor.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveGenerated.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveI18nString.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveLinkTo.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveRedirectedTo.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedHttpStatus.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedRackResponse.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers.html +1 -1
- data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/RSpecSetup.html +1 -1
- data/docs/api/Brut/SpecSupport/SessionSupport.html +1 -1
- data/docs/api/Brut/SpecSupport.html +1 -1
- data/docs/api/Brut.html +1 -1
- data/docs/api/Clock.html +1 -1
- data/docs/api/RichString.html +150 -343
- data/docs/api/SemanticLogger/Appender/Async.html +1 -1
- data/docs/api/Sequel/Extensions/BrutInstrumentation.html +1 -1
- data/docs/api/Sequel/Extensions/BrutMigrations.html +1 -1
- data/docs/api/Sequel/Extensions.html +1 -1
- data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +1 -1
- data/docs/api/Sequel/Plugins/CreatedAt.html +1 -1
- data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +1 -1
- data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +1 -1
- data/docs/api/Sequel/Plugins/ExternalId.html +1 -1
- data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +1 -1
- data/docs/api/Sequel/Plugins/FindBang.html +1 -1
- data/docs/api/Sequel/Plugins.html +1 -1
- data/docs/api/Sequel.html +1 -1
- data/docs/api/_index.html +5 -5
- data/docs/api/class_list.html +1 -1
- data/docs/api/file.README.html +22 -3
- data/docs/api/index.html +22 -3
- data/docs/api/method_list.html +290 -306
- data/docs/api/top-level-namespace.html +1 -1
- data/docs/assets/DevEnvironment.DaFcVfwP.png +0 -0
- data/docs/assets/LogoStop.Gb3tDhL1.png +0 -0
- data/docs/assets/adrs.md.JRxZ5uYE.js +1 -0
- data/docs/assets/adrs.md.JRxZ5uYE.lean.js +1 -0
- data/docs/assets/{ai.md._6HCDL6d.js → ai.md.Cy9GWnER.js} +1 -1
- data/docs/assets/ai.md.Cy9GWnER.lean.js +1 -0
- data/docs/assets/{app.BhrfSt68.js → app.Dm3x-DQc.js} +1 -1
- data/docs/assets/{assets.md.D3wunzLx.js → assets.md.7C3HWkga.js} +3 -3
- data/docs/assets/{assets.md.D3wunzLx.lean.js → assets.md.7C3HWkga.lean.js} +1 -1
- data/docs/assets/{brut-js.md.o2DAO2s2.js → brut-js.md.B4GYxQVw.js} +1 -1
- data/docs/assets/{brut-js.md.o2DAO2s2.lean.js → brut-js.md.B4GYxQVw.lean.js} +1 -1
- data/docs/assets/chunks/@localSearchIndexroot.BqRrkR00.js +1 -0
- data/docs/assets/chunks/{VPLocalSearchBox.Dpot_2H4.js → VPLocalSearchBox.DL6bnqee.js} +1 -1
- data/docs/assets/chunks/{theme.N2SNVLgU.js → theme.BXdlf6e8.js} +2 -2
- data/docs/assets/{cli.md.RmeA2b0i.js → cli.md.CjsktgFz.js} +15 -20
- data/docs/assets/components.md.Pg_Lo35G.js +96 -0
- data/docs/assets/{components.md.CRUMdRoN.lean.js → components.md.Pg_Lo35G.lean.js} +1 -1
- data/docs/assets/{configuration.md.LG-zIBww.js → configuration.md.BfeGnEci.js} +3 -3
- data/docs/assets/{css.md.DJgj2clw.js → css.md.CltvJqAa.js} +3 -3
- data/docs/assets/{custom-element-tests.md.BrYJQEl3.js → custom-element-tests.md.B_rbta32.js} +3 -3
- data/docs/assets/{database-access.md.C7l-Vuvb.js → database-access.md.gnluu54N.js} +1 -1
- data/docs/assets/{database-schema.md.BUjR0VS1.js → database-schema.md.CSYk6E6v.js} +6 -6
- data/docs/assets/{database-schema.md.BUjR0VS1.lean.js → database-schema.md.CSYk6E6v.lean.js} +1 -1
- data/docs/assets/dev-environment.md.Dy6EldaM.js +16 -0
- data/docs/assets/dev-environment.md.Dy6EldaM.lean.js +1 -0
- data/docs/assets/dir-structure.md.CWir1pic.js +46 -0
- data/docs/assets/dir-structure.md.CWir1pic.lean.js +1 -0
- data/docs/assets/doc-conventions.md.DOkAuXlt.js +1 -0
- data/docs/assets/doc-conventions.md.DOkAuXlt.lean.js +1 -0
- data/docs/assets/{end-to-end-tests.md.yfQHC0b5.js → end-to-end-tests.md.DzqRpZ43.js} +5 -3
- data/docs/assets/end-to-end-tests.md.DzqRpZ43.lean.js +1 -0
- data/docs/assets/features.md.DPFXsy0z.js +154 -0
- data/docs/assets/features.md.DPFXsy0z.lean.js +1 -0
- data/docs/assets/flash-and-session.md.nPvUpnUx.js +79 -0
- data/docs/assets/{flash-and-session.md.BXY8RvT0.lean.js → flash-and-session.md.nPvUpnUx.lean.js} +1 -1
- data/docs/assets/form-constraints.md.x5tNpTTI.js +90 -0
- data/docs/assets/form-constraints.md.x5tNpTTI.lean.js +1 -0
- data/docs/assets/forms.md.BQZlCwvi.js +64 -0
- data/docs/assets/forms.md.BQZlCwvi.lean.js +1 -0
- data/docs/assets/{getting-started.md.Dj0qtZI2.js → getting-started.md.BcXnNuD6.js} +5 -5
- data/docs/assets/{getting-started.md.Dj0qtZI2.lean.js → getting-started.md.BcXnNuD6.lean.js} +1 -1
- data/docs/assets/handlers.md.Chyri6KA.js +54 -0
- data/docs/assets/handlers.md.Chyri6KA.lean.js +1 -0
- data/docs/assets/{hooks.md.C4-moMny.js → hooks.md.Jmb5VOLA.js} +4 -4
- data/docs/assets/{hooks.md.C4-moMny.lean.js → hooks.md.Jmb5VOLA.lean.js} +1 -1
- data/docs/assets/{i18n.md.Do9i1qWl.js → i18n.md.xQhiGo1G.js} +2 -2
- data/docs/assets/{i18n.md.Do9i1qWl.lean.js → i18n.md.xQhiGo1G.lean.js} +1 -1
- data/docs/assets/index.md.Bn9e0sRJ.js +1 -0
- data/docs/assets/index.md.Bn9e0sRJ.lean.js +1 -0
- data/docs/assets/{instrumentation.md.a9Pjps4P.js → instrumentation.md.BgcaGVYH.js} +2 -2
- data/docs/assets/{instrumentation.md.a9Pjps4P.lean.js → instrumentation.md.BgcaGVYH.lean.js} +1 -1
- data/docs/assets/{javascript.md.GWbhRS51.js → javascript.md.DzrMxUmI.js} +7 -7
- data/docs/assets/{javascript.md.GWbhRS51.lean.js → javascript.md.DzrMxUmI.lean.js} +1 -1
- data/docs/assets/keyword-injection.md.95Zgh2eN.js +21 -0
- data/docs/assets/{keyword-injection.md.Dt2tKREs.lean.js → keyword-injection.md.95Zgh2eN.lean.js} +1 -1
- data/docs/assets/{layouts.md.cPnh3NId.js → layouts.md.CJGDFY-m.js} +2 -15
- data/docs/assets/layouts.md.CJGDFY-m.lean.js +1 -0
- data/docs/assets/{lsp.md.Bsu-f6VU.js → lsp.md.Dn1rIiW0.js} +1 -1
- data/docs/assets/{lsp.md.Bsu-f6VU.lean.js → lsp.md.Dn1rIiW0.lean.js} +1 -1
- data/docs/assets/overview.md.iMnwLO4x.js +1 -0
- data/docs/assets/overview.md.iMnwLO4x.lean.js +1 -0
- data/docs/assets/pages.md.B7Hc-i6H.js +45 -0
- data/docs/assets/pages.md.B7Hc-i6H.lean.js +1 -0
- data/docs/assets/recipes_alternate-layouts.md.BwEytl59.js +22 -0
- data/docs/assets/recipes_alternate-layouts.md.BwEytl59.lean.js +1 -0
- data/docs/assets/recipes_authentication.md.Dzvi_g69.js +156 -0
- data/docs/assets/recipes_authentication.md.Dzvi_g69.lean.js +1 -0
- data/docs/assets/recipes_blank-layouts.md.fyAUJyJR.js +15 -0
- data/docs/assets/recipes_blank-layouts.md.fyAUJyJR.lean.js +1 -0
- data/docs/assets/recipes_custom-flash.md.CrQbI5eH.js +26 -0
- data/docs/assets/recipes_custom-flash.md.CrQbI5eH.lean.js +1 -0
- data/docs/assets/recipes_indexed-forms.md.CstYyOSo.js +74 -0
- data/docs/assets/recipes_indexed-forms.md.CstYyOSo.lean.js +1 -0
- data/docs/assets/recipes_text-field-component.md.H4wLAK0Z.js +101 -0
- data/docs/assets/recipes_text-field-component.md.H4wLAK0Z.lean.js +1 -0
- data/docs/assets/roadmap.md.C6PRi0DX.js +1 -0
- data/docs/assets/roadmap.md.C6PRi0DX.lean.js +1 -0
- data/docs/assets/routes.md.B8kfUPHU.js +21 -0
- data/docs/assets/{routes.md.BMM7peut.lean.js → routes.md.B8kfUPHU.lean.js} +1 -1
- data/docs/assets/{security.md.C668yXCi.js → security.md.C0G_AZR-.js} +1 -1
- data/docs/assets/{security.md.C668yXCi.lean.js → security.md.C0G_AZR-.lean.js} +1 -1
- data/docs/assets/space-time-continuum.md.xl44xDos.js +1 -0
- data/docs/assets/{space-time-continuum.md.KPUIKysQ.lean.js → space-time-continuum.md.xl44xDos.lean.js} +1 -1
- data/docs/assets/{style.B2o1L9eN.css → style.B1z60PPQ.css} +1 -1
- data/docs/assets/tutorial.md.BYXj4cOu.js +1 -0
- data/docs/assets/tutorial.md.BYXj4cOu.lean.js +1 -0
- data/docs/assets/why.md.C-hk5xgJ.js +1 -0
- data/docs/assets/why.md.C-hk5xgJ.lean.js +1 -0
- data/docs/assets.html +12 -7
- data/docs/brut-js/api/AjaxSubmit.html +1 -1
- data/docs/brut-js/api/AjaxSubmit.js.html +1 -1
- data/docs/brut-js/api/Autosubmit.html +1 -1
- data/docs/brut-js/api/Autosubmit.js.html +1 -1
- data/docs/brut-js/api/BaseCustomElement.html +1 -1
- data/docs/brut-js/api/BaseCustomElement.js.html +1 -1
- data/docs/brut-js/api/BrutCustomElements.html +1 -1
- data/docs/brut-js/api/BufferedLogger.html +1 -1
- data/docs/brut-js/api/ConfirmSubmit.html +1 -1
- data/docs/brut-js/api/ConfirmSubmit.js.html +1 -1
- data/docs/brut-js/api/ConfirmationDialog.html +1 -1
- data/docs/brut-js/api/ConfirmationDialog.js.html +1 -1
- data/docs/brut-js/api/ConstraintViolationMessage.html +1 -1
- data/docs/brut-js/api/ConstraintViolationMessage.js.html +1 -1
- data/docs/brut-js/api/ConstraintViolationMessages.html +1 -1
- data/docs/brut-js/api/ConstraintViolationMessages.js.html +1 -1
- data/docs/brut-js/api/CopyToClipboard.html +1 -1
- data/docs/brut-js/api/CopyToClipboard.js.html +1 -1
- data/docs/brut-js/api/Form.html +1 -1
- data/docs/brut-js/api/Form.js.html +1 -1
- data/docs/brut-js/api/I18nTranslation.html +1 -1
- data/docs/brut-js/api/I18nTranslation.js.html +1 -1
- data/docs/brut-js/api/LocaleDetection.html +1 -1
- data/docs/brut-js/api/LocaleDetection.js.html +1 -1
- data/docs/brut-js/api/Logger.html +1 -1
- data/docs/brut-js/api/Logger.js.html +1 -1
- data/docs/brut-js/api/Message.html +1 -1
- data/docs/brut-js/api/Message.js.html +1 -1
- data/docs/brut-js/api/PrefixedLogger.html +1 -1
- data/docs/brut-js/api/RichString.html +1 -1
- data/docs/brut-js/api/RichString.js.html +1 -1
- data/docs/brut-js/api/Tabs.html +1 -1
- data/docs/brut-js/api/Tabs.js.html +1 -1
- data/docs/brut-js/api/Tracing.html +1 -1
- data/docs/brut-js/api/Tracing.js.html +1 -1
- data/docs/brut-js/api/external-CustomElementRegistry.html +1 -1
- data/docs/brut-js/api/external-Performance.html +1 -1
- data/docs/brut-js/api/external-Promise.html +1 -1
- data/docs/brut-js/api/external-ValidityState.html +1 -1
- data/docs/brut-js/api/external-Window.html +1 -1
- data/docs/brut-js/api/external-fetch.html +1 -1
- data/docs/brut-js/api/global.html +1 -1
- data/docs/brut-js/api/index.html +1 -1
- data/docs/brut-js/api/index.js.html +1 -1
- data/docs/brut-js/api/module-testing.html +1 -1
- data/docs/brut-js/api/testing.AssetMetadata.html +1 -1
- data/docs/brut-js/api/testing.AssetMetadataLoader.html +1 -1
- data/docs/brut-js/api/testing.CustomElementTest.html +1 -1
- data/docs/brut-js/api/testing.DOMCreator.html +1 -1
- data/docs/brut-js/api/testing_AssetMetadata.js.html +1 -1
- data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +1 -1
- data/docs/brut-js/api/testing_CustomElementTest.js.html +1 -1
- data/docs/brut-js/api/testing_DOMCreator.js.html +1 -1
- data/docs/brut-js/api/testing_index.js.html +1 -1
- data/docs/brut-js.html +12 -7
- data/docs/business-logic.html +10 -5
- data/docs/cli.html +26 -26
- data/docs/components.html +61 -64
- data/docs/configuration.html +13 -8
- data/docs/css.html +14 -9
- data/docs/custom-element-tests.html +14 -9
- data/docs/database-access.html +12 -7
- data/docs/database-schema.html +15 -10
- data/docs/deployment.html +10 -5
- data/docs/dev-environment.html +17 -7
- data/docs/dir-structure.html +74 -0
- data/docs/doc-conventions.html +11 -6
- data/docs/end-to-end-tests.html +15 -8
- data/docs/favicon.ico +0 -0
- data/docs/features.html +182 -0
- data/docs/flash-and-session.html +73 -82
- data/docs/form-constraints.html +118 -0
- data/docs/forms.html +57 -367
- data/docs/getting-started.html +15 -10
- data/docs/handlers.html +51 -61
- data/docs/hashmap.json +1 -1
- data/docs/hooks.html +14 -9
- data/docs/i18n.html +12 -7
- data/docs/index.html +11 -6
- data/docs/instrumentation.html +12 -7
- data/docs/javascript.html +17 -12
- data/docs/jobs.html +10 -5
- data/docs/keyword-injection.html +22 -21
- data/docs/layouts.html +12 -20
- data/docs/lsp.html +11 -6
- data/docs/markdown-examples.html +10 -5
- data/docs/middleware.html +10 -5
- data/docs/overview.html +11 -138
- data/docs/pages.html +49 -121
- data/docs/recipes/alternate-layouts.html +50 -0
- data/docs/recipes/authentication.html +166 -6
- data/docs/recipes/blank-layouts.html +43 -0
- data/docs/recipes/custom-flash.html +54 -0
- data/docs/recipes/indexed-forms.html +102 -0
- data/docs/recipes/text-field-component.html +129 -0
- data/docs/roadmap.html +29 -0
- data/docs/routes.html +16 -19
- data/docs/security.html +11 -6
- data/docs/seed-data.html +10 -5
- data/docs/space-time-continuum.html +11 -6
- data/docs/tutorial.html +11 -6
- data/docs/unit-tests.html +10 -5
- data/docs/why.html +29 -0
- data/lib/brut/cli/apps/test.rb +1 -1
- data/lib/brut/front_end/components/inputs/select_tag_with_options.rb +2 -2
- data/lib/brut/front_end/form.rb +8 -8
- data/lib/brut/front_end/forms/radio_button_group_input.rb +8 -1
- data/lib/brut/front_end/forms/select_input.rb +8 -1
- data/lib/brut/junk_drawer.rb +48 -9
- data/lib/brut/version.rb +1 -1
- data/specs/brut/front_end/forms/radio_button_group_input.spec.rb +54 -0
- data/specs/brut/front_end/forms/select_input.spec.rb +54 -0
- data/specs/brut/junk_drawer.spec.rb +75 -0
- metadata +129 -82
- data/brutrb.com/images/logo-300.png +0 -0
- data/brutrb.com/images/logo.png +0 -0
- data/brutrb.com/not-released.md +0 -5
- data/brutrb.com/public/images/logo-300.png +0 -0
- data/brutrb.com/public/images/logo.png +0 -0
- data/docs/assets/LogoStop.X8x-4riz.png +0 -0
- data/docs/assets/ai.md._6HCDL6d.lean.js +0 -1
- data/docs/assets/chunks/@localSearchIndexroot.CeRAdP1K.js +0 -1
- data/docs/assets/components.md.CRUMdRoN.js +0 -104
- data/docs/assets/dev-env-overview.Gj7NWM8-.png +0 -0
- data/docs/assets/dev-environment.md.GZv6xvi9.js +0 -11
- data/docs/assets/dev-environment.md.GZv6xvi9.lean.js +0 -1
- data/docs/assets/doc-conventions.md.-kN3Xo5C.js +0 -1
- data/docs/assets/doc-conventions.md.-kN3Xo5C.lean.js +0 -1
- data/docs/assets/end-to-end-tests.md.yfQHC0b5.lean.js +0 -1
- data/docs/assets/flash-and-session.md.BXY8RvT0.js +0 -93
- data/docs/assets/forms.md.B-koVgyw.js +0 -379
- data/docs/assets/forms.md.B-koVgyw.lean.js +0 -1
- data/docs/assets/handlers.md.089DVD3v.js +0 -69
- data/docs/assets/handlers.md.089DVD3v.lean.js +0 -1
- data/docs/assets/index.md.CuBB-BdM.js +0 -1
- data/docs/assets/index.md.CuBB-BdM.lean.js +0 -1
- data/docs/assets/keyword-injection.md.Dt2tKREs.js +0 -25
- data/docs/assets/layouts.md.cPnh3NId.lean.js +0 -1
- data/docs/assets/not-released.md.BBy28McC.js +0 -1
- data/docs/assets/not-released.md.BBy28McC.lean.js +0 -1
- data/docs/assets/overview.md.DVKRM8zl.js +0 -133
- data/docs/assets/overview.md.DVKRM8zl.lean.js +0 -1
- data/docs/assets/pages.md.BE3kfOc5.js +0 -122
- data/docs/assets/pages.md.BE3kfOc5.lean.js +0 -1
- data/docs/assets/recipes_authentication.md.CAsXf7hk.js +0 -1
- data/docs/assets/recipes_authentication.md.CAsXf7hk.lean.js +0 -1
- data/docs/assets/routes.md.BMM7peut.js +0 -29
- data/docs/assets/space-time-continuum.md.KPUIKysQ.js +0 -1
- data/docs/assets/tutorial.md.BnoGjrdK.js +0 -1
- data/docs/assets/tutorial.md.BnoGjrdK.lean.js +0 -1
- data/docs/images/logo-300.png +0 -0
- data/docs/images/logo.png +0 -0
- data/docs/not-released.html +0 -24
- /data/docs/assets/{cli.md.RmeA2b0i.lean.js → cli.md.CjsktgFz.lean.js} +0 -0
- /data/docs/assets/{configuration.md.LG-zIBww.lean.js → configuration.md.BfeGnEci.lean.js} +0 -0
- /data/docs/assets/{css.md.DJgj2clw.lean.js → css.md.CltvJqAa.lean.js} +0 -0
- /data/docs/assets/{custom-element-tests.md.BrYJQEl3.lean.js → custom-element-tests.md.B_rbta32.lean.js} +0 -0
- /data/docs/assets/{database-access.md.C7l-Vuvb.lean.js → database-access.md.gnluu54N.lean.js} +0 -0
@@ -0,0 +1,96 @@
|
|
1
|
+
import{_ as i,c as a,o as n,ag as e}from"./chunks/framework.1L-BeKqY.js";const c=JSON.parse('{"title":"Components","description":"","frontmatter":{},"headers":[],"relativePath":"components.md","filePath":"components.md"}'),t={name:"components.md"};function p(l,s,h,k,o,d){return n(),a("div",null,s[0]||(s[0]=[e(`<h1 id="components" tabindex="-1">Components <a class="header-anchor" href="#components" aria-label="Permalink to "Components""></a></h1><p>Components in Brut are Phlex Components: a class that can hold data and use that to generate HTML. Components are the primary way you achieve re-use of markup or view logic.</p><h2 id="overview" tabindex="-1">Overview <a class="header-anchor" href="#overview" aria-label="Permalink to "Overview""></a></h2><p><a href="/api/Brut/FrontEnd/Component.html" target="_self" rel="noopener" data-no-router><code>Brut::FrontEnd::Component</code></a> inherits from <code>Phlex::HTML</code>, which means that to create a component you must do three things:</p><ol><li>Create a class in <code>app/src/front_end/components</code> that inherites from <code>AppComponent</code> (which is part of your app and inherits from <a href="/api/Brut/FrontEnd/Component.html" target="_self" rel="noopener" data-no-router><code>Brut::FrontEnd::Component</code></a>)</li><li>Implement an initializer that receives anything your component needs to do its job. It is recommended (but not required) that your initializer use only keyword arguments.</li><li>Implement <code>view_template</code> in which you make calls to Phlex' API.</li></ol><h3 id="simple-component" tabindex="-1">Simple Component <a class="header-anchor" href="#simple-component" aria-label="Permalink to "Simple Component""></a></h3><p>For example, suppose you want a re-usable button that can be gray, green, or red, and have an optional <code>formaction</code>.</p><p>You can create a component with <code>bin/scaffold component</code>:</p><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span>bin/scaffold component button</span></span>
|
2
|
+
<span class="line"><span># => app/src/front_end/components/button_component.rb</span></span>
|
3
|
+
<span class="line"><span># => specs/front_end/components/button_component.spec.rb</span></span></code></pre></div><p>Component inititalizers are called by you when you use them, so you can define it how you like. Brut uses keyword arguments by convention.</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># app/src/front_end/components/button_component.rb</span></span>
|
4
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> ButtonComponent</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> <</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> AppComponent</span></span>
|
5
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> initialize</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">color:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> :gray</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
6
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> formaction:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> nil</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
|
7
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> @color </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> color</span></span>
|
8
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> @formaction </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> formaction</span></span>
|
9
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
10
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>Since it's a Phlex component, implement <code>view_template</code> to generate the HTML you like. Our <code>view_template</code> will <code>yield</code> so the button's contents can be controlled by the caller. Note that the CSS here is <a href="/brut-css/index.html">BrutCSS</a>, but it can be anything you are using in your oapp.</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># app/src/front_end/components/button_component.rb</span></span>
|
11
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> ButtonComponent</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> <</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> AppComponent</span></span>
|
12
|
+
<span class="line"></span>
|
13
|
+
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # ...</span></span>
|
14
|
+
<span class="line"></span>
|
15
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> view_template</span></span>
|
16
|
+
<span class="line"><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;"> attributes</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> = {</span></span>
|
17
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> class:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> [</span></span>
|
18
|
+
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "tc"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># centered text</span></span>
|
19
|
+
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "br-3"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># border radius @ 3rd step of scale</span></span>
|
20
|
+
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "bn"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># no border</span></span>
|
21
|
+
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "f-3"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># font size @ 3rd step of scale</span></span>
|
22
|
+
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "ph-4"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># horizontal padding @ 4th step of scale</span></span>
|
23
|
+
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "pv-2"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># vertical padding @ 2nd step of scale</span></span>
|
24
|
+
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "bg-</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">#{</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">@color</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">}</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">-800"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># background is second lighest of scale</span></span>
|
25
|
+
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">#{</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">@color</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">}</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">-300"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># text is third darkest of scale</span></span>
|
26
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> ],</span></span>
|
27
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> formaction:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> @formaction</span></span>
|
28
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
|
29
|
+
<span class="line"></span>
|
30
|
+
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> button</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">**</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">attributes) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
|
31
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> yield</span></span>
|
32
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
33
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
34
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>Here are two examples of how you'd use this component and the HTML that would be generated:</p><div class="vp-code-group vp-adaptive-theme"><div class="tabs"><input type="radio" name="group-AMLDa" id="tab-Z4kcYne" checked><label data-title="ruby" for="tab-Z4kcYne">ruby</label><input type="radio" name="group-AMLDa" id="tab-MYoImbo"><label data-title="html" for="tab-MYoImbo">html</label></div><div class="blocks"><div class="language-ruby vp-adaptive-theme active"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">render </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">ButtonComponent</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">color:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> :green</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
|
35
|
+
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "Click Here"</span></span>
|
36
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><div class="language-html vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">html</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"><</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">button</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> class</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"tc br-3 bn f-3 ph-4 pv-2 bg-green-800 green-300"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">></span></span>
|
37
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> Click Here</span></span>
|
38
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"><</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">button</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">></span></span></code></pre></div></div></div><div class="vp-code-group vp-adaptive-theme"><div class="tabs"><input type="radio" name="group-LRTvr" id="tab-b8CnG8y" checked><label data-title="ruby" for="tab-b8CnG8y">ruby</label><input type="radio" name="group-LRTvr" id="tab-bThuucj"><label data-title="html" for="tab-bThuucj">html</label></div><div class="blocks"><div class="language-ruby vp-adaptive-theme active"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">render </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">ButtonComponent</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">color:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> :red</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">formaction:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> DeleteWidget</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">routing</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
|
39
|
+
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "Delete Widget"</span></span>
|
40
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><div class="language-html vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">html</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"><</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">button</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> class</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"tc br-3 bn f-3 ph-4 pv-2 bg-red-800 green-300"</span></span>
|
41
|
+
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> formaction</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"/delete_widget"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">></span></span>
|
42
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> Delete Widget</span></span>
|
43
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"><</span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">button</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">></span></span></code></pre></div></div></div><p>One issue with components is that you must pass them all their initializer arguments to use them. This means that if your component needs access to, say, the session, any page or component that uses your component must also require the session to be passed in.</p><p>Brut provides a partial solution to this called <em>global components</em>.</p><h3 id="global-components" tabindex="-1">Global Components <a class="header-anchor" href="#global-components" aria-label="Permalink to "Global Components""></a></h3><p>A global component can be created by Brut using <a href="/keyword-injection.html">keyword injection</a>. This means that, in our example above, a page that uses your component does not need to be given the session. It can have Brut inject it.</p><p>This provides a partial solution to so-called "prop drilling".</p><p>In <a href="/features.html">the features overview</a>, we saw a basic component for rendering a flash:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># components/flash_component.rb</span></span>
|
44
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> FlashComponent</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> <</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> AppComponent</span></span>
|
45
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> initialize</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">flash:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
|
46
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> flash.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">notice?</span></span>
|
47
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> @message_key </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> flash.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">notice</span></span>
|
48
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> @role </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> :info</span></span>
|
49
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> elsif</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> flash.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">alert?</span></span>
|
50
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> @message_key </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> flash.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">alert</span></span>
|
51
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> @role </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> :alert</span></span>
|
52
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
53
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
54
|
+
<span class="line"></span>
|
55
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> any_message?</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> !</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">@message_key.nil?</span></span>
|
56
|
+
<span class="line"></span>
|
57
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> view_template</span></span>
|
58
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> any_message?</span></span>
|
59
|
+
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> div</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">role:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> @role) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
|
60
|
+
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> t</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">([ </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">:flash</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, @message_key ])</span></span>
|
61
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
62
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
63
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
64
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>Instead of requiring each user of this component to manually inject the flash, we can call <code>global_component</code>, provided by <a href="/api/Brut/FrontEnd/Component/Helpers.html" target="_self" rel="noopener" data-no-router><code>Brut::FrontEnd::Component::Helpers</code></a>, which is included in all pages and components.</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> view_template</span></span>
|
65
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> header </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
|
66
|
+
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> global_component</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">FlashComponent</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
|
67
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
68
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>Components used in layouts will tend to be global components, to avoid creating odd dependencies between pages.</p><div class="important custom-block github-alert"><p class="custom-block-title">IMPORTANT</p><p>Brut currently requires an all-or-nothing approach to global components. Either the component can be injected with all its initializer parameters or it must be created explicitly by the page or component. You cannot have a component receive request-level keyword injection for some parameters with the page providing the rest.</p></div><p>Components can also be scoped to a page.</p><h3 id="page-private-components" tabindex="-1">Page Private Components <a class="header-anchor" href="#page-private-components" aria-label="Permalink to "Page Private Components""></a></h3><p>Often, components are helpful to simplifying a page's template or managing re-use within a page, but such a component isn't designed for use outside that page. For example, if the page renders a table, but the logic for each row is complex, you may want that in a separate component, even though it would be useless outside the page.</p><p>Brut provides a way to create a <em>page private</em> component that exists as an inner class of a page. It's not truly private, since it's still a Ruby class anyone can use, but it's form and source location communicate intent.</p><p>They can be created with <code>bin/scaffold</code>:</p><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span>bin/scaffold component --page HomePage Widget</span></span>
|
69
|
+
<span class="line"><span># => app/src/front_end/page/home_page/widget_component.rb</span></span>
|
70
|
+
<span class="line"><span># => specs/front_end/page/home_page/widget_component.spec.rb</span></span></code></pre></div><p>The class will be an inner class of <code>HomePage</code> in this example, <code>HomePage::WidgetComponent</code>. You build them and use them like normal:</p><div class="vp-code-group vp-adaptive-theme"><div class="tabs"><input type="radio" name="group-mga-x" id="tab-7lm0u4Y" checked><label data-title="Page" for="tab-7lm0u4Y">Page</label><input type="radio" name="group-mga-x" id="tab-9pnhbEO"><label data-title="Page Private Component" for="tab-9pnhbEO">Page Private Component</label></div><div class="blocks"><div class="language-ruby vp-adaptive-theme active"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> HomePage</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> <</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> AppPage</span></span>
|
71
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> page_template</span></span>
|
72
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> header </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
|
73
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> h1 { </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"Check out these Widgets!"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
|
74
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
75
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> main </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
|
76
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> ul </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
|
77
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> DB</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">Widget</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">all</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">each</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> do</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> |widget|</span></span>
|
78
|
+
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> render</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">HomePage</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">WidgetListItem</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">widget:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">))</span></span>
|
79
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
80
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
81
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
82
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
83
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> HomePage</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">WidgetListItem</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> <</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> AppComponent</span></span>
|
84
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> initialize</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">widget:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
|
85
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> @widget </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> widget</span></span>
|
86
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
87
|
+
<span class="line"></span>
|
88
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> view_template</span></span>
|
89
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> li </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
|
90
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> h2 { @widget.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
|
91
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { @widget.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">description</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
|
92
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
93
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
94
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div></div></div><p>The main difference between a page-private component and a normal component's behavior is how <a href="/i18n.html">I18n</a> strings are resolved. In short, given this:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">p</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> do</span></span>
|
95
|
+
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> t</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">:hello</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
|
96
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>In a normal component named <code>WidgetComponent</code>, the keys searched for translations would be <code>"components.WidgetComponent.hello"</code> and <code>"hello"</code> . For the page-private component <code>HomePage::WidgetComponent</code>, the keys searched would be <code>"pages.HomePage.hello"</code> and <code>"hello"</code>. This means that page private components can access a page's translations.</p><h2 id="testing" tabindex="-1">Testing <a class="header-anchor" href="#testing" aria-label="Permalink to "Testing""></a></h2><p>Test widgets exactly as you would <a href="/pages.html#testing">pages</a>. The only difference is that components always render HTML and have no <code>before_generate</code> concept.</p><h2 id="recommended-practices" tabindex="-1">Recommended Practices <a class="header-anchor" href="#recommended-practices" aria-label="Permalink to "Recommended Practices""></a></h2><p>The <a href="/pages.html#recommended-practices">recommended practices for pages</a> all apply to components, too.</p><p>Beyond that, components are intended to be lightweight, so use them liberally. Any page or component that has complex markup can be extracted to another component and more easily unit-tested.</p><h2 id="technical-notes" tabindex="-1">Technical Notes <a class="header-anchor" href="#technical-notes" aria-label="Permalink to "Technical Notes""></a></h2><div class="important custom-block github-alert"><p class="custom-block-title">IMPORTANT</p><p>Technical Notes are for deeper understanding and debugging. While we will try to keep them up-to-date with changes to Brut's internals, the source code is always more correct.</p></div><p><em>Last Updated May 5, 2025</em></p><p>As mentioned, components are Phlex components, but have various helpers mixed-into them. Components are currently tightly coupled to Phlex and there is no plan to allow alternate implementations of view logic that isn't supported by Phlex.</p>`,47)]))}const g=i(t,[["render",p]]);export{c as __pageData,g as default};
|
@@ -1 +1 @@
|
|
1
|
-
import{_ as i,c as a,o as n,ag as e}from"./chunks/framework.1L-BeKqY.js";const c=JSON.parse('{"title":"Components","description":"","frontmatter":{},"headers":[],"relativePath":"components.md","filePath":"components.md"}'),t={name:"components.md"};function l
|
1
|
+
import{_ as i,c as a,o as n,ag as e}from"./chunks/framework.1L-BeKqY.js";const c=JSON.parse('{"title":"Components","description":"","frontmatter":{},"headers":[],"relativePath":"components.md","filePath":"components.md"}'),t={name:"components.md"};function p(l,s,h,k,o,d){return n(),a("div",null,s[0]||(s[0]=[e("",47)]))}const g=i(t,[["render",p]]);export{c as __pageData,g as default};
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import{_ as i,c as a,o as e,ag as n}from"./chunks/framework.1L-BeKqY.js";const c=JSON.parse('{"title":"Configuration","description":"","frontmatter":{},"headers":[],"relativePath":"configuration.md","filePath":"configuration.md"}'),t={name:"configuration.md"};function l(h,s,p,r,o,k){return e(),a("div",null,s[0]||(s[0]=[n(`<h1 id="configuration" tabindex="-1">Configuration <a class="header-anchor" href="#configuration" aria-label="Permalink to "Configuration""></a></h1><p>Brut strives to avoid configuration and flexibility. Much of Brut's behavior is convention-based, however several aspects of Brut's behavior relate to literal values like file paths. Some of this set up is designed to be overridden.</p><h2 id="overview" tabindex="-1">Overview <a class="header-anchor" href="#overview" aria-label="Permalink to "Overview""></a></h2><p>Any configured value, or value otherwise intended to be abstracted from its literal value, is available via <code>Brut.container</code>, which returns an instance of <a href="/api/Brut/Framework/Container.html" target="_self" rel="noopener" data-no-router><code>Brut::Framework::Container</code></a>. This class uses <code>method_missing</code> to provide direct access to configured values. It can also be used to store or override configured values.</p><p>An instance of <a href="/api/Brut/Framework/Config.html" target="_self" rel="noopener" data-no-router><code>Brut::Framework::Config</code></a> is used to set up all of Brut's initial values. This file is a good reference for determining the literal values of various configuration options.</p><h3 id="basics-of-configuration" tabindex="-1">Basics of Configuration <a class="header-anchor" href="#basics-of-configuration" aria-label="Permalink to "Basics of Configuration""></a></h3><p>The configuration system can be used to set literal values, or lazily-derived values. The method <code>store</code> is used for both purposes. Lazy values are managed by calling <code>store</code> with a block.</p><div class="vp-code-group vp-adaptive-theme"><div class="tabs"><input type="radio" name="group-
|
1
|
+
import{_ as i,c as a,o as e,ag as n}from"./chunks/framework.1L-BeKqY.js";const c=JSON.parse('{"title":"Configuration","description":"","frontmatter":{},"headers":[],"relativePath":"configuration.md","filePath":"configuration.md"}'),t={name:"configuration.md"};function l(h,s,p,r,o,k){return e(),a("div",null,s[0]||(s[0]=[n(`<h1 id="configuration" tabindex="-1">Configuration <a class="header-anchor" href="#configuration" aria-label="Permalink to "Configuration""></a></h1><p>Brut strives to avoid configuration and flexibility. Much of Brut's behavior is convention-based, however several aspects of Brut's behavior relate to literal values like file paths. Some of this set up is designed to be overridden.</p><h2 id="overview" tabindex="-1">Overview <a class="header-anchor" href="#overview" aria-label="Permalink to "Overview""></a></h2><p>Any configured value, or value otherwise intended to be abstracted from its literal value, is available via <code>Brut.container</code>, which returns an instance of <a href="/api/Brut/Framework/Container.html" target="_self" rel="noopener" data-no-router><code>Brut::Framework::Container</code></a>. This class uses <code>method_missing</code> to provide direct access to configured values. It can also be used to store or override configured values.</p><p>An instance of <a href="/api/Brut/Framework/Config.html" target="_self" rel="noopener" data-no-router><code>Brut::Framework::Config</code></a> is used to set up all of Brut's initial values. This file is a good reference for determining the literal values of various configuration options.</p><h3 id="basics-of-configuration" tabindex="-1">Basics of Configuration <a class="header-anchor" href="#basics-of-configuration" aria-label="Permalink to "Basics of Configuration""></a></h3><p>The configuration system can be used to set literal values, or lazily-derived values. The method <code>store</code> is used for both purposes. Lazy values are managed by calling <code>store</code> with a block.</p><div class="vp-code-group vp-adaptive-theme"><div class="tabs"><input type="radio" name="group-TdxOE" id="tab-fGpW2DF" checked><label data-title="Storing Litral Values" for="tab-fGpW2DF">Storing Litral Values</label><input type="radio" name="group-TdxOE" id="tab-JXNYEYG"><label data-title="Storing Lazy Values" for="tab-JXNYEYG">Storing Lazy Values</label></div><div class="blocks"><div class="language-ruby vp-adaptive-theme active"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">Brut</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">container</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">store</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span></span>
|
2
2
|
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "database_url"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
3
3
|
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> String</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
4
4
|
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "URL to the primary database"</span></span>
|
@@ -26,13 +26,13 @@ import{_ as i,c as a,o as e,ag as n}from"./chunks/framework.1L-BeKqY.js";const c
|
|
26
26
|
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "Path to where page classes and templates are stored"</span></span>
|
27
27
|
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> |front_end_src_dir|</span></span>
|
28
28
|
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> front_end_src_dir </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">/</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "pages"</span></span>
|
29
|
-
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><h3 id="type-and-name-enforcement" tabindex="-1">Type and Name Enforcement <a class="header-anchor" href="#type-and-name-enforcement" aria-label="Permalink to "Type and Name Enforcement""></a></h3><p>You'll note that <code>store</code
|
29
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><h3 id="type-and-name-enforcement" tabindex="-1">Type and Name Enforcement <a class="header-anchor" href="#type-and-name-enforcement" aria-label="Permalink to "Type and Name Enforcement""></a></h3><p>You'll note that <code>store</code> accepts a class parameter. This is mostly used for documentation, with two exceptions:</p><ul><li>If the type is <code>Pathname</code> (which is what is used by <code>store_ensured_path</code> and <code>store_required_path</code>), the configuration parameter name <em>must</em> end in <code>_file</code> or <code>_dir</code>.</li><li>If the type is <code>"boolean"</code> or <code>:boolean</code>, the configuration parameter name <em>must</em> end in a question mark. In this case, the value itself is coerced into <code>true</code> or <code>false</code>.</li></ul><p>Brut may add more constraints or conversions over time.</p><h3 id="overridable-and-nilable-values" tabindex="-1">Overridable and <code>nil</code>able Values <a class="header-anchor" href="#overridable-and-nilable-values" aria-label="Permalink to "Overridable and \`nil\`able Values""></a></h3><p>By default, Brut configuration values cannot be overridden and they cannot be <code>nil</code>. When calling <code>store</code>, <code>allow_app_override: true</code> and <code>allow_nil: true</code>, can be passed to change this behavior.</p><p>In <a href="/flash-and-session.html">Flash and Session</a>, we discussed that you can set your own class for the flash. This is possible due to how Brut defines the configuration parameter <code>flash_class</code>:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">Brut</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">container</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">store</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span></span>
|
30
30
|
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "flash_class"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
31
31
|
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> Class</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
32
32
|
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "Class to use to represent the Flash"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
33
33
|
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> Brut</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">FrontEnd</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">Flash</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
34
34
|
<span class="line highlighted"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> allow_app_override:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
35
|
-
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span></code></pre></div><p>Since <code>allow_app_override</code> is true, you can call <code>override</code> in your <code>App</code>:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">Brut</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">container</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">override</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"flash_class"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">AppFlash</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span></code></pre></div><p>Calling <code>override</code> for parameters where <code>allow_app_override</code> is not true results in an error. Further, calling <code>store</code> on a previously <code>store</code>-d parameter results in an error.</p><p>The idea is to make it extremely clear what values are being set and overridden, and to avoid setting values that don't exist.</p><p>Some values can be <code>nil</code>. Generally, <code>nil</code> is a pain and will cause you great hardship. On occasion, it's needed. For example, <a href="/database-schema.html#external-ids">external IDs</a> only work if the app provides an app-wide prefix.</p><p>Here is how Brut sets this up by default:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">Brut</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">container</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">store</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span></span>
|
35
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span></code></pre></div><p>Since <code>allow_app_override</code> is true, you can call <code>override</code> in your <code>App</code>:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">Brut</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">container</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">override</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"flash_class"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">AppFlash</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span></code></pre></div><p>Calling <code>override</code> for parameters where <code>allow_app_override</code> is not true results in an error. Further, calling <code>store</code> on a previously <code>store</code>-d parameter results in an error.</p><p>The idea is to make it extremely clear what values are being set and overridden, and to avoid setting values that don't exist or aren't relevant.</p><p>Some values can be <code>nil</code>. Generally, <code>nil</code> is a pain and will cause you great hardship. On occasion, it's needed. For example, <a href="/database-schema.html#external-ids">external IDs</a> only work if the app provides an app-wide prefix.</p><p>Here is how Brut sets this up by default:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">Brut</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">container</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">store</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span></span>
|
36
36
|
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "external_id_prefix"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
37
37
|
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> String</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
38
38
|
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "String to use as a prefix for external ids in tables using the external_id feature. Nil means the feature is disabled"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
@@ -12,10 +12,10 @@ import{_ as i,c as a,o as e,ag as t}from"./chunks/framework.1L-BeKqY.js";const u
|
|
12
12
|
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> "mocha"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"^10.7.3"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
13
13
|
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> "playwright"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"^1.50.1"</span></span>
|
14
14
|
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
|
15
|
-
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><p>Next, <code>app/src/front_end/css/index.css</code> would import both <code>"foobar-css"</code> and <code>"pages/HomePage.css"</code>.</p><div class="language-
|
16
|
-
<span class="line"><span style="--shiki-light:#
|
15
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><p>Next, <code>app/src/front_end/css/index.css</code> would import both <code>"foobar-css"</code> and <code>"pages/HomePage.css"</code>.</p><div class="language-css vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">css</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">@import</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "foobar-css/everything.css"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
|
16
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">@import</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "pages/HomePage.css"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span></code></pre></div><p>In the browser <code>@import</code> accepts any URL and will resolve it when the file is loaded. In Brut, when bundled using esbuild, these strings are relative paths to files in either <code>node_modules/</code> or <code>app/src/front_end/css/</code>. Unlike JavaScript imports, you don't need <code>"./"</code> to differentiate the two. Also unlike JavaScript imports, these must be the first lines of your <code>index.css</code> file or they will be ignored. All imports must come before any CSS.</p><h2 id="importing-third-party-css" tabindex="-1">Importing Third Party CSS <a class="header-anchor" href="#importing-third-party-css" aria-label="Permalink to "Importing Third Party CSS""></a></h2><p>Because there are so many ways to use NPM modules, it's not common to find reliable documentation on how to use a CSS library you bring in via NPM/<code>package.json</code>.</p><p>The key to figuring out what to use with <code>@import</code> is that the value is relative to <code>node_modules</code> and should an actual filename, including path and extension. You can figure this out by looking inside <code>node_modules</code> after you've done <code>bin/setup</code> (or <code>npm install</code>).</p><p>Suppose "foobar-css" has this directory structure:</p><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span>node_modules/</span></span>
|
17
17
|
<span class="line"><span> foobar-css/</span></span>
|
18
18
|
<span class="line"><span> css/</span></span>
|
19
19
|
<span class="line"><span> foobar-all.css</span></span>
|
20
|
-
<span class="line"><span> foobar-thin.css</span></span></code></pre></div><p>To use <code>foobar-thin.css</code>, you'd write this <code>@import</code> directive:</p><div class="language-css vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">css</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">@import</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "foobar-css/css/foobar-thin.css"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span></code></pre></div><h2 id="using-brut-css" tabindex="-1">Using Brut-CSS <a class="header-anchor" href="#using-brut-css" aria-label="Permalink to "Using Brut-CSS""></a></h2><p>By default, Brut includes a lightweight functional CSS library called
|
20
|
+
<span class="line"><span> foobar-thin.css</span></span></code></pre></div><p>To use <code>foobar-thin.css</code>, you'd write this <code>@import</code> directive:</p><div class="language-css vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">css</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">@import</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "foobar-css/css/foobar-thin.css"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span></code></pre></div><h2 id="using-brut-css" tabindex="-1">Using Brut-CSS <a class="header-anchor" href="#using-brut-css" aria-label="Permalink to "Using Brut-CSS""></a></h2><p>By default, Brut includes a lightweight functional CSS library called <a href="/brut-css/index.html">BrutCSS</a>. It provides a basic design system and single-purpose classes to allow you to quickly prototype or build UIs. It is similar to TailwindCSS but far far smaller and simpler (and less powered).</p><p>It is included so you have something to start with. You can use it by using its various classes like <code>bg-green-300</code> and <code>m-4</code>, or you can use its provided custom properties like <code>var(--green-300)</code> and <code>var(--sp-4)</code>.</p><p>To remove it, remove this line from <code>index.css</code></p><div class="language-css vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">css</span><pre class="shiki shiki-themes github-light github-dark has-diff vp-code" tabindex="0"><code><span class="line diff remove"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">@import</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "brut-css/brut.css"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">; </span></span>
|
21
21
|
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">@import</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "your/css/here.css"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span></code></pre></div><h2 id="using-tailwindcss" tabindex="-1">Using TailwindCSS <a class="header-anchor" href="#using-tailwindcss" aria-label="Permalink to "Using TailwindCSS""></a></h2><p>TBD</p><h2 id="technical-notes" tabindex="-1">Technical Notes <a class="header-anchor" href="#technical-notes" aria-label="Permalink to "Technical Notes""></a></h2><div class="important custom-block github-alert"><p class="custom-block-title">IMPORTANT</p><p>Technical Notes are for deeper understanding and debugging. While we will try to keep them up-to-date with changes to Brut's internals, the source code is always more correct.</p></div><p><em>Last Updated May 8, 2025</em></p><p>Currently, Brut only supports a single entry point and bundle. This could be easily made more flexible if there is a desire to finely tweak the CSS loaded on specific pages.</p><p>Brut also does not expose any esbuild configuration. This could be provided in the future, but for now, it is hard-coded.</p>`,31)]))}const k=i(n,[["render",o]]);export{u as __pageData,k as default};
|
data/docs/assets/{custom-element-tests.md.BrYJQEl3.js → custom-element-tests.md.B_rbta32.js}
RENAMED
@@ -7,7 +7,7 @@ import{_ as i,c as a,o as t,ag as n}from"./chunks/framework.1L-BeKqY.js";const o
|
|
7
7
|
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> \`</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">test</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"description here"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, ({</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">document</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">window</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">assert</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
|
8
8
|
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> assert.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">fail</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"test goes here"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
|
9
9
|
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> })</span></span>
|
10
|
-
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">})</span></span></code></pre></div><p><code>withHTML</code> creates a JSDom-based document with the given HTML. This HTML is in effect inside the test. Mock versions of <code>document</code> and <code>window</code> are passed to the test, and any other functions you need can be as well, such as <code>assert</code>.</p><p>The idea is that you use the browser APIs to examine the DOM and assert the behavior of the custom element (as opposed to interacting with the custom element's class).</p><p>Suppose that <code>my-element</code> transform text inside it based on the <code>transform</code> attribute. By default, it's <code>lower</code
|
10
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">})</span></span></code></pre></div><p><code>withHTML</code> creates a JSDom-based document with the given HTML. This HTML is in effect inside the test. Mock versions of <code>document</code> and <code>window</code> are passed to the test, and any other functions you need can be as well, such as <code>assert</code>.</p><p>The idea is that you use the browser APIs to examine the DOM and assert the behavior of the custom element (as opposed to interacting with the custom element's class).</p><p>Suppose that <code>my-element</code> transform text inside it based on the <code>transform</code> attribute. By default, it's <code>lower</code> (which will lower-case the text), but can be set to <code>upper</code> to upper case the text inside.</p><p>This means you'll need three tests, each with a different DOM:</p><div class="language-javascript vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">javascript</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { withHTML } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "brut-js/testing/index.js"</span></span>
|
11
11
|
<span class="line"></span>
|
12
12
|
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">describe</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"<some-element>"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, () </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
|
13
13
|
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> withHTML</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">\`</span></span>
|
@@ -33,7 +33,7 @@ import{_ as i,c as a,o as t,ag as n}from"./chunks/framework.1L-BeKqY.js";const o
|
|
33
33
|
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> \`</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">test</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"upper-cases explicitly"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, ({</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">document</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">window</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">assert</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
|
34
34
|
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // TBD</span></span>
|
35
35
|
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> })</span></span>
|
36
|
-
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">})</span></span></code></pre></div><p>
|
36
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">})</span></span></code></pre></div><p>When the function you give to <code>test</code> is executed, the DOM will have been setup, so you can rely on your custom elements <code>connectedCallback</code> having been called. Assuming the text transformation for <code>my-element</code> occurs in <code>connectedCallback</code>, here is how you'd test all three cases:</p><div class="language-javascript vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">javascript</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { withHTML } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "brut-js/testing/index.js"</span></span>
|
37
37
|
<span class="line"></span>
|
38
38
|
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">describe</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"<some-element>"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, () </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
|
39
39
|
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> withHTML</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">\`</span></span>
|
@@ -62,7 +62,7 @@ import{_ as i,c as a,o as t,ag as n}from"./chunks/framework.1L-BeKqY.js";const o
|
|
62
62
|
<span class="line highlighted"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> const</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> element</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> document.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">querySelector</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"my-element"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
|
63
63
|
<span class="line highlighted"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> assert.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">equal</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(element.textContent.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">trim</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(),</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"SOME TEXT"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
|
64
64
|
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> })</span></span>
|
65
|
-
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">})</span></span></code></pre></div><p>You'll notice almost all of this uses the browser APIs you (should
|
65
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">})</span></span></code></pre></div><p>You'll notice almost all of this uses the browser APIs you (should) know and (hopefully) love.</p><p>You can manipulate the DOM inside a test as well, and it should behave as if you are doing it in a browser. Note that many browser APIs are synchronous, so you don't have to add <code>await</code> before every single line of code.</p><p>Note that all of these test run under NodeJS, which is different from a browser. This means that code like <code>new InputEvent()</code> will succeed in returning an <code>InputEvent</code>, but said object is in no way the <code>InputEvent</code> you'd use in a browser. You must use <code>window.</code>:</p><div class="language-javascript vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">javascript</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">// does not work, but doesn't raise an error either</span></span>
|
66
66
|
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">input.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">dispatchEvent</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">new</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> InputEvent</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"input"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, {})) </span></span>
|
67
67
|
<span class="line"></span>
|
68
68
|
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;">// works</span></span>
|
@@ -60,4 +60,4 @@ import{_ as a,c as i,o as e,ag as t}from"./chunks/framework.1L-BeKqY.js";const c
|
|
60
60
|
<span class="line highlighted"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">to</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> raise_error</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#DBEDFF;">/email_must_be_domain/</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) </span></span>
|
61
61
|
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
62
62
|
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
63
|
-
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><h2 id="recommended-practices" tabindex="-1">Recommended Practices <a class="header-anchor" href="#recommended-practices" aria-label="Permalink to "Recommended Practices""></a></h2><h3 id="do-not-put-business-logic-on-your-database-models" tabindex="-1">Do Not Put Business Logic On Your Database Models <a class="header-anchor" href="#do-not-put-business-logic-on-your-database-models" aria-label="Permalink to "Do Not Put Business Logic On Your Database Models""></a></h3><p>There's no reason to, or benefit to doing so. What you'll find is that any app of even moderate complexity will not have a strict mapping from page to business concept to database table. Rather these things will all differ greatly, and each serves a different purpose.</p><p>The job of your data models—and the tables they provide access to—is to store reliable and unambiguous data. Their job is to ensure there is no bad data such that when you ask the database a question, you get a reliable and correct answer.</p><p>Your views and business logic do not have this exact same job.</p><p>As such, your models should only contain:</p><ul><li>configuration to allow navigating the database.</li><li>methods to manage type conversions between your types and the strings or numbers required in the database</li><li>methods to query the data based on data definitions (not business logic).</li></ul><p>Business logic and data models <em>do</em> overlap at times, so there is some judgement in maintaining a clear separation of concerns. One way to manage this is to always put all logic elsewhere until you see a pattern of re-use that leads you to extract that logic to a data model.</p><h3 id="do-not-use-validations-on-models-unless-there-is-no-other-choice" tabindex="-1">Do Not Use Validations on Models Unless There is No Other Choice <a class="header-anchor" href="#do-not-use-validations-on-models-unless-there-is-no-other-choice" aria-label="Permalink to "Do Not Use Validations on Models Unless There is No Other Choice""></a></h3><p>Sequel provides a validation layer for use on models. You should not generally use this, since a) data integrity is baked into your database design, and b) user interactions and constraints are part of the front-end.</p><p>That said, there are times when you have data constraints that cannot be modeled in the database. In that case, a validation on the data model is better than nothing. Since all data access for your app should go through your data models, a validation on a data model has a high chance of being checked.</p><div class="note custom-block github-alert"><p class="custom-block-title">NOTE</p><p>Since any process, app, or tool can manipulate your database, model-based validations won't be in effect, and therefore won't be applied. This is why you design your schema to avoid invalid data wherever possible.</p></div><h2 id="technical-notes" tabindex="-1">Technical Notes <a class="header-anchor" href="#technical-notes" aria-label="Permalink to "Technical Notes""></a></h2><div class="important custom-block github-alert"><p class="custom-block-title">IMPORTANT</p><p>Technical Notes are for deeper understanding and debugging. While we will try to keep them up-to-date with changes to Brut's internals, the source code is always more correct.</p></div><p><em>Last Updated May 8, 2025</em></p><p>None at this time</p>`,47)]))}const g=a(n,[["render",l]]);export{c as __pageData,g as default};
|
63
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><h2 id="recommended-practices" tabindex="-1">Recommended Practices <a class="header-anchor" href="#recommended-practices" aria-label="Permalink to "Recommended Practices""></a></h2><h3 id="do-not-put-business-logic-on-your-database-models" tabindex="-1">Do Not Put Business Logic On Your Database Models <a class="header-anchor" href="#do-not-put-business-logic-on-your-database-models" aria-label="Permalink to "Do Not Put Business Logic On Your Database Models""></a></h3><p>There's no reason to, or benefit to doing so. What you'll find is that any app of even moderate complexity will not have a strict mapping from page to business concept to database table. Rather, these things will all differ greatly, and each serves a different purpose.</p><p>The job of your data models—and the tables they provide access to—is to store reliable and unambiguous data. Their job is to ensure there is no bad data such that when you ask the database a question, you get a reliable and correct answer.</p><p>Your views and business logic do not have this exact same job.</p><p>As such, your models should only contain:</p><ul><li>configuration to allow navigating the database.</li><li>methods to manage type conversions between your types and the strings or numbers required in the database</li><li>methods to query the data based on data definitions (not business logic).</li></ul><p>Business logic and data models <em>do</em> overlap at times, so there is some judgement in maintaining a clear separation of concerns. One way to manage this is to always put all logic elsewhere until you see a pattern of re-use that leads you to extract that logic to a data model.</p><h3 id="do-not-use-validations-on-models-unless-there-is-no-other-choice" tabindex="-1">Do Not Use Validations on Models Unless There is No Other Choice <a class="header-anchor" href="#do-not-use-validations-on-models-unless-there-is-no-other-choice" aria-label="Permalink to "Do Not Use Validations on Models Unless There is No Other Choice""></a></h3><p>Sequel provides a validation layer for use on models. You should not generally use this, since a) data integrity is baked into your database design, and b) user interactions and constraints are part of the front-end.</p><p>That said, there are times when you have data constraints that cannot be modeled in the database. In that case, a validation on the data model is better than nothing. Since all data access for your app should go through your data models, a validation on a data model has a high chance of being checked.</p><div class="note custom-block github-alert"><p class="custom-block-title">NOTE</p><p>Since any process, app, or tool can manipulate your database, model-based validations won't be in effect, and therefore won't be applied. This is why you design your schema to avoid invalid data wherever possible.</p></div><h2 id="technical-notes" tabindex="-1">Technical Notes <a class="header-anchor" href="#technical-notes" aria-label="Permalink to "Technical Notes""></a></h2><div class="important custom-block github-alert"><p class="custom-block-title">IMPORTANT</p><p>Technical Notes are for deeper understanding and debugging. While we will try to keep them up-to-date with changes to Brut's internals, the source code is always more correct.</p></div><p><em>Last Updated May 8, 2025</em></p><p>None at this time</p>`,47)]))}const g=a(n,[["render",l]]);export{c as __pageData,g as default};
|
@@ -1,9 +1,9 @@
|
|
1
|
-
import{_ as
|
1
|
+
import{_ as e,c as a,o as i,ag as t}from"./chunks/framework.1L-BeKqY.js";const u=JSON.parse('{"title":"Database Schema / Migrations","description":"","frontmatter":{},"headers":[],"relativePath":"database-schema.md","filePath":"database-schema.md"}'),n={name:"database-schema.md"};function l(o,s,h,r,p,d){return i(),a("div",null,s[0]||(s[0]=[t(`<h1 id="database-schema-migrations" tabindex="-1">Database Schema / Migrations <a class="header-anchor" href="#database-schema-migrations" aria-label="Permalink to "Database Schema / Migrations""></a></h1><p>Brut provides access to the database via the <a href="https://sequel.jeremyevans.net/" target="_blank" rel="noreferrer">Sequel library</a>. To manage your database schema, Brut uses Sequel's facility for this, with some of its own enhancements.</p><div class="note custom-block github-alert"><p class="custom-block-title">NOTE</p><p>Brut currently only supports Postgres. Sequel supports many database systems, however Brut's extensions are currently geared toward Postgres only.</p></div><h2 id="overview" tabindex="-1">Overview <a class="header-anchor" href="#overview" aria-label="Permalink to "Overview""></a></h2><p>Your database schema is managed by a series of changes that build upon one another called <em>migrations</em>.</p><p>For example, if you have a table <code>widgets</code> that has a <code>name</code> and <code>description</code>, to add a <code>status</code> field, you cannot <code>drop table widgets</code> and then <code>create table widgets(...)</code> with the fields. You must instead <code>alter table widgets(...)</code> to add the new column.</p><p>Thus, each migration file is a change to the schema produced by all previous migration files.</p><p>Brut's provides this via Sequel. See <a href="https://sequel.jeremyevans.net/rdoc/files/doc/schema_modification_rdoc.html" target="_blank" rel="noreferrer">both</a> <a href="https://sequel.jeremyevans.net/rdoc/files/doc/migration_rdoc.html" target="_blank" rel="noreferrer">docs</a> for details on the API. Any schema modification method Sequel documents is available, however some default behavior has changed.</p><h3 id="creating-migrations" tabindex="-1">Creating Migrations <a class="header-anchor" href="#creating-migrations" aria-label="Permalink to "Creating Migrations""></a></h3><p>To create a migration, use <code>bin/db new-migration</code>. It accepts any number of arguments that will be joined together to form the filename:</p><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span>> bin/db new-migration user accounts</span></span>
|
2
2
|
<span class="line"><span>[ bin/db ] Migration created:</span></span>
|
3
|
-
<span class="line"><span> app/src/back_end/data_models/migrations/20250508132646_user-accounts.rb</span></span></code></pre></div><p>The file is created mostly blank:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">Sequel</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">migration</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> do</span></span>
|
3
|
+
<span class="line"><span> app/src/back_end/data_models/migrations/20250508132646_user-accounts.rb</span></span></code></pre></div><p>Note that the files are located in <code>app/src/back_end/data_models/migrations</code> and have a name prefixed with a timestamp. This timestamp determins an ordering of how the files are applied to the database.</p><p>The file is created mostly blank:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">Sequel</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">migration</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> do</span></span>
|
4
4
|
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> up </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
|
5
5
|
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
6
|
-
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>Sequels' migration
|
6
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><div class="note custom-block github-alert"><p class="custom-block-title">NOTE</p><p>Sequels' migration API is similar in concept to Rails', but differs significantly in specifics. Please consult Sequel's documentation and don't assume Railsism will work the same way.</p></div><p>Brut encourages only "up" migrations. Since Brut treats your development database as ephemeral, there is little value to managing "down" migrations.</p><p>This is why Sequel's <code>change</code> method is not included in the scaffolded code. <code>change</code>, like Active Record's method of the same name, automagically creates both "up" and "down" migrations, but <em>only</em> if you use the DSL. If you use raw SQL, <code>change</code> doesn't work. By using only <code>up</code>, you won't have to worry about this.</p><p>Let's create an accounts table that has an email field, a <code>deactivated_at</code> timestamp, and a <code>created_at</code> timestamp:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">Sequel</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">migration</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> do</span></span>
|
7
7
|
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> up </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
|
8
8
|
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> create_table </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">:accounts</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
9
9
|
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> comment:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "People or systems who can access this system"</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> do</span></span>
|
@@ -13,7 +13,7 @@ import{_ as a,c as s,o as i,ag as t}from"./chunks/framework.1L-BeKqY.js";const u
|
|
13
13
|
<span class="line"></span>
|
14
14
|
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
15
15
|
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
|
16
|
-
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>A few notes that aren't obvious without knowing about Brut's extensions:</p><ul><li><code>comment:</code> is required. You must provide documentation about what table is for</li><li>The table has a primary key named <code>id</code> of type <code>int</code> that is a serial.</li><li><code>created_at</code> is created by default, with time <code>timestamptz</code> (AKA <code>timestamp with time zone</code>, see <a href="/space-time-continuum.html">Space/Time Continuum</a>).</li><li><code>email</code> is not null by default. <code>deactivated_at</code> <em>is</em> null because it's specified as such.</li></ul><p>To apply this migration use <code>bin/db migrate</code></p><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span>> bin/db migrate</span></span></code></pre></div><
|
16
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>A few notes that aren't obvious without knowing about Brut's extensions:</p><ul><li><code>comment:</code> is required. You must provide documentation about what table is for</li><li>The table has a primary key named <code>id</code> of type <code>int</code> that is a serial.</li><li><code>created_at</code> is created by default, with time <code>timestamptz</code> (AKA <code>timestamp with time zone</code>, see <a href="/space-time-continuum.html">Space/Time Continuum</a>).</li><li><code>email</code> is not null by default. <code>deactivated_at</code> <em>is</em> null because it's specified as such.</li></ul><p>To apply this migration use <code>bin/db migrate</code></p><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span>> bin/db migrate</span></span></code></pre></div><h3 id="managing-migrations" tabindex="-1">Managing Migrations <a class="header-anchor" href="#managing-migrations" aria-label="Permalink to "Managing Migrations""></a></h3><p>Sequel uses a special database table to understand which migrations have been run. This table will exist in production and prevent you from applying migrations twice or skipping a migration.</p><p>Note that managing a production database in this way requires knowledge of both your database system and the data itself. Brut can only provide so much to make this process manageable. You should consult <a href="https://github.com/ankane/strong_migrations?tab=readme-ov-file" target="_blank" rel="noreferrer">Strong Migrations' README</a> and learn it deeply. Although it's targeted at Rails developers, the information here applies to any database management system.</p><h3 id="brut-extensions-and-changes-in-sequel-s-behavior" tabindex="-1">Brut Extensions and Changes in Sequel's Behavior <a class="header-anchor" href="#brut-extensions-and-changes-in-sequel-s-behavior" aria-label="Permalink to "Brut Extensions and Changes in Sequel's Behavior""></a></h3><p>Brut includes the following standard plugins and extensions:</p><ul><li><a href="https://sequel.jeremyevans.net/rdoc-plugins/files/lib/sequel/extensions/pg_array_rb.html" target="_blank" rel="noreferrer"><code>pg_array</code></a></li><li><a href="https://sequel.jeremyevans.net/rdoc-plugins/files/lib/sequel/extensions/pg_json_rb.html" target="_blank" rel="noreferrer"><code>pg_json</code></a></li><li><a href="https://sequel.jeremyevans.net/rdoc-plugins/classes/Sequel/Plugins/TableSelect.html" target="_blank" rel="noreferrer"><code>table_select</code></a>, which changes queries to prepend <code>*</code> with the table name, e.g. <code>select accounts.*</code> instead of <code>select *</code>.</li><li><a href="https://sequel.jeremyevans.net/rdoc-plugins/classes/Sequel/Plugins/SkipSavingColumns.html" target="_blank" rel="noreferrer"><code>skip_saving_columns</code></a> which will skip saving columns that the database generates.</li></ul><p>Brut also provides the following plugins and behavior changes:</p><ul><li><code>Sequel::Extensions::BrutInstrumentation</code>, which adds OpenTelemetry instrumentation to Sequel (see <a href="/instrumentation.html">Instrumentation</a>).</li><li><code>Sequel::Plugins::FindBang</code>, which adds <code>find!</code> to all models. This wraps Sequel's <code>first!</code> method, but provides a more helpful error message when no records are found</li><li><code>Sequel::Plugins::CreatedAt</code>, which automatically sets <code>created_at</code> when a record is created.</li><li><code>Sequel::Plugins::ExternalId</code>, which adds support for external IDs (see below)</li><li><code>Sequel::Extensions::BrutMigrations</code>, which enhances the migrations API (see below)</li></ul><h4 id="external-ids" tabindex="-1">External IDs <a class="header-anchor" href="#external-ids" aria-label="Permalink to "External IDs""></a></h4><p>It's often useful to provide a unique identifier for a record that is not the database primary key. There are many advantages to doing so, the main being that your primary and foreign keys are considered private and for developer use only. Creating additional externalizable unique keys is trivial, so Brut provides a way to do that.</p><div class="note custom-block github-alert"><p class="custom-block-title">NOTE</p><p><strong>Primary keys</strong> and <strong>keys</strong> are not the same thing. <strong>Primary keys</strong> are what is used to identify a record for the purposes of referential integrity. A <strong>key</strong> simply uniquely identifies a row or is a unique constraint on a table. Tables have only one primary key, but potentially many keys. Brut uses <em>synthetic</em> (sometimes called <em>surrogate</em>) keys as primary keys. This means they have no business meaning and can be safely used for foreighn keys and other cases without conflating them with domain concepts.</p></div><p>In Brut, an external ID is automatically generated by the database when a record is created. By convention, it is prefixed with a short string representing your app and a short string representing the table, followed by a unique hash.</p><p>For example, if our app's prefix is, say, "my" (for "my app"), and the accounts table's prefix is "ac" (for "accounts"), an external ID might look like <code>myac_3457238947239487</code>. This double-prefixing is extremely useful when sharing these values with the outside world. You can immediately identify an ID from your app <em>and</em> know what sort of thing it refers to.</p><p>To use external IDs in Brut, you must do three things:</p><ol><li><p>You must set your external ID prefix in <code>app/src/app.rb</code>. This should have been done when you created your Brut app, but it looks like so:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> App</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> <</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> Brut</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">App</span></span>
|
17
17
|
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # ...</span></span>
|
18
18
|
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> initialize</span></span>
|
19
19
|
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # ...</span></span>
|
@@ -36,7 +36,7 @@ import{_ as a,c as s,o as i,ag as t}from"./chunks/framework.1L-BeKqY.js";const u
|
|
36
36
|
<span class="line"><span> has_external_id :ac</span></span>
|
37
37
|
<span class="line"><span></span></span>
|
38
38
|
<span class="line"><span> # ...</span></span>
|
39
|
-
<span class="line"><span>end</span></span></code></pre></div></li></ol><p>Brut creates the external ID using Ruby code as part of Sequel's lifecycle hooks. It's only set a) on creation, and b) if there is no value provided when creating the record.</p><p>This means that you can set values explicitly if you like, <em>and</em> you can change them later. This is useful if you shared the value with someone you didn't mean to. Because these external IDs aren't use for referential integrity/foreign keys, they can be changed at any time, as long as the value is unique (which will be enforced by the database).</p><h3 id="brut-migration-changes-and-enhancement" tabindex="-1">Brut Migration Changes and Enhancement <a class="header-anchor" href="#brut-migration-changes-and-enhancement" aria-label="Permalink to "Brut Migration Changes and Enhancement""></a></h3><p>Brut attempts to set default behavior for migrations to encourage a modicum of best practices.
|
39
|
+
<span class="line"><span>end</span></span></code></pre></div></li></ol><p>Brut creates the external ID using Ruby code as part of Sequel's lifecycle hooks. It's only set a) on creation, and b) if there is no value provided when creating the record.</p><p>This means that you can set values explicitly if you like, <em>and</em> you can change them later. This is useful if you shared the value with someone you didn't mean to. Because these external IDs aren't use for referential integrity/foreign keys, they can be changed at any time, as long as the value is unique (which will be enforced by the database).</p><h3 id="brut-migration-changes-and-enhancement" tabindex="-1">Brut Migration Changes and Enhancement <a class="header-anchor" href="#brut-migration-changes-and-enhancement" aria-label="Permalink to "Brut Migration Changes and Enhancement""></a></h3><p>Brut attempts to set default behavior for migrations to encourage a modicum of best practices. These are:</p><ul><li><p><strong>Automatic synthetic primary key named <code>id</code> of type <code>int</code>.</strong> You almost always want this. You can change the primary key configuration per table if you like, but if you do nothing, you get a primary key that works for 99% of your needs.</p></li><li><p><strong>Automatic <code>created_at</code> of type <code>timestamptz</code>.</strong> It's a good practice to store the date a record was created. This can help with debugging and provide a reliable sort key for data that otherwise has none. It uses <code>timestamp with time zone</code>, which you are encouraged to use always. See <a href="/space-time-continuum.html">Space/Time Continuum</a> for details.</p></li><li><p><strong>No automatic <code>updated_at</code>.</strong> While you are free to add <code>updated_at</code>, in practice this column creates more problems than it solves. If you need to know when data has changed, it is almost always better to do this with an audit table, event log, or special-purpose field.</p></li><li><p><strong><code>create_table</code> requires <code>comment:</code>.</strong> Just document your tables. It takes two seconds and can save a lot of time later.</p></li><li><p><strong>Support for external IDs via <code>external_id:</code>.</strong> As discussed above, this will create a unique <code>external_id</code> column on your table and ensure it has a value on creation.</p></li><li><p><strong>Columns are <code>NOT NULL</code> by default.</strong> Null is not a valid value. In many cases, your columns should not allow <code>NULL</code> (<code>nil</code>), so in Brut apps, you must opt into nullable columns. You can use <code>null: true</code> to make a column nullable.</p></li><li><p><strong>Foreign keys are <code>NOT NULL</code> and have an index created for them by default.</strong> Foreign keys should rarely be <code>NULL</code> and you almost always want an index on them, since you are likely to using them in queries, e.g. <code>account.widgets</code> would join on <code>accounts.widget_id</code>. You can opt out of either via <code>null: true</code> and <code>index: false</code>.</p></li><li><p><strong>The method <code>key</code> allows you to specify a non-primary key, AKA a unique index</strong>. Suppose our <code>accounts</code> table allowed duplicate email addresses, but only one per <code>organization_id</code>. You'd model this by creating a unique index on <code>(email,organization_id)</code>. In Brut:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">Sequel</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">migration</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> do</span></span>
|
40
40
|
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> up </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
|
41
41
|
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> create_table </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">:accounts</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
42
42
|
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> comment:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "People or systems who can access this system"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
@@ -60,4 +60,4 @@ import{_ as a,c as s,o as i,ag as t}from"./chunks/framework.1L-BeKqY.js";const u
|
|
60
60
|
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> setweight(to_tsvector('english', coalesce(description,'')),'B')</span></span>
|
61
61
|
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> )</span></span>
|
62
62
|
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> }</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">),</span></span>
|
63
|
-
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> generated_type:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> :stored</span></span></code></pre></div><p>If you are using Postgtes, why <em>not</em> use its features? Unless your app is database-agnostic, you should be using the features of your database, even if they aren't explicitly exposed via Sequel's Ruby API (that's why <code>Sequel.lit</code> exists).</p><h2 id="technical-notes" tabindex="-1">Technical Notes <a class="header-anchor" href="#technical-notes" aria-label="Permalink to "Technical Notes""></a></h2><div class="important custom-block github-alert"><p class="custom-block-title">IMPORTANT</p><p>Technical Notes are for deeper understanding and debugging. While we will try to keep them up-to-date with changes to Brut's internals, the source code is always more correct.</p></div><p><em>Last Updated May 8, 2025</em></p><p>As mentioned, Brut uses Sequel under the covers. This is unlikely to change.</p><p>As also mentioned, Brut's extensions often rely on Postgres. While we can all dream of a world where every developer uses the same database server, we don't live in that world. Brut should, some day, support all the databases that Sequel supports. For now, however, it only supports Postgres.</p><p>This hard-coded support is due to:</p><ul><li><code>pg_array</code></li><li><code>pg_json</code></li><li>Reliance on <code>citext</code> and <code>comment</code></li><li>Reliance on <code>timestamptz</code></li></ul><p>Brut is likely to add more Postgres-specific features before adding support for other databases.</p>`,
|
63
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> generated_type:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> :stored</span></span></code></pre></div><p>If you are using Postgtes, why <em>not</em> use its features? Unless your app is database-agnostic, you should be using the features of your database, even if they aren't explicitly exposed via Sequel's Ruby API (that's why <code>Sequel.lit</code> exists).</p><h2 id="technical-notes" tabindex="-1">Technical Notes <a class="header-anchor" href="#technical-notes" aria-label="Permalink to "Technical Notes""></a></h2><div class="important custom-block github-alert"><p class="custom-block-title">IMPORTANT</p><p>Technical Notes are for deeper understanding and debugging. While we will try to keep them up-to-date with changes to Brut's internals, the source code is always more correct.</p></div><p><em>Last Updated May 8, 2025</em></p><p>As mentioned, Brut uses Sequel under the covers. This is unlikely to change.</p><p>As also mentioned, Brut's extensions often rely on Postgres. While we can all dream of a world where every developer uses the same database server, we don't live in that world. Brut should, some day, support all the databases that Sequel supports. For now, however, it only supports Postgres.</p><p>This hard-coded support is due to:</p><ul><li><code>pg_array</code></li><li><code>pg_json</code></li><li>Reliance on <code>citext</code> and <code>comment</code></li><li>Reliance on <code>timestamptz</code></li></ul><p>Brut is likely to add more Postgres-specific features before adding support for other databases.</p>`,67)]))}const k=e(n,[["render",l]]);export{u as __pageData,k as default};
|
data/docs/assets/{database-schema.md.BUjR0VS1.lean.js → database-schema.md.CSYk6E6v.lean.js}
RENAMED
@@ -1 +1 @@
|
|
1
|
-
import{_ as
|
1
|
+
import{_ as e,c as a,o as i,ag as t}from"./chunks/framework.1L-BeKqY.js";const u=JSON.parse('{"title":"Database Schema / Migrations","description":"","frontmatter":{},"headers":[],"relativePath":"database-schema.md","filePath":"database-schema.md"}'),n={name:"database-schema.md"};function l(o,s,h,r,p,d){return i(),a("div",null,s[0]||(s[0]=[t("",67)]))}const k=e(n,[["render",l]]);export{u as __pageData,k as default};
|