brut 0.0.29 → 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/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 +45 -8
- data/brutrb.com/.vitepress/theme/style.css +6 -5
- 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 +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/LogoStop.png +0 -0
- 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/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/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 +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 +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 +1 -1
- data/docs/api/file.README.html +22 -3
- data/docs/api/index.html +22 -3
- data/docs/api/method_list.html +16 -0
- data/docs/api/top-level-namespace.html +1 -1
- data/docs/assets/LogoStop.Gb3tDhL1.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.BhrfSt68.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.Dpot_2H4.js → VPLocalSearchBox.DtgDfde2.js} +1 -1
- data/docs/assets/chunks/{theme.N2SNVLgU.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.LG-zIBww.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/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.Dj0qtZI2.js → getting-started.md.C93e0odB.js} +5 -5
- data/docs/assets/{getting-started.md.Dj0qtZI2.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.CuBB-BdM.js → index.md.CAMqGBJE.js} +1 -1
- data/docs/assets/{index.md.CuBB-BdM.lean.js → index.md.CAMqGBJE.lean.js} +1 -1
- 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.B2o1L9eN.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 +10 -5
- 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/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/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
- metadata +117 -75
- 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-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/keyword-injection.md.Dt2tKREs.js +0 -25
- data/docs/assets/layouts.md.cPnh3NId.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/assets/{cli.md.RmeA2b0i.lean.js → cli.md.CjsktgFz.lean.js} +0 -0
- /data/docs/assets/{configuration.md.LG-zIBww.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/pages.md
CHANGED
@@ -1,65 +1,34 @@
|
|
1
1
|
# Pages
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
A web page is fetched by the browser using an HTTP `GET` request to a URL. When that happens, Brut instantiates an object of a *page class* and uses its `page_template` method to generate its HTML (using calls to Phlex's API).
|
3
|
+
A core abstraction of Brut is the core concept of the web: the web page.
|
6
4
|
|
7
5
|
## Overview
|
8
6
|
|
9
|
-
|
7
|
+
To create a web page, you'll need:
|
10
8
|
|
11
|
-
|
12
|
-
|
13
|
-
|
9
|
+
* A [Route](/routes) using `page`.
|
10
|
+
* A class in `app/src/front_end/pages/` that extends `Brut::FrontEnd::Page`, named [conventionally](/routes#class-naming-conventions) (though in reality, your page willextend `AppPage` in `app/src/front_end/pages/app_page.rb`, which extends `Brut::FrontEnd::Page`).
|
11
|
+
* [Optional, but recommended] A test in `specs/front_end/pages`.
|
14
12
|
|
15
|
-
You can
|
13
|
+
You can create all this with `bin/scaffold`, which accepts the route you want:
|
16
14
|
|
17
15
|
```shell
|
18
|
-
> bin/scaffold
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
page "/new-widgets"
|
24
|
-
|
25
|
-
[ bin/scaffold ] app/src/front_end/pages/new_widgets_page.rb
|
26
|
-
[ bin/scaffold ] will contain:
|
27
|
-
|
28
|
-
class NewWidgetsPage < AppPage
|
29
|
-
def initialize # add needed arguments here
|
30
|
-
end
|
31
|
-
|
32
|
-
def page_template
|
33
|
-
h1 { "Your page is ready" }
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
[ bin/scaffold ] specs/front_end/pages/new_widgets_page.spec.rb
|
38
|
-
[ bin/scaffold ] will contain:
|
39
|
-
|
40
|
-
require "spec_helper"
|
41
|
-
|
42
|
-
RSpec.describe NewWidgetsPage do
|
43
|
-
it "should have tests" do
|
44
|
-
expect(true).to eq(false)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
[ bin/scaffold ] app/config/i18n/en/2_app.rb
|
49
|
-
[ bin/scaffold ] will contain:
|
16
|
+
> bin/scaffold page /new_widgets
|
17
|
+
# => app/src/front_end/pages/new_widgets_page.rb
|
18
|
+
# => specs/front_end/pages/new_widgets_page.spec.rb
|
19
|
+
# => add `page "/new_widgets"` to app/src/app.rb
|
20
|
+
```
|
50
21
|
|
51
|
-
|
52
|
-
title: "New widgets page",
|
53
|
-
},
|
22
|
+
or
|
54
23
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
24
|
+
```shell
|
25
|
+
> bin/scaffold page /widget/:id
|
26
|
+
# => app/src/front_end/pages/widget_by_id_page.rb
|
27
|
+
# => specs/front_end/pages/widget_by_id_page.spec.rb
|
28
|
+
# => add `page "/widget/:id"` to app/src/app.rb
|
60
29
|
```
|
61
30
|
|
62
|
-
You can
|
31
|
+
You can also perform these steps manually.
|
63
32
|
|
64
33
|
> [!WARNING]
|
65
34
|
> Adding a `page` route without the corresponding class may not always
|
@@ -73,59 +42,73 @@ You can, of course, edit `app.rb` and create the classes yourself.
|
|
73
42
|
|
74
43
|
### Creating a Page
|
75
44
|
|
76
|
-
|
45
|
+
Pages need a `page_template` method that contains calls to Phlex, which will produce
|
46
|
+
the page's HTML.
|
77
47
|
|
78
|
-
|
48
|
+
If you have not used Phlex before, it's relatively straightfoward. For each HTML
|
49
|
+
tag that exists, Phlex provides a method. So, for `<div>`, Phlex provides `div`.
|
79
50
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
For example, `Admin::WidgetsByIdPage` and its template might look like so:
|
84
|
-
|
85
|
-
```ruby
|
86
|
-
# pages/admin/widgets_by_id_page.rb
|
87
|
-
class Admin::WidgetsByIdPage < AppPage
|
88
|
-
def initialize(id:)
|
89
|
-
@widget = DB::Widget.find!(id:)
|
90
|
-
end
|
91
|
-
|
92
|
-
private attr_reader :widget
|
51
|
+
Each method accepts parameters which are converted into attributes. Methods can
|
52
|
+
also accept blocks that can be used to add more HTML by calling more of Phlex's API.
|
93
53
|
|
54
|
+
```ruby
|
55
|
+
class DashboardPage < AppPage
|
94
56
|
def page_template
|
95
|
-
|
96
|
-
|
57
|
+
header do
|
58
|
+
h1 { "Welcome to My App!" }
|
59
|
+
time { Date.today }
|
60
|
+
end
|
61
|
+
main do
|
62
|
+
p(class: "body-text") do
|
63
|
+
"This is my awesome app! I hope you stay awhile!"
|
64
|
+
end
|
65
|
+
end
|
97
66
|
end
|
98
67
|
end
|
99
68
|
```
|
100
69
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
70
|
+
By default, this page will be rendered inside `DefaultLayout`, located in
|
71
|
+
`app/src/front_end/layouts/default_layout.rb` and discussed in [the layouts
|
72
|
+
module](/layouts). The HTML this page will generate, that would then be inserted
|
73
|
+
into the layout's HTML, looks like so:
|
74
|
+
|
75
|
+
```html
|
76
|
+
<header>
|
77
|
+
<h1>Welcome to My App!</h1>
|
78
|
+
<time>2025-07-05</time>
|
79
|
+
</header>
|
80
|
+
<main>
|
81
|
+
<p class="body-text">
|
82
|
+
This is my awesome app! I hope you stay awhile!"
|
83
|
+
</p>
|
84
|
+
</main>
|
85
|
+
```
|
108
86
|
|
109
|
-
|
110
|
-
h1 { widget.name }
|
111
|
-
h2 { widget.status }
|
112
|
-
end
|
87
|
+
### Accessing Data in a Page
|
113
88
|
|
114
|
-
|
89
|
+
Building static pages is fine, but not really why we use web app libraries. Your
|
90
|
+
page is a normal class, so you can create instance variables and methods, which can
|
91
|
+
do whatever you need.
|
115
92
|
|
116
|
-
|
93
|
+
That being said, the initializer is called by Brut and can be given special
|
94
|
+
arguments. For example, if your route has as placeholder, e.g. `/widgets/:id`, then
|
95
|
+
your initializer will be given the value of `:id` if its initializer has a keyword
|
96
|
+
argument named `id:`:
|
117
97
|
|
98
|
+
```ruby
|
99
|
+
def initialize(id:)
|
118
100
|
end
|
119
101
|
```
|
120
102
|
|
121
|
-
|
103
|
+
Query string parameters are also avaiable this way, but your page can access a wide
|
104
|
+
variety of request-level information simply by declaring a keyword argument to its
|
105
|
+
initializer.
|
122
106
|
|
123
|
-
|
107
|
+
This mechanism is called [keyword injection](/keyword-injection) and is available to many class you create, including pages.
|
124
108
|
|
125
|
-
|
126
|
-
specifying keyword arguments.
|
109
|
+
Here is a list of what is available:
|
127
110
|
|
128
|
-
|
|
111
|
+
| Keyword Argument | Type | Description |
|
129
112
|
|-------|------|-------------|
|
130
113
|
`session:` | `Brut::FrontEnd::Session` (or your app's subclass) | The current session, even if it's empty. See [Flash and Session](/flash-and-session)|
|
131
114
|
`flash:` | `Brut::FrontEnd::Flash` (or your app's subclass) | The current flash, even if it's empty. See [Flash and Session](/flash-and-session) |
|
@@ -133,6 +116,7 @@ specifying keyword arguments.
|
|
133
116
|
`csrf_token:` | `String`| The current CSRF token. |
|
134
117
|
`clock:` | `Clock` | Used when you need to access the current date and time, potentially accounting for time zones. See [Space/Time Continuum](/space-time-continuum)|
|
135
118
|
`http_*` | `String` or `nil` | Any parameter that starts with `http_` is assumed to be for an HTTP header. For example, `http_accept_language` would be given the value for the "Accept-Language" header. See [HTTP Headers](/keyword-injection#http-headers) |
|
119
|
+
`rack_request_*` | `String` or `nil` | Any parameter that starts with `rack_request_` is assumed to be for a value from the `Rack::Request`. For example, `rack_request_id` would provide the `ip` value from `Rack::Request` |
|
136
120
|
`env:` | `Hash` | The Rack env. You are discouraged from using this directly in your pages, but if you need it, it's available. |
|
137
121
|
Placeholders | `String` | Any placeholder value from the route definition |
|
138
122
|
Any query string paramter | `String` | the value given is always a string.
|
@@ -150,23 +134,22 @@ def initialize(id:,
|
|
150
134
|
```
|
151
135
|
|
152
136
|
> [!CAUTION]
|
153
|
-
> Keyword arguments for query string parameters **must** have default values
|
154
|
-
> when they are omitted.
|
137
|
+
> Keyword arguments for query string parameters **must** have default values
|
138
|
+
> or Brut will be unable to instantiate your page class when they are omitted.
|
139
|
+
> We recommended that **no other keywords arguments have defaults** to ensure your
|
140
|
+
> pages aren't created with `nil` values.
|
155
141
|
|
156
142
|
> [!NOTE]
|
157
143
|
> Omitting a default for an HTTP header is OK, but you should know what the behavior is. See [the HTTP Headers
|
158
144
|
> section](/keyword-injection#http-headers) for details.
|
159
145
|
|
160
|
-
### Hooks
|
161
146
|
|
162
|
-
|
163
|
-
reason for this could be a lack of authorization by that visitor to view the page.
|
147
|
+
### Page Hooks
|
164
148
|
|
165
|
-
|
166
|
-
initialized, but before the template creationg process starts. Depending on what `before_generate`
|
167
|
-
returns, the visitor may be redirected, an error could be sent, or HTML generation may proceed as normal.
|
149
|
+
Occasionally, you want to prevent a page from rendering after the visitor has been routed to it. A common reason for this could be a lack of authorization by that visitor to view the page.
|
168
150
|
|
169
|
-
|
151
|
+
`before_generate` achieves this. It's called after construction, so has access to
|
152
|
+
any injected values, and its return value tells Brut what should happen:
|
170
153
|
|
171
154
|
* `URI` - the visitor will be redirected to the given URI. Instead of creating a `URI`, you may use the method `redirect_to`, which
|
172
155
|
accepts a page and its parameters.
|
@@ -179,83 +162,63 @@ an `HttpStatus` from a number.
|
|
179
162
|
|
180
163
|
See [Unit Testing](/unit-tests) for some basic assumptions and configuration available for all Brut unit tests.
|
181
164
|
|
182
|
-
|
183
|
-
|
165
|
+
Although pages are Plain Old Ruby Objects, you likely want to test the HTML they
|
166
|
+
generate. Brut provides convenience methods to do this based on Nokogiri.
|
184
167
|
|
185
|
-
|
186
|
-
the same sorts of CSS selectors you'd use with `document.querySelector` to debug your app in a browser.
|
168
|
+
### Generating a Response
|
187
169
|
|
188
|
-
|
170
|
+
* If your page has no before hook, or you aren't testing that, call `generate_and_parse(page_instance)`. This returns a `Brut::SpecSupport::EnhancedNode`, which is a delegate to Nokogiri's `Nokogiri::XML::Node` (see below for why this exists)
|
171
|
+
* If you want to assert behavior of the before hook, call `generate_result`, which
|
172
|
+
will return whatever the page's internal `handle!` method called.
|
173
|
+
will use one of these matchers on the result:
|
189
174
|
|
190
|
-
|
175
|
+
### Asserting Results
|
191
176
|
|
192
|
-
|
193
|
-
`Brut::SpecSupport::EnhancedNode
|
194
|
-
|
195
|
-
Below, we use the method `e!`, which is provided by `EnhancedNode`. This works just like Nokogiri's `css`, except
|
196
|
-
that requires exactly one element to match the selector. If not, the test fails. This allows a more compact test
|
197
|
-
when you know there should only be one element matching the selector you've provided.
|
177
|
+
When using `generate_and_parse`, you have access to all of Nokogiri, however
|
178
|
+
`Brut::SpecSupport::EnhancedNode` provides two methods to simplify your test:
|
198
179
|
|
199
180
|
```ruby
|
200
|
-
|
201
|
-
|
202
|
-
it "shows the company name and location address" do
|
203
|
-
company = create(:company) # You must implement
|
204
|
-
location = create(:location) # You must implement
|
205
|
-
|
206
|
-
page = described_class.new(company_id: company.id.to_s,
|
207
|
-
location_id: location.id.to_s)
|
208
|
-
|
209
|
-
parsed_html = generate_and_parse(page)
|
210
|
-
|
211
|
-
h1 = parsed_html.e!("h1")
|
212
|
-
h2 = parsed_html.e!("h2")
|
181
|
+
it "should work" do
|
182
|
+
result = generate_and_parse(described_class.new)
|
213
183
|
|
214
|
-
|
215
|
-
|
216
|
-
end
|
217
|
-
end
|
184
|
+
expect(result.e!("h1").text).to include("Welcome")
|
185
|
+
expect(result.e("h2")).to eq(nil)
|
218
186
|
end
|
219
187
|
```
|
220
188
|
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
### `generate_result` Tests `before_generate`
|
226
|
-
|
227
|
-
If your page uses `before_generate`, when you call `generate_and_parse`, it will fail unless the page generated
|
228
|
-
HTML. In those cases, you can use `generate_result`, which will return what `before_generate` returned, unless
|
229
|
-
it returned `nil`, in which case it will return the unparsed HTML.
|
189
|
+
* `e!` returns the node matching the given CSS selector, failing the test if there
|
190
|
+
is not exactly one matching node.
|
191
|
+
* `e` (no bang) returns the node matching the given CSS selector, or `nil` if none matched. If there is more than one match, the test fails.
|
230
192
|
|
231
|
-
|
232
|
-
|
233
|
-
describe "render" do
|
234
|
-
it "redirects back to the home page for expired companies" do
|
235
|
-
company = create(:company, :expired) # You must implement
|
236
|
-
location = create(:location) # You must implement
|
193
|
+
When using `generate_result`, you will want to use one of two special purpose
|
194
|
+
matchers:
|
237
195
|
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
expect(result).to have_redirected_to(HomePage)
|
196
|
+
```ruby
|
197
|
+
it "redirects" do
|
198
|
+
result = generate_result(described_class.new)
|
199
|
+
expect(result).to have_redirected_to(AuthPage)
|
200
|
+
end
|
244
201
|
|
245
|
-
|
246
|
-
|
202
|
+
it "404's" do
|
203
|
+
result = generate_result(described_class.new)
|
204
|
+
expect(result).to have_returned_http_status(404)
|
247
205
|
end
|
248
206
|
```
|
249
207
|
|
250
|
-
`have_redirected_to`
|
251
|
-
`
|
252
|
-
calling `before_generate` directly is that you want to use the page in a test the way it's used in your app. You
|
253
|
-
will also get higher-quality test failure messages.
|
208
|
+
- `have_redirected_to` to check that a redirect happened to the URI you set (see `Brut::SpecSupport::Matchers::HaveRedirectedTo`)
|
209
|
+
- `have_returned_http_status` to check that a given HTTP response was returned (see `Brut::SpecSupport::Matchers::HaveReturnedHttpStatus`)
|
254
210
|
|
255
|
-
|
211
|
+
Beyond this, you can use Nokogiri as usual to navigate the DOM that's generated and
|
212
|
+
make assertions. A few additional matchers to help are:
|
256
213
|
|
257
|
-
|
258
|
-
|
214
|
+
- `be_routing_for` - expect a URI to be a routing for a certain page or
|
215
|
+
page/parameter combination. See `Brut::SpecSupport::Matchers::BeRoutingFor`.
|
216
|
+
- `have_html_attribute` - check that a node has an attribute or an attribute with a
|
217
|
+
specific value. See `Brut::SpecSupport::Matchers::HaveHTMLAttribute`.
|
218
|
+
- `have_i18n_string` - check that a node's text has a string from your [I18n](/i18n)
|
219
|
+
configuration. See `Brut::SpecSupport::Matchers::HaveI18nString`.
|
220
|
+
|
221
|
+
## Recommended Practices
|
259
222
|
|
260
223
|
### Instance variables (ivars) are fine.
|
261
224
|
|
@@ -273,59 +236,7 @@ below, you won't need to use `before_generate` as a failsafe check on authorizat
|
|
273
236
|
|
274
237
|
The list of available data for injection above will always be available to your page, with the exception of query string parameters. The real power comes when you learn how to [inject your own data](/keyword-injection#injecting-custom-data) into the request context.
|
275
238
|
|
276
|
-
|
277
|
-
|
278
|
-
```ruby{2}
|
279
|
-
class WidgetsController < ApplicationController
|
280
|
-
before_action :require_login!
|
281
|
-
|
282
|
-
# ...
|
283
|
-
end
|
284
|
-
```
|
285
|
-
|
286
|
-
`before_action` is the failsafe - in case someone hacks a URL to find this page, or there is a bug in your app where unauthorized visitors are sent to this page, the `before_action` prevents the page from working.
|
287
|
-
|
288
|
-
In Brut, you could mimic this behavior using `before_generate`, however this isn't necessary. Instead, you can take advantage of keyword injection.
|
289
|
-
|
290
|
-
Consider this implementation of `WidgetsByIdPage`:
|
291
|
-
|
292
|
-
```ruby
|
293
|
-
class WidgetsByIdPage < AppPage
|
294
|
-
def initialize(id:, current_user:)
|
295
|
-
# ...
|
296
|
-
end
|
297
|
-
end
|
298
|
-
```
|
299
|
-
|
300
|
-
`id:` is injected because it is a route placeholder. `current_user:` however, is completely custom to our app. We can arrange to
|
301
|
-
have it injected. We'll create a [Route Hook](/hooks) to do this.
|
302
|
-
|
303
|
-
> [!CAUTION]
|
304
|
-
> This hook is not production-ready. It lacks certain error-handling code and
|
305
|
-
> makes an assumption about how the session is managed. It's for demonstration only.
|
306
|
-
> The [route hooks](/hooks) section has a more
|
307
|
-
> appropriate example.
|
308
|
-
|
309
|
-
```ruby{6}
|
310
|
-
class RequireAuthBeforeHook < Brut::FrontEnd::RouteHook
|
311
|
-
def before(request_context:,session:)
|
312
|
-
if session.current_user_id
|
313
|
-
user = DB::User.find(id: session.current_user_id)
|
314
|
-
if user
|
315
|
-
request_context[:current_user] = user
|
316
|
-
end
|
317
|
-
end
|
318
|
-
end
|
319
|
-
end
|
320
|
-
```
|
321
|
-
|
322
|
-
Before any route is handled, this before hook is run and passed the `Brut::FrontEnd::RequestContext`. This is where all the
|
323
|
-
injectible values live. `request_context[:current_user] = user` makes `user` available to be injected into a page or handler.
|
324
|
-
|
325
|
-
What this means is that when a visitor is not logged in, there will be no injectible value for `:current_user`. Brut will not be able
|
326
|
-
to instantiate `WidgetsByIdPage`, and an error is generated. It is literally impossible to route a logged-out visitor to that page.
|
327
|
-
|
328
|
-
In practice, this means that any page that requires a logged-in visitor will specify the `current_user:` keyword argument, and **not provide a default value**. You are still required to make sure no one routes a logged-out visitor to a page requiring authentication, but now you don't have to remember to add logic to each page that requires login—you bake it into the page class' type.
|
239
|
+
A great example of this is in the [recipe for keywords and auth](/recipes/authentication), which results in a much simpler and less error-prone way to prevent unauthorized access to pages when compared to how you might do it in Rails.
|
329
240
|
|
330
241
|
### In Tests, It's Fine to Locate Elements Via CSS Selectors
|
331
242
|
|
@@ -367,9 +278,10 @@ you.
|
|
367
278
|
|
368
279
|
### Helpers in Templates
|
369
280
|
|
370
|
-
`Brut::FrontEnd::Page` is a subclass of `Brut::FrontEnd::Component`, so all your pages will have access to the helpers included there. This is how, for example, `t` can be called to perform translations
|
281
|
+
`Brut::FrontEnd::Page` is a subclass of `Brut::FrontEnd::Component`, so all your pages will have access to the helpers included there. This is how, for example, `t` can be called to perform translations.
|
371
282
|
|
372
|
-
|
283
|
+
Note that Brut does *not* include `Brut::FrontEnd::Components` (pluralized). You
|
284
|
+
can include that in `AppPage` to access Brut's builtin components as a Phlex kit.
|
373
285
|
|
374
286
|
### So You Don't Like Phlex?
|
375
287
|
|
Binary file
|
Binary file
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# Alternate Layouts
|
2
|
+
|
3
|
+
To create an alternate layout, your page can override `layout` to return a string.
|
4
|
+
That string will be camel-cased and preped to `Layout` to form a class that is
|
5
|
+
expected to exist and provide the layout. That class must extend
|
6
|
+
`Brut::FrontEnd::Layout`.
|
7
|
+
|
8
|
+
```ruby
|
9
|
+
class MyOtherPage < AppPage
|
10
|
+
def layout = "other_design"
|
11
|
+
|
12
|
+
# ...
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
class OtherDesignLayout < Brut::FrontEnd::Layout
|
17
|
+
def view_template
|
18
|
+
doctype
|
19
|
+
html do
|
20
|
+
head do
|
21
|
+
link(rel: "preload", as: "style", href: asset_path("/css/other-styles.css"))
|
22
|
+
link(rel: "stylesheet", href: asset_path("/css/other-styles.css"))
|
23
|
+
script(defer: true, src: asset_path("/js/app.js"))
|
24
|
+
end
|
25
|
+
body do
|
26
|
+
yield
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
```
|
32
|
+
|