brut 0.3.0 → 0.4.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/CHANGELOG.md +22 -0
- data/Gemfile.lock +1 -1
- data/brut-js/CHANGELOG.md +5 -0
- data/brut-js/package-lock.json +2 -2
- data/brut-js/package.json +1 -1
- data/brut-js/specs/ConstraintViolationMessage.spec.js +1 -1
- data/brut-js/specs/ConstraintViolationMessages.spec.js +2 -2
- data/brut-js/specs/Form.spec.js +5 -5
- data/brut-js/src/ConstraintViolationMessage.js +3 -3
- data/brut-js/src/ConstraintViolationMessages.js +2 -2
- data/brutrb.com/.vitepress/config.mjs +1 -0
- data/brutrb.com/database-schema.md +22 -2
- data/brutrb.com/dev-environment.md +1 -0
- data/brutrb.com/form-constraints.md +2 -2
- data/brutrb.com/handlers.md +1 -1
- data/brutrb.com/i18n.md +2 -2
- data/brutrb.com/layouts.md +1 -1
- data/brutrb.com/recipes/migrations.md +210 -0
- data/docs/404.html +2 -2
- data/docs/adrs.html +4 -4
- data/docs/ai.html +4 -4
- 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 +2 -2
- data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +6 -6
- data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +6 -6
- data/docs/api/Brut/CLI/Apps/BuildAssets.html +2 -2
- 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 +6 -2
- 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 +9 -9
- 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 +8 -12
- data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +5 -5
- data/docs/api/Brut/CLI/Apps/Scaffold/DbModel.html +384 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +5 -5
- 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 +36 -36
- data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold.html +2 -2
- 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 +29 -31
- 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 +16 -2
- 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 +3 -3
- data/docs/api/Brut/FrontEnd/Components/FormTag.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +10 -14
- data/docs/api/Brut/FrontEnd/Components/Input.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +3 -3
- data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/TimeTag.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Traceparent.html +1 -1
- data/docs/api/Brut/FrontEnd/Components.html +1 -1
- data/docs/api/Brut/FrontEnd/Download.html +1 -1
- data/docs/api/Brut/FrontEnd/Flash.html +1 -1
- data/docs/api/Brut/FrontEnd/Form.html +92 -20
- 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 +1 -1
- data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +1 -1
- 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 +2 -2
- 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/ModuleName.html +595 -0
- 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 +24 -12
- data/docs/api/class_list.html +1 -1
- data/docs/api/file.README.html +1 -1
- data/docs/api/index.html +1 -1
- data/docs/api/method_list.html +478 -406
- data/docs/api/top-level-namespace.html +2 -2
- data/docs/assets/{app._UukLsdv.js → app.AkS4fHzI.js} +1 -1
- data/docs/assets/chunks/@localSearchIndexroot.C9FqcQxD.js +1 -0
- data/docs/assets/chunks/{VPLocalSearchBox.DhYUVvsa.js → VPLocalSearchBox.By6un1FD.js} +1 -1
- data/docs/assets/chunks/{theme.DHNq8t6D.js → theme.DwiUPdci.js} +2 -2
- data/docs/assets/{components.md.DP8maNoF.js → components.md.BfeWD9sX.js} +3 -3
- data/docs/assets/{configuration.md.BDXwuZHU.js → configuration.md.DoSBNc0H.js} +1 -1
- data/docs/assets/{database-schema.md.CSYk6E6v.js → database-schema.md.C5gXexJi.js} +10 -3
- data/docs/assets/{database-schema.md.CSYk6E6v.lean.js → database-schema.md.C5gXexJi.lean.js} +1 -1
- data/docs/assets/{dev-environment.md.Dy6EldaM.js → dev-environment.md.DRH2D2-O.js} +3 -3
- data/docs/assets/dev-environment.md.DRH2D2-O.lean.js +1 -0
- data/docs/assets/{form-constraints.md.x5tNpTTI.js → form-constraints.md.DK5adCgM.js} +2 -2
- data/docs/assets/{forms.md.BwBNYrrL.js → forms.md.DFveP5g_.js} +1 -1
- data/docs/assets/{getting-started.md.B4LlHfav.js → getting-started.md.DZWjCVC0.js} +2 -2
- data/docs/assets/{handlers.md.Chyri6KA.js → handlers.md.h84MMB1R.js} +1 -1
- data/docs/assets/{i18n.md.xQhiGo1G.js → i18n.md.BAm9t9JJ.js} +1 -1
- data/docs/assets/{layouts.md.CJGDFY-m.js → layouts.md.CVGl9xIO.js} +1 -1
- data/docs/assets/recipes_migrations.md.DPN3gQE3.js +97 -0
- data/docs/assets/recipes_migrations.md.DPN3gQE3.lean.js +1 -0
- data/docs/assets.html +4 -4
- 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 +2 -2
- data/docs/brut-js/api/ConstraintViolationMessage.js.html +4 -4
- data/docs/brut-js/api/ConstraintViolationMessages.html +3 -3
- data/docs/brut-js/api/ConstraintViolationMessages.js.html +3 -3
- 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 +4 -4
- data/docs/business-logic.html +4 -4
- data/docs/cli.html +4 -4
- data/docs/components.html +8 -8
- data/docs/configuration.html +5 -5
- data/docs/css.html +4 -4
- data/docs/custom-element-tests.html +4 -4
- data/docs/database-access.html +4 -4
- data/docs/database-schema.html +13 -6
- data/docs/deployment.html +4 -4
- data/docs/dev-environment.html +6 -6
- data/docs/dir-structure.html +4 -4
- data/docs/doc-conventions.html +4 -4
- data/docs/end-to-end-tests.html +4 -4
- data/docs/features.html +4 -4
- data/docs/flash-and-session.html +4 -4
- data/docs/form-constraints.html +7 -7
- data/docs/forms.html +6 -6
- data/docs/getting-started.html +7 -7
- data/docs/handlers.html +6 -6
- data/docs/hashmap.json +1 -1
- data/docs/hooks.html +4 -4
- data/docs/i18n.html +6 -6
- data/docs/index.html +3 -3
- data/docs/instrumentation.html +4 -4
- data/docs/javascript.html +4 -4
- data/docs/jobs.html +4 -4
- data/docs/keyword-injection.html +4 -4
- data/docs/layouts.html +6 -6
- data/docs/lsp.html +4 -4
- data/docs/markdown-examples.html +4 -4
- data/docs/middleware.html +4 -4
- data/docs/overview.html +4 -4
- data/docs/pages.html +4 -4
- data/docs/recipes/alternate-layouts.html +4 -4
- data/docs/recipes/authentication.html +5 -5
- data/docs/recipes/blank-layouts.html +4 -4
- data/docs/recipes/custom-flash.html +4 -4
- data/docs/recipes/indexed-forms.html +4 -4
- data/docs/recipes/migrations.html +125 -0
- data/docs/recipes/text-field-component.html +4 -4
- data/docs/roadmap.html +4 -4
- data/docs/routes.html +4 -4
- data/docs/security.html +4 -4
- data/docs/seed-data.html +4 -4
- data/docs/space-time-continuum.html +4 -4
- data/docs/tutorial.html +4 -4
- data/docs/unit-tests.html +4 -4
- data/docs/why.html +4 -4
- data/lib/brut/cli/apps/db.rb +2 -0
- data/lib/brut/cli/apps/scaffold.rb +77 -0
- data/lib/brut/cli/command.rb +1 -2
- data/lib/brut/framework/config.rb +7 -0
- data/lib/brut/front_end/components/constraint_violations.rb +2 -2
- data/lib/brut/front_end/components/i18n_translations.rb +6 -10
- data/lib/brut/front_end/form.rb +5 -2
- data/lib/brut/junk_drawer.rb +53 -0
- data/lib/brut/version.rb +1 -1
- metadata +32 -25
- data/docs/assets/chunks/@localSearchIndexroot.DkpwyjLd.js +0 -1
- data/docs/assets/dev-environment.md.Dy6EldaM.lean.js +0 -1
- /data/docs/assets/{components.md.DP8maNoF.lean.js → components.md.BfeWD9sX.lean.js} +0 -0
- /data/docs/assets/{configuration.md.BDXwuZHU.lean.js → configuration.md.DoSBNc0H.lean.js} +0 -0
- /data/docs/assets/{form-constraints.md.x5tNpTTI.lean.js → form-constraints.md.DK5adCgM.lean.js} +0 -0
- /data/docs/assets/{forms.md.BwBNYrrL.lean.js → forms.md.DFveP5g_.lean.js} +0 -0
- /data/docs/assets/{getting-started.md.B4LlHfav.lean.js → getting-started.md.DZWjCVC0.lean.js} +0 -0
- /data/docs/assets/{handlers.md.Chyri6KA.lean.js → handlers.md.h84MMB1R.lean.js} +0 -0
- /data/docs/assets/{i18n.md.xQhiGo1G.lean.js → i18n.md.BAm9t9JJ.lean.js} +0 -0
- /data/docs/assets/{layouts.md.CJGDFY-m.lean.js → layouts.md.CVGl9xIO.lean.js} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6715d0f8a6d8d1471cd31f4ab2ebe27ecec83d1e758545f477c8c1438d266219
|
4
|
+
data.tar.gz: 7aa0e9b6d0b1c18be69fe89c96ba4faed49b0169dd65ce310a9aeac55896fad3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e6889484ea8547d47751b7fff9a680d82508831a692081dfb441fb8a58faade8486c5201f9161bc478572d8915e0aa1657161f156d5adf912d94d4a199f763b2
|
7
|
+
data.tar.gz: b882301738459b9df68b093d03fcd69c7f62d800169bc6044615577e7e3337b79637b126aa10827e58fbbf16b49c58d680f777900c11d60f11410492e9993fa3
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,27 @@
|
|
1
1
|
# Brut CHANGELOG
|
2
2
|
|
3
|
+
## v0.4.0 - July 12, 2025
|
4
|
+
|
5
|
+
* **Breaking Change** - changed `cv.fe` and `cv.be` I18n keys to `cv.cs` and `cv.ss`
|
6
|
+
- This is to be consistent with Brut terminology
|
7
|
+
- To address this change:
|
8
|
+
1. Update brut-js to 0.0.22
|
9
|
+
2. Modify your `app/config/*/*.rb` i18n files to change `cv:` to `cs:` and `be:`
|
10
|
+
to `ss:`
|
11
|
+
3. Change `app/src/front_end/layouts/default_layout.rb`:
|
12
|
+
|
13
|
+
```diff
|
14
|
+
- I18nTranslations("cv.fe")
|
15
|
+
+ I18nTranslations("cv.cs")
|
16
|
+
```
|
17
|
+
4. Change anywhere in your code or tests that you refer to those keys
|
18
|
+
* Added `#valid?` to `Brut::FrontEnd::Form` to make it easier to check a lack
|
19
|
+
of constraint violations
|
20
|
+
|
21
|
+
## v0.3.1 - July 12, 2025
|
22
|
+
|
23
|
+
* **`bin/db new_migration` includes link to the migrations recipe**
|
24
|
+
|
3
25
|
## v0.3.0 - July 11, 2025
|
4
26
|
|
5
27
|
* **`bin/scaffold form` generates correct handler code**
|
data/Gemfile.lock
CHANGED
data/brut-js/package-lock.json
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
{
|
2
2
|
"name": "brut-js",
|
3
|
-
"version": "0.0.
|
3
|
+
"version": "0.0.22",
|
4
4
|
"lockfileVersion": 3,
|
5
5
|
"requires": true,
|
6
6
|
"packages": {
|
7
7
|
"": {
|
8
8
|
"name": "brut-js",
|
9
|
-
"version": "0.0.
|
9
|
+
"version": "0.0.22",
|
10
10
|
"license": "Hippocratic-2.1",
|
11
11
|
"devDependencies": {
|
12
12
|
"esbuild": "^0.24.0",
|
data/brut-js/package.json
CHANGED
@@ -23,7 +23,7 @@ describe("<brut-cv>", () => {
|
|
23
23
|
withHTML(`
|
24
24
|
<brut-i18n-translation key="problem" value="%{field} has a problem"></brut-i18n-translation>
|
25
25
|
<brut-i18n-translation key="cv.this_field" value="THAT FIELD"></brut-i18n-translation>
|
26
|
-
<brut-i18n-translation key="cv.
|
26
|
+
<brut-i18n-translation key="cv.cs.fieldNames.some-field" value="Some Field"></brut-i18n-translation>
|
27
27
|
|
28
28
|
<brut-cv input-name="some-field" key="problem"></brut-cv>
|
29
29
|
`).test("Inserts using the this_field key as the placeholder", ({document,assert}) => {
|
@@ -2,8 +2,8 @@ import { withHTML } from "./SpecHelper.js"
|
|
2
2
|
|
3
3
|
describe("<brut-cv-messages>", () => {
|
4
4
|
withHTML(`
|
5
|
-
<brut-i18n-translation key="cv.
|
6
|
-
<brut-i18n-translation key="cv.
|
5
|
+
<brut-i18n-translation key="cv.cs.patternMismatch" value="%{field} does not match the pattern"></brut-i18n-translation>
|
6
|
+
<brut-i18n-translation key="cv.cs.rangeOverflow" value="%{field} is above the range"></brut-i18n-translation>
|
7
7
|
|
8
8
|
<brut-cv-messages input-name="some-field"></brut-cv-messages>
|
9
9
|
`).test("Inserts constraint violation messages based on validity state", ({document,assert}) => {
|
data/brut-js/specs/Form.spec.js
CHANGED
@@ -51,9 +51,9 @@ describe("<brut-form>", () => {
|
|
51
51
|
assert(gotInvalid)
|
52
52
|
assert.equal(brutForm.getAttribute("submitted-invalid"),"")
|
53
53
|
|
54
|
-
let error = textFieldLabel.querySelector("brut-cv[key='cv.
|
54
|
+
let error = textFieldLabel.querySelector("brut-cv[key='cv.cs.valueMissing']")
|
55
55
|
assert(error)
|
56
|
-
error = numberFieldLabel.querySelector("brut-cv[key='cv.
|
56
|
+
error = numberFieldLabel.querySelector("brut-cv[key='cv.cs.valueMissing']")
|
57
57
|
assert(error)
|
58
58
|
|
59
59
|
const textField = textFieldLabel.querySelector("input")
|
@@ -71,9 +71,9 @@ describe("<brut-form>", () => {
|
|
71
71
|
assert(gotInvalid)
|
72
72
|
assert.equal(brutForm.getAttribute("submitted-invalid"),"")
|
73
73
|
|
74
|
-
error = textFieldLabel.querySelector("brut-cv[key='cv.
|
74
|
+
error = textFieldLabel.querySelector("brut-cv[key='cv.cs.valueMissing']")
|
75
75
|
assert(!error)
|
76
|
-
error = numberFieldLabel.querySelector("brut-cv[key='cv.
|
76
|
+
error = numberFieldLabel.querySelector("brut-cv[key='cv.cs.valueMissing']")
|
77
77
|
assert(error)
|
78
78
|
|
79
79
|
const numberField = numberFieldLabel.querySelector("input")
|
@@ -174,7 +174,7 @@ describe("<brut-form>", () => {
|
|
174
174
|
assert(gotInvalid)
|
175
175
|
assert.equal(brutForm.getAttribute("submitted-invalid"),"")
|
176
176
|
|
177
|
-
let error = brutForm.querySelector("brut-cv[input-name='text'][key='cv.
|
177
|
+
let error = brutForm.querySelector("brut-cv[input-name='text'][key='cv.cs.valueMissing']")
|
178
178
|
assert(error)
|
179
179
|
|
180
180
|
})
|
@@ -7,7 +7,7 @@ import I18nTranslation from "./I18nTranslation"
|
|
7
7
|
*
|
8
8
|
* Here is how the field's name is determined:
|
9
9
|
*
|
10
|
-
* 1. It will look for a `<brut-i18n-translation>` element with the `key` `cv.
|
10
|
+
* 1. It will look for a `<brut-i18n-translation>` element with the `key` `cv.cs.fieldNames.«input-name»`.
|
11
11
|
* 2. If that's not found, it will attempt to use "this field" by locating a `<brut-i18n-translation>` element with the `key`
|
12
12
|
* `cv.this_field` (the underscore being what is used on Brut's server side).
|
13
13
|
* 3. If that is not found, it will use the literaly string "this field" and emit a console warning.
|
@@ -37,7 +37,7 @@ class ConstraintViolationMessage extends BaseCustomElement {
|
|
37
37
|
|
38
38
|
static createElement(document,attributes) {
|
39
39
|
const element = document.createElement(ConstraintViolationMessage.tagName)
|
40
|
-
element.setAttribute("key",this.i18nKey("
|
40
|
+
element.setAttribute("key",this.i18nKey("cs", attributes.key))
|
41
41
|
element.setAttribute("input-name",attributes["input-name"])
|
42
42
|
if (Object.hasOwn(attributes,"show-warnings")) {
|
43
43
|
element.setAttribute("show-warnings",attributes["show-warnings"])
|
@@ -79,7 +79,7 @@ class ConstraintViolationMessage extends BaseCustomElement {
|
|
79
79
|
}
|
80
80
|
|
81
81
|
inputNameChangedCallback({newValue}) {
|
82
|
-
this.#inputNameKey = this.#i18nKey("
|
82
|
+
this.#inputNameKey = this.#i18nKey("cs", "fieldNames", newValue)
|
83
83
|
}
|
84
84
|
|
85
85
|
serverSideChangedCallback({newValueAsBoolean}) {
|
@@ -38,9 +38,9 @@ class ConstraintViolationMessages extends BaseCustomElement {
|
|
38
38
|
* This should be called as part of a Form validation event to provide a customized UX for
|
39
39
|
* the error messages, beyond what the browser would do by default. The keys used are the same
|
40
40
|
* as the attributes of a `ValidityState`, so for example, a range underflow would mean that `validity.rangeUnderflow` would return
|
41
|
-
* true. Thus, a `<brut-cv>` would be created with `key="cv.
|
41
|
+
* true. Thus, a `<brut-cv>` would be created with `key="cv.cs.rangeUnderflow"`.
|
42
42
|
*
|
43
|
-
* The `cv.
|
43
|
+
* The `cv.cs` is hard-coded to be consistent with Brut's server-side translation management.
|
44
44
|
*
|
45
45
|
* @param {ValidityState} validityState - the return from an element's `validity` when it's found to have constraint violations.
|
46
46
|
* @param {String} inputName - the element's `name`.
|
@@ -126,6 +126,7 @@ export default defineConfig({
|
|
126
126
|
text: "Recipes",
|
127
127
|
collapsed: true,
|
128
128
|
items: [
|
129
|
+
{ text: "Migration Basics", link: "/recipes/migrations" },
|
129
130
|
{ text: "Authentication", link: "/recipes/authentication" },
|
130
131
|
{ text: "Alternate Layouts", link: "/recipes/alternate-layouts" },
|
131
132
|
{ text: "Blank Layouts", link: "/recipes/blank-layouts" },
|
@@ -19,8 +19,7 @@ Brut's provides this via Sequel. See [both](https://sequel.jeremyevans.net/rdoc/
|
|
19
19
|
|
20
20
|
### Creating Migrations
|
21
21
|
|
22
|
-
To create a migration, use `bin/db new-migration`. It accepts any number of arguments that will be joined
|
23
|
-
together to form the filename:
|
22
|
+
To create a migration, use `bin/db new-migration`. It accepts any number of arguments that will be joined together to form the filename:
|
24
23
|
|
25
24
|
```
|
26
25
|
> bin/db new-migration user accounts
|
@@ -28,6 +27,27 @@ together to form the filename:
|
|
28
27
|
app/src/back_end/data_models/migrations/20250508132646_user-accounts.rb
|
29
28
|
```
|
30
29
|
|
30
|
+
If you will be creating [database models](/database-access) as well, you may find it
|
31
|
+
easier to use `bin/scaffold db_model`, which will create an empty database model
|
32
|
+
class, empty test, and empty factory, along with an outline of your migration:
|
33
|
+
|
34
|
+
```
|
35
|
+
bin/scaffold db_model widget
|
36
|
+
[ bin/scaffold ] Executing ["bin/db new_migration create_widget"]
|
37
|
+
[ bin/db ] Migration created:
|
38
|
+
app/src/back_end/data_models/migrations/20250712182257_create_widgets.rb
|
39
|
+
[ bin/scaffold ] ["bin/db new_migration create_widgets"] succeeded
|
40
|
+
[ bin/scaffold ] Creating DB::Foo in app/src/back_end/data_models/db/widget.rb
|
41
|
+
[ bin/scaffold ] Creating spec for DB::Foo in specs/back_end/data_models/db/widget.spec.rb
|
42
|
+
[ bin/scaffold ] Creating factory for DB::Foo in specs/factories/db/widget.factory.rb
|
43
|
+
```
|
44
|
+
|
45
|
+
> [!IMPORTANT]
|
46
|
+
> Brut doesn't do pluralization logic. Although Sequel does do some, you should
|
47
|
+
> not refer to your database model plurally. If you were do run `bin/scaffold
|
48
|
+
> db_model widgets`, you'd create the class `DB::Widgets`, which would not work.
|
49
|
+
> Be aware.
|
50
|
+
|
31
51
|
Note that the files are located in `app/src/back_end/data_models/migrations` and
|
32
52
|
have a name prefixed with a timestamp. This timestamp determins an ordering of how
|
33
53
|
the files are applied to the database.
|
@@ -107,6 +107,7 @@ you full documentation about what the command and subcommands do.
|
|
107
107
|
| | `custom_element_test` | Create a test for a custom element in your app |
|
108
108
|
| | `form` | Create a form and handler |
|
109
109
|
| | `page` | Create a new page and associated test |
|
110
|
+
| | `db_model` | Create one or more database models, specs, and factories, plus a migration to create the tables for those models |
|
110
111
|
| | `test` | Create the shell of a unit test based on an existing source file |
|
111
112
|
| | `test:e2e` | Create the shell of an end-to-end test |
|
112
113
|
| <code style="white-space: nowrap">bin/test</code> | | Run tests |
|
@@ -31,7 +31,7 @@ form.server_side_constraint_violation(
|
|
31
31
|
```
|
32
32
|
|
33
33
|
The `input_name` is the same value you used when creating your form class, and `key`
|
34
|
-
is an [I18n](/i18n) key that will have `cv.
|
34
|
+
is an [I18n](/i18n) key that will have `cv.ss` prepended to it (for **c*onstratin **v**iolation, **s**server **s**ide). Thus, the key in the above example is `"cv.ss.name_is_taken"`.
|
35
35
|
|
36
36
|
Brut forms will automatically add client-side constraints based on the value
|
37
37
|
assigned to the input. For example, since `name` must be 3 or more characters, this
|
@@ -128,7 +128,7 @@ They `key` attribute is for an I18n key that is expected to be on the page insid
|
|
128
128
|
`<brut-i18n-translation>` element. These are typically included in the [layout](/layouts), and generate HTML like so:
|
129
129
|
|
130
130
|
```html
|
131
|
-
<brut-i18n-translation key="cv.
|
131
|
+
<brut-i18n-translation key="cv.cs.rangeUnderflow"
|
132
132
|
value="%{field} is too short"></brut-i18n-translation>
|
133
133
|
```
|
134
134
|
|
data/brutrb.com/handlers.md
CHANGED
data/brutrb.com/i18n.md
CHANGED
@@ -155,14 +155,14 @@ error.
|
|
155
155
|
If included, it will work as normal:
|
156
156
|
|
157
157
|
```ruby
|
158
|
-
t("cv.
|
158
|
+
t("cv.ss.required", field: "Email") # => Email is required
|
159
159
|
```
|
160
160
|
|
161
161
|
If omitted, the value of `"cv.this_field"` is used. This is included in `1_default.rb`, but if it's
|
162
162
|
missing, Brut will raise. Assuming the value is `"This field"`:
|
163
163
|
|
164
164
|
```ruby
|
165
|
-
t("cv.
|
165
|
+
t("cv.ss.required") # => This field is required
|
166
166
|
```
|
167
167
|
|
168
168
|
## Testing
|
data/brutrb.com/layouts.md
CHANGED
@@ -35,7 +35,7 @@ class DefaultLayout < Brut::FrontEnd::Layout
|
|
35
35
|
script(defer: true, src: asset_path("/js/app.js"))
|
36
36
|
title { app_name }
|
37
37
|
PageIdentifier(@page_name)
|
38
|
-
I18nTranslations("cv.
|
38
|
+
I18nTranslations("cv.cs")
|
39
39
|
I18nTranslations("cv.this_field")
|
40
40
|
Traceparent()
|
41
41
|
render(
|
@@ -0,0 +1,210 @@
|
|
1
|
+
# Migration Example
|
2
|
+
|
3
|
+
If you've not used [Sequel](https://sequel.jeremyevans.net/) before, this recipe will show you the basics of creating [migrations](https://sequel.jeremyevans.net/rdoc/files/doc/migration_rdoc.html),
|
4
|
+
which are how the database schema is managed in Brut.
|
5
|
+
|
6
|
+
## Feature
|
7
|
+
|
8
|
+
* An accounts table will store an email and a deactivated date
|
9
|
+
* A blog posts table will store a title and content, and be attributed to an
|
10
|
+
account.
|
11
|
+
|
12
|
+
## Recipe
|
13
|
+
|
14
|
+
We'll create the migration, create data models, create and lint factories, then create seed data.
|
15
|
+
|
16
|
+
### Create the Migration
|
17
|
+
|
18
|
+
Create the migration file with `bin/db new_migration`:
|
19
|
+
|
20
|
+
```
|
21
|
+
> bin/db new_migration Accounts and Blog Posts
|
22
|
+
[ bin/db ] Migration created:
|
23
|
+
app/src/back_end/data_models/migrations/20250711215310_Accounts-and-Blog-Posts.rb
|
24
|
+
```
|
25
|
+
|
26
|
+
> [!NOTE]
|
27
|
+
> Your filename will be different, since it embeds a timestamp for when `bin/db new_migration` was run.
|
28
|
+
|
29
|
+
Now, use Sequel's migrations API, keeping in mind [Brut's augmentations](/database-schema), to create our tables.
|
30
|
+
|
31
|
+
Our tables will use [an external ids](/database-schema#external-ids). Note that
|
32
|
+
Brut will ensure both tables have primary keys and have `created_at` fields.
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
# app/src/back_end/data_models/migrations/20250711215310_Accounts-and-Blog-Posts.rb
|
36
|
+
Sequel.migration do
|
37
|
+
up do
|
38
|
+
create_table :accounts,
|
39
|
+
comment: "People or systems who can access this system",
|
40
|
+
external_id: true do
|
41
|
+
|
42
|
+
column :email, :text, unique: true
|
43
|
+
column :deactivated_at, :timestamptz, null: true
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
create_table :blog_posts,
|
48
|
+
comment: "Posts on our amazing blog",
|
49
|
+
external_id: true do
|
50
|
+
|
51
|
+
column :title, :text
|
52
|
+
column :content, :text
|
53
|
+
|
54
|
+
foreign_key :account_id, :accounts
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
```
|
59
|
+
|
60
|
+
You can apply this migration with `bin/db migrate`:
|
61
|
+
|
62
|
+
```
|
63
|
+
bin/db migrate
|
64
|
+
```
|
65
|
+
|
66
|
+
> [!IMPORTANT]
|
67
|
+
> This only applied migrations to the dev database. `bin/ci` and `bin/test e2e`
|
68
|
+
> will apply them to the test database, but you may need to do it yourself via
|
69
|
+
> `bin/db migration -e test`
|
70
|
+
|
71
|
+
> [!NOTE]
|
72
|
+
> There is no down migration. If you need to change and re-apply this before
|
73
|
+
> you have promoted it to production, rebuild your dev database with `bin/db
|
74
|
+
> rebuild`. It will apply all migrations from a fresh, empty database.
|
75
|
+
|
76
|
+
You can examine the tables with `psql`, via `bin/dbconsole`:
|
77
|
+
|
78
|
+
```
|
79
|
+
> bin/dbconsole
|
80
|
+
development=# \d accounts
|
81
|
+
Table "public.accounts"
|
82
|
+
Column | Type | Collation | Nullable | Default
|
83
|
+
----------------+--------------------------+-----------+----------+----------------------------------
|
84
|
+
id | integer | | not null | generated by default as identity
|
85
|
+
email | text | | not null |
|
86
|
+
deactivated_at | timestamp with time zone | | |
|
87
|
+
created_at | timestamp with time zone | | not null |
|
88
|
+
external_id | citext | | not null |
|
89
|
+
Indexes:
|
90
|
+
"accounts_pkey" PRIMARY KEY, btree (id)
|
91
|
+
"accounts_email_key" UNIQUE CONSTRAINT, btree (email)
|
92
|
+
"accounts_external_id_key" UNIQUE CONSTRAINT, btree (external_id)
|
93
|
+
Referenced by:
|
94
|
+
TABLE "blog_posts" CONSTRAINT "blog_posts_account_id_fkey" FOREIGN KEY (account_id) REFERENCES accounts(id)
|
95
|
+
|
96
|
+
development=# \d blog_posts
|
97
|
+
Table "public.blog_posts"
|
98
|
+
Column | Type | Collation | Nullable | Default
|
99
|
+
-------------+--------------------------+-----------+----------+----------------------------------
|
100
|
+
id | integer | | not null | generated by default as identity
|
101
|
+
title | text | | not null |
|
102
|
+
content | text | | not null |
|
103
|
+
account_id | integer | | not null |
|
104
|
+
created_at | timestamp with time zone | | not null |
|
105
|
+
external_id | citext | | not null |
|
106
|
+
Indexes:
|
107
|
+
"blog_posts_pkey" PRIMARY KEY, btree (id)
|
108
|
+
"blog_posts_account_id_index" btree (account_id)
|
109
|
+
"blog_posts_external_id_key" UNIQUE CONSTRAINT, btree (external_id)
|
110
|
+
Foreign-key constraints:
|
111
|
+
"blog_posts_account_id_fkey" FOREIGN KEY (account_id) REFERENCES accounts(id)
|
112
|
+
```
|
113
|
+
|
114
|
+
Note that all columns are `NOT NULL` except `deactivated_at`, which we explicitly
|
115
|
+
set as nullable. Note that the foreign key on `account_id` has an index and is
|
116
|
+
non-nullable. And note that both tables have `external_id` and `created_at`
|
117
|
+
columns. Brut will manage their contents.
|
118
|
+
|
119
|
+
### Create Data Models
|
120
|
+
|
121
|
+
Brut doesn't create your data models for you, since it assumes you prefer writing
|
122
|
+
code in your editor and not on the command line. Your data models will initially be
|
123
|
+
pretty short.
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
# app/src/back_end/data_models/db/account.rb
|
127
|
+
class DB::Account < AppDataModel
|
128
|
+
has_external_id :ac
|
129
|
+
one_to_many :blog_posts
|
130
|
+
end
|
131
|
+
|
132
|
+
# app/src/back_end/data_models/db/blog_post.rb
|
133
|
+
class DB::BlogPost < AppDataModel
|
134
|
+
many_to_one :account
|
135
|
+
end
|
136
|
+
```
|
137
|
+
|
138
|
+
You can run `bin/console` to try to test these, but it's easier to create factories
|
139
|
+
and use the `specs/lint_factories.spec.rb` to do that for us.
|
140
|
+
|
141
|
+
### Create Factories
|
142
|
+
|
143
|
+
Factories go in `specs/factories/db` and have a `.factory.db` suffix:
|
144
|
+
|
145
|
+
```ruby
|
146
|
+
# specs/factories/db/account.factory.rb
|
147
|
+
FactoryBot.define do
|
148
|
+
factory :account, class: "DB::Account" do
|
149
|
+
email { Faker::Internet.unique.email }
|
150
|
+
|
151
|
+
trait :inactive do
|
152
|
+
deactivated_at { Time.now }
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# specs/factories/db/blog_post.factory.rb
|
158
|
+
FactoryBot.define do
|
159
|
+
factory :blog_post, class: "DB::BlogPost" do
|
160
|
+
title { Faker::Lorem.sentence }
|
161
|
+
content { Faker::Lorem.paragraphs.join("\n\n") }
|
162
|
+
|
163
|
+
account
|
164
|
+
end
|
165
|
+
end
|
166
|
+
```
|
167
|
+
|
168
|
+
Note that the blog post factory creates an account. This is so that
|
169
|
+
`create(:blog_post)` will always succeeed in creating valid data.
|
170
|
+
|
171
|
+
To prove it, we'll lint our factories:
|
172
|
+
|
173
|
+
```
|
174
|
+
bin/test run specs/lint_factories.spec.rb
|
175
|
+
```
|
176
|
+
|
177
|
+
This will create every combination of every factory and fail if doing so raises an
|
178
|
+
error.
|
179
|
+
|
180
|
+
### Create Seed Data
|
181
|
+
|
182
|
+
Now, we'll set up seed data. `mkbrut` should've created
|
183
|
+
`app/src/back_end/data_models/seed/seed_data.rb`, so we'll use that.
|
184
|
+
|
185
|
+
```ruby
|
186
|
+
require "brut/back_end/seed_data"
|
187
|
+
class SeedData < Brut::BackEnd::SeedData
|
188
|
+
include FactoryBot::Syntax::Methods
|
189
|
+
def seed!
|
190
|
+
pat = create(:account, email: "pat@example.com")
|
191
|
+
chris = create(:account, :inactive, email: "chris@example.com")
|
192
|
+
|
193
|
+
5.times do
|
194
|
+
create(:blog_post, account: pat)
|
195
|
+
create(:blog_post, account: chris)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
```
|
200
|
+
|
201
|
+
We can apply this with `bin/db seed`:
|
202
|
+
|
203
|
+
```
|
204
|
+
bin/db seed
|
205
|
+
```
|
206
|
+
|
207
|
+
> [!IMPORTANT]
|
208
|
+
> `bin/db rebuild` will *not* apply seed data, however `bin/setup` should.
|
209
|
+
> For now, if you want to totally reset your database, you will need to do `bin/db
|
210
|
+
> rebuild && bin/db seed && bin/db rebuild -e test`
|
data/docs/404.html
CHANGED
@@ -9,7 +9,7 @@
|
|
9
9
|
<link rel="preload stylesheet" href="/assets/style.B1z60PPQ.css" as="style">
|
10
10
|
<link rel="preload stylesheet" href="/vp-icons.css" as="style">
|
11
11
|
|
12
|
-
<script type="module" src="/assets/app.
|
12
|
+
<script type="module" src="/assets/app.AkS4fHzI.js"></script>
|
13
13
|
<link rel="icon" href="/favicon.ico">
|
14
14
|
<meta property="og:title" content="BrutRB Documentation">
|
15
15
|
<meta property="og:type" content="website">
|
@@ -20,7 +20,7 @@
|
|
20
20
|
</head>
|
21
21
|
<body>
|
22
22
|
<div id="app"></div>
|
23
|
-
<script>window.__VP_HASH_MAP__=JSON.parse("{\"adrs.md\":\"JRxZ5uYE\",\"ai.md\":\"Cy9GWnER\",\"assets.md\":\"7C3HWkga\",\"brut-js.md\":\"B4GYxQVw\",\"business-logic.md\":\"BY4hGy0m\",\"cli.md\":\"CjsktgFz\",\"components.md\":\"
|
23
|
+
<script>window.__VP_HASH_MAP__=JSON.parse("{\"adrs.md\":\"JRxZ5uYE\",\"ai.md\":\"Cy9GWnER\",\"assets.md\":\"7C3HWkga\",\"brut-js.md\":\"B4GYxQVw\",\"business-logic.md\":\"BY4hGy0m\",\"cli.md\":\"CjsktgFz\",\"components.md\":\"BfeWD9sX\",\"configuration.md\":\"DoSBNc0H\",\"css.md\":\"CltvJqAa\",\"custom-element-tests.md\":\"B_rbta32\",\"database-access.md\":\"gnluu54N\",\"database-schema.md\":\"C5gXexJi\",\"deployment.md\":\"BLseERGV\",\"dev-environment.md\":\"DRH2D2-O\",\"dir-structure.md\":\"CWir1pic\",\"doc-conventions.md\":\"DOkAuXlt\",\"end-to-end-tests.md\":\"DzqRpZ43\",\"features.md\":\"DPFXsy0z\",\"flash-and-session.md\":\"nPvUpnUx\",\"form-constraints.md\":\"DK5adCgM\",\"forms.md\":\"DFveP5g_\",\"getting-started.md\":\"DZWjCVC0\",\"handlers.md\":\"h84MMB1R\",\"hooks.md\":\"Jmb5VOLA\",\"i18n.md\":\"BAm9t9JJ\",\"index.md\":\"Bn9e0sRJ\",\"instrumentation.md\":\"BgcaGVYH\",\"javascript.md\":\"DzrMxUmI\",\"jobs.md\":\"S-2amAYp\",\"keyword-injection.md\":\"95Zgh2eN\",\"layouts.md\":\"CVGl9xIO\",\"lsp.md\":\"Dn1rIiW0\",\"markdown-examples.md\":\"CCFEQO44\",\"middleware.md\":\"Czz_UlJN\",\"overview.md\":\"iMnwLO4x\",\"pages.md\":\"B7Hc-i6H\",\"recipes_alternate-layouts.md\":\"BwEytl59\",\"recipes_authentication.md\":\"Dzvi_g69\",\"recipes_blank-layouts.md\":\"fyAUJyJR\",\"recipes_custom-flash.md\":\"CrQbI5eH\",\"recipes_indexed-forms.md\":\"CstYyOSo\",\"recipes_migrations.md\":\"DPN3gQE3\",\"recipes_text-field-component.md\":\"H4wLAK0Z\",\"roadmap.md\":\"C6PRi0DX\",\"routes.md\":\"BD6y2i-f\",\"security.md\":\"C0G_AZR-\",\"seed-data.md\":\"BvFZlqIk\",\"space-time-continuum.md\":\"xl44xDos\",\"tutorial.md\":\"BYXj4cOu\",\"unit-tests.md\":\"DUGrnLj5\",\"why.md\":\"C-hk5xgJ\"}");window.__VP_SITE_DATA__=JSON.parse("{\"lang\":\"en-US\",\"dir\":\"ltr\",\"title\":\"Brut RB\",\"description\":\"Documentation for the Brut.RB web framework.\",\"base\":\"/\",\"head\":[],\"router\":{\"prefetchLinks\":true},\"appearance\":true,\"themeConfig\":{\"search\":{\"provider\":\"local\"},\"nav\":[{\"text\":\"Home\",\"link\":\"/\"},{\"text\":\"Getting Started\",\"link\":\"/getting-started\"},{\"text\":\"Overview\",\"link\":\"/overview\"},{\"text\":\"Brut API\",\"link\":\"/api/index.html\",\"target\":\"_self\"},{\"text\":\"BrutJS\",\"link\":\"/brut-js/api/index.html\",\"target\":\"_self\"},{\"text\":\"BrutCSS\",\"link\":\"/brut-css/index.html\",\"target\":\"_self\"}],\"outline\":[2,3],\"sidebar\":[{\"text\":\"Overview\",\"collapsed\":false,\"items\":[{\"text\":\"Getting Started\",\"link\":\"/getting-started\"},{\"text\":\"Concepts\",\"link\":\"/overview\"},{\"text\":\"Features\",\"link\":\"/features\"},{\"text\":\"Directory Structure\",\"link\":\"/dir-structure\"},{\"text\":\"Dev Environment\",\"link\":\"/dev-environment\"},{\"text\":\"Tutorial\",\"link\":\"/tutorial\"},{\"text\":\"Documentation Conventions\",\"link\":\"/doc-conventions\"}]},{\"text\":\"Front-End\",\"collapsed\":false,\"items\":[{\"text\":\"Routes\",\"link\":\"/routes\"},{\"text\":\"Pages\",\"link\":\"/pages\"},{\"text\":\"Layouts\",\"link\":\"/layouts\"},{\"text\":\"Forms\",\"link\":\"/forms\"},{\"text\":\"Form Constraints\",\"link\":\"/form-constraints\"},{\"text\":\"Handlers and Actions\",\"link\":\"/handlers\"},{\"text\":\"Components\",\"link\":\"/components\"},{\"text\":\"Flash and Session\",\"link\":\"/flash-and-session\"},{\"text\":\"Space/Time Continuum\",\"link\":\"/space-time-continuum\"},{\"text\":\"JavaScript\",\"link\":\"/javascript\"},{\"text\":\"CSS\",\"link\":\"/css\"},{\"text\":\"Assets\",\"link\":\"/assets\"},{\"text\":\"BrutJS\",\"link\":\"/brut-js\"}]},{\"text\":\"Back-End\",\"collapsed\":false,\"items\":[{\"text\":\"Database Schema\",\"link\":\"/database-schema\"},{\"text\":\"Database Access\",\"link\":\"/database-access\"},{\"text\":\"Seed Data\",\"link\":\"/seed-data\"},{\"text\":\"Jobs\",\"link\":\"/jobs\"},{\"text\":\"Business Logic\",\"link\":\"/business-logic\"}]},{\"text\":\"Framework\",\"collapsed\":false,\"items\":[{\"text\":\"Configuration\",\"link\":\"/configuration\"},{\"text\":\"Keyword Injection\",\"link\":\"/keyword-injection\"},{\"text\":\"I18n\",\"link\":\"/i18n\"},{\"text\":\"CLI / Tasks\",\"link\":\"/cli\"},{\"text\":\"Deployment\",\"link\":\"/deployment\"}]},{\"text\":\"Testing\",\"collapsed\":false,\"items\":[{\"text\":\"Unit Tests\",\"link\":\"/unit-tests\"},{\"text\":\"End-to-End Tests\",\"link\":\"/end-to-end-tests\"},{\"text\":\"Testing Custom Elements\",\"link\":\"/custom-element-tests\"}]},{\"text\":\"Advanced Topics\",\"collapsed\":true,\"items\":[{\"text\":\"Route Hooks\",\"link\":\"/hooks\"},{\"text\":\"Middleware\",\"link\":\"/middleware\"},{\"text\":\"Instrumentation\",\"link\":\"/instrumentation\"},{\"text\":\"Security\",\"link\":\"/security\"},{\"text\":\"LSP Support\",\"link\":\"/lsp\"}]},{\"text\":\"Recipes\",\"collapsed\":true,\"items\":[{\"text\":\"Migration Basics\",\"link\":\"/recipes/migrations\"},{\"text\":\"Authentication\",\"link\":\"/recipes/authentication\"},{\"text\":\"Alternate Layouts\",\"link\":\"/recipes/alternate-layouts\"},{\"text\":\"Blank Layouts\",\"link\":\"/recipes/blank-layouts\"},{\"text\":\"Custom Flash Class\",\"link\":\"/recipes/custom-flash\"},{\"text\":\"Indexed Form Elements\",\"link\":\"/recipes/indexed-forms\"},{\"text\":\"Text Field Component\",\"link\":\"/recipes/text-field-component\"}]},{\"text\":\"Meta\",\"collapsed\":false,\"items\":[{\"text\":\"Why?!\",\"link\":\"/why\"},{\"text\":\"ADRs\",\"link\":\"/adrs\"},{\"text\":\"Roadmap to 1.0\",\"link\":\"/roadmap\"},{\"text\":\"AI Declaration\",\"link\":\"/ai\"}]}],\"socialLinks\":[{\"icon\":\"github\",\"link\":\"https://github.com/thirdtank/brut\"}]},\"locales\":{},\"scrollOffset\":134,\"cleanUrls\":false}");</script>
|
24
24
|
|
25
25
|
</body>
|
26
26
|
</html>
|