brut 0.0.28 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (532) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +6 -0
  3. data/.projections.json +10 -0
  4. data/.rspec +3 -0
  5. data/Dockerfile.dx +32 -14
  6. data/Gemfile.lock +1 -1
  7. data/README.md +23 -2
  8. data/assets/Logo-Square.pxd +0 -0
  9. data/assets/LogoPylon.pxd +0 -0
  10. data/assets/LogoStop.pxd +0 -0
  11. data/assets/LogoTall.pxd +0 -0
  12. data/assets/MetroLogo.graffle +0 -0
  13. data/assets/SocialImage.png +0 -0
  14. data/assets/SocialImage.pxd +0 -0
  15. data/bin/docs +24 -2
  16. data/bin/rspec +27 -0
  17. data/bin/setup +3 -3
  18. data/brutrb.com/.vitepress/config.mjs +45 -8
  19. data/brutrb.com/.vitepress/theme/custom.css +7 -0
  20. data/brutrb.com/.vitepress/theme/style.css +29 -17
  21. data/brutrb.com/ai.md +10 -15
  22. data/brutrb.com/assets.md +2 -9
  23. data/brutrb.com/brut-js.md +12 -2
  24. data/brutrb.com/cli.md +9 -13
  25. data/brutrb.com/components.md +118 -96
  26. data/brutrb.com/configuration.md +3 -4
  27. data/brutrb.com/css.md +2 -2
  28. data/brutrb.com/custom-element-tests.md +3 -4
  29. data/brutrb.com/database-access.md +1 -1
  30. data/brutrb.com/database-schema.md +29 -41
  31. data/brutrb.com/deployment.md +123 -45
  32. data/brutrb.com/dev-environment.md +7 -7
  33. data/brutrb.com/dir-structure.md +120 -0
  34. data/brutrb.com/doc-conventions.md +18 -15
  35. data/brutrb.com/dx +1 -0
  36. data/brutrb.com/end-to-end-tests.md +12 -10
  37. data/brutrb.com/features.md +373 -0
  38. data/brutrb.com/flash-and-session.md +115 -131
  39. data/brutrb.com/form-constraints.md +266 -0
  40. data/brutrb.com/forms.md +140 -765
  41. data/brutrb.com/getting-started.md +10 -11
  42. data/brutrb.com/handlers.md +119 -95
  43. data/brutrb.com/hooks.md +18 -20
  44. data/brutrb.com/i18n.md +6 -4
  45. data/brutrb.com/images/LogoPylon.png +0 -0
  46. data/brutrb.com/images/LogoSquare.png +0 -0
  47. data/brutrb.com/images/LogoStop.png +0 -0
  48. data/brutrb.com/images/LogoTall.png +0 -0
  49. data/brutrb.com/images/OverviewMetro.graffle +0 -0
  50. data/brutrb.com/images/OverviewMetro.png +0 -0
  51. data/brutrb.com/index.md +4 -3
  52. data/brutrb.com/instrumentation.md +7 -10
  53. data/brutrb.com/javascript.md +14 -14
  54. data/brutrb.com/keyword-injection.md +72 -114
  55. data/brutrb.com/layouts.md +20 -52
  56. data/brutrb.com/lsp.md +1 -1
  57. data/brutrb.com/overview.md +35 -377
  58. data/brutrb.com/pages.md +119 -207
  59. data/brutrb.com/public/SocialImage.png +0 -0
  60. data/brutrb.com/public/favicon.ico +0 -0
  61. data/brutrb.com/recipes/alternate-layouts.md +32 -0
  62. data/brutrb.com/recipes/authentication.md +315 -6
  63. data/brutrb.com/recipes/blank-layouts.md +22 -0
  64. data/brutrb.com/recipes/custom-flash.md +51 -0
  65. data/brutrb.com/recipes/indexed-forms.md +149 -0
  66. data/brutrb.com/recipes/text-field-component.md +182 -0
  67. data/brutrb.com/routes.md +56 -82
  68. data/brutrb.com/security.md +0 -3
  69. data/brutrb.com/space-time-continuum.md +8 -12
  70. data/brutrb.com/tutorial.md +1 -1
  71. data/brutrb.com/why.md +19 -0
  72. data/docker-compose.dx.yml +5 -2
  73. data/docs/404.html +8 -3
  74. data/docs/SocialImage.png +0 -0
  75. data/docs/ai.html +11 -6
  76. data/docs/api/Brut/BackEnd/SeedData.html +1 -1
  77. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +1 -1
  78. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +1 -1
  79. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +1 -1
  80. data/docs/api/Brut/BackEnd/Sidekiq.html +1 -1
  81. data/docs/api/Brut/BackEnd/Validators/FormValidator.html +1 -1
  82. data/docs/api/Brut/BackEnd/Validators.html +1 -1
  83. data/docs/api/Brut/BackEnd.html +1 -1
  84. data/docs/api/Brut/CLI/App.html +1 -1
  85. data/docs/api/Brut/CLI/AppRunner.html +1 -1
  86. data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +1 -1
  87. data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +1 -1
  88. data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +1 -1
  89. data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +1 -1
  90. data/docs/api/Brut/CLI/Apps/BuildAssets.html +1 -1
  91. data/docs/api/Brut/CLI/Apps/DB/Create.html +1 -1
  92. data/docs/api/Brut/CLI/Apps/DB/Drop.html +1 -1
  93. data/docs/api/Brut/CLI/Apps/DB/Migrate.html +1 -1
  94. data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +1 -1
  95. data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +1 -1
  96. data/docs/api/Brut/CLI/Apps/DB/Seed.html +1 -1
  97. data/docs/api/Brut/CLI/Apps/DB/Status.html +1 -1
  98. data/docs/api/Brut/CLI/Apps/DB.html +1 -1
  99. data/docs/api/Brut/CLI/Apps/DeployBase/GitChecks.html +1 -1
  100. data/docs/api/Brut/CLI/Apps/DeployBase.html +1 -1
  101. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy/Deploy.html +1 -1
  102. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy.html +1 -1
  103. data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +1 -1
  104. data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +1 -1
  105. data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +1 -1
  106. data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +1 -1
  107. data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +1 -1
  108. data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +1 -1
  109. data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +1 -1
  110. data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +1 -1
  111. data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +1 -1
  112. data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +1 -1
  113. data/docs/api/Brut/CLI/Apps/Scaffold.html +1 -1
  114. data/docs/api/Brut/CLI/Apps/Test/Audit.html +1 -1
  115. data/docs/api/Brut/CLI/Apps/Test/E2e.html +1 -1
  116. data/docs/api/Brut/CLI/Apps/Test/JS.html +1 -1
  117. data/docs/api/Brut/CLI/Apps/Test/Run.html +1 -1
  118. data/docs/api/Brut/CLI/Apps/Test.html +1 -1
  119. data/docs/api/Brut/CLI/Apps.html +1 -1
  120. data/docs/api/Brut/CLI/Command.html +1 -1
  121. data/docs/api/Brut/CLI/Error.html +1 -1
  122. data/docs/api/Brut/CLI/ExecutionResults/Result.html +1 -1
  123. data/docs/api/Brut/CLI/ExecutionResults.html +1 -1
  124. data/docs/api/Brut/CLI/Executor.html +1 -1
  125. data/docs/api/Brut/CLI/InvalidOption.html +1 -1
  126. data/docs/api/Brut/CLI/Options.html +1 -1
  127. data/docs/api/Brut/CLI/Output.html +1 -1
  128. data/docs/api/Brut/CLI/SystemExecError.html +1 -1
  129. data/docs/api/Brut/CLI.html +1 -1
  130. data/docs/api/Brut/FactoryBot.html +1 -1
  131. data/docs/api/Brut/Framework/App.html +1 -1
  132. data/docs/api/Brut/Framework/Config.html +1 -1
  133. data/docs/api/Brut/Framework/Container.html +1 -1
  134. data/docs/api/Brut/Framework/Error.html +1 -1
  135. data/docs/api/Brut/Framework/Errors/AbstractMethod.html +1 -1
  136. data/docs/api/Brut/Framework/Errors/Bug.html +1 -1
  137. data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +1 -1
  138. data/docs/api/Brut/Framework/Errors/MissingParameter.html +1 -1
  139. data/docs/api/Brut/Framework/Errors/NoClassForPath.html +1 -1
  140. data/docs/api/Brut/Framework/Errors/NotFound.html +1 -1
  141. data/docs/api/Brut/Framework/Errors/NotImplemented.html +1 -1
  142. data/docs/api/Brut/Framework/Errors.html +1 -1
  143. data/docs/api/Brut/Framework/FussyTypeEnforcement.html +1 -1
  144. data/docs/api/Brut/Framework/MCP.html +1 -1
  145. data/docs/api/Brut/Framework/ProjectEnvironment.html +1 -1
  146. data/docs/api/Brut/Framework.html +1 -1
  147. data/docs/api/Brut/FrontEnd/AssetPathResolver.html +1 -1
  148. data/docs/api/Brut/FrontEnd/Component/Helpers.html +1 -1
  149. data/docs/api/Brut/FrontEnd/Component.html +1 -1
  150. data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +1 -1
  151. data/docs/api/Brut/FrontEnd/Components/FormTag.html +1 -1
  152. data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +1 -1
  153. data/docs/api/Brut/FrontEnd/Components/Input.html +1 -1
  154. data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +1 -1
  155. data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +1 -1
  156. data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +1 -1
  157. data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +1 -1
  158. data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +1 -1
  159. data/docs/api/Brut/FrontEnd/Components/Inputs.html +1 -1
  160. data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +1 -1
  161. data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +1 -1
  162. data/docs/api/Brut/FrontEnd/Components/TimeTag.html +1 -1
  163. data/docs/api/Brut/FrontEnd/Components/Traceparent.html +1 -1
  164. data/docs/api/Brut/FrontEnd/Components.html +1 -1
  165. data/docs/api/Brut/FrontEnd/Download.html +1 -1
  166. data/docs/api/Brut/FrontEnd/Flash.html +1 -1
  167. data/docs/api/Brut/FrontEnd/Form.html +9 -11
  168. data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +1 -1
  169. data/docs/api/Brut/FrontEnd/Forms/Input/Color.html +201 -0
  170. data/docs/api/Brut/FrontEnd/Forms/Input/TimeOfDay.html +535 -0
  171. data/docs/api/Brut/FrontEnd/Forms/Input.html +983 -35
  172. data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +1 -1
  173. data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +29 -19
  174. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +141 -20
  175. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +1 -1
  176. data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +141 -20
  177. data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +1 -1
  178. data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +1 -1
  179. data/docs/api/Brut/FrontEnd/Forms.html +1 -1
  180. data/docs/api/Brut/FrontEnd/GenericResponse.html +1 -1
  181. data/docs/api/Brut/FrontEnd/Handler.html +1 -1
  182. data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +1 -1
  183. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +1 -1
  184. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +1 -1
  185. data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +1 -1
  186. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +1 -1
  187. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +1 -1
  188. data/docs/api/Brut/FrontEnd/Handlers.html +1 -1
  189. data/docs/api/Brut/FrontEnd/HandlingResults.html +1 -1
  190. data/docs/api/Brut/FrontEnd/HttpMethod.html +1 -1
  191. data/docs/api/Brut/FrontEnd/HttpStatus.html +1 -1
  192. data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +1 -1
  193. data/docs/api/Brut/FrontEnd/Layout.html +1 -1
  194. data/docs/api/Brut/FrontEnd/Middleware.html +1 -1
  195. data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +1 -1
  196. data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +1 -1
  197. data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +1 -1
  198. data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +1 -1
  199. data/docs/api/Brut/FrontEnd/Middlewares.html +1 -1
  200. data/docs/api/Brut/FrontEnd/Page.html +1 -1
  201. data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +1 -1
  202. data/docs/api/Brut/FrontEnd/Pages.html +1 -1
  203. data/docs/api/Brut/FrontEnd/RequestContext.html +1 -1
  204. data/docs/api/Brut/FrontEnd/RouteHook.html +1 -1
  205. data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +1 -1
  206. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +1 -1
  207. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +1 -1
  208. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +1 -1
  209. data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +1 -1
  210. data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +1 -1
  211. data/docs/api/Brut/FrontEnd/RouteHooks.html +1 -1
  212. data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +1 -1
  213. data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +1 -1
  214. data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +1 -1
  215. data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +1 -1
  216. data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +1 -1
  217. data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +1 -1
  218. data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +1 -1
  219. data/docs/api/Brut/FrontEnd/Routing/Route.html +1 -1
  220. data/docs/api/Brut/FrontEnd/Routing.html +1 -1
  221. data/docs/api/Brut/FrontEnd/Session.html +1 -1
  222. data/docs/api/Brut/FrontEnd.html +1 -1
  223. data/docs/api/Brut/I18n/BaseMethods.html +1 -1
  224. data/docs/api/Brut/I18n/ForBackEnd.html +1 -1
  225. data/docs/api/Brut/I18n/ForCLI.html +1 -1
  226. data/docs/api/Brut/I18n/ForHTML.html +1 -1
  227. data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +1 -1
  228. data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +1 -1
  229. data/docs/api/Brut/I18n.html +1 -1
  230. data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +1 -1
  231. data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +1 -1
  232. data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +1 -1
  233. data/docs/api/Brut/Instrumentation/OpenTelemetry.html +1 -1
  234. data/docs/api/Brut/Instrumentation.html +1 -1
  235. data/docs/api/Brut/SinatraHelpers/ClassMethods.html +1 -1
  236. data/docs/api/Brut/SinatraHelpers.html +1 -1
  237. data/docs/api/Brut/SpecSupport/ClockSupport.html +1 -1
  238. data/docs/api/Brut/SpecSupport/ComponentSupport.html +1 -1
  239. data/docs/api/Brut/SpecSupport/E2ETestServer.html +1 -1
  240. data/docs/api/Brut/SpecSupport/E2eSupport.html +1 -1
  241. data/docs/api/Brut/SpecSupport/EnhancedNode.html +1 -1
  242. data/docs/api/Brut/SpecSupport/FlashSupport.html +1 -1
  243. data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +1 -1
  244. data/docs/api/Brut/SpecSupport/GeneralSupport.html +1 -1
  245. data/docs/api/Brut/SpecSupport/HandlerSupport.html +1 -1
  246. data/docs/api/Brut/SpecSupport/Matchers/BeABug.html +1 -1
  247. data/docs/api/Brut/SpecSupport/Matchers/BePageFor.html +1 -1
  248. data/docs/api/Brut/SpecSupport/Matchers/BeRoutingFor.html +1 -1
  249. data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +1 -1
  250. data/docs/api/Brut/SpecSupport/Matchers/HaveGenerated.html +1 -1
  251. data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +1 -1
  252. data/docs/api/Brut/SpecSupport/Matchers/HaveI18nString.html +1 -1
  253. data/docs/api/Brut/SpecSupport/Matchers/HaveLinkTo.html +1 -1
  254. data/docs/api/Brut/SpecSupport/Matchers/HaveRedirectedTo.html +1 -1
  255. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedHttpStatus.html +1 -1
  256. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedRackResponse.html +1 -1
  257. data/docs/api/Brut/SpecSupport/Matchers.html +1 -1
  258. data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +1 -1
  259. data/docs/api/Brut/SpecSupport/RSpecSetup.html +1 -1
  260. data/docs/api/Brut/SpecSupport/SessionSupport.html +1 -1
  261. data/docs/api/Brut/SpecSupport.html +1 -1
  262. data/docs/api/Brut.html +1 -1
  263. data/docs/api/Clock.html +1 -1
  264. data/docs/api/RichString.html +1 -1
  265. data/docs/api/SemanticLogger/Appender/Async.html +1 -1
  266. data/docs/api/Sequel/Extensions/BrutInstrumentation.html +1 -1
  267. data/docs/api/Sequel/Extensions/BrutMigrations.html +1 -1
  268. data/docs/api/Sequel/Extensions.html +1 -1
  269. data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +1 -1
  270. data/docs/api/Sequel/Plugins/CreatedAt.html +1 -1
  271. data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +1 -1
  272. data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +1 -1
  273. data/docs/api/Sequel/Plugins/ExternalId.html +1 -1
  274. data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +1 -1
  275. data/docs/api/Sequel/Plugins/FindBang.html +1 -1
  276. data/docs/api/Sequel/Plugins.html +1 -1
  277. data/docs/api/Sequel.html +1 -1
  278. data/docs/api/_index.html +15 -1
  279. data/docs/api/class_list.html +1 -1
  280. data/docs/api/css/full_list.css +2 -1
  281. data/docs/api/css/style.css +14 -13
  282. data/docs/api/file.README.html +22 -3
  283. data/docs/api/index.html +22 -3
  284. data/docs/api/method_list.html +435 -275
  285. data/docs/api/top-level-namespace.html +1 -1
  286. data/docs/assets/LogoStop.Gb3tDhL1.png +0 -0
  287. data/docs/assets/OverviewMetro.DUS-5fUZ.png +0 -0
  288. data/docs/assets/{ai.md._6HCDL6d.js → ai.md.Cy9GWnER.js} +1 -1
  289. data/docs/assets/ai.md.Cy9GWnER.lean.js +1 -0
  290. data/docs/assets/{app.BX81XO4N.js → app.ClaS47Ru.js} +1 -1
  291. data/docs/assets/{assets.md.D3wunzLx.js → assets.md.7C3HWkga.js} +3 -3
  292. data/docs/assets/{assets.md.D3wunzLx.lean.js → assets.md.7C3HWkga.lean.js} +1 -1
  293. data/docs/assets/{brut-js.md.o2DAO2s2.js → brut-js.md.B4GYxQVw.js} +1 -1
  294. data/docs/assets/{brut-js.md.o2DAO2s2.lean.js → brut-js.md.B4GYxQVw.lean.js} +1 -1
  295. data/docs/assets/chunks/@localSearchIndexroot.Biqy1A4t.js +1 -0
  296. data/docs/assets/chunks/{VPLocalSearchBox.gABXcTWp.js → VPLocalSearchBox.DtgDfde2.js} +1 -1
  297. data/docs/assets/chunks/{theme.DwUXXAL3.js → theme.B45bvibT.js} +2 -2
  298. data/docs/assets/{cli.md.RmeA2b0i.js → cli.md.CjsktgFz.js} +15 -20
  299. data/docs/assets/components.md.DatoNgFo.js +96 -0
  300. data/docs/assets/{components.md.CRUMdRoN.lean.js → components.md.DatoNgFo.lean.js} +1 -1
  301. data/docs/assets/{configuration.md.BGHl8oRC.js → configuration.md.DeyhpqEx.js} +3 -3
  302. data/docs/assets/{css.md.DJgj2clw.js → css.md.CltvJqAa.js} +3 -3
  303. data/docs/assets/{custom-element-tests.md.BrYJQEl3.js → custom-element-tests.md.B_rbta32.js} +3 -3
  304. data/docs/assets/{database-access.md.C7l-Vuvb.js → database-access.md.gnluu54N.js} +1 -1
  305. data/docs/assets/{database-schema.md.BUjR0VS1.js → database-schema.md.CSYk6E6v.js} +6 -6
  306. data/docs/assets/{database-schema.md.BUjR0VS1.lean.js → database-schema.md.CSYk6E6v.lean.js} +1 -1
  307. data/docs/assets/deployment.md.BLseERGV.js +48 -0
  308. data/docs/assets/deployment.md.BLseERGV.lean.js +1 -0
  309. data/docs/assets/dev-environment.md.BroAOLhF.js +11 -0
  310. data/docs/assets/dir-structure.md.CWir1pic.js +46 -0
  311. data/docs/assets/dir-structure.md.CWir1pic.lean.js +1 -0
  312. data/docs/assets/doc-conventions.md.BzmSrTEW.js +1 -0
  313. data/docs/assets/doc-conventions.md.BzmSrTEW.lean.js +1 -0
  314. data/docs/assets/{end-to-end-tests.md.yfQHC0b5.js → end-to-end-tests.md.DzqRpZ43.js} +5 -3
  315. data/docs/assets/end-to-end-tests.md.DzqRpZ43.lean.js +1 -0
  316. data/docs/assets/features.md.DPFXsy0z.js +154 -0
  317. data/docs/assets/features.md.DPFXsy0z.lean.js +1 -0
  318. data/docs/assets/flash-and-session.md.nPvUpnUx.js +79 -0
  319. data/docs/assets/{flash-and-session.md.BXY8RvT0.lean.js → flash-and-session.md.nPvUpnUx.lean.js} +1 -1
  320. data/docs/assets/form-constraints.md.x5tNpTTI.js +90 -0
  321. data/docs/assets/form-constraints.md.x5tNpTTI.lean.js +1 -0
  322. data/docs/assets/forms.md.C2Dizvzq.js +64 -0
  323. data/docs/assets/forms.md.C2Dizvzq.lean.js +1 -0
  324. data/docs/assets/{getting-started.md.Ciz82L0m.js → getting-started.md.C93e0odB.js} +5 -5
  325. data/docs/assets/{getting-started.md.Ciz82L0m.lean.js → getting-started.md.C93e0odB.lean.js} +1 -1
  326. data/docs/assets/handlers.md.Chyri6KA.js +54 -0
  327. data/docs/assets/handlers.md.Chyri6KA.lean.js +1 -0
  328. data/docs/assets/{hooks.md.C4-moMny.js → hooks.md.Jmb5VOLA.js} +4 -4
  329. data/docs/assets/{hooks.md.C4-moMny.lean.js → hooks.md.Jmb5VOLA.lean.js} +1 -1
  330. data/docs/assets/{i18n.md.Do9i1qWl.js → i18n.md.xQhiGo1G.js} +2 -2
  331. data/docs/assets/{i18n.md.Do9i1qWl.lean.js → i18n.md.xQhiGo1G.lean.js} +1 -1
  332. data/docs/assets/index.md.CAMqGBJE.js +1 -0
  333. data/docs/assets/index.md.CAMqGBJE.lean.js +1 -0
  334. data/docs/assets/{instrumentation.md.a9Pjps4P.js → instrumentation.md.BgcaGVYH.js} +2 -2
  335. data/docs/assets/{instrumentation.md.a9Pjps4P.lean.js → instrumentation.md.BgcaGVYH.lean.js} +1 -1
  336. data/docs/assets/{javascript.md.GWbhRS51.js → javascript.md.DzrMxUmI.js} +7 -7
  337. data/docs/assets/{javascript.md.GWbhRS51.lean.js → javascript.md.DzrMxUmI.lean.js} +1 -1
  338. data/docs/assets/keyword-injection.md.95Zgh2eN.js +21 -0
  339. data/docs/assets/{keyword-injection.md.Dt2tKREs.lean.js → keyword-injection.md.95Zgh2eN.lean.js} +1 -1
  340. data/docs/assets/{layouts.md.cPnh3NId.js → layouts.md.CJGDFY-m.js} +2 -15
  341. data/docs/assets/layouts.md.CJGDFY-m.lean.js +1 -0
  342. data/docs/assets/{lsp.md.Bsu-f6VU.js → lsp.md.Dn1rIiW0.js} +1 -1
  343. data/docs/assets/{lsp.md.Bsu-f6VU.lean.js → lsp.md.Dn1rIiW0.lean.js} +1 -1
  344. data/docs/assets/overview.md.Bdq4qt3L.js +1 -0
  345. data/docs/assets/overview.md.Bdq4qt3L.lean.js +1 -0
  346. data/docs/assets/pages.md.B7Hc-i6H.js +45 -0
  347. data/docs/assets/pages.md.B7Hc-i6H.lean.js +1 -0
  348. data/docs/assets/recipes_alternate-layouts.md.BwEytl59.js +22 -0
  349. data/docs/assets/recipes_alternate-layouts.md.BwEytl59.lean.js +1 -0
  350. data/docs/assets/recipes_authentication.md.Dzvi_g69.js +156 -0
  351. data/docs/assets/recipes_authentication.md.Dzvi_g69.lean.js +1 -0
  352. data/docs/assets/recipes_blank-layouts.md.fyAUJyJR.js +15 -0
  353. data/docs/assets/recipes_blank-layouts.md.fyAUJyJR.lean.js +1 -0
  354. data/docs/assets/recipes_custom-flash.md.CrQbI5eH.js +26 -0
  355. data/docs/assets/recipes_custom-flash.md.CrQbI5eH.lean.js +1 -0
  356. data/docs/assets/recipes_indexed-forms.md.CstYyOSo.js +74 -0
  357. data/docs/assets/recipes_indexed-forms.md.CstYyOSo.lean.js +1 -0
  358. data/docs/assets/recipes_text-field-component.md.H4wLAK0Z.js +101 -0
  359. data/docs/assets/recipes_text-field-component.md.H4wLAK0Z.lean.js +1 -0
  360. data/docs/assets/routes.md.B8kfUPHU.js +21 -0
  361. data/docs/assets/{routes.md.BMM7peut.lean.js → routes.md.B8kfUPHU.lean.js} +1 -1
  362. data/docs/assets/{security.md.C668yXCi.js → security.md.C0G_AZR-.js} +1 -1
  363. data/docs/assets/{security.md.C668yXCi.lean.js → security.md.C0G_AZR-.lean.js} +1 -1
  364. data/docs/assets/space-time-continuum.md.xl44xDos.js +1 -0
  365. data/docs/assets/{space-time-continuum.md.KPUIKysQ.lean.js → space-time-continuum.md.xl44xDos.lean.js} +1 -1
  366. data/docs/assets/{style.D73IYGCX.css → style.prAgp4yQ.css} +1 -1
  367. data/docs/assets/tutorial.md.a4a0eVOy.js +1 -0
  368. data/docs/assets/tutorial.md.a4a0eVOy.lean.js +1 -0
  369. data/docs/assets/why.md.C-hk5xgJ.js +1 -0
  370. data/docs/assets/why.md.C-hk5xgJ.lean.js +1 -0
  371. data/docs/assets.html +12 -7
  372. data/docs/brut-js/api/AjaxSubmit.html +1 -1
  373. data/docs/brut-js/api/AjaxSubmit.js.html +1 -1
  374. data/docs/brut-js/api/Autosubmit.html +1 -1
  375. data/docs/brut-js/api/Autosubmit.js.html +1 -1
  376. data/docs/brut-js/api/BaseCustomElement.html +1 -1
  377. data/docs/brut-js/api/BaseCustomElement.js.html +1 -1
  378. data/docs/brut-js/api/BrutCustomElements.html +1 -1
  379. data/docs/brut-js/api/BufferedLogger.html +1 -1
  380. data/docs/brut-js/api/ConfirmSubmit.html +1 -1
  381. data/docs/brut-js/api/ConfirmSubmit.js.html +1 -1
  382. data/docs/brut-js/api/ConfirmationDialog.html +1 -1
  383. data/docs/brut-js/api/ConfirmationDialog.js.html +1 -1
  384. data/docs/brut-js/api/ConstraintViolationMessage.html +1 -1
  385. data/docs/brut-js/api/ConstraintViolationMessage.js.html +1 -1
  386. data/docs/brut-js/api/ConstraintViolationMessages.html +1 -1
  387. data/docs/brut-js/api/ConstraintViolationMessages.js.html +1 -1
  388. data/docs/brut-js/api/CopyToClipboard.html +1 -1
  389. data/docs/brut-js/api/CopyToClipboard.js.html +1 -1
  390. data/docs/brut-js/api/Form.html +1 -1
  391. data/docs/brut-js/api/Form.js.html +1 -1
  392. data/docs/brut-js/api/I18nTranslation.html +1 -1
  393. data/docs/brut-js/api/I18nTranslation.js.html +1 -1
  394. data/docs/brut-js/api/LocaleDetection.html +1 -1
  395. data/docs/brut-js/api/LocaleDetection.js.html +1 -1
  396. data/docs/brut-js/api/Logger.html +1 -1
  397. data/docs/brut-js/api/Logger.js.html +1 -1
  398. data/docs/brut-js/api/Message.html +1 -1
  399. data/docs/brut-js/api/Message.js.html +1 -1
  400. data/docs/brut-js/api/PrefixedLogger.html +1 -1
  401. data/docs/brut-js/api/RichString.html +1 -1
  402. data/docs/brut-js/api/RichString.js.html +1 -1
  403. data/docs/brut-js/api/Tabs.html +1 -1
  404. data/docs/brut-js/api/Tabs.js.html +1 -1
  405. data/docs/brut-js/api/Tracing.html +1 -1
  406. data/docs/brut-js/api/Tracing.js.html +1 -1
  407. data/docs/brut-js/api/external-CustomElementRegistry.html +1 -1
  408. data/docs/brut-js/api/external-Performance.html +1 -1
  409. data/docs/brut-js/api/external-Promise.html +1 -1
  410. data/docs/brut-js/api/external-ValidityState.html +1 -1
  411. data/docs/brut-js/api/external-Window.html +1 -1
  412. data/docs/brut-js/api/external-fetch.html +1 -1
  413. data/docs/brut-js/api/global.html +1 -1
  414. data/docs/brut-js/api/index.html +1 -1
  415. data/docs/brut-js/api/index.js.html +1 -1
  416. data/docs/brut-js/api/module-testing.html +1 -1
  417. data/docs/brut-js/api/testing.AssetMetadata.html +1 -1
  418. data/docs/brut-js/api/testing.AssetMetadataLoader.html +1 -1
  419. data/docs/brut-js/api/testing.CustomElementTest.html +1 -1
  420. data/docs/brut-js/api/testing.DOMCreator.html +1 -1
  421. data/docs/brut-js/api/testing_AssetMetadata.js.html +1 -1
  422. data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +1 -1
  423. data/docs/brut-js/api/testing_CustomElementTest.js.html +1 -1
  424. data/docs/brut-js/api/testing_DOMCreator.js.html +1 -1
  425. data/docs/brut-js/api/testing_index.js.html +1 -1
  426. data/docs/brut-js.html +12 -7
  427. data/docs/business-logic.html +10 -5
  428. data/docs/cli.html +26 -26
  429. data/docs/components.html +61 -64
  430. data/docs/configuration.html +13 -8
  431. data/docs/css.html +14 -9
  432. data/docs/custom-element-tests.html +14 -9
  433. data/docs/database-access.html +12 -7
  434. data/docs/database-schema.html +15 -10
  435. data/docs/deployment.html +58 -6
  436. data/docs/dev-environment.html +12 -7
  437. data/docs/dir-structure.html +74 -0
  438. data/docs/doc-conventions.html +11 -6
  439. data/docs/end-to-end-tests.html +15 -8
  440. data/docs/favicon.ico +0 -0
  441. data/docs/features.html +182 -0
  442. data/docs/flash-and-session.html +73 -82
  443. data/docs/form-constraints.html +118 -0
  444. data/docs/forms.html +57 -367
  445. data/docs/getting-started.html +15 -10
  446. data/docs/handlers.html +51 -61
  447. data/docs/hashmap.json +1 -1
  448. data/docs/hooks.html +14 -9
  449. data/docs/i18n.html +12 -7
  450. data/docs/index.html +11 -6
  451. data/docs/instrumentation.html +12 -7
  452. data/docs/javascript.html +17 -12
  453. data/docs/jobs.html +10 -5
  454. data/docs/keyword-injection.html +22 -21
  455. data/docs/layouts.html +12 -20
  456. data/docs/lsp.html +11 -6
  457. data/docs/markdown-examples.html +10 -5
  458. data/docs/middleware.html +10 -5
  459. data/docs/not-released.html +10 -5
  460. data/docs/overview.html +11 -138
  461. data/docs/pages.html +49 -121
  462. data/docs/recipes/alternate-layouts.html +50 -0
  463. data/docs/recipes/authentication.html +166 -6
  464. data/docs/recipes/blank-layouts.html +43 -0
  465. data/docs/recipes/custom-flash.html +54 -0
  466. data/docs/recipes/indexed-forms.html +102 -0
  467. data/docs/recipes/text-field-component.html +129 -0
  468. data/docs/routes.html +16 -19
  469. data/docs/security.html +11 -6
  470. data/docs/seed-data.html +10 -5
  471. data/docs/space-time-continuum.html +11 -6
  472. data/docs/tutorial.html +11 -6
  473. data/docs/unit-tests.html +10 -5
  474. data/docs/why.html +29 -0
  475. data/dx/bash_customizations +7 -0
  476. data/dx/build +13 -2
  477. data/dx/docker-compose.env +1 -1
  478. data/dx/exec +25 -8
  479. data/lib/brut/front_end/form.rb +8 -8
  480. data/lib/brut/front_end/forms/input.rb +253 -20
  481. data/lib/brut/front_end/forms/input_definition.rb +15 -12
  482. data/lib/brut/front_end/forms/radio_button_group_input.rb +8 -1
  483. data/lib/brut/front_end/forms/select_input.rb +8 -1
  484. data/lib/brut/front_end.rb +1 -0
  485. data/lib/brut/version.rb +1 -1
  486. data/specs/brut/front_end/forms/input.spec.rb +978 -0
  487. data/specs/brut/front_end/forms/radio_button_group_input.spec.rb +54 -0
  488. data/specs/brut/front_end/forms/select_input.spec.rb +54 -0
  489. data/specs/spec_helper.rb +27 -0
  490. data/specs/support/matchers/have_constraint_violation.rb +23 -0
  491. data/specs/support/matchers.rb +5 -0
  492. data/specs/support.rb +3 -0
  493. metadata +141 -77
  494. data/brutrb.com/public/images/logo-300.png +0 -0
  495. data/brutrb.com/public/images/logo.png +0 -0
  496. data/docs/assets/ai.md._6HCDL6d.lean.js +0 -1
  497. data/docs/assets/chunks/@localSearchIndexroot.CoYzciVi.js +0 -1
  498. data/docs/assets/components.md.CRUMdRoN.js +0 -104
  499. data/docs/assets/deployment.md.Dbka4OTr.js +0 -1
  500. data/docs/assets/deployment.md.Dbka4OTr.lean.js +0 -1
  501. data/docs/assets/dev-environment.md.GZv6xvi9.js +0 -11
  502. data/docs/assets/doc-conventions.md.-kN3Xo5C.js +0 -1
  503. data/docs/assets/doc-conventions.md.-kN3Xo5C.lean.js +0 -1
  504. data/docs/assets/end-to-end-tests.md.yfQHC0b5.lean.js +0 -1
  505. data/docs/assets/flash-and-session.md.BXY8RvT0.js +0 -93
  506. data/docs/assets/forms.md.B-koVgyw.js +0 -379
  507. data/docs/assets/forms.md.B-koVgyw.lean.js +0 -1
  508. data/docs/assets/handlers.md.089DVD3v.js +0 -69
  509. data/docs/assets/handlers.md.089DVD3v.lean.js +0 -1
  510. data/docs/assets/index.md.B28EwVpq.js +0 -1
  511. data/docs/assets/index.md.B28EwVpq.lean.js +0 -1
  512. data/docs/assets/keyword-injection.md.Dt2tKREs.js +0 -25
  513. data/docs/assets/layouts.md.cPnh3NId.lean.js +0 -1
  514. data/docs/assets/overview.Da81cB9R.png +0 -0
  515. data/docs/assets/overview.md.C5wlBcR5.js +0 -133
  516. data/docs/assets/overview.md.C5wlBcR5.lean.js +0 -1
  517. data/docs/assets/pages.md.BE3kfOc5.js +0 -122
  518. data/docs/assets/pages.md.BE3kfOc5.lean.js +0 -1
  519. data/docs/assets/recipes_authentication.md.CAsXf7hk.js +0 -1
  520. data/docs/assets/recipes_authentication.md.CAsXf7hk.lean.js +0 -1
  521. data/docs/assets/routes.md.BMM7peut.js +0 -29
  522. data/docs/assets/space-time-continuum.md.KPUIKysQ.js +0 -1
  523. data/docs/assets/tutorial.md.BnoGjrdK.js +0 -1
  524. data/docs/assets/tutorial.md.BnoGjrdK.lean.js +0 -1
  525. data/docs/images/logo-300.png +0 -0
  526. data/docs/images/logo.png +0 -0
  527. /data/docs/assets/{cli.md.RmeA2b0i.lean.js → cli.md.CjsktgFz.lean.js} +0 -0
  528. /data/docs/assets/{configuration.md.BGHl8oRC.lean.js → configuration.md.DeyhpqEx.lean.js} +0 -0
  529. /data/docs/assets/{css.md.DJgj2clw.lean.js → css.md.CltvJqAa.lean.js} +0 -0
  530. /data/docs/assets/{custom-element-tests.md.BrYJQEl3.lean.js → custom-element-tests.md.B_rbta32.lean.js} +0 -0
  531. /data/docs/assets/{database-access.md.C7l-Vuvb.lean.js → database-access.md.gnluu54N.lean.js} +0 -0
  532. /data/docs/assets/{dev-environment.md.GZv6xvi9.lean.js → dev-environment.md.BroAOLhF.lean.js} +0 -0
@@ -12,136 +12,159 @@ Components in Brut are Phlex Components: a class that can hold data and use that
12
12
 
13
13
  ### Simple Component
14
14
 
15
- For example, suppose you want a re-usable button component whose HTML would look like so:
16
15
 
17
- ```html
18
- <button class="button button__small button__red">
19
- Delete Files
20
- </button>
21
- ```
16
+ For example, suppose you want a re-usable button that can be gray, green, or red, and have an optional `formaction`.
22
17
 
23
- Your button can be large (the default) or small, and could be gray (default), green, or red. Your button could also have an optional `formaction` attribute. Let's also suppose the label could be a string or embedded HTML.
24
18
 
25
- Your constructor would need to accept the size, color, label, and formaction.
26
-
27
- You can create a scaffold via `bin/scaffold`:
19
+ You can create a component with `bin/scaffold component`:
28
20
 
29
21
  ```
30
- > bin/scaffold component Button
22
+ bin/scaffold component button
23
+ # => app/src/front_end/components/button_component.rb
24
+ # => specs/front_end/components/button_component.spec.rb
31
25
  ```
32
26
 
33
- You'll first implement the initializer, like so:
27
+ Component inititalizers are called by you when you use them, so you can define it
28
+ how you like. Brut uses keyword arguments by convention.
34
29
 
35
30
  ```ruby
36
- # app/src/front_end/components/button.rb
37
- class Button < AppComponent
38
- def initialize(
39
- size: :large,
40
- color: :gray,
41
- formaction: nil,
42
- label: :use_block
43
- )
44
- @size = size
31
+ # app/src/front_end/components/button_component.rb
32
+ class ButtonComponent < AppComponent
33
+ def initialize(color: :gray,
34
+ formaction: nil)
45
35
  @color = color
46
36
  @formaction = formaction
47
- @label = label
48
37
  end
49
38
  end
50
39
  ```
51
40
 
52
- This initializer is rather simplistic. You may want to validate the values here to prevent the construction of an invalid component.
53
-
54
- Now, implement `view_template`. This method will receive a block if one is given when the component is used. We'll see an example in a minute.
41
+ Since it's a Phlex component, implement `view_template` to generate the HTML you
42
+ like. Our `view_template` will `yield` so the button's contents can be controlled
43
+ by the caller. Note that the CSS here is [BrutCSS](/brut-css/index.html), but it
44
+ can be anything you are using in your oapp.
55
45
 
56
46
  ```ruby
57
- # app/src/front_end/components/button.rb
58
- class Button < AppComponent
47
+ # app/src/front_end/components/button_component.rb
48
+ class ButtonComponent < AppComponent
59
49
 
60
50
  # ...
61
51
 
62
52
  def view_template
63
53
  attributes = {
64
54
  class: [
65
- "button",
66
- "button__#{@size}",
67
- "button__#@{color}",
55
+ "tc", # centered text
56
+ "br-3", # border radius @ 3rd step of scale
57
+ "bn", # no border
58
+ "f-3", # font size @ 3rd step of scale
59
+ "ph-4", # horizontal padding @ 4th step of scale
60
+ "pv-2", # vertical padding @ 2nd step of scale
61
+ "bg-#{@color}-800", # background is second lighest of scale
62
+ "#{@color}-300", # text is third darkest of scale
68
63
  ],
69
- formaction: @formaction,
64
+ formaction: @formaction
70
65
  }
71
66
 
72
67
  button(**attributes) do
73
- if @label == :use_block
74
- yield
75
- else
76
- @label
77
- end
68
+ yield
78
69
  end
79
70
  end
80
71
  end
81
72
  ```
82
73
 
83
- If you've never used Phlex before, it's refreshingly straightforward:
74
+ Here are two examples of how you'd use this component and the HTML that would be
75
+ generated:
76
+
77
+ ::: code-group
78
+
79
+ ```ruby
80
+ render ButtonComponent(color: :green) do
81
+ "Click Here"
82
+ end
83
+ ```
84
+
85
+ ```html
86
+ <button class="tc br-3 bn f-3 ph-4 pv-2 bg-green-800 green-300">
87
+ Click Here
88
+ <button>
89
+ ```
84
90
 
85
- * There's a method for each HTML element.
86
- * The method's parameters produce attributes in the HTML that is generated.
87
- * If a parameter's value is an array (like `class:`), the values are joined with strings to form the atttribute's value in HTML.
88
- * If the element can have inner content, whatever happens inside a yielded block becomes that inner content.
91
+ :::
89
92
 
90
- To use this component, we can call `render` in either the `view_template` of another component or the `page_template` of a [page](/pages):
93
+ ::: code-group
91
94
 
92
95
  ```ruby
93
- def view_template
94
- form do
95
- render Button.new(label: "Submit")
96
- render Button.new(label: "Nevermind", size: :small, color: :red)
97
- render Button.new(color: :green) do
98
- img(src: "/images/ok.png", alt: "OK icon")
99
- end
100
- end
96
+ render ButtonComponent(color: :red, formaction: DeleteWidget.routing) do
97
+ "Delete Widget"
101
98
  end
102
99
  ```
103
100
 
104
- Note that the block passed to `render` is the block available when `yield` is called inside `view_template`.
101
+ ```html
102
+ <button class="tc br-3 bn f-3 ph-4 pv-2 bg-red-800 green-300"
103
+ formaction="/delete_widget">
104
+ Delete Widget
105
+ <button>
106
+ ```
107
+ :::
105
108
 
106
- There are two special types of components beyond what we have just seen. *Global* Components and *Page private* components.
109
+ One issue with components is that you must pass them all their initializer arguments
110
+ to use them. This means that if your component needs access to, say, the session,
111
+ any page or component that uses your component must also require the session to
112
+ be passed in.
113
+
114
+ Brut provides a partial solution to this called *global components*.
107
115
 
108
116
  ### Global Components
109
117
 
110
- As we saw above, creating a component is just like creating any Ruby class: you call `.new` on it. If you create a component that uses request-level data, such as the flash or session, it would mean that any page or component that used *that* component would need to accept the flash or session as a parameter to its initializer, even if it it was otherwise not needed.
118
+ A global component can be created by Brut using [keyword
119
+ injection](/keyword-injection). This means that, in our example above, a page that
120
+ uses your component does not need to be given the session. It can have Brut inject
121
+ it.
111
122
 
112
- In those cases, `global_component` can be used to leverage [keyword injection](/keyword-injection) to have Brut create the component. That way, its initializer's parameters don't need to be passed into the page or component using the global component.
123
+ This provides a partial solution to so-called "prop drilling".
113
124
 
114
- Suppose we had a component to display the flash:
125
+ In [the features overview](/features), we saw a basic component for rendering a
126
+ flash:
115
127
 
116
128
  ```ruby
117
- class FlashMessage < AppComponent
118
-
129
+ # components/flash_component.rb
130
+ class FlashComponent < AppComponent
119
131
  def initialize(flash:)
120
- @flash = flash
132
+ if flash.notice?
133
+ @message_key = flash.notice
134
+ @role = :info
135
+ elsif flash.alert?
136
+ @message_key = flash.alert
137
+ @role = :alert
138
+ end
121
139
  end
122
140
 
141
+ def any_message? = !@message_key.nil?
142
+
123
143
  def view_template
124
- if @flash.notice?
125
- div(role: "status") { @flash.notice }
126
- elsif @flash.alert?
127
- div(role: "alert") { @flash.alert }
144
+ if any_message?
145
+ div(role: @role) do
146
+ t([ :flash, @message_key ])
147
+ end
128
148
  end
129
149
  end
130
150
  end
131
151
  ```
132
152
 
133
- To use it without having to instantiate it, call `global_component` with the component's class:
153
+ Instead of requiring each user of this component to manually inject the flash, we
154
+ can call `global_component`, provided by `Brut::FrontEnd::Component::Helpers`, which
155
+ is included in all pages and components.
134
156
 
135
157
  ```ruby
136
- class HomePage < AppPage
137
- def page_template
138
- header do
139
- global_component(FlashMessage) # note: render not required
140
- end
158
+ def view_template
159
+ header do
160
+ global_component(FlashComponent)
141
161
  end
142
162
  end
143
163
  ```
144
164
 
165
+ Components used in layouts will tend to be global components, to avoid creating odd
166
+ dependencies between pages.
167
+
145
168
  > [!IMPORTANT]
146
169
  > Brut currently requires an all-or-nothing approach to global components. Either
147
170
  > the component can be injected with all its initializer parameters or it must
@@ -157,9 +180,19 @@ Often, components are helpful to simplifying a page's template or managing re-us
157
180
 
158
181
  Brut provides a way to create a *page private* component that exists as an inner class of a page. It's not truly private, since it's still a Ruby class anyone can use, but it's form and source location communicate intent.
159
182
 
160
- Suppose our `HomePage` has a list of widgets on it, but we want each widget's HTML managed by a separate component:
183
+ They can be created with `bin/scaffold`:
161
184
 
162
- ```ruby
185
+ ```
186
+ bin/scaffold component --page HomePage Widget
187
+ # => app/src/front_end/page/home_page/widget_component.rb
188
+ # => specs/front_end/page/home_page/widget_component.spec.rb
189
+ ```
190
+
191
+ The class will be an inner class of `HomePage` in this example, `HomePage::WidgetComponent`. You build them and use them like normal:
192
+
193
+ ::: code-group
194
+
195
+ ```ruby [Page]
163
196
  class HomePage < AppPage
164
197
  def page_template
165
198
  header do
@@ -176,16 +209,7 @@ class HomePage < AppPage
176
209
  end
177
210
  ```
178
211
 
179
- `HomePage::WidgetListItem` can be created like so:
180
-
181
- ```
182
- bin/scaffold component --page=HomePage WidgetListItem
183
- ```
184
-
185
- This will create `app/src/front_end/pages/home_page/widget_list_item.rb`, which you can then implement like a normal component:
186
-
187
- ```ruby
188
- # app/src/front_end/pages/home_page/widget_list_item.rb
212
+ ```ruby [Page Private Component]
189
213
  class HomePage::WidgetListItem < AppComponent
190
214
  def initialize(widget:)
191
215
  @widget = widget
@@ -200,27 +224,25 @@ class HomePage::WidgetListItem < AppComponent
200
224
  end
201
225
  ```
202
226
 
203
- The only special thing about a page private component is that it can access the page's I18n translations. As we'll discussion in [I18n](/i18n), The `t` method will try to locate translations based on the page on which `t` is called. A page private component will also trigger this behavior, but a normal component will not.
204
-
205
- For example, the following code will look for `pages.HomePage.status.«status»` when generated the `<h3>`:
227
+ :::
206
228
 
207
- ```ruby {10}
208
- # app/src/front_end/pages/home_page/widget_list_item.rb
209
- class HomePage::WidgetListItem < AppComponent
210
- def initialize(widget:)
211
- @widget = widget
212
- end
229
+ The main difference between a page-private component and a normal component's
230
+ behavior is how [I18n](/i18n) strings are resolved. In short, given this:
213
231
 
214
- def view_template
215
- li do
216
- h2 { @widget.name }
217
- h3 { t([ :status, @widget.status ]) }
218
- p { @widget.description }
219
- end
220
- end
232
+ ```ruby
233
+ p do
234
+ t(:hello)
221
235
  end
222
236
  ```
223
237
 
238
+ In a normal component named `WidgetComponent`, the keys searched for translations
239
+ would be `"components.WidgetComponent.hello"` and `"hello"` . For the page-private
240
+ component `HomePage::WidgetComponent`, the keys searched would be
241
+ `"pages.HomePage.hello"` and `"hello"`. This means that page private components can
242
+ access a page's translations.
243
+
244
+
245
+
224
246
  ## Testing
225
247
 
226
248
  Test widgets exactly as you would [pages](/pages#testing). The only difference is that components always render HTML and have no `before_generate` concept.
@@ -92,7 +92,7 @@ end
92
92
 
93
93
  ### Type and Name Enforcement
94
94
 
95
- You'll note that `store`, accepts a class parameter. This is mostly used for documentation, with two exceptions:
95
+ You'll note that `store` accepts a class parameter. This is mostly used for documentation, with two exceptions:
96
96
 
97
97
  * If the type is `Pathname` (which is what is used by `store_ensured_path` and `store_required_path`), the configuration parameter name *must* end in `_file` or `_dir`.
98
98
  * If the type is `"boolean"` or `:boolean`, the configuration parameter name *must* end in a question mark. In this case, the value itself is coerced into `true` or `false`.
@@ -103,8 +103,7 @@ Brut may add more constraints or conversions over time.
103
103
 
104
104
  By default, Brut configuration values cannot be overridden and they cannot be `nil`. When calling `store`, `allow_app_override: true` and `allow_nil: true`, can be passed to change this behavior.
105
105
 
106
- In [Flash and Session](/flash-and-session), we discussed setting your own custom class for managing the flash.
107
- This is possible due to how Brut defines the configuration parameter `flash_class`:
106
+ In [Flash and Session](/flash-and-session), we discussed that you can set your own class for the flash. This is possible due to how Brut defines the configuration parameter `flash_class`:
108
107
 
109
108
  ```ruby {6}
110
109
  Brut.container.store(
@@ -126,7 +125,7 @@ Calling `override` for parameters where `allow_app_override` is not true results
126
125
  `store` on a previously `store`-d parameter results in an error.
127
126
 
128
127
  The idea is to make it extremely clear what values are being set and overridden, and to avoid setting values that
129
- don't exist.
128
+ don't exist or aren't relevant.
130
129
 
131
130
  Some values can be `nil`. Generally, `nil` is a pain and will cause you great hardship. On occasion, it's
132
131
  needed. For example, [external IDs](/database-schema#external-ids) only work if the app provides an app-wide
data/brutrb.com/css.md CHANGED
@@ -42,7 +42,7 @@ First, `package.json` (in your app's root) would include `"foobar-css"`:
42
42
 
43
43
  Next, `app/src/front_end/css/index.css` would import both `"foobar-css"` and `"pages/HomePage.css"`.
44
44
 
45
- ```javascript
45
+ ```css
46
46
  @import "foobar-css/everything.css";
47
47
  @import "pages/HomePage.css";
48
48
  ```
@@ -73,7 +73,7 @@ To use `foobar-thin.css`, you'd write this `@import` directive:
73
73
 
74
74
  ## Using Brut-CSS
75
75
 
76
- By default, Brut includes a lightweight functional CSS library called "brut-css". It provides a basic design system and single-purpose classes to allow you to quickly prototype or build UIs. It is similar to TailwindCSS by far far smaller and simpler (and less powered).
76
+ By default, Brut includes a lightweight functional CSS library called [BrutCSS](/brut-css/index.html). It provides a basic design system and single-purpose classes to allow you to quickly prototype or build UIs. It is similar to TailwindCSS but far far smaller and simpler (and less powered).
77
77
 
78
78
  It is included so you have something to start with. You can use it by using its various classes like `bg-green-300` and `m-4`, or you can use its provided custom properties like `var(--green-300)` and `var(--sp-4)`.
79
79
 
@@ -37,8 +37,7 @@ as well, such as `assert`.
37
37
  The idea is that you use the browser APIs to examine the DOM and assert the behavior of the custom element
38
38
  (as opposed to interacting with the custom element's class).
39
39
 
40
- Suppose that `my-element` transform text inside it based on the `transform` attribute. By default, it's
41
- `lower`, but can be set to `upper` to lower case or upper case, respectively, the text inside.
40
+ Suppose that `my-element` transform text inside it based on the `transform` attribute. By default, it's `lower` (which will lower-case the text), but can be set to `upper` to upper case the text inside.
42
41
 
43
42
  This means you'll need three tests, each with a different DOM:
44
43
 
@@ -72,7 +71,7 @@ describe("<some-element>", () => {
72
71
  })
73
72
  ```
74
73
 
75
- when the function you give to `test` is executed, the DOM will have been setup, so you can rely on your
74
+ When the function you give to `test` is executed, the DOM will have been setup, so you can rely on your
76
75
  custom elements `connectedCallback` having been called. Assuming the text transformation for `my-element`
77
76
  occurs in `connectedCallback`, here is how you'd test all three cases:
78
77
 
@@ -109,7 +108,7 @@ describe("<some-element>", () => {
109
108
  })
110
109
  ```
111
110
 
112
- You'll notice almost all of this uses the browser APIs you (should :) know and (hopefully :) love.
111
+ You'll notice almost all of this uses the browser APIs you (should) know and (hopefully) love.
113
112
 
114
113
  You can manipulate the DOM inside a test as well, and it should behave as if you are doing it in a
115
114
  browser. Note that many browser APIs are synchronous, so you don't have to add `await` before every
@@ -156,7 +156,7 @@ end
156
156
  ### Do Not Put Business Logic On Your Database Models
157
157
 
158
158
  There's no reason to, or benefit to doing so. What you'll find is that any app of even moderate complexity will
159
- not have a strict mapping from page to business concept to database table. Rather these things will all differ
159
+ not have a strict mapping from page to business concept to database table. Rather, these things will all differ
160
160
  greatly, and each serves a different purpose.
161
161
 
162
162
  The job of your data models—and the tables they provide access to—is to store reliable and unambiguous data.
@@ -1,10 +1,6 @@
1
1
  # Database Schema / Migrations
2
2
 
3
- Brut provides access to the database via the [Sequel library](https://sequel.jeremyevans.net/). Sequel is fully featured and provides a lot of ways of interacting with and managing your database. Brut includes several plugins and extensions to provide opinionated default behavior or additional features.
4
-
5
- One thing to keep in mind is that Brut refers to your database layer as *database models* (notably not the un-qualified "models"). Brut treats this layer as a *model* of your database, not a model of your *domain* (though you are free to conflate the two).
6
-
7
- This section details how to manage your database schema.
3
+ Brut provides access to the database via the [Sequel library](https://sequel.jeremyevans.net/). To manage your database schema, Brut uses Sequel's facility for this, with some of its own enhancements.
8
4
 
9
5
  > [!NOTE]
10
6
  > Brut currently only supports Postgres. Sequel supports many database systems, however Brut's extensions are
@@ -12,22 +8,14 @@ This section details how to manage your database schema.
12
8
 
13
9
  ## Overview
14
10
 
15
- Brut uses *migrations* to control and manage the schema of your database. Migrations are changes to the
16
- schema that depend on the changes before them. In a running production database, you will not be able to
17
- create the database schema from scratch—you will have to modify the existing schema to produce the schema
18
- you want.
11
+ Your database schema is managed by a series of changes that build upon one another
12
+ called *migrations*.
19
13
 
20
- For example, if you have a table `widgets` that has a `name` and `description`, to add a `status` field,
21
- you cannot `drop table widgets` and then `create table widgets(...)` with the fields. You must instead
22
- `alter table widgets(...)` to add the new column.
14
+ For example, if you have a table `widgets` that has a `name` and `description`, to add a `status` field, you cannot `drop table widgets` and then `create table widgets(...)` with the fields. You must instead `alter table widgets(...)` to add the new column.
23
15
 
24
16
  Thus, each migration file is a change to the schema produced by all previous migration files.
25
17
 
26
- Brut's provides this via Sequels. See [both](https://sequel.jeremyevans.net/rdoc/files/doc/schema_modification_rdoc.html) [docs](https://sequel.jeremyevans.net/rdoc/files/doc/migration_rdoc.html) for details on the API. Any schema modification method Sequel documents is available, however some default behavior has changed.
27
-
28
- Schema files are located in `app/src/back_end/data_models/migrations` and are named using a timestamp-based
29
- scheme. This means that when you create a new migration, its name will be based on the time and date you
30
- created it, and any migrations that have not been applied will be applied in timestamp order.
18
+ Brut's provides this via Sequel. See [both](https://sequel.jeremyevans.net/rdoc/files/doc/schema_modification_rdoc.html) [docs](https://sequel.jeremyevans.net/rdoc/files/doc/migration_rdoc.html) for details on the API. Any schema modification method Sequel documents is available, however some default behavior has changed.
31
19
 
32
20
  ### Creating Migrations
33
21
 
@@ -40,6 +28,10 @@ together to form the filename:
40
28
  app/src/back_end/data_models/migrations/20250508132646_user-accounts.rb
41
29
  ```
42
30
 
31
+ Note that the files are located in `app/src/back_end/data_models/migrations` and
32
+ have a name prefixed with a timestamp. This timestamp determins an ordering of how
33
+ the files are applied to the database.
34
+
43
35
  The file is created mostly blank:
44
36
 
45
37
  ```ruby
@@ -49,19 +41,17 @@ Sequel.migration do
49
41
  end
50
42
  ```
51
43
 
52
- Sequels' migration system is similar to Active Record's/Rails' in design and spirit, but the API is different.
53
- Please consult the documentation and don't assume Active Record's DSL will work. It will not.
44
+ > [!NOTE]
45
+ > Sequels' migration API is similar in concept to Rails', but differs
46
+ > significantly in specifics. Please consult Sequel's documentation and
47
+ > don't assume Railsism will work the same way.
54
48
 
55
- One thing to note is that Brut encourages the creation of only "up" migrations. That is, migrations that change a
56
- database. "Down" migrations, which revert a change, are discouraged. See *Recommended Practices* for a detailed
57
- explanation.
49
+ Brut encourages only "up" migrations. Since Brut treats your development database
50
+ as ephemeral, there is little value to managing "down" migrations.
58
51
 
59
- This is also why Sequel's `change` method is not included in the scaffolded code. `change`, like Active Record's
60
- method of the same name, automagically creates both "up" and "down" migrations, but *only* if you use the DSL. If
61
- you use raw SQL, `change` doesn't work. But that doesn't matter for Brut (again, see *Recommended Practices*).
52
+ This is why Sequel's `change` method is not included in the scaffolded code. `change`, like Active Record's method of the same name, automagically creates both "up" and "down" migrations, but *only* if you use the DSL. If you use raw SQL, `change` doesn't work. By using only `up`, you won't have to worry about this.
62
53
 
63
- Let's create a user accounts table that has an email field, a `deactivated_at` timestamp, and a `created_at`
64
- timestamp:
54
+ Let's create an accounts table that has an email field, a `deactivated_at` timestamp, and a `created_at` timestamp:
65
55
 
66
56
  ```ruby
67
57
  Sequel.migration do
@@ -90,10 +80,6 @@ To apply this migration use `bin/db migrate`
90
80
  > bin/db migrate
91
81
  ```
92
82
 
93
- If you create a new migration, it will use a timestamp that is alphanumerically greater than the one we just made
94
- and thus that migration will be applied after this one. Thus, you can rely on previous migrations having been
95
- applied when authoring new ones.
96
-
97
83
  ### Managing Migrations
98
84
 
99
85
  Sequel uses a special database table to understand which migrations have been run. This table will exist in
@@ -126,16 +112,19 @@ provides a more helpful error message when no records are found
126
112
  #### External IDs
127
113
 
128
114
  It's often useful to provide a unique identifier for a record that is not the database primary key. There are
129
- many advantages to doing so, but the core value Brut has regarding this is that database primary and foreign keys
130
- are considered private and internal and for developer use only. It is trivial to produce externalizable keys, as
131
- you'll see, so there's no reason to expose primary keys.
115
+ many advantages to doing so, the main being that your primary and foreign keys are
116
+ considered private and for developer use only. Creating additional externalizable
117
+ unique keys is trivial, so Brut provides a way to do that.
132
118
 
133
119
  > [!NOTE]
134
- > Take care to differentiate the terms *primary key* and *key*. In relational database literature, and thus
135
- > in Brut, a *key* is any value that uniquely identifies a record. `email` in the `accounts` table above
136
- > is a key. The *primary key* is single source of truth for identifying records internally in the database.
137
- > Thus, it can be used as a *foreign key* to create relationships between tables. Brut further implements
138
- > this as a *surrogate* or *synthetic* key, which means the value itself has no business or domain meaning.
120
+ > **Primary keys** and **keys** are not the same thing. **Primary keys** are
121
+ > what is used to identify a record for the purposes of referential integrity.
122
+ > A **key** simply uniquely identifies a row or is a unique constraint on a table.
123
+ > Tables have only one primary key, but potentially many keys. Brut uses
124
+ > *synthetic* (sometimes called *surrogate*) keys as primary keys. This means
125
+ > they have no business meaning and can be safely used for foreighn keys
126
+ > and other cases without conflating them with domain concepts.
127
+
139
128
 
140
129
  In Brut, an external ID is automatically generated by the database when a record is created. By convention, it
141
130
  is prefixed with a short string representing your app and a short string representing the table, followed by a
@@ -190,8 +179,7 @@ This means that you can set values explicitly if you like, *and* you can change
190
179
 
191
180
  ### Brut Migration Changes and Enhancement
192
181
 
193
- Brut attempts to set default behavior for migrations to encourage a modicum of best practices. This lists out
194
- the changes and a brief explanation for the purpose of the change.
182
+ Brut attempts to set default behavior for migrations to encourage a modicum of best practices. These are:
195
183
 
196
184
  * **Automatic synthetic primary key named `id` of type `int`.** You almost always want this. You can change the
197
185
  primary key configuration per table if you like, but if you do nothing, you get a primary key that works for 99%