brut 0.0.28 → 0.1.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/.gitignore +6 -0
- data/.projections.json +10 -0
- data/.rspec +3 -0
- data/Dockerfile.dx +32 -14
- data/Gemfile.lock +1 -1
- data/README.md +23 -2
- data/assets/Logo-Square.pxd +0 -0
- data/assets/LogoPylon.pxd +0 -0
- data/assets/LogoStop.pxd +0 -0
- data/assets/LogoTall.pxd +0 -0
- data/assets/MetroLogo.graffle +0 -0
- data/assets/SocialImage.png +0 -0
- data/assets/SocialImage.pxd +0 -0
- data/bin/docs +24 -2
- data/bin/rspec +27 -0
- data/bin/setup +3 -3
- data/brutrb.com/.vitepress/config.mjs +45 -8
- data/brutrb.com/.vitepress/theme/custom.css +7 -0
- data/brutrb.com/.vitepress/theme/style.css +29 -17
- 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/deployment.md +123 -45
- data/brutrb.com/dev-environment.md +7 -7
- data/brutrb.com/dir-structure.md +120 -0
- data/brutrb.com/doc-conventions.md +18 -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/LogoPylon.png +0 -0
- data/brutrb.com/images/LogoSquare.png +0 -0
- data/brutrb.com/images/LogoStop.png +0 -0
- data/brutrb.com/images/LogoTall.png +0 -0
- data/brutrb.com/images/OverviewMetro.graffle +0 -0
- data/brutrb.com/images/OverviewMetro.png +0 -0
- data/brutrb.com/index.md +4 -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 +35 -377
- 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/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 +1 -1
- data/brutrb.com/why.md +19 -0
- data/docker-compose.dx.yml +5 -2
- data/docs/404.html +8 -3
- data/docs/SocialImage.png +0 -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 +1 -1
- 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 +1 -1
- 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 +1 -1
- data/docs/api/Brut/CLI/Apps/Test/Run.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test.html +1 -1
- data/docs/api/Brut/CLI/Apps.html +1 -1
- data/docs/api/Brut/CLI/Command.html +1 -1
- 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 +1 -1
- 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 +201 -0
- data/docs/api/Brut/FrontEnd/Forms/Input/TimeOfDay.html +535 -0
- data/docs/api/Brut/FrontEnd/Forms/Input.html +983 -35
- data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +29 -19
- data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +141 -20
- data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +141 -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 +1 -1
- 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 +1 -1
- 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 +15 -1
- data/docs/api/class_list.html +1 -1
- data/docs/api/css/full_list.css +2 -1
- data/docs/api/css/style.css +14 -13
- data/docs/api/file.README.html +22 -3
- data/docs/api/index.html +22 -3
- data/docs/api/method_list.html +435 -275
- data/docs/api/top-level-namespace.html +1 -1
- data/docs/assets/LogoStop.Gb3tDhL1.png +0 -0
- data/docs/assets/OverviewMetro.DUS-5fUZ.png +0 -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.BX81XO4N.js → app.ClaS47Ru.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.Biqy1A4t.js +1 -0
- data/docs/assets/chunks/{VPLocalSearchBox.gABXcTWp.js → VPLocalSearchBox.DtgDfde2.js} +1 -1
- data/docs/assets/chunks/{theme.DwUXXAL3.js → theme.B45bvibT.js} +2 -2
- data/docs/assets/{cli.md.RmeA2b0i.js → cli.md.CjsktgFz.js} +15 -20
- data/docs/assets/components.md.DatoNgFo.js +96 -0
- data/docs/assets/{components.md.CRUMdRoN.lean.js → components.md.DatoNgFo.lean.js} +1 -1
- data/docs/assets/{configuration.md.BGHl8oRC.js → configuration.md.DeyhpqEx.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/deployment.md.BLseERGV.js +48 -0
- data/docs/assets/deployment.md.BLseERGV.lean.js +1 -0
- data/docs/assets/dev-environment.md.BroAOLhF.js +11 -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.BzmSrTEW.js +1 -0
- data/docs/assets/doc-conventions.md.BzmSrTEW.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.C2Dizvzq.js +64 -0
- data/docs/assets/forms.md.C2Dizvzq.lean.js +1 -0
- data/docs/assets/{getting-started.md.Ciz82L0m.js → getting-started.md.C93e0odB.js} +5 -5
- data/docs/assets/{getting-started.md.Ciz82L0m.lean.js → getting-started.md.C93e0odB.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.CAMqGBJE.js +1 -0
- data/docs/assets/index.md.CAMqGBJE.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.Bdq4qt3L.js +1 -0
- data/docs/assets/overview.md.Bdq4qt3L.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/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.D73IYGCX.css → style.prAgp4yQ.css} +1 -1
- data/docs/assets/tutorial.md.a4a0eVOy.js +1 -0
- data/docs/assets/tutorial.md.a4a0eVOy.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 +58 -6
- data/docs/dev-environment.html +12 -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/not-released.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/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/dx/bash_customizations +7 -0
- data/dx/build +13 -2
- data/dx/docker-compose.env +1 -1
- data/dx/exec +25 -8
- data/lib/brut/front_end/form.rb +8 -8
- data/lib/brut/front_end/forms/input.rb +253 -20
- data/lib/brut/front_end/forms/input_definition.rb +15 -12
- 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/front_end.rb +1 -0
- data/lib/brut/version.rb +1 -1
- data/specs/brut/front_end/forms/input.spec.rb +978 -0
- 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/spec_helper.rb +27 -0
- data/specs/support/matchers/have_constraint_violation.rb +23 -0
- data/specs/support/matchers.rb +5 -0
- data/specs/support.rb +3 -0
- metadata +141 -77
- data/brutrb.com/public/images/logo-300.png +0 -0
- data/brutrb.com/public/images/logo.png +0 -0
- data/docs/assets/ai.md._6HCDL6d.lean.js +0 -1
- data/docs/assets/chunks/@localSearchIndexroot.CoYzciVi.js +0 -1
- data/docs/assets/components.md.CRUMdRoN.js +0 -104
- data/docs/assets/deployment.md.Dbka4OTr.js +0 -1
- data/docs/assets/deployment.md.Dbka4OTr.lean.js +0 -1
- data/docs/assets/dev-environment.md.GZv6xvi9.js +0 -11
- 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.B28EwVpq.js +0 -1
- data/docs/assets/index.md.B28EwVpq.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/overview.Da81cB9R.png +0 -0
- data/docs/assets/overview.md.C5wlBcR5.js +0 -133
- data/docs/assets/overview.md.C5wlBcR5.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/assets/{cli.md.RmeA2b0i.lean.js → cli.md.CjsktgFz.lean.js} +0 -0
- /data/docs/assets/{configuration.md.BGHl8oRC.lean.js → configuration.md.DeyhpqEx.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
- /data/docs/assets/{dev-environment.md.GZv6xvi9.lean.js → dev-environment.md.BroAOLhF.lean.js} +0 -0
data/brutrb.com/components.md
CHANGED
@@ -12,136 +12,159 @@ Components in Brut are Phlex Components: a class that can hold data and use that
|
|
12
12
|
|
13
13
|
### Simple Component
|
14
14
|
|
15
|
-
For example, suppose you want a re-usable button component whose HTML would look like so:
|
16
15
|
|
17
|
-
|
18
|
-
<button class="button button__small button__red">
|
19
|
-
Delete Files
|
20
|
-
</button>
|
21
|
-
```
|
16
|
+
For example, suppose you want a re-usable button that can be gray, green, or red, and have an optional `formaction`.
|
22
17
|
|
23
|
-
Your button can be large (the default) or small, and could be gray (default), green, or red. Your button could also have an optional `formaction` attribute. Let's also suppose the label could be a string or embedded HTML.
|
24
18
|
|
25
|
-
|
26
|
-
|
27
|
-
You can create a scaffold via `bin/scaffold`:
|
19
|
+
You can create a component with `bin/scaffold component`:
|
28
20
|
|
29
21
|
```
|
30
|
-
|
22
|
+
bin/scaffold component button
|
23
|
+
# => app/src/front_end/components/button_component.rb
|
24
|
+
# => specs/front_end/components/button_component.spec.rb
|
31
25
|
```
|
32
26
|
|
33
|
-
|
27
|
+
Component inititalizers are called by you when you use them, so you can define it
|
28
|
+
how you like. Brut uses keyword arguments by convention.
|
34
29
|
|
35
30
|
```ruby
|
36
|
-
# app/src/front_end/components/
|
37
|
-
class
|
38
|
-
def initialize(
|
39
|
-
|
40
|
-
color: :gray,
|
41
|
-
formaction: nil,
|
42
|
-
label: :use_block
|
43
|
-
)
|
44
|
-
@size = size
|
31
|
+
# app/src/front_end/components/button_component.rb
|
32
|
+
class ButtonComponent < AppComponent
|
33
|
+
def initialize(color: :gray,
|
34
|
+
formaction: nil)
|
45
35
|
@color = color
|
46
36
|
@formaction = formaction
|
47
|
-
@label = label
|
48
37
|
end
|
49
38
|
end
|
50
39
|
```
|
51
40
|
|
52
|
-
|
53
|
-
|
54
|
-
|
41
|
+
Since it's a Phlex component, implement `view_template` to generate the HTML you
|
42
|
+
like. Our `view_template` will `yield` so the button's contents can be controlled
|
43
|
+
by the caller. Note that the CSS here is [BrutCSS](/brut-css/index.html), but it
|
44
|
+
can be anything you are using in your oapp.
|
55
45
|
|
56
46
|
```ruby
|
57
|
-
# app/src/front_end/components/
|
58
|
-
class
|
47
|
+
# app/src/front_end/components/button_component.rb
|
48
|
+
class ButtonComponent < AppComponent
|
59
49
|
|
60
50
|
# ...
|
61
51
|
|
62
52
|
def view_template
|
63
53
|
attributes = {
|
64
54
|
class: [
|
65
|
-
"
|
66
|
-
"
|
67
|
-
"
|
55
|
+
"tc", # centered text
|
56
|
+
"br-3", # border radius @ 3rd step of scale
|
57
|
+
"bn", # no border
|
58
|
+
"f-3", # font size @ 3rd step of scale
|
59
|
+
"ph-4", # horizontal padding @ 4th step of scale
|
60
|
+
"pv-2", # vertical padding @ 2nd step of scale
|
61
|
+
"bg-#{@color}-800", # background is second lighest of scale
|
62
|
+
"#{@color}-300", # text is third darkest of scale
|
68
63
|
],
|
69
|
-
formaction: @formaction
|
64
|
+
formaction: @formaction
|
70
65
|
}
|
71
66
|
|
72
67
|
button(**attributes) do
|
73
|
-
|
74
|
-
yield
|
75
|
-
else
|
76
|
-
@label
|
77
|
-
end
|
68
|
+
yield
|
78
69
|
end
|
79
70
|
end
|
80
71
|
end
|
81
72
|
```
|
82
73
|
|
83
|
-
|
74
|
+
Here are two examples of how you'd use this component and the HTML that would be
|
75
|
+
generated:
|
76
|
+
|
77
|
+
::: code-group
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
render ButtonComponent(color: :green) do
|
81
|
+
"Click Here"
|
82
|
+
end
|
83
|
+
```
|
84
|
+
|
85
|
+
```html
|
86
|
+
<button class="tc br-3 bn f-3 ph-4 pv-2 bg-green-800 green-300">
|
87
|
+
Click Here
|
88
|
+
<button>
|
89
|
+
```
|
84
90
|
|
85
|
-
|
86
|
-
* The method's parameters produce attributes in the HTML that is generated.
|
87
|
-
* If a parameter's value is an array (like `class:`), the values are joined with strings to form the atttribute's value in HTML.
|
88
|
-
* If the element can have inner content, whatever happens inside a yielded block becomes that inner content.
|
91
|
+
:::
|
89
92
|
|
90
|
-
|
93
|
+
::: code-group
|
91
94
|
|
92
95
|
```ruby
|
93
|
-
|
94
|
-
|
95
|
-
render Button.new(label: "Submit")
|
96
|
-
render Button.new(label: "Nevermind", size: :small, color: :red)
|
97
|
-
render Button.new(color: :green) do
|
98
|
-
img(src: "/images/ok.png", alt: "OK icon")
|
99
|
-
end
|
100
|
-
end
|
96
|
+
render ButtonComponent(color: :red, formaction: DeleteWidget.routing) do
|
97
|
+
"Delete Widget"
|
101
98
|
end
|
102
99
|
```
|
103
100
|
|
104
|
-
|
101
|
+
```html
|
102
|
+
<button class="tc br-3 bn f-3 ph-4 pv-2 bg-red-800 green-300"
|
103
|
+
formaction="/delete_widget">
|
104
|
+
Delete Widget
|
105
|
+
<button>
|
106
|
+
```
|
107
|
+
:::
|
105
108
|
|
106
|
-
|
109
|
+
One issue with components is that you must pass them all their initializer arguments
|
110
|
+
to use them. This means that if your component needs access to, say, the session,
|
111
|
+
any page or component that uses your component must also require the session to
|
112
|
+
be passed in.
|
113
|
+
|
114
|
+
Brut provides a partial solution to this called *global components*.
|
107
115
|
|
108
116
|
### Global Components
|
109
117
|
|
110
|
-
|
118
|
+
A global component can be created by Brut using [keyword
|
119
|
+
injection](/keyword-injection). This means that, in our example above, a page that
|
120
|
+
uses your component does not need to be given the session. It can have Brut inject
|
121
|
+
it.
|
111
122
|
|
112
|
-
|
123
|
+
This provides a partial solution to so-called "prop drilling".
|
113
124
|
|
114
|
-
|
125
|
+
In [the features overview](/features), we saw a basic component for rendering a
|
126
|
+
flash:
|
115
127
|
|
116
128
|
```ruby
|
117
|
-
|
118
|
-
|
129
|
+
# components/flash_component.rb
|
130
|
+
class FlashComponent < AppComponent
|
119
131
|
def initialize(flash:)
|
120
|
-
|
132
|
+
if flash.notice?
|
133
|
+
@message_key = flash.notice
|
134
|
+
@role = :info
|
135
|
+
elsif flash.alert?
|
136
|
+
@message_key = flash.alert
|
137
|
+
@role = :alert
|
138
|
+
end
|
121
139
|
end
|
122
140
|
|
141
|
+
def any_message? = !@message_key.nil?
|
142
|
+
|
123
143
|
def view_template
|
124
|
-
if
|
125
|
-
div(role:
|
126
|
-
|
127
|
-
|
144
|
+
if any_message?
|
145
|
+
div(role: @role) do
|
146
|
+
t([ :flash, @message_key ])
|
147
|
+
end
|
128
148
|
end
|
129
149
|
end
|
130
150
|
end
|
131
151
|
```
|
132
152
|
|
133
|
-
|
153
|
+
Instead of requiring each user of this component to manually inject the flash, we
|
154
|
+
can call `global_component`, provided by `Brut::FrontEnd::Component::Helpers`, which
|
155
|
+
is included in all pages and components.
|
134
156
|
|
135
157
|
```ruby
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
global_component(FlashMessage) # note: render not required
|
140
|
-
end
|
158
|
+
def view_template
|
159
|
+
header do
|
160
|
+
global_component(FlashComponent)
|
141
161
|
end
|
142
162
|
end
|
143
163
|
```
|
144
164
|
|
165
|
+
Components used in layouts will tend to be global components, to avoid creating odd
|
166
|
+
dependencies between pages.
|
167
|
+
|
145
168
|
> [!IMPORTANT]
|
146
169
|
> Brut currently requires an all-or-nothing approach to global components. Either
|
147
170
|
> the component can be injected with all its initializer parameters or it must
|
@@ -157,9 +180,19 @@ Often, components are helpful to simplifying a page's template or managing re-us
|
|
157
180
|
|
158
181
|
Brut provides a way to create a *page private* 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.
|
159
182
|
|
160
|
-
|
183
|
+
They can be created with `bin/scaffold`:
|
161
184
|
|
162
|
-
```
|
185
|
+
```
|
186
|
+
bin/scaffold component --page HomePage Widget
|
187
|
+
# => app/src/front_end/page/home_page/widget_component.rb
|
188
|
+
# => specs/front_end/page/home_page/widget_component.spec.rb
|
189
|
+
```
|
190
|
+
|
191
|
+
The class will be an inner class of `HomePage` in this example, `HomePage::WidgetComponent`. You build them and use them like normal:
|
192
|
+
|
193
|
+
::: code-group
|
194
|
+
|
195
|
+
```ruby [Page]
|
163
196
|
class HomePage < AppPage
|
164
197
|
def page_template
|
165
198
|
header do
|
@@ -176,16 +209,7 @@ class HomePage < AppPage
|
|
176
209
|
end
|
177
210
|
```
|
178
211
|
|
179
|
-
|
180
|
-
|
181
|
-
```
|
182
|
-
bin/scaffold component --page=HomePage WidgetListItem
|
183
|
-
```
|
184
|
-
|
185
|
-
This will create `app/src/front_end/pages/home_page/widget_list_item.rb`, which you can then implement like a normal component:
|
186
|
-
|
187
|
-
```ruby
|
188
|
-
# app/src/front_end/pages/home_page/widget_list_item.rb
|
212
|
+
```ruby [Page Private Component]
|
189
213
|
class HomePage::WidgetListItem < AppComponent
|
190
214
|
def initialize(widget:)
|
191
215
|
@widget = widget
|
@@ -200,27 +224,25 @@ class HomePage::WidgetListItem < AppComponent
|
|
200
224
|
end
|
201
225
|
```
|
202
226
|
|
203
|
-
|
204
|
-
|
205
|
-
For example, the following code will look for `pages.HomePage.status.«status»` when generated the `<h3>`:
|
227
|
+
:::
|
206
228
|
|
207
|
-
|
208
|
-
|
209
|
-
class HomePage::WidgetListItem < AppComponent
|
210
|
-
def initialize(widget:)
|
211
|
-
@widget = widget
|
212
|
-
end
|
229
|
+
The main difference between a page-private component and a normal component's
|
230
|
+
behavior is how [I18n](/i18n) strings are resolved. In short, given this:
|
213
231
|
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
h3 { t([ :status, @widget.status ]) }
|
218
|
-
p { @widget.description }
|
219
|
-
end
|
220
|
-
end
|
232
|
+
```ruby
|
233
|
+
p do
|
234
|
+
t(:hello)
|
221
235
|
end
|
222
236
|
```
|
223
237
|
|
238
|
+
In a normal component named `WidgetComponent`, the keys searched for translations
|
239
|
+
would be `"components.WidgetComponent.hello"` and `"hello"` . For the page-private
|
240
|
+
component `HomePage::WidgetComponent`, the keys searched would be
|
241
|
+
`"pages.HomePage.hello"` and `"hello"`. This means that page private components can
|
242
|
+
access a page's translations.
|
243
|
+
|
244
|
+
|
245
|
+
|
224
246
|
## Testing
|
225
247
|
|
226
248
|
Test widgets exactly as you would [pages](/pages#testing). The only difference is that components always render HTML and have no `before_generate` concept.
|
data/brutrb.com/configuration.md
CHANGED
@@ -92,7 +92,7 @@ end
|
|
92
92
|
|
93
93
|
### Type and Name Enforcement
|
94
94
|
|
95
|
-
You'll note that `store
|
95
|
+
You'll note that `store` accepts a class parameter. This is mostly used for documentation, with two exceptions:
|
96
96
|
|
97
97
|
* If the type is `Pathname` (which is what is used by `store_ensured_path` and `store_required_path`), the configuration parameter name *must* end in `_file` or `_dir`.
|
98
98
|
* If the type is `"boolean"` or `:boolean`, the configuration parameter name *must* end in a question mark. In this case, the value itself is coerced into `true` or `false`.
|
@@ -103,8 +103,7 @@ Brut may add more constraints or conversions over time.
|
|
103
103
|
|
104
104
|
By default, Brut configuration values cannot be overridden and they cannot be `nil`. When calling `store`, `allow_app_override: true` and `allow_nil: true`, can be passed to change this behavior.
|
105
105
|
|
106
|
-
In [Flash and Session](/flash-and-session), we discussed
|
107
|
-
This is possible due to how Brut defines the configuration parameter `flash_class`:
|
106
|
+
In [Flash and Session](/flash-and-session), we discussed that you can set your own class for the flash. This is possible due to how Brut defines the configuration parameter `flash_class`:
|
108
107
|
|
109
108
|
```ruby {6}
|
110
109
|
Brut.container.store(
|
@@ -126,7 +125,7 @@ Calling `override` for parameters where `allow_app_override` is not true results
|
|
126
125
|
`store` on a previously `store`-d parameter results in an error.
|
127
126
|
|
128
127
|
The idea is to make it extremely clear what values are being set and overridden, and to avoid setting values that
|
129
|
-
don't exist.
|
128
|
+
don't exist or aren't relevant.
|
130
129
|
|
131
130
|
Some values can be `nil`. Generally, `nil` is a pain and will cause you great hardship. On occasion, it's
|
132
131
|
needed. For example, [external IDs](/database-schema#external-ids) only work if the app provides an app-wide
|
data/brutrb.com/css.md
CHANGED
@@ -42,7 +42,7 @@ First, `package.json` (in your app's root) would include `"foobar-css"`:
|
|
42
42
|
|
43
43
|
Next, `app/src/front_end/css/index.css` would import both `"foobar-css"` and `"pages/HomePage.css"`.
|
44
44
|
|
45
|
-
```
|
45
|
+
```css
|
46
46
|
@import "foobar-css/everything.css";
|
47
47
|
@import "pages/HomePage.css";
|
48
48
|
```
|
@@ -73,7 +73,7 @@ To use `foobar-thin.css`, you'd write this `@import` directive:
|
|
73
73
|
|
74
74
|
## Using Brut-CSS
|
75
75
|
|
76
|
-
By default, Brut includes a lightweight functional CSS library called
|
76
|
+
By default, Brut includes a lightweight functional CSS library called [BrutCSS](/brut-css/index.html). 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).
|
77
77
|
|
78
78
|
It is included so you have something to start with. You can use it by using its various classes like `bg-green-300` and `m-4`, or you can use its provided custom properties like `var(--green-300)` and `var(--sp-4)`.
|
79
79
|
|
@@ -37,8 +37,7 @@ as well, such as `assert`.
|
|
37
37
|
The idea is that you use the browser APIs to examine the DOM and assert the behavior of the custom element
|
38
38
|
(as opposed to interacting with the custom element's class).
|
39
39
|
|
40
|
-
Suppose that `my-element` transform text inside it based on the `transform` attribute. By default, it's
|
41
|
-
`lower`, but can be set to `upper` to lower case or upper case, respectively, the text inside.
|
40
|
+
Suppose that `my-element` transform text inside it based on the `transform` attribute. By default, it's `lower` (which will lower-case the text), but can be set to `upper` to upper case the text inside.
|
42
41
|
|
43
42
|
This means you'll need three tests, each with a different DOM:
|
44
43
|
|
@@ -72,7 +71,7 @@ describe("<some-element>", () => {
|
|
72
71
|
})
|
73
72
|
```
|
74
73
|
|
75
|
-
|
74
|
+
When the function you give to `test` is executed, the DOM will have been setup, so you can rely on your
|
76
75
|
custom elements `connectedCallback` having been called. Assuming the text transformation for `my-element`
|
77
76
|
occurs in `connectedCallback`, here is how you'd test all three cases:
|
78
77
|
|
@@ -109,7 +108,7 @@ describe("<some-element>", () => {
|
|
109
108
|
})
|
110
109
|
```
|
111
110
|
|
112
|
-
You'll notice almost all of this uses the browser APIs you (should
|
111
|
+
You'll notice almost all of this uses the browser APIs you (should) know and (hopefully) love.
|
113
112
|
|
114
113
|
You can manipulate the DOM inside a test as well, and it should behave as if you are doing it in a
|
115
114
|
browser. Note that many browser APIs are synchronous, so you don't have to add `await` before every
|
@@ -156,7 +156,7 @@ end
|
|
156
156
|
### Do Not Put Business Logic On Your Database Models
|
157
157
|
|
158
158
|
There's no reason to, or benefit to doing so. What you'll find is that any app of even moderate complexity will
|
159
|
-
not have a strict mapping from page to business concept to database table. Rather these things will all differ
|
159
|
+
not have a strict mapping from page to business concept to database table. Rather, these things will all differ
|
160
160
|
greatly, and each serves a different purpose.
|
161
161
|
|
162
162
|
The job of your data models—and the tables they provide access to—is to store reliable and unambiguous data.
|
@@ -1,10 +1,6 @@
|
|
1
1
|
# Database Schema / Migrations
|
2
2
|
|
3
|
-
Brut provides access to the database via the [Sequel library](https://sequel.jeremyevans.net/).
|
4
|
-
|
5
|
-
One thing to keep in mind is that Brut refers to your database layer as *database models* (notably not the un-qualified "models"). Brut treats this layer as a *model* of your database, not a model of your *domain* (though you are free to conflate the two).
|
6
|
-
|
7
|
-
This section details how to manage your database schema.
|
3
|
+
Brut provides access to the database via the [Sequel library](https://sequel.jeremyevans.net/). To manage your database schema, Brut uses Sequel's facility for this, with some of its own enhancements.
|
8
4
|
|
9
5
|
> [!NOTE]
|
10
6
|
> Brut currently only supports Postgres. Sequel supports many database systems, however Brut's extensions are
|
@@ -12,22 +8,14 @@ This section details how to manage your database schema.
|
|
12
8
|
|
13
9
|
## Overview
|
14
10
|
|
15
|
-
|
16
|
-
|
17
|
-
create the database schema from scratch—you will have to modify the existing schema to produce the schema
|
18
|
-
you want.
|
11
|
+
Your database schema is managed by a series of changes that build upon one another
|
12
|
+
called *migrations*.
|
19
13
|
|
20
|
-
For example, if you have a table `widgets` that has a `name` and `description`, to add a `status` field,
|
21
|
-
you cannot `drop table widgets` and then `create table widgets(...)` with the fields. You must instead
|
22
|
-
`alter table widgets(...)` to add the new column.
|
14
|
+
For example, if you have a table `widgets` that has a `name` and `description`, to add a `status` field, you cannot `drop table widgets` and then `create table widgets(...)` with the fields. You must instead `alter table widgets(...)` to add the new column.
|
23
15
|
|
24
16
|
Thus, each migration file is a change to the schema produced by all previous migration files.
|
25
17
|
|
26
|
-
Brut's provides this via
|
27
|
-
|
28
|
-
Schema files are located in `app/src/back_end/data_models/migrations` and are named using a timestamp-based
|
29
|
-
scheme. This means that when you create a new migration, its name will be based on the time and date you
|
30
|
-
created it, and any migrations that have not been applied will be applied in timestamp order.
|
18
|
+
Brut's provides this via Sequel. See [both](https://sequel.jeremyevans.net/rdoc/files/doc/schema_modification_rdoc.html) [docs](https://sequel.jeremyevans.net/rdoc/files/doc/migration_rdoc.html) for details on the API. Any schema modification method Sequel documents is available, however some default behavior has changed.
|
31
19
|
|
32
20
|
### Creating Migrations
|
33
21
|
|
@@ -40,6 +28,10 @@ together to form the filename:
|
|
40
28
|
app/src/back_end/data_models/migrations/20250508132646_user-accounts.rb
|
41
29
|
```
|
42
30
|
|
31
|
+
Note that the files are located in `app/src/back_end/data_models/migrations` and
|
32
|
+
have a name prefixed with a timestamp. This timestamp determins an ordering of how
|
33
|
+
the files are applied to the database.
|
34
|
+
|
43
35
|
The file is created mostly blank:
|
44
36
|
|
45
37
|
```ruby
|
@@ -49,19 +41,17 @@ Sequel.migration do
|
|
49
41
|
end
|
50
42
|
```
|
51
43
|
|
52
|
-
|
53
|
-
|
44
|
+
> [!NOTE]
|
45
|
+
> Sequels' migration API is similar in concept to Rails', but differs
|
46
|
+
> significantly in specifics. Please consult Sequel's documentation and
|
47
|
+
> don't assume Railsism will work the same way.
|
54
48
|
|
55
|
-
|
56
|
-
|
57
|
-
explanation.
|
49
|
+
Brut encourages only "up" migrations. Since Brut treats your development database
|
50
|
+
as ephemeral, there is little value to managing "down" migrations.
|
58
51
|
|
59
|
-
This is
|
60
|
-
method of the same name, automagically creates both "up" and "down" migrations, but *only* if you use the DSL. If
|
61
|
-
you use raw SQL, `change` doesn't work. But that doesn't matter for Brut (again, see *Recommended Practices*).
|
52
|
+
This is why Sequel's `change` method is not included in the scaffolded code. `change`, like Active Record's method of the same name, automagically creates both "up" and "down" migrations, but *only* if you use the DSL. If you use raw SQL, `change` doesn't work. By using only `up`, you won't have to worry about this.
|
62
53
|
|
63
|
-
Let's create
|
64
|
-
timestamp:
|
54
|
+
Let's create an accounts table that has an email field, a `deactivated_at` timestamp, and a `created_at` timestamp:
|
65
55
|
|
66
56
|
```ruby
|
67
57
|
Sequel.migration do
|
@@ -90,10 +80,6 @@ To apply this migration use `bin/db migrate`
|
|
90
80
|
> bin/db migrate
|
91
81
|
```
|
92
82
|
|
93
|
-
If you create a new migration, it will use a timestamp that is alphanumerically greater than the one we just made
|
94
|
-
and thus that migration will be applied after this one. Thus, you can rely on previous migrations having been
|
95
|
-
applied when authoring new ones.
|
96
|
-
|
97
83
|
### Managing Migrations
|
98
84
|
|
99
85
|
Sequel uses a special database table to understand which migrations have been run. This table will exist in
|
@@ -126,16 +112,19 @@ provides a more helpful error message when no records are found
|
|
126
112
|
#### External IDs
|
127
113
|
|
128
114
|
It's often useful to provide a unique identifier for a record that is not the database primary key. There are
|
129
|
-
many advantages to doing so,
|
130
|
-
|
131
|
-
|
115
|
+
many advantages to doing so, the main being that your primary and foreign keys are
|
116
|
+
considered private and for developer use only. Creating additional externalizable
|
117
|
+
unique keys is trivial, so Brut provides a way to do that.
|
132
118
|
|
133
119
|
> [!NOTE]
|
134
|
-
>
|
135
|
-
>
|
136
|
-
>
|
137
|
-
>
|
138
|
-
>
|
120
|
+
> **Primary keys** and **keys** are not the same thing. **Primary keys** are
|
121
|
+
> what is used to identify a record for the purposes of referential integrity.
|
122
|
+
> A **key** simply uniquely identifies a row or is a unique constraint on a table.
|
123
|
+
> Tables have only one primary key, but potentially many keys. Brut uses
|
124
|
+
> *synthetic* (sometimes called *surrogate*) keys as primary keys. This means
|
125
|
+
> they have no business meaning and can be safely used for foreighn keys
|
126
|
+
> and other cases without conflating them with domain concepts.
|
127
|
+
|
139
128
|
|
140
129
|
In Brut, an external ID is automatically generated by the database when a record is created. By convention, it
|
141
130
|
is prefixed with a short string representing your app and a short string representing the table, followed by a
|
@@ -190,8 +179,7 @@ This means that you can set values explicitly if you like, *and* you can change
|
|
190
179
|
|
191
180
|
### Brut Migration Changes and Enhancement
|
192
181
|
|
193
|
-
Brut attempts to set default behavior for migrations to encourage a modicum of best practices.
|
194
|
-
the changes and a brief explanation for the purpose of the change.
|
182
|
+
Brut attempts to set default behavior for migrations to encourage a modicum of best practices. These are:
|
195
183
|
|
196
184
|
* **Automatic synthetic primary key named `id` of type `int`.** You almost always want this. You can change the
|
197
185
|
primary key configuration per table if you like, but if you do nothing, you get a primary key that works for 99%
|