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.
Files changed (374) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/Gemfile.lock +1 -1
  4. data/brut-js/CHANGELOG.md +5 -0
  5. data/brut-js/package-lock.json +2 -2
  6. data/brut-js/package.json +1 -1
  7. data/brut-js/specs/ConstraintViolationMessage.spec.js +1 -1
  8. data/brut-js/specs/ConstraintViolationMessages.spec.js +2 -2
  9. data/brut-js/specs/Form.spec.js +5 -5
  10. data/brut-js/src/ConstraintViolationMessage.js +3 -3
  11. data/brut-js/src/ConstraintViolationMessages.js +2 -2
  12. data/brutrb.com/.vitepress/config.mjs +1 -0
  13. data/brutrb.com/database-schema.md +22 -2
  14. data/brutrb.com/dev-environment.md +1 -0
  15. data/brutrb.com/form-constraints.md +2 -2
  16. data/brutrb.com/handlers.md +1 -1
  17. data/brutrb.com/i18n.md +2 -2
  18. data/brutrb.com/layouts.md +1 -1
  19. data/brutrb.com/recipes/migrations.md +210 -0
  20. data/docs/404.html +2 -2
  21. data/docs/adrs.html +4 -4
  22. data/docs/ai.html +4 -4
  23. data/docs/api/Brut/BackEnd/SeedData.html +1 -1
  24. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +1 -1
  25. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +1 -1
  26. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +1 -1
  27. data/docs/api/Brut/BackEnd/Sidekiq.html +1 -1
  28. data/docs/api/Brut/BackEnd/Validators/FormValidator.html +1 -1
  29. data/docs/api/Brut/BackEnd/Validators.html +1 -1
  30. data/docs/api/Brut/BackEnd.html +1 -1
  31. data/docs/api/Brut/CLI/App.html +1 -1
  32. data/docs/api/Brut/CLI/AppRunner.html +1 -1
  33. data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +2 -2
  34. data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +6 -6
  35. data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +1 -1
  36. data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +6 -6
  37. data/docs/api/Brut/CLI/Apps/BuildAssets.html +2 -2
  38. data/docs/api/Brut/CLI/Apps/DB/Create.html +1 -1
  39. data/docs/api/Brut/CLI/Apps/DB/Drop.html +1 -1
  40. data/docs/api/Brut/CLI/Apps/DB/Migrate.html +1 -1
  41. data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +6 -2
  42. data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +1 -1
  43. data/docs/api/Brut/CLI/Apps/DB/Seed.html +1 -1
  44. data/docs/api/Brut/CLI/Apps/DB/Status.html +9 -9
  45. data/docs/api/Brut/CLI/Apps/DB.html +1 -1
  46. data/docs/api/Brut/CLI/Apps/DeployBase/GitChecks.html +1 -1
  47. data/docs/api/Brut/CLI/Apps/DeployBase.html +1 -1
  48. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy/Deploy.html +1 -1
  49. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy.html +1 -1
  50. data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +1 -1
  51. data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +8 -12
  52. data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +1 -1
  53. data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +5 -5
  54. data/docs/api/Brut/CLI/Apps/Scaffold/DbModel.html +384 -0
  55. data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +1 -1
  56. data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +5 -5
  57. data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +1 -1
  58. data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +1 -1
  59. data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +36 -36
  60. data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +1 -1
  61. data/docs/api/Brut/CLI/Apps/Scaffold.html +2 -2
  62. data/docs/api/Brut/CLI/Apps/Test/Audit.html +1 -1
  63. data/docs/api/Brut/CLI/Apps/Test/E2e.html +1 -1
  64. data/docs/api/Brut/CLI/Apps/Test/JS.html +1 -1
  65. data/docs/api/Brut/CLI/Apps/Test/Run.html +1 -1
  66. data/docs/api/Brut/CLI/Apps/Test.html +1 -1
  67. data/docs/api/Brut/CLI/Apps.html +1 -1
  68. data/docs/api/Brut/CLI/Command.html +29 -31
  69. data/docs/api/Brut/CLI/Error.html +1 -1
  70. data/docs/api/Brut/CLI/ExecutionResults/Result.html +1 -1
  71. data/docs/api/Brut/CLI/ExecutionResults.html +1 -1
  72. data/docs/api/Brut/CLI/Executor.html +1 -1
  73. data/docs/api/Brut/CLI/InvalidOption.html +1 -1
  74. data/docs/api/Brut/CLI/Options.html +1 -1
  75. data/docs/api/Brut/CLI/Output.html +1 -1
  76. data/docs/api/Brut/CLI/SystemExecError.html +1 -1
  77. data/docs/api/Brut/CLI.html +1 -1
  78. data/docs/api/Brut/FactoryBot.html +1 -1
  79. data/docs/api/Brut/Framework/App.html +1 -1
  80. data/docs/api/Brut/Framework/Config.html +16 -2
  81. data/docs/api/Brut/Framework/Container.html +1 -1
  82. data/docs/api/Brut/Framework/Error.html +1 -1
  83. data/docs/api/Brut/Framework/Errors/AbstractMethod.html +1 -1
  84. data/docs/api/Brut/Framework/Errors/Bug.html +1 -1
  85. data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +1 -1
  86. data/docs/api/Brut/Framework/Errors/MissingParameter.html +1 -1
  87. data/docs/api/Brut/Framework/Errors/NoClassForPath.html +1 -1
  88. data/docs/api/Brut/Framework/Errors/NotFound.html +1 -1
  89. data/docs/api/Brut/Framework/Errors/NotImplemented.html +1 -1
  90. data/docs/api/Brut/Framework/Errors.html +1 -1
  91. data/docs/api/Brut/Framework/FussyTypeEnforcement.html +1 -1
  92. data/docs/api/Brut/Framework/MCP.html +1 -1
  93. data/docs/api/Brut/Framework/ProjectEnvironment.html +1 -1
  94. data/docs/api/Brut/Framework.html +1 -1
  95. data/docs/api/Brut/FrontEnd/AssetPathResolver.html +1 -1
  96. data/docs/api/Brut/FrontEnd/Component/Helpers.html +1 -1
  97. data/docs/api/Brut/FrontEnd/Component.html +1 -1
  98. data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +3 -3
  99. data/docs/api/Brut/FrontEnd/Components/FormTag.html +1 -1
  100. data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +10 -14
  101. data/docs/api/Brut/FrontEnd/Components/Input.html +1 -1
  102. data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +1 -1
  103. data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +1 -1
  104. data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +1 -1
  105. data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +3 -3
  106. data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +1 -1
  107. data/docs/api/Brut/FrontEnd/Components/Inputs.html +1 -1
  108. data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +1 -1
  109. data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +1 -1
  110. data/docs/api/Brut/FrontEnd/Components/TimeTag.html +1 -1
  111. data/docs/api/Brut/FrontEnd/Components/Traceparent.html +1 -1
  112. data/docs/api/Brut/FrontEnd/Components.html +1 -1
  113. data/docs/api/Brut/FrontEnd/Download.html +1 -1
  114. data/docs/api/Brut/FrontEnd/Flash.html +1 -1
  115. data/docs/api/Brut/FrontEnd/Form.html +92 -20
  116. data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +1 -1
  117. data/docs/api/Brut/FrontEnd/Forms/Input/Color.html +1 -1
  118. data/docs/api/Brut/FrontEnd/Forms/Input/TimeOfDay.html +1 -1
  119. data/docs/api/Brut/FrontEnd/Forms/Input.html +1 -1
  120. data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +1 -1
  121. data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +1 -1
  122. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +1 -1
  123. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +1 -1
  124. data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +1 -1
  125. data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +1 -1
  126. data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +1 -1
  127. data/docs/api/Brut/FrontEnd/Forms.html +1 -1
  128. data/docs/api/Brut/FrontEnd/GenericResponse.html +1 -1
  129. data/docs/api/Brut/FrontEnd/Handler.html +1 -1
  130. data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +1 -1
  131. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +1 -1
  132. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +1 -1
  133. data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +1 -1
  134. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +2 -2
  135. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +1 -1
  136. data/docs/api/Brut/FrontEnd/Handlers.html +1 -1
  137. data/docs/api/Brut/FrontEnd/HandlingResults.html +1 -1
  138. data/docs/api/Brut/FrontEnd/HttpMethod.html +1 -1
  139. data/docs/api/Brut/FrontEnd/HttpStatus.html +1 -1
  140. data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +1 -1
  141. data/docs/api/Brut/FrontEnd/Layout.html +1 -1
  142. data/docs/api/Brut/FrontEnd/Middleware.html +1 -1
  143. data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +1 -1
  144. data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +1 -1
  145. data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +1 -1
  146. data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +1 -1
  147. data/docs/api/Brut/FrontEnd/Middlewares.html +1 -1
  148. data/docs/api/Brut/FrontEnd/Page.html +1 -1
  149. data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +1 -1
  150. data/docs/api/Brut/FrontEnd/Pages.html +1 -1
  151. data/docs/api/Brut/FrontEnd/RequestContext.html +1 -1
  152. data/docs/api/Brut/FrontEnd/RouteHook.html +1 -1
  153. data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +1 -1
  154. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +1 -1
  155. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +1 -1
  156. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +1 -1
  157. data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +1 -1
  158. data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +1 -1
  159. data/docs/api/Brut/FrontEnd/RouteHooks.html +1 -1
  160. data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +1 -1
  161. data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +1 -1
  162. data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +1 -1
  163. data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +1 -1
  164. data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +1 -1
  165. data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +1 -1
  166. data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +1 -1
  167. data/docs/api/Brut/FrontEnd/Routing/Route.html +1 -1
  168. data/docs/api/Brut/FrontEnd/Routing.html +1 -1
  169. data/docs/api/Brut/FrontEnd/Session.html +1 -1
  170. data/docs/api/Brut/FrontEnd.html +1 -1
  171. data/docs/api/Brut/I18n/BaseMethods.html +1 -1
  172. data/docs/api/Brut/I18n/ForBackEnd.html +1 -1
  173. data/docs/api/Brut/I18n/ForCLI.html +1 -1
  174. data/docs/api/Brut/I18n/ForHTML.html +1 -1
  175. data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +1 -1
  176. data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +1 -1
  177. data/docs/api/Brut/I18n.html +1 -1
  178. data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +1 -1
  179. data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +1 -1
  180. data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +1 -1
  181. data/docs/api/Brut/Instrumentation/OpenTelemetry.html +1 -1
  182. data/docs/api/Brut/Instrumentation.html +1 -1
  183. data/docs/api/Brut/SinatraHelpers/ClassMethods.html +1 -1
  184. data/docs/api/Brut/SinatraHelpers.html +1 -1
  185. data/docs/api/Brut/SpecSupport/ClockSupport.html +1 -1
  186. data/docs/api/Brut/SpecSupport/ComponentSupport.html +1 -1
  187. data/docs/api/Brut/SpecSupport/E2ETestServer.html +1 -1
  188. data/docs/api/Brut/SpecSupport/E2eSupport.html +1 -1
  189. data/docs/api/Brut/SpecSupport/EnhancedNode.html +1 -1
  190. data/docs/api/Brut/SpecSupport/FlashSupport.html +1 -1
  191. data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +1 -1
  192. data/docs/api/Brut/SpecSupport/GeneralSupport.html +1 -1
  193. data/docs/api/Brut/SpecSupport/HandlerSupport.html +1 -1
  194. data/docs/api/Brut/SpecSupport/Matchers/BeABug.html +1 -1
  195. data/docs/api/Brut/SpecSupport/Matchers/BePageFor.html +1 -1
  196. data/docs/api/Brut/SpecSupport/Matchers/BeRoutingFor.html +1 -1
  197. data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +1 -1
  198. data/docs/api/Brut/SpecSupport/Matchers/HaveGenerated.html +1 -1
  199. data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +1 -1
  200. data/docs/api/Brut/SpecSupport/Matchers/HaveI18nString.html +1 -1
  201. data/docs/api/Brut/SpecSupport/Matchers/HaveLinkTo.html +1 -1
  202. data/docs/api/Brut/SpecSupport/Matchers/HaveRedirectedTo.html +1 -1
  203. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedHttpStatus.html +1 -1
  204. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedRackResponse.html +1 -1
  205. data/docs/api/Brut/SpecSupport/Matchers.html +1 -1
  206. data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +1 -1
  207. data/docs/api/Brut/SpecSupport/RSpecSetup.html +1 -1
  208. data/docs/api/Brut/SpecSupport/SessionSupport.html +1 -1
  209. data/docs/api/Brut/SpecSupport.html +1 -1
  210. data/docs/api/Brut.html +1 -1
  211. data/docs/api/Clock.html +1 -1
  212. data/docs/api/ModuleName.html +595 -0
  213. data/docs/api/RichString.html +1 -1
  214. data/docs/api/SemanticLogger/Appender/Async.html +1 -1
  215. data/docs/api/Sequel/Extensions/BrutInstrumentation.html +1 -1
  216. data/docs/api/Sequel/Extensions/BrutMigrations.html +1 -1
  217. data/docs/api/Sequel/Extensions.html +1 -1
  218. data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +1 -1
  219. data/docs/api/Sequel/Plugins/CreatedAt.html +1 -1
  220. data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +1 -1
  221. data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +1 -1
  222. data/docs/api/Sequel/Plugins/ExternalId.html +1 -1
  223. data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +1 -1
  224. data/docs/api/Sequel/Plugins/FindBang.html +1 -1
  225. data/docs/api/Sequel/Plugins.html +1 -1
  226. data/docs/api/Sequel.html +1 -1
  227. data/docs/api/_index.html +24 -12
  228. data/docs/api/class_list.html +1 -1
  229. data/docs/api/file.README.html +1 -1
  230. data/docs/api/index.html +1 -1
  231. data/docs/api/method_list.html +478 -406
  232. data/docs/api/top-level-namespace.html +2 -2
  233. data/docs/assets/{app._UukLsdv.js → app.AkS4fHzI.js} +1 -1
  234. data/docs/assets/chunks/@localSearchIndexroot.C9FqcQxD.js +1 -0
  235. data/docs/assets/chunks/{VPLocalSearchBox.DhYUVvsa.js → VPLocalSearchBox.By6un1FD.js} +1 -1
  236. data/docs/assets/chunks/{theme.DHNq8t6D.js → theme.DwiUPdci.js} +2 -2
  237. data/docs/assets/{components.md.DP8maNoF.js → components.md.BfeWD9sX.js} +3 -3
  238. data/docs/assets/{configuration.md.BDXwuZHU.js → configuration.md.DoSBNc0H.js} +1 -1
  239. data/docs/assets/{database-schema.md.CSYk6E6v.js → database-schema.md.C5gXexJi.js} +10 -3
  240. data/docs/assets/{database-schema.md.CSYk6E6v.lean.js → database-schema.md.C5gXexJi.lean.js} +1 -1
  241. data/docs/assets/{dev-environment.md.Dy6EldaM.js → dev-environment.md.DRH2D2-O.js} +3 -3
  242. data/docs/assets/dev-environment.md.DRH2D2-O.lean.js +1 -0
  243. data/docs/assets/{form-constraints.md.x5tNpTTI.js → form-constraints.md.DK5adCgM.js} +2 -2
  244. data/docs/assets/{forms.md.BwBNYrrL.js → forms.md.DFveP5g_.js} +1 -1
  245. data/docs/assets/{getting-started.md.B4LlHfav.js → getting-started.md.DZWjCVC0.js} +2 -2
  246. data/docs/assets/{handlers.md.Chyri6KA.js → handlers.md.h84MMB1R.js} +1 -1
  247. data/docs/assets/{i18n.md.xQhiGo1G.js → i18n.md.BAm9t9JJ.js} +1 -1
  248. data/docs/assets/{layouts.md.CJGDFY-m.js → layouts.md.CVGl9xIO.js} +1 -1
  249. data/docs/assets/recipes_migrations.md.DPN3gQE3.js +97 -0
  250. data/docs/assets/recipes_migrations.md.DPN3gQE3.lean.js +1 -0
  251. data/docs/assets.html +4 -4
  252. data/docs/brut-js/api/AjaxSubmit.html +1 -1
  253. data/docs/brut-js/api/AjaxSubmit.js.html +1 -1
  254. data/docs/brut-js/api/Autosubmit.html +1 -1
  255. data/docs/brut-js/api/Autosubmit.js.html +1 -1
  256. data/docs/brut-js/api/BaseCustomElement.html +1 -1
  257. data/docs/brut-js/api/BaseCustomElement.js.html +1 -1
  258. data/docs/brut-js/api/BrutCustomElements.html +1 -1
  259. data/docs/brut-js/api/BufferedLogger.html +1 -1
  260. data/docs/brut-js/api/ConfirmSubmit.html +1 -1
  261. data/docs/brut-js/api/ConfirmSubmit.js.html +1 -1
  262. data/docs/brut-js/api/ConfirmationDialog.html +1 -1
  263. data/docs/brut-js/api/ConfirmationDialog.js.html +1 -1
  264. data/docs/brut-js/api/ConstraintViolationMessage.html +2 -2
  265. data/docs/brut-js/api/ConstraintViolationMessage.js.html +4 -4
  266. data/docs/brut-js/api/ConstraintViolationMessages.html +3 -3
  267. data/docs/brut-js/api/ConstraintViolationMessages.js.html +3 -3
  268. data/docs/brut-js/api/CopyToClipboard.html +1 -1
  269. data/docs/brut-js/api/CopyToClipboard.js.html +1 -1
  270. data/docs/brut-js/api/Form.html +1 -1
  271. data/docs/brut-js/api/Form.js.html +1 -1
  272. data/docs/brut-js/api/I18nTranslation.html +1 -1
  273. data/docs/brut-js/api/I18nTranslation.js.html +1 -1
  274. data/docs/brut-js/api/LocaleDetection.html +1 -1
  275. data/docs/brut-js/api/LocaleDetection.js.html +1 -1
  276. data/docs/brut-js/api/Logger.html +1 -1
  277. data/docs/brut-js/api/Logger.js.html +1 -1
  278. data/docs/brut-js/api/Message.html +1 -1
  279. data/docs/brut-js/api/Message.js.html +1 -1
  280. data/docs/brut-js/api/PrefixedLogger.html +1 -1
  281. data/docs/brut-js/api/RichString.html +1 -1
  282. data/docs/brut-js/api/RichString.js.html +1 -1
  283. data/docs/brut-js/api/Tabs.html +1 -1
  284. data/docs/brut-js/api/Tabs.js.html +1 -1
  285. data/docs/brut-js/api/Tracing.html +1 -1
  286. data/docs/brut-js/api/Tracing.js.html +1 -1
  287. data/docs/brut-js/api/external-CustomElementRegistry.html +1 -1
  288. data/docs/brut-js/api/external-Performance.html +1 -1
  289. data/docs/brut-js/api/external-Promise.html +1 -1
  290. data/docs/brut-js/api/external-ValidityState.html +1 -1
  291. data/docs/brut-js/api/external-Window.html +1 -1
  292. data/docs/brut-js/api/external-fetch.html +1 -1
  293. data/docs/brut-js/api/global.html +1 -1
  294. data/docs/brut-js/api/index.html +1 -1
  295. data/docs/brut-js/api/index.js.html +1 -1
  296. data/docs/brut-js/api/module-testing.html +1 -1
  297. data/docs/brut-js/api/testing.AssetMetadata.html +1 -1
  298. data/docs/brut-js/api/testing.AssetMetadataLoader.html +1 -1
  299. data/docs/brut-js/api/testing.CustomElementTest.html +1 -1
  300. data/docs/brut-js/api/testing.DOMCreator.html +1 -1
  301. data/docs/brut-js/api/testing_AssetMetadata.js.html +1 -1
  302. data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +1 -1
  303. data/docs/brut-js/api/testing_CustomElementTest.js.html +1 -1
  304. data/docs/brut-js/api/testing_DOMCreator.js.html +1 -1
  305. data/docs/brut-js/api/testing_index.js.html +1 -1
  306. data/docs/brut-js.html +4 -4
  307. data/docs/business-logic.html +4 -4
  308. data/docs/cli.html +4 -4
  309. data/docs/components.html +8 -8
  310. data/docs/configuration.html +5 -5
  311. data/docs/css.html +4 -4
  312. data/docs/custom-element-tests.html +4 -4
  313. data/docs/database-access.html +4 -4
  314. data/docs/database-schema.html +13 -6
  315. data/docs/deployment.html +4 -4
  316. data/docs/dev-environment.html +6 -6
  317. data/docs/dir-structure.html +4 -4
  318. data/docs/doc-conventions.html +4 -4
  319. data/docs/end-to-end-tests.html +4 -4
  320. data/docs/features.html +4 -4
  321. data/docs/flash-and-session.html +4 -4
  322. data/docs/form-constraints.html +7 -7
  323. data/docs/forms.html +6 -6
  324. data/docs/getting-started.html +7 -7
  325. data/docs/handlers.html +6 -6
  326. data/docs/hashmap.json +1 -1
  327. data/docs/hooks.html +4 -4
  328. data/docs/i18n.html +6 -6
  329. data/docs/index.html +3 -3
  330. data/docs/instrumentation.html +4 -4
  331. data/docs/javascript.html +4 -4
  332. data/docs/jobs.html +4 -4
  333. data/docs/keyword-injection.html +4 -4
  334. data/docs/layouts.html +6 -6
  335. data/docs/lsp.html +4 -4
  336. data/docs/markdown-examples.html +4 -4
  337. data/docs/middleware.html +4 -4
  338. data/docs/overview.html +4 -4
  339. data/docs/pages.html +4 -4
  340. data/docs/recipes/alternate-layouts.html +4 -4
  341. data/docs/recipes/authentication.html +5 -5
  342. data/docs/recipes/blank-layouts.html +4 -4
  343. data/docs/recipes/custom-flash.html +4 -4
  344. data/docs/recipes/indexed-forms.html +4 -4
  345. data/docs/recipes/migrations.html +125 -0
  346. data/docs/recipes/text-field-component.html +4 -4
  347. data/docs/roadmap.html +4 -4
  348. data/docs/routes.html +4 -4
  349. data/docs/security.html +4 -4
  350. data/docs/seed-data.html +4 -4
  351. data/docs/space-time-continuum.html +4 -4
  352. data/docs/tutorial.html +4 -4
  353. data/docs/unit-tests.html +4 -4
  354. data/docs/why.html +4 -4
  355. data/lib/brut/cli/apps/db.rb +2 -0
  356. data/lib/brut/cli/apps/scaffold.rb +77 -0
  357. data/lib/brut/cli/command.rb +1 -2
  358. data/lib/brut/framework/config.rb +7 -0
  359. data/lib/brut/front_end/components/constraint_violations.rb +2 -2
  360. data/lib/brut/front_end/components/i18n_translations.rb +6 -10
  361. data/lib/brut/front_end/form.rb +5 -2
  362. data/lib/brut/junk_drawer.rb +53 -0
  363. data/lib/brut/version.rb +1 -1
  364. metadata +32 -25
  365. data/docs/assets/chunks/@localSearchIndexroot.DkpwyjLd.js +0 -1
  366. data/docs/assets/dev-environment.md.Dy6EldaM.lean.js +0 -1
  367. /data/docs/assets/{components.md.DP8maNoF.lean.js → components.md.BfeWD9sX.lean.js} +0 -0
  368. /data/docs/assets/{configuration.md.BDXwuZHU.lean.js → configuration.md.DoSBNc0H.lean.js} +0 -0
  369. /data/docs/assets/{form-constraints.md.x5tNpTTI.lean.js → form-constraints.md.DK5adCgM.lean.js} +0 -0
  370. /data/docs/assets/{forms.md.BwBNYrrL.lean.js → forms.md.DFveP5g_.lean.js} +0 -0
  371. /data/docs/assets/{getting-started.md.B4LlHfav.lean.js → getting-started.md.DZWjCVC0.lean.js} +0 -0
  372. /data/docs/assets/{handlers.md.Chyri6KA.lean.js → handlers.md.h84MMB1R.lean.js} +0 -0
  373. /data/docs/assets/{i18n.md.xQhiGo1G.lean.js → i18n.md.BAm9t9JJ.lean.js} +0 -0
  374. /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: a56f9e1afcd603bfe486a0698fb8072544704e86ac9c2389c7729212153b0848
4
- data.tar.gz: 0d1355defb5b29e4bc16e458d4434ed96e9c6bd87f511639b8794626ad1c2c48
3
+ metadata.gz: 6715d0f8a6d8d1471cd31f4ab2ebe27ecec83d1e758545f477c8c1438d266219
4
+ data.tar.gz: 7aa0e9b6d0b1c18be69fe89c96ba4faed49b0169dd65ce310a9aeac55896fad3
5
5
  SHA512:
6
- metadata.gz: 1ddf86edb8b5a3faed88bf49d64c9c31fe56adae7ae7b8bc882be328f1c2c7aca8849088e631d999e486bd8d5f9ae757b4facd785a03d5f00be8ddfb34582068
7
- data.tar.gz: 004bd287e73955fdaf7a61bce74c542663c460edc3703eec4be7a8e42fd92b956c93f60e6f9cdd8592def55bb2a01e0f462f0d8f0aa1f14cd4e2c54805d6402d
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- brut (0.3.0)
4
+ brut (0.4.0)
5
5
  concurrent-ruby
6
6
  i18n
7
7
  irb
@@ -0,0 +1,5 @@
1
+ # BrutJS CHANGELOG
2
+
3
+ ## v0.0.22
4
+
5
+ * Changed i18n key from cv.fe to cv.cs ("front-end" to "client-side") to be more consistent with Brut terminology
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "brut-js",
3
- "version": "0.0.21",
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.21",
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brut-js",
3
- "version": "0.0.21",
3
+ "version": "0.0.22",
4
4
  "type": "module",
5
5
  "keywords": [ "WebComponents", "Custom Elements" ],
6
6
  "bugs": {
@@ -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.fe.fieldNames.some-field" value="Some Field"></brut-i18n-translation>
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.fe.patternMismatch" value="%{field} does not match the pattern"></brut-i18n-translation>
6
- <brut-i18n-translation key="cv.fe.rangeOverflow" value="%{field} is above the range"></brut-i18n-translation>
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}) => {
@@ -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.fe.valueMissing']")
54
+ let error = textFieldLabel.querySelector("brut-cv[key='cv.cs.valueMissing']")
55
55
  assert(error)
56
- error = numberFieldLabel.querySelector("brut-cv[key='cv.fe.valueMissing']")
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.fe.valueMissing']")
74
+ error = textFieldLabel.querySelector("brut-cv[key='cv.cs.valueMissing']")
75
75
  assert(!error)
76
- error = numberFieldLabel.querySelector("brut-cv[key='cv.fe.valueMissing']")
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.fe.valueMissing']")
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.fe.fieldNames.«input-name»`.
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("fe", attributes.key))
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("fe", "fieldNames", newValue)
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.fe.rangeUnderflow"`.
41
+ * true. Thus, a `<brut-cv>` would be created with `key="cv.cs.rangeUnderflow"`.
42
42
  *
43
- * The `cv.fe` is hard-coded to be consistent with Brut's server-side translation management.
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.be` prepended to it (for **c*onstratin **v**iolation, **b**ack **e**nd). Thus, the key in the above example is `"cv.be.name_is_taken"`.
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.fe.rangeUnderflow"
131
+ <brut-i18n-translation key="cv.cs.rangeUnderflow"
132
132
  value="%{field} is too short"></brut-i18n-translation>
133
133
  ```
134
134
 
@@ -51,7 +51,7 @@ class NewWidgetHandler < AppHandler
51
51
 
52
52
  def handle
53
53
  # if no client-side violations were submitted
54
- if !@form.constraint_violations?
54
+ if @form.valid?
55
55
  widget = DB::Widget.find(name: form.name)
56
56
  if widget
57
57
  @form.server_side_constraint_violation(
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.be.required", field: "Email") # => Email is required
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.be.required") # => This field is required
165
+ t("cv.ss.required") # => This field is required
166
166
  ```
167
167
 
168
168
  ## Testing
@@ -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.fe")
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._UukLsdv.js"></script>
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\":\"DP8maNoF\",\"configuration.md\":\"BDXwuZHU\",\"css.md\":\"CltvJqAa\",\"custom-element-tests.md\":\"B_rbta32\",\"database-access.md\":\"gnluu54N\",\"database-schema.md\":\"CSYk6E6v\",\"deployment.md\":\"BLseERGV\",\"dev-environment.md\":\"Dy6EldaM\",\"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\":\"x5tNpTTI\",\"forms.md\":\"BwBNYrrL\",\"getting-started.md\":\"B4LlHfav\",\"handlers.md\":\"Chyri6KA\",\"hooks.md\":\"Jmb5VOLA\",\"i18n.md\":\"xQhiGo1G\",\"index.md\":\"Bn9e0sRJ\",\"instrumentation.md\":\"BgcaGVYH\",\"javascript.md\":\"DzrMxUmI\",\"jobs.md\":\"S-2amAYp\",\"keyword-injection.md\":\"95Zgh2eN\",\"layouts.md\":\"CJGDFY-m\",\"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_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\":\"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>
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>