brut 0.0.29 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (491) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/README.md +23 -2
  4. data/assets/LogoStop.pxd +0 -0
  5. data/assets/MetroLogo.graffle +0 -0
  6. data/assets/SocialImage.png +0 -0
  7. data/assets/SocialImage.pxd +0 -0
  8. data/brutrb.com/.vitepress/config.mjs +45 -8
  9. data/brutrb.com/.vitepress/theme/style.css +6 -5
  10. data/brutrb.com/ai.md +10 -15
  11. data/brutrb.com/assets.md +2 -9
  12. data/brutrb.com/brut-js.md +12 -2
  13. data/brutrb.com/cli.md +9 -13
  14. data/brutrb.com/components.md +118 -96
  15. data/brutrb.com/configuration.md +3 -4
  16. data/brutrb.com/css.md +2 -2
  17. data/brutrb.com/custom-element-tests.md +3 -4
  18. data/brutrb.com/database-access.md +1 -1
  19. data/brutrb.com/database-schema.md +29 -41
  20. data/brutrb.com/dev-environment.md +7 -7
  21. data/brutrb.com/dir-structure.md +120 -0
  22. data/brutrb.com/doc-conventions.md +18 -15
  23. data/brutrb.com/dx +1 -0
  24. data/brutrb.com/end-to-end-tests.md +12 -10
  25. data/brutrb.com/features.md +373 -0
  26. data/brutrb.com/flash-and-session.md +115 -131
  27. data/brutrb.com/form-constraints.md +266 -0
  28. data/brutrb.com/forms.md +140 -765
  29. data/brutrb.com/getting-started.md +10 -11
  30. data/brutrb.com/handlers.md +119 -95
  31. data/brutrb.com/hooks.md +18 -20
  32. data/brutrb.com/i18n.md +6 -4
  33. data/brutrb.com/images/LogoStop.png +0 -0
  34. data/brutrb.com/instrumentation.md +7 -10
  35. data/brutrb.com/javascript.md +14 -14
  36. data/brutrb.com/keyword-injection.md +72 -114
  37. data/brutrb.com/layouts.md +20 -52
  38. data/brutrb.com/lsp.md +1 -1
  39. data/brutrb.com/overview.md +30 -372
  40. data/brutrb.com/pages.md +119 -207
  41. data/brutrb.com/public/SocialImage.png +0 -0
  42. data/brutrb.com/public/favicon.ico +0 -0
  43. data/brutrb.com/recipes/alternate-layouts.md +32 -0
  44. data/brutrb.com/recipes/authentication.md +315 -6
  45. data/brutrb.com/recipes/blank-layouts.md +22 -0
  46. data/brutrb.com/recipes/custom-flash.md +51 -0
  47. data/brutrb.com/recipes/indexed-forms.md +149 -0
  48. data/brutrb.com/recipes/text-field-component.md +182 -0
  49. data/brutrb.com/routes.md +56 -82
  50. data/brutrb.com/security.md +0 -3
  51. data/brutrb.com/space-time-continuum.md +8 -12
  52. data/brutrb.com/tutorial.md +1 -1
  53. data/brutrb.com/why.md +19 -0
  54. data/docs/404.html +8 -3
  55. data/docs/SocialImage.png +0 -0
  56. data/docs/ai.html +11 -6
  57. data/docs/api/Brut/BackEnd/SeedData.html +1 -1
  58. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +1 -1
  59. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +1 -1
  60. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +1 -1
  61. data/docs/api/Brut/BackEnd/Sidekiq.html +1 -1
  62. data/docs/api/Brut/BackEnd/Validators/FormValidator.html +1 -1
  63. data/docs/api/Brut/BackEnd/Validators.html +1 -1
  64. data/docs/api/Brut/BackEnd.html +1 -1
  65. data/docs/api/Brut/CLI/App.html +1 -1
  66. data/docs/api/Brut/CLI/AppRunner.html +1 -1
  67. data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +1 -1
  68. data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +1 -1
  69. data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +1 -1
  70. data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +1 -1
  71. data/docs/api/Brut/CLI/Apps/BuildAssets.html +1 -1
  72. data/docs/api/Brut/CLI/Apps/DB/Create.html +1 -1
  73. data/docs/api/Brut/CLI/Apps/DB/Drop.html +1 -1
  74. data/docs/api/Brut/CLI/Apps/DB/Migrate.html +1 -1
  75. data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +1 -1
  76. data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +1 -1
  77. data/docs/api/Brut/CLI/Apps/DB/Seed.html +1 -1
  78. data/docs/api/Brut/CLI/Apps/DB/Status.html +1 -1
  79. data/docs/api/Brut/CLI/Apps/DB.html +1 -1
  80. data/docs/api/Brut/CLI/Apps/DeployBase/GitChecks.html +1 -1
  81. data/docs/api/Brut/CLI/Apps/DeployBase.html +1 -1
  82. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy/Deploy.html +1 -1
  83. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy.html +1 -1
  84. data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +1 -1
  85. data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +1 -1
  86. data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +1 -1
  87. data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +1 -1
  88. data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +1 -1
  89. data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +1 -1
  90. data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +1 -1
  91. data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +1 -1
  92. data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +1 -1
  93. data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +1 -1
  94. data/docs/api/Brut/CLI/Apps/Scaffold.html +1 -1
  95. data/docs/api/Brut/CLI/Apps/Test/Audit.html +1 -1
  96. data/docs/api/Brut/CLI/Apps/Test/E2e.html +1 -1
  97. data/docs/api/Brut/CLI/Apps/Test/JS.html +1 -1
  98. data/docs/api/Brut/CLI/Apps/Test/Run.html +1 -1
  99. data/docs/api/Brut/CLI/Apps/Test.html +1 -1
  100. data/docs/api/Brut/CLI/Apps.html +1 -1
  101. data/docs/api/Brut/CLI/Command.html +1 -1
  102. data/docs/api/Brut/CLI/Error.html +1 -1
  103. data/docs/api/Brut/CLI/ExecutionResults/Result.html +1 -1
  104. data/docs/api/Brut/CLI/ExecutionResults.html +1 -1
  105. data/docs/api/Brut/CLI/Executor.html +1 -1
  106. data/docs/api/Brut/CLI/InvalidOption.html +1 -1
  107. data/docs/api/Brut/CLI/Options.html +1 -1
  108. data/docs/api/Brut/CLI/Output.html +1 -1
  109. data/docs/api/Brut/CLI/SystemExecError.html +1 -1
  110. data/docs/api/Brut/CLI.html +1 -1
  111. data/docs/api/Brut/FactoryBot.html +1 -1
  112. data/docs/api/Brut/Framework/App.html +1 -1
  113. data/docs/api/Brut/Framework/Config.html +1 -1
  114. data/docs/api/Brut/Framework/Container.html +1 -1
  115. data/docs/api/Brut/Framework/Error.html +1 -1
  116. data/docs/api/Brut/Framework/Errors/AbstractMethod.html +1 -1
  117. data/docs/api/Brut/Framework/Errors/Bug.html +1 -1
  118. data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +1 -1
  119. data/docs/api/Brut/Framework/Errors/MissingParameter.html +1 -1
  120. data/docs/api/Brut/Framework/Errors/NoClassForPath.html +1 -1
  121. data/docs/api/Brut/Framework/Errors/NotFound.html +1 -1
  122. data/docs/api/Brut/Framework/Errors/NotImplemented.html +1 -1
  123. data/docs/api/Brut/Framework/Errors.html +1 -1
  124. data/docs/api/Brut/Framework/FussyTypeEnforcement.html +1 -1
  125. data/docs/api/Brut/Framework/MCP.html +1 -1
  126. data/docs/api/Brut/Framework/ProjectEnvironment.html +1 -1
  127. data/docs/api/Brut/Framework.html +1 -1
  128. data/docs/api/Brut/FrontEnd/AssetPathResolver.html +1 -1
  129. data/docs/api/Brut/FrontEnd/Component/Helpers.html +1 -1
  130. data/docs/api/Brut/FrontEnd/Component.html +1 -1
  131. data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +1 -1
  132. data/docs/api/Brut/FrontEnd/Components/FormTag.html +1 -1
  133. data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +1 -1
  134. data/docs/api/Brut/FrontEnd/Components/Input.html +1 -1
  135. data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +1 -1
  136. data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +1 -1
  137. data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +1 -1
  138. data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +1 -1
  139. data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +1 -1
  140. data/docs/api/Brut/FrontEnd/Components/Inputs.html +1 -1
  141. data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +1 -1
  142. data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +1 -1
  143. data/docs/api/Brut/FrontEnd/Components/TimeTag.html +1 -1
  144. data/docs/api/Brut/FrontEnd/Components/Traceparent.html +1 -1
  145. data/docs/api/Brut/FrontEnd/Components.html +1 -1
  146. data/docs/api/Brut/FrontEnd/Download.html +1 -1
  147. data/docs/api/Brut/FrontEnd/Flash.html +1 -1
  148. data/docs/api/Brut/FrontEnd/Form.html +9 -11
  149. data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +1 -1
  150. data/docs/api/Brut/FrontEnd/Forms/Input/Color.html +1 -1
  151. data/docs/api/Brut/FrontEnd/Forms/Input/TimeOfDay.html +1 -1
  152. data/docs/api/Brut/FrontEnd/Forms/Input.html +1 -1
  153. data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +1 -1
  154. data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +1 -1
  155. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +135 -20
  156. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +1 -1
  157. data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +135 -20
  158. data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +1 -1
  159. data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +1 -1
  160. data/docs/api/Brut/FrontEnd/Forms.html +1 -1
  161. data/docs/api/Brut/FrontEnd/GenericResponse.html +1 -1
  162. data/docs/api/Brut/FrontEnd/Handler.html +1 -1
  163. data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +1 -1
  164. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +1 -1
  165. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +1 -1
  166. data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +1 -1
  167. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +1 -1
  168. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +1 -1
  169. data/docs/api/Brut/FrontEnd/Handlers.html +1 -1
  170. data/docs/api/Brut/FrontEnd/HandlingResults.html +1 -1
  171. data/docs/api/Brut/FrontEnd/HttpMethod.html +1 -1
  172. data/docs/api/Brut/FrontEnd/HttpStatus.html +1 -1
  173. data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +1 -1
  174. data/docs/api/Brut/FrontEnd/Layout.html +1 -1
  175. data/docs/api/Brut/FrontEnd/Middleware.html +1 -1
  176. data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +1 -1
  177. data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +1 -1
  178. data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +1 -1
  179. data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +1 -1
  180. data/docs/api/Brut/FrontEnd/Middlewares.html +1 -1
  181. data/docs/api/Brut/FrontEnd/Page.html +1 -1
  182. data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +1 -1
  183. data/docs/api/Brut/FrontEnd/Pages.html +1 -1
  184. data/docs/api/Brut/FrontEnd/RequestContext.html +1 -1
  185. data/docs/api/Brut/FrontEnd/RouteHook.html +1 -1
  186. data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +1 -1
  187. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +1 -1
  188. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +1 -1
  189. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +1 -1
  190. data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +1 -1
  191. data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +1 -1
  192. data/docs/api/Brut/FrontEnd/RouteHooks.html +1 -1
  193. data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +1 -1
  194. data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +1 -1
  195. data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +1 -1
  196. data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +1 -1
  197. data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +1 -1
  198. data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +1 -1
  199. data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +1 -1
  200. data/docs/api/Brut/FrontEnd/Routing/Route.html +1 -1
  201. data/docs/api/Brut/FrontEnd/Routing.html +1 -1
  202. data/docs/api/Brut/FrontEnd/Session.html +1 -1
  203. data/docs/api/Brut/FrontEnd.html +1 -1
  204. data/docs/api/Brut/I18n/BaseMethods.html +1 -1
  205. data/docs/api/Brut/I18n/ForBackEnd.html +1 -1
  206. data/docs/api/Brut/I18n/ForCLI.html +1 -1
  207. data/docs/api/Brut/I18n/ForHTML.html +1 -1
  208. data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +1 -1
  209. data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +1 -1
  210. data/docs/api/Brut/I18n.html +1 -1
  211. data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +1 -1
  212. data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +1 -1
  213. data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +1 -1
  214. data/docs/api/Brut/Instrumentation/OpenTelemetry.html +1 -1
  215. data/docs/api/Brut/Instrumentation.html +1 -1
  216. data/docs/api/Brut/SinatraHelpers/ClassMethods.html +1 -1
  217. data/docs/api/Brut/SinatraHelpers.html +1 -1
  218. data/docs/api/Brut/SpecSupport/ClockSupport.html +1 -1
  219. data/docs/api/Brut/SpecSupport/ComponentSupport.html +1 -1
  220. data/docs/api/Brut/SpecSupport/E2ETestServer.html +1 -1
  221. data/docs/api/Brut/SpecSupport/E2eSupport.html +1 -1
  222. data/docs/api/Brut/SpecSupport/EnhancedNode.html +1 -1
  223. data/docs/api/Brut/SpecSupport/FlashSupport.html +1 -1
  224. data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +1 -1
  225. data/docs/api/Brut/SpecSupport/GeneralSupport.html +1 -1
  226. data/docs/api/Brut/SpecSupport/HandlerSupport.html +1 -1
  227. data/docs/api/Brut/SpecSupport/Matchers/BeABug.html +1 -1
  228. data/docs/api/Brut/SpecSupport/Matchers/BePageFor.html +1 -1
  229. data/docs/api/Brut/SpecSupport/Matchers/BeRoutingFor.html +1 -1
  230. data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +1 -1
  231. data/docs/api/Brut/SpecSupport/Matchers/HaveGenerated.html +1 -1
  232. data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +1 -1
  233. data/docs/api/Brut/SpecSupport/Matchers/HaveI18nString.html +1 -1
  234. data/docs/api/Brut/SpecSupport/Matchers/HaveLinkTo.html +1 -1
  235. data/docs/api/Brut/SpecSupport/Matchers/HaveRedirectedTo.html +1 -1
  236. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedHttpStatus.html +1 -1
  237. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedRackResponse.html +1 -1
  238. data/docs/api/Brut/SpecSupport/Matchers.html +1 -1
  239. data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +1 -1
  240. data/docs/api/Brut/SpecSupport/RSpecSetup.html +1 -1
  241. data/docs/api/Brut/SpecSupport/SessionSupport.html +1 -1
  242. data/docs/api/Brut/SpecSupport.html +1 -1
  243. data/docs/api/Brut.html +1 -1
  244. data/docs/api/Clock.html +1 -1
  245. data/docs/api/RichString.html +1 -1
  246. data/docs/api/SemanticLogger/Appender/Async.html +1 -1
  247. data/docs/api/Sequel/Extensions/BrutInstrumentation.html +1 -1
  248. data/docs/api/Sequel/Extensions/BrutMigrations.html +1 -1
  249. data/docs/api/Sequel/Extensions.html +1 -1
  250. data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +1 -1
  251. data/docs/api/Sequel/Plugins/CreatedAt.html +1 -1
  252. data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +1 -1
  253. data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +1 -1
  254. data/docs/api/Sequel/Plugins/ExternalId.html +1 -1
  255. data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +1 -1
  256. data/docs/api/Sequel/Plugins/FindBang.html +1 -1
  257. data/docs/api/Sequel/Plugins.html +1 -1
  258. data/docs/api/Sequel.html +1 -1
  259. data/docs/api/_index.html +1 -1
  260. data/docs/api/file.README.html +22 -3
  261. data/docs/api/index.html +22 -3
  262. data/docs/api/method_list.html +16 -0
  263. data/docs/api/top-level-namespace.html +1 -1
  264. data/docs/assets/LogoStop.Gb3tDhL1.png +0 -0
  265. data/docs/assets/{ai.md._6HCDL6d.js → ai.md.Cy9GWnER.js} +1 -1
  266. data/docs/assets/ai.md.Cy9GWnER.lean.js +1 -0
  267. data/docs/assets/{app.BhrfSt68.js → app.ClaS47Ru.js} +1 -1
  268. data/docs/assets/{assets.md.D3wunzLx.js → assets.md.7C3HWkga.js} +3 -3
  269. data/docs/assets/{assets.md.D3wunzLx.lean.js → assets.md.7C3HWkga.lean.js} +1 -1
  270. data/docs/assets/{brut-js.md.o2DAO2s2.js → brut-js.md.B4GYxQVw.js} +1 -1
  271. data/docs/assets/{brut-js.md.o2DAO2s2.lean.js → brut-js.md.B4GYxQVw.lean.js} +1 -1
  272. data/docs/assets/chunks/@localSearchIndexroot.Biqy1A4t.js +1 -0
  273. data/docs/assets/chunks/{VPLocalSearchBox.Dpot_2H4.js → VPLocalSearchBox.DtgDfde2.js} +1 -1
  274. data/docs/assets/chunks/{theme.N2SNVLgU.js → theme.B45bvibT.js} +2 -2
  275. data/docs/assets/{cli.md.RmeA2b0i.js → cli.md.CjsktgFz.js} +15 -20
  276. data/docs/assets/components.md.DatoNgFo.js +96 -0
  277. data/docs/assets/{components.md.CRUMdRoN.lean.js → components.md.DatoNgFo.lean.js} +1 -1
  278. data/docs/assets/{configuration.md.LG-zIBww.js → configuration.md.DeyhpqEx.js} +3 -3
  279. data/docs/assets/{css.md.DJgj2clw.js → css.md.CltvJqAa.js} +3 -3
  280. data/docs/assets/{custom-element-tests.md.BrYJQEl3.js → custom-element-tests.md.B_rbta32.js} +3 -3
  281. data/docs/assets/{database-access.md.C7l-Vuvb.js → database-access.md.gnluu54N.js} +1 -1
  282. data/docs/assets/{database-schema.md.BUjR0VS1.js → database-schema.md.CSYk6E6v.js} +6 -6
  283. data/docs/assets/{database-schema.md.BUjR0VS1.lean.js → database-schema.md.CSYk6E6v.lean.js} +1 -1
  284. data/docs/assets/dev-environment.md.BroAOLhF.js +11 -0
  285. data/docs/assets/dir-structure.md.CWir1pic.js +46 -0
  286. data/docs/assets/dir-structure.md.CWir1pic.lean.js +1 -0
  287. data/docs/assets/doc-conventions.md.BzmSrTEW.js +1 -0
  288. data/docs/assets/doc-conventions.md.BzmSrTEW.lean.js +1 -0
  289. data/docs/assets/{end-to-end-tests.md.yfQHC0b5.js → end-to-end-tests.md.DzqRpZ43.js} +5 -3
  290. data/docs/assets/end-to-end-tests.md.DzqRpZ43.lean.js +1 -0
  291. data/docs/assets/features.md.DPFXsy0z.js +154 -0
  292. data/docs/assets/features.md.DPFXsy0z.lean.js +1 -0
  293. data/docs/assets/flash-and-session.md.nPvUpnUx.js +79 -0
  294. data/docs/assets/{flash-and-session.md.BXY8RvT0.lean.js → flash-and-session.md.nPvUpnUx.lean.js} +1 -1
  295. data/docs/assets/form-constraints.md.x5tNpTTI.js +90 -0
  296. data/docs/assets/form-constraints.md.x5tNpTTI.lean.js +1 -0
  297. data/docs/assets/forms.md.C2Dizvzq.js +64 -0
  298. data/docs/assets/forms.md.C2Dizvzq.lean.js +1 -0
  299. data/docs/assets/{getting-started.md.Dj0qtZI2.js → getting-started.md.C93e0odB.js} +5 -5
  300. data/docs/assets/{getting-started.md.Dj0qtZI2.lean.js → getting-started.md.C93e0odB.lean.js} +1 -1
  301. data/docs/assets/handlers.md.Chyri6KA.js +54 -0
  302. data/docs/assets/handlers.md.Chyri6KA.lean.js +1 -0
  303. data/docs/assets/{hooks.md.C4-moMny.js → hooks.md.Jmb5VOLA.js} +4 -4
  304. data/docs/assets/{hooks.md.C4-moMny.lean.js → hooks.md.Jmb5VOLA.lean.js} +1 -1
  305. data/docs/assets/{i18n.md.Do9i1qWl.js → i18n.md.xQhiGo1G.js} +2 -2
  306. data/docs/assets/{i18n.md.Do9i1qWl.lean.js → i18n.md.xQhiGo1G.lean.js} +1 -1
  307. data/docs/assets/{index.md.CuBB-BdM.js → index.md.CAMqGBJE.js} +1 -1
  308. data/docs/assets/{index.md.CuBB-BdM.lean.js → index.md.CAMqGBJE.lean.js} +1 -1
  309. data/docs/assets/{instrumentation.md.a9Pjps4P.js → instrumentation.md.BgcaGVYH.js} +2 -2
  310. data/docs/assets/{instrumentation.md.a9Pjps4P.lean.js → instrumentation.md.BgcaGVYH.lean.js} +1 -1
  311. data/docs/assets/{javascript.md.GWbhRS51.js → javascript.md.DzrMxUmI.js} +7 -7
  312. data/docs/assets/{javascript.md.GWbhRS51.lean.js → javascript.md.DzrMxUmI.lean.js} +1 -1
  313. data/docs/assets/keyword-injection.md.95Zgh2eN.js +21 -0
  314. data/docs/assets/{keyword-injection.md.Dt2tKREs.lean.js → keyword-injection.md.95Zgh2eN.lean.js} +1 -1
  315. data/docs/assets/{layouts.md.cPnh3NId.js → layouts.md.CJGDFY-m.js} +2 -15
  316. data/docs/assets/layouts.md.CJGDFY-m.lean.js +1 -0
  317. data/docs/assets/{lsp.md.Bsu-f6VU.js → lsp.md.Dn1rIiW0.js} +1 -1
  318. data/docs/assets/{lsp.md.Bsu-f6VU.lean.js → lsp.md.Dn1rIiW0.lean.js} +1 -1
  319. data/docs/assets/overview.md.Bdq4qt3L.js +1 -0
  320. data/docs/assets/overview.md.Bdq4qt3L.lean.js +1 -0
  321. data/docs/assets/pages.md.B7Hc-i6H.js +45 -0
  322. data/docs/assets/pages.md.B7Hc-i6H.lean.js +1 -0
  323. data/docs/assets/recipes_alternate-layouts.md.BwEytl59.js +22 -0
  324. data/docs/assets/recipes_alternate-layouts.md.BwEytl59.lean.js +1 -0
  325. data/docs/assets/recipes_authentication.md.Dzvi_g69.js +156 -0
  326. data/docs/assets/recipes_authentication.md.Dzvi_g69.lean.js +1 -0
  327. data/docs/assets/recipes_blank-layouts.md.fyAUJyJR.js +15 -0
  328. data/docs/assets/recipes_blank-layouts.md.fyAUJyJR.lean.js +1 -0
  329. data/docs/assets/recipes_custom-flash.md.CrQbI5eH.js +26 -0
  330. data/docs/assets/recipes_custom-flash.md.CrQbI5eH.lean.js +1 -0
  331. data/docs/assets/recipes_indexed-forms.md.CstYyOSo.js +74 -0
  332. data/docs/assets/recipes_indexed-forms.md.CstYyOSo.lean.js +1 -0
  333. data/docs/assets/recipes_text-field-component.md.H4wLAK0Z.js +101 -0
  334. data/docs/assets/recipes_text-field-component.md.H4wLAK0Z.lean.js +1 -0
  335. data/docs/assets/routes.md.B8kfUPHU.js +21 -0
  336. data/docs/assets/{routes.md.BMM7peut.lean.js → routes.md.B8kfUPHU.lean.js} +1 -1
  337. data/docs/assets/{security.md.C668yXCi.js → security.md.C0G_AZR-.js} +1 -1
  338. data/docs/assets/{security.md.C668yXCi.lean.js → security.md.C0G_AZR-.lean.js} +1 -1
  339. data/docs/assets/space-time-continuum.md.xl44xDos.js +1 -0
  340. data/docs/assets/{space-time-continuum.md.KPUIKysQ.lean.js → space-time-continuum.md.xl44xDos.lean.js} +1 -1
  341. data/docs/assets/{style.B2o1L9eN.css → style.prAgp4yQ.css} +1 -1
  342. data/docs/assets/tutorial.md.a4a0eVOy.js +1 -0
  343. data/docs/assets/tutorial.md.a4a0eVOy.lean.js +1 -0
  344. data/docs/assets/why.md.C-hk5xgJ.js +1 -0
  345. data/docs/assets/why.md.C-hk5xgJ.lean.js +1 -0
  346. data/docs/assets.html +12 -7
  347. data/docs/brut-js/api/AjaxSubmit.html +1 -1
  348. data/docs/brut-js/api/AjaxSubmit.js.html +1 -1
  349. data/docs/brut-js/api/Autosubmit.html +1 -1
  350. data/docs/brut-js/api/Autosubmit.js.html +1 -1
  351. data/docs/brut-js/api/BaseCustomElement.html +1 -1
  352. data/docs/brut-js/api/BaseCustomElement.js.html +1 -1
  353. data/docs/brut-js/api/BrutCustomElements.html +1 -1
  354. data/docs/brut-js/api/BufferedLogger.html +1 -1
  355. data/docs/brut-js/api/ConfirmSubmit.html +1 -1
  356. data/docs/brut-js/api/ConfirmSubmit.js.html +1 -1
  357. data/docs/brut-js/api/ConfirmationDialog.html +1 -1
  358. data/docs/brut-js/api/ConfirmationDialog.js.html +1 -1
  359. data/docs/brut-js/api/ConstraintViolationMessage.html +1 -1
  360. data/docs/brut-js/api/ConstraintViolationMessage.js.html +1 -1
  361. data/docs/brut-js/api/ConstraintViolationMessages.html +1 -1
  362. data/docs/brut-js/api/ConstraintViolationMessages.js.html +1 -1
  363. data/docs/brut-js/api/CopyToClipboard.html +1 -1
  364. data/docs/brut-js/api/CopyToClipboard.js.html +1 -1
  365. data/docs/brut-js/api/Form.html +1 -1
  366. data/docs/brut-js/api/Form.js.html +1 -1
  367. data/docs/brut-js/api/I18nTranslation.html +1 -1
  368. data/docs/brut-js/api/I18nTranslation.js.html +1 -1
  369. data/docs/brut-js/api/LocaleDetection.html +1 -1
  370. data/docs/brut-js/api/LocaleDetection.js.html +1 -1
  371. data/docs/brut-js/api/Logger.html +1 -1
  372. data/docs/brut-js/api/Logger.js.html +1 -1
  373. data/docs/brut-js/api/Message.html +1 -1
  374. data/docs/brut-js/api/Message.js.html +1 -1
  375. data/docs/brut-js/api/PrefixedLogger.html +1 -1
  376. data/docs/brut-js/api/RichString.html +1 -1
  377. data/docs/brut-js/api/RichString.js.html +1 -1
  378. data/docs/brut-js/api/Tabs.html +1 -1
  379. data/docs/brut-js/api/Tabs.js.html +1 -1
  380. data/docs/brut-js/api/Tracing.html +1 -1
  381. data/docs/brut-js/api/Tracing.js.html +1 -1
  382. data/docs/brut-js/api/external-CustomElementRegistry.html +1 -1
  383. data/docs/brut-js/api/external-Performance.html +1 -1
  384. data/docs/brut-js/api/external-Promise.html +1 -1
  385. data/docs/brut-js/api/external-ValidityState.html +1 -1
  386. data/docs/brut-js/api/external-Window.html +1 -1
  387. data/docs/brut-js/api/external-fetch.html +1 -1
  388. data/docs/brut-js/api/global.html +1 -1
  389. data/docs/brut-js/api/index.html +1 -1
  390. data/docs/brut-js/api/index.js.html +1 -1
  391. data/docs/brut-js/api/module-testing.html +1 -1
  392. data/docs/brut-js/api/testing.AssetMetadata.html +1 -1
  393. data/docs/brut-js/api/testing.AssetMetadataLoader.html +1 -1
  394. data/docs/brut-js/api/testing.CustomElementTest.html +1 -1
  395. data/docs/brut-js/api/testing.DOMCreator.html +1 -1
  396. data/docs/brut-js/api/testing_AssetMetadata.js.html +1 -1
  397. data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +1 -1
  398. data/docs/brut-js/api/testing_CustomElementTest.js.html +1 -1
  399. data/docs/brut-js/api/testing_DOMCreator.js.html +1 -1
  400. data/docs/brut-js/api/testing_index.js.html +1 -1
  401. data/docs/brut-js.html +12 -7
  402. data/docs/business-logic.html +10 -5
  403. data/docs/cli.html +26 -26
  404. data/docs/components.html +61 -64
  405. data/docs/configuration.html +13 -8
  406. data/docs/css.html +14 -9
  407. data/docs/custom-element-tests.html +14 -9
  408. data/docs/database-access.html +12 -7
  409. data/docs/database-schema.html +15 -10
  410. data/docs/deployment.html +10 -5
  411. data/docs/dev-environment.html +12 -7
  412. data/docs/dir-structure.html +74 -0
  413. data/docs/doc-conventions.html +11 -6
  414. data/docs/end-to-end-tests.html +15 -8
  415. data/docs/favicon.ico +0 -0
  416. data/docs/features.html +182 -0
  417. data/docs/flash-and-session.html +73 -82
  418. data/docs/form-constraints.html +118 -0
  419. data/docs/forms.html +57 -367
  420. data/docs/getting-started.html +15 -10
  421. data/docs/handlers.html +51 -61
  422. data/docs/hashmap.json +1 -1
  423. data/docs/hooks.html +14 -9
  424. data/docs/i18n.html +12 -7
  425. data/docs/index.html +11 -6
  426. data/docs/instrumentation.html +12 -7
  427. data/docs/javascript.html +17 -12
  428. data/docs/jobs.html +10 -5
  429. data/docs/keyword-injection.html +22 -21
  430. data/docs/layouts.html +12 -20
  431. data/docs/lsp.html +11 -6
  432. data/docs/markdown-examples.html +10 -5
  433. data/docs/middleware.html +10 -5
  434. data/docs/not-released.html +10 -5
  435. data/docs/overview.html +11 -138
  436. data/docs/pages.html +49 -121
  437. data/docs/recipes/alternate-layouts.html +50 -0
  438. data/docs/recipes/authentication.html +166 -6
  439. data/docs/recipes/blank-layouts.html +43 -0
  440. data/docs/recipes/custom-flash.html +54 -0
  441. data/docs/recipes/indexed-forms.html +102 -0
  442. data/docs/recipes/text-field-component.html +129 -0
  443. data/docs/routes.html +16 -19
  444. data/docs/security.html +11 -6
  445. data/docs/seed-data.html +10 -5
  446. data/docs/space-time-continuum.html +11 -6
  447. data/docs/tutorial.html +11 -6
  448. data/docs/unit-tests.html +10 -5
  449. data/docs/why.html +29 -0
  450. data/lib/brut/front_end/form.rb +8 -8
  451. data/lib/brut/front_end/forms/radio_button_group_input.rb +8 -1
  452. data/lib/brut/front_end/forms/select_input.rb +8 -1
  453. data/lib/brut/version.rb +1 -1
  454. data/specs/brut/front_end/forms/radio_button_group_input.spec.rb +54 -0
  455. data/specs/brut/front_end/forms/select_input.spec.rb +54 -0
  456. metadata +117 -75
  457. data/brutrb.com/public/images/logo-300.png +0 -0
  458. data/brutrb.com/public/images/logo.png +0 -0
  459. data/docs/assets/LogoStop.X8x-4riz.png +0 -0
  460. data/docs/assets/ai.md._6HCDL6d.lean.js +0 -1
  461. data/docs/assets/chunks/@localSearchIndexroot.CeRAdP1K.js +0 -1
  462. data/docs/assets/components.md.CRUMdRoN.js +0 -104
  463. data/docs/assets/dev-environment.md.GZv6xvi9.js +0 -11
  464. data/docs/assets/doc-conventions.md.-kN3Xo5C.js +0 -1
  465. data/docs/assets/doc-conventions.md.-kN3Xo5C.lean.js +0 -1
  466. data/docs/assets/end-to-end-tests.md.yfQHC0b5.lean.js +0 -1
  467. data/docs/assets/flash-and-session.md.BXY8RvT0.js +0 -93
  468. data/docs/assets/forms.md.B-koVgyw.js +0 -379
  469. data/docs/assets/forms.md.B-koVgyw.lean.js +0 -1
  470. data/docs/assets/handlers.md.089DVD3v.js +0 -69
  471. data/docs/assets/handlers.md.089DVD3v.lean.js +0 -1
  472. data/docs/assets/keyword-injection.md.Dt2tKREs.js +0 -25
  473. data/docs/assets/layouts.md.cPnh3NId.lean.js +0 -1
  474. data/docs/assets/overview.md.DVKRM8zl.js +0 -133
  475. data/docs/assets/overview.md.DVKRM8zl.lean.js +0 -1
  476. data/docs/assets/pages.md.BE3kfOc5.js +0 -122
  477. data/docs/assets/pages.md.BE3kfOc5.lean.js +0 -1
  478. data/docs/assets/recipes_authentication.md.CAsXf7hk.js +0 -1
  479. data/docs/assets/recipes_authentication.md.CAsXf7hk.lean.js +0 -1
  480. data/docs/assets/routes.md.BMM7peut.js +0 -29
  481. data/docs/assets/space-time-continuum.md.KPUIKysQ.js +0 -1
  482. data/docs/assets/tutorial.md.BnoGjrdK.js +0 -1
  483. data/docs/assets/tutorial.md.BnoGjrdK.lean.js +0 -1
  484. data/docs/images/logo-300.png +0 -0
  485. data/docs/images/logo.png +0 -0
  486. /data/docs/assets/{cli.md.RmeA2b0i.lean.js → cli.md.CjsktgFz.lean.js} +0 -0
  487. /data/docs/assets/{configuration.md.LG-zIBww.lean.js → configuration.md.DeyhpqEx.lean.js} +0 -0
  488. /data/docs/assets/{css.md.DJgj2clw.lean.js → css.md.CltvJqAa.lean.js} +0 -0
  489. /data/docs/assets/{custom-element-tests.md.BrYJQEl3.lean.js → custom-element-tests.md.B_rbta32.lean.js} +0 -0
  490. /data/docs/assets/{database-access.md.C7l-Vuvb.lean.js → database-access.md.gnluu54N.lean.js} +0 -0
  491. /data/docs/assets/{dev-environment.md.GZv6xvi9.lean.js → dev-environment.md.BroAOLhF.lean.js} +0 -0
data/brutrb.com/pages.md CHANGED
@@ -1,65 +1,34 @@
1
1
  # Pages
2
2
 
3
- The core abstraction of Brut is the core concept of the web: the web page.
4
-
5
- A web page is fetched by the browser using an HTTP `GET` request to a URL. When that happens, Brut instantiates an object of a *page class* and uses its `page_template` method to generate its HTML (using calls to Phlex's API).
3
+ A core abstraction of Brut is the core concept of the web: the web page.
6
4
 
7
5
  ## Overview
8
6
 
9
- You can create everything you need for a page by using `bin/scaffold`:
7
+ To create a web page, you'll need:
10
8
 
11
- ```shell
12
- > bin/scaffold page /new-widgets
13
- ```
9
+ * A [Route](/routes) using `page`.
10
+ * A class in `app/src/front_end/pages/` that extends `Brut::FrontEnd::Page`, named [conventionally](/routes#class-naming-conventions) (though in reality, your page willextend `AppPage` in `app/src/front_end/pages/app_page.rb`, which extends `Brut::FrontEnd::Page`).
11
+ * [Optional, but recommended] A test in `specs/front_end/pages`.
14
12
 
15
- You can use `--dry-run` to see what it will do:
13
+ You can create all this with `bin/scaffold`, which accepts the route you want:
16
14
 
17
15
  ```shell
18
- > bin/scaffold --dry-run /new-widgets
19
- bin/scaffold --dry-run page /new-widgets
20
- [ bin/scaffold ] app/src/app.rb
21
- [ bin/scaffold ] will contain:
22
-
23
- page "/new-widgets"
24
-
25
- [ bin/scaffold ] app/src/front_end/pages/new_widgets_page.rb
26
- [ bin/scaffold ] will contain:
27
-
28
- class NewWidgetsPage < AppPage
29
- def initialize # add needed arguments here
30
- end
31
-
32
- def page_template
33
- h1 { "Your page is ready" }
34
- end
35
- end
36
-
37
- [ bin/scaffold ] specs/front_end/pages/new_widgets_page.spec.rb
38
- [ bin/scaffold ] will contain:
39
-
40
- require "spec_helper"
41
-
42
- RSpec.describe NewWidgetsPage do
43
- it "should have tests" do
44
- expect(true).to eq(false)
45
- end
46
- end
47
-
48
- [ bin/scaffold ] app/config/i18n/en/2_app.rb
49
- [ bin/scaffold ] will contain:
16
+ > bin/scaffold page /new_widgets
17
+ # => app/src/front_end/pages/new_widgets_page.rb
18
+ # => specs/front_end/pages/new_widgets_page.spec.rb
19
+ # => add `page "/new_widgets"` to app/src/app.rb
20
+ ```
50
21
 
51
- "NewWidgetsPage": {
52
- title: "New widgets page",
53
- },
22
+ or
54
23
 
55
- [ bin/scaffold ] Page source is in app/src/front_end/pages/new_widgets_page.rb
56
- [ bin/scaffold ] Page HTML template is in app/src/front_end/pages/new_widgets_page.html.erb
57
- [ bin/scaffold ] Page test is in specs/front_end/pages/new_widgets_page.spec.rb
58
- [ bin/scaffold ] Added title to app/config/i18n/en/2_app.rb
59
- [ bin/scaffold ] Added route to app/src/app.rb
24
+ ```shell
25
+ > bin/scaffold page /widget/:id
26
+ # => app/src/front_end/pages/widget_by_id_page.rb
27
+ # => specs/front_end/pages/widget_by_id_page.spec.rb
28
+ # => add `page "/widget/:id"` to app/src/app.rb
60
29
  ```
61
30
 
62
- You can, of course, edit `app.rb` and create the classes yourself.
31
+ You can also perform these steps manually.
63
32
 
64
33
  > [!WARNING]
65
34
  > Adding a `page` route without the corresponding class may not always
@@ -73,59 +42,73 @@ You can, of course, edit `app.rb` and create the classes yourself.
73
42
 
74
43
  ### Creating a Page
75
44
 
76
- Page classes are expected to be in `app/src/front_end/pages`, named conventionally the way Zeitwerk would expect. For example, `Admin::WidgetsByIdPage` would be expected in `app/src/front_end/pages/admin/widgets_by_id_page.rb`.
45
+ Pages need a `page_template` method that contains calls to Phlex, which will produce
46
+ the page's HTML.
77
47
 
78
- A page class must be a subclass of `Brut::FrontEnd::Page`, however in practice it will be a subclass of `AppPage` in your app, which is a subclass of `Brut::FrontEnd::Page`. All Brut components have an app-specific base class to allow sharing of logic, if needed.
48
+ If you have not used Phlex before, it's relatively straightfoward. For each HTML
49
+ tag that exists, Phlex provides a method. So, for `<div>`, Phlex provides `div`.
79
50
 
80
- Brut will create the instance of the page class, passing in the keyword
81
- arguments the initializer specifies (see [Keyword Injection](/keyword-injection)). In particular, any placeholders in the route will be passed-in to the initializer. This is why those placeholders must be valid Ruby keyword argument names.
82
-
83
- For example, `Admin::WidgetsByIdPage` and its template might look like so:
84
-
85
- ```ruby
86
- # pages/admin/widgets_by_id_page.rb
87
- class Admin::WidgetsByIdPage < AppPage
88
- def initialize(id:)
89
- @widget = DB::Widget.find!(id:)
90
- end
91
-
92
- private attr_reader :widget
51
+ Each method accepts parameters which are converted into attributes. Methods can
52
+ also accept blocks that can be used to add more HTML by calling more of Phlex's API.
93
53
 
54
+ ```ruby
55
+ class DashboardPage < AppPage
94
56
  def page_template
95
- h1 { widget.name }
96
- h2 { widget.status }
57
+ header do
58
+ h1 { "Welcome to My App!" }
59
+ time { Date.today }
60
+ end
61
+ main do
62
+ p(class: "body-text") do
63
+ "This is my awesome app! I hope you stay awhile!"
64
+ end
65
+ end
97
66
  end
98
67
  end
99
68
  ```
100
69
 
101
- Note that `Admin::WidgetsByIdPage` is a normal Ruby class, so you could implement `#widget` as a method, and lazy-load the widget:
102
-
103
- ```ruby {13}
104
- class Admin::WidgetsByIdPage < AppPage
105
- def initialize(id:)
106
- @widget_id = id
107
- end
70
+ By default, this page will be rendered inside `DefaultLayout`, located in
71
+ `app/src/front_end/layouts/default_layout.rb` and discussed in [the layouts
72
+ module](/layouts). The HTML this page will generate, that would then be inserted
73
+ into the layout's HTML, looks like so:
74
+
75
+ ```html
76
+ <header>
77
+ <h1>Welcome to My App!</h1>
78
+ <time>2025-07-05</time>
79
+ </header>
80
+ <main>
81
+ <p class="body-text">
82
+ This is my awesome app! I hope you stay awhile!"
83
+ </p>
84
+ </main>
85
+ ```
108
86
 
109
- def page_template
110
- h1 { widget.name }
111
- h2 { widget.status }
112
- end
87
+ ### Accessing Data in a Page
113
88
 
114
- private
89
+ Building static pages is fine, but not really why we use web app libraries. Your
90
+ page is a normal class, so you can create instance variables and methods, which can
91
+ do whatever you need.
115
92
 
116
- def widget = DB::Widget.find!(id: @widget_id)
93
+ That being said, the initializer is called by Brut and can be given special
94
+ arguments. For example, if your route has as placeholder, e.g. `/widgets/:id`, then
95
+ your initializer will be given the value of `:id` if its initializer has a keyword
96
+ argument named `id:`:
117
97
 
98
+ ```ruby
99
+ def initialize(id:)
118
100
  end
119
101
  ```
120
102
 
121
- A page's initializer can also accept other parameters, provided by Brut.
103
+ Query string parameters are also avaiable this way, but your page can access a wide
104
+ variety of request-level information simply by declaring a keyword argument to its
105
+ initializer.
122
106
 
123
- ### Arguments Available to Initializer
107
+ This mechanism is called [keyword injection](/keyword-injection) and is available to many class you create, including pages.
124
108
 
125
- Brut's [keyword injection](/keyword-injection) is used to create the instance of your page. You can have Brut inject what you need by
126
- specifying keyword arguments.
109
+ Here is a list of what is available:
127
110
 
128
- | Value | Type | Description |
111
+ | Keyword Argument | Type | Description |
129
112
  |-------|------|-------------|
130
113
  `session:` | `Brut::FrontEnd::Session` (or your app's subclass) | The current session, even if it's empty. See [Flash and Session](/flash-and-session)|
131
114
  `flash:` | `Brut::FrontEnd::Flash` (or your app's subclass) | The current flash, even if it's empty. See [Flash and Session](/flash-and-session) |
@@ -133,6 +116,7 @@ specifying keyword arguments.
133
116
  `csrf_token:` | `String`| The current CSRF token. |
134
117
  `clock:` | `Clock` | Used when you need to access the current date and time, potentially accounting for time zones. See [Space/Time Continuum](/space-time-continuum)|
135
118
  `http_*` | `String` or `nil` | Any parameter that starts with `http_` is assumed to be for an HTTP header. For example, `http_accept_language` would be given the value for the "Accept-Language" header. See [HTTP Headers](/keyword-injection#http-headers) |
119
+ `rack_request_*` | `String` or `nil` | Any parameter that starts with `rack_request_` is assumed to be for a value from the `Rack::Request`. For example, `rack_request_id` would provide the `ip` value from `Rack::Request` |
136
120
  `env:` | `Hash` | The Rack env. You are discouraged from using this directly in your pages, but if you need it, it's available. |
137
121
  Placeholders | `String` | Any placeholder value from the route definition |
138
122
  Any query string paramter | `String` | the value given is always a string.
@@ -150,23 +134,22 @@ def initialize(id:,
150
134
  ```
151
135
 
152
136
  > [!CAUTION]
153
- > Keyword arguments for query string parameters **must** have default values or Brut will be unable to instantiate your page class
154
- > when they are omitted.
137
+ > Keyword arguments for query string parameters **must** have default values
138
+ > or Brut will be unable to instantiate your page class when they are omitted.
139
+ > We recommended that **no other keywords arguments have defaults** to ensure your
140
+ > pages aren't created with `nil` values.
155
141
 
156
142
  > [!NOTE]
157
143
  > Omitting a default for an HTTP header is OK, but you should know what the behavior is. See [the HTTP Headers
158
144
  > section](/keyword-injection#http-headers) for details.
159
145
 
160
- ### Hooks
161
146
 
162
- Occasionally, you want to prevent a page from rendering after the visitor has been routed to it. A common
163
- reason for this could be a lack of authorization by that visitor to view the page.
147
+ ### Page Hooks
164
148
 
165
- `before_generate` achieves this. If your page class implements it, it will be called after the page is
166
- initialized, but before the template creationg process starts. Depending on what `before_generate`
167
- returns, the visitor may be redirected, an error could be sent, or HTML generation may proceed as normal.
149
+ Occasionally, you want to prevent a page from rendering after the visitor has been routed to it. A common reason for this could be a lack of authorization by that visitor to view the page.
168
150
 
169
- The return value of `before_generate` determines what will happen:
151
+ `before_generate` achieves this. It's called after construction, so has access to
152
+ any injected values, and its return value tells Brut what should happen:
170
153
 
171
154
  * `URI` - the visitor will be redirected to the given URI. Instead of creating a `URI`, you may use the method `redirect_to`, which
172
155
  accepts a page and its parameters.
@@ -179,83 +162,63 @@ an `HttpStatus` from a number.
179
162
 
180
163
  See [Unit Testing](/unit-tests) for some basic assumptions and configuration available for all Brut unit tests.
181
164
 
182
- Since pages are Plain Ole Ruby Objects, you could test them using conventional means. However, since the ultimate behavior of a
183
- page is to produce HTML based on its template, it's recommended that your page tests generate HTML and you make assertions about the page's behavior by examining that HTML.
165
+ Although pages are Plain Old Ruby Objects, you likely want to test the HTML they
166
+ generate. Brut provides convenience methods to do this based on Nokogiri.
184
167
 
185
- Brut provides convenience methods for this, based on Nokogiri. With them, you should be able to access elements of your page using
186
- the same sorts of CSS selectors you'd use with `document.querySelector` to debug your app in a browser.
168
+ ### Generating a Response
187
169
 
188
- ### `generate_and_parse` Parses the Generated HTML
170
+ * If your page has no before hook, or you aren't testing that, call `generate_and_parse(page_instance)`. This returns a `Brut::SpecSupport::EnhancedNode`, which is a delegate to Nokogiri's `Nokogiri::XML::Node` (see below for why this exists)
171
+ * If you want to assert behavior of the before hook, call `generate_result`, which
172
+ will return whatever the page's internal `handle!` method called.
173
+ will use one of these matchers on the result:
189
174
 
190
- Brut uses RSpec, so when a page test is detected, Brut will include `Brut::SpecSupport::ComponentSupport`, which provides useful methods and includes other modules you'll need to make testing more straightforward.
175
+ ### Asserting Results
191
176
 
192
- The main method you'll use is `generate_and_parse`, which accepts an instance of your page and returns a
193
- `Brut::SpecSupport::EnhancedNode`, which is a delegate to a Nokogiri node.
194
-
195
- Below, we use the method `e!`, which is provided by `EnhancedNode`. This works just like Nokogiri's `css`, except
196
- that requires exactly one element to match the selector. If not, the test fails. This allows a more compact test
197
- when you know there should only be one element matching the selector you've provided.
177
+ When using `generate_and_parse`, you have access to all of Nokogiri, however
178
+ `Brut::SpecSupport::EnhancedNode` provides two methods to simplify your test:
198
179
 
199
180
  ```ruby
200
- RSpec.describe CompanyByCompanyId::LocationsByLocationIdPage do
201
- describe "render" do
202
- it "shows the company name and location address" do
203
- company = create(:company) # You must implement
204
- location = create(:location) # You must implement
205
-
206
- page = described_class.new(company_id: company.id.to_s,
207
- location_id: location.id.to_s)
208
-
209
- parsed_html = generate_and_parse(page)
210
-
211
- h1 = parsed_html.e!("h1")
212
- h2 = parsed_html.e!("h2")
181
+ it "should work" do
182
+ result = generate_and_parse(described_class.new)
213
183
 
214
- expect(h1.text).to include(company.name)
215
- expect(h2.text).to include(location.address)
216
- end
217
- end
184
+ expect(result.e!("h1").text).to include("Welcome")
185
+ expect(result.e("h2")).to eq(nil)
218
186
  end
219
187
  ```
220
188
 
221
- `e` (without a bang/`!`) is also provided, which will allow zero or one elements to match the selector (i.e. it only fails if there is more than one match). `e` and `e!` are key methods that allow the use of CSS selectors to be usable in your tests.
222
-
223
- See `Brut::SpecSupport::ClockSupport`, `Brut::SpecSupport::FlashSupport`, and `Brut::SpecSupport::SessionSupport` for additional methods you can use to make it easier to work with clocks, flashes, and sessions, respectively.
224
-
225
- ### `generate_result` Tests `before_generate`
226
-
227
- If your page uses `before_generate`, when you call `generate_and_parse`, it will fail unless the page generated
228
- HTML. In those cases, you can use `generate_result`, which will return what `before_generate` returned, unless
229
- it returned `nil`, in which case it will return the unparsed HTML.
189
+ * `e!` returns the node matching the given CSS selector, failing the test if there
190
+ is not exactly one matching node.
191
+ * `e` (no bang) returns the node matching the given CSS selector, or `nil` if none matched. If there is more than one match, the test fails.
230
192
 
231
- ```ruby {4,10,12}
232
- RSpec.describe CompanyByCompanyId::LocationsByLocationIdPage do
233
- describe "render" do
234
- it "redirects back to the home page for expired companies" do
235
- company = create(:company, :expired) # You must implement
236
- location = create(:location) # You must implement
193
+ When using `generate_result`, you will want to use one of two special purpose
194
+ matchers:
237
195
 
238
- page = described_class.new(company_id: company.id.to_s,
239
- location_id: location.id.to_s)
240
-
241
- result = generate_result(page)
242
-
243
- expect(result).to have_redirected_to(HomePage)
196
+ ```ruby
197
+ it "redirects" do
198
+ result = generate_result(described_class.new)
199
+ expect(result).to have_redirected_to(AuthPage)
200
+ end
244
201
 
245
- end
246
- end
202
+ it "404's" do
203
+ result = generate_result(described_class.new)
204
+ expect(result).to have_returned_http_status(404)
247
205
  end
248
206
  ```
249
207
 
250
- `have_redirected_to` is a matcher provided by Brut. `have_returned_http_status` is also available to assert that
251
- `before_generate` returned an HTTP status. The reason to use these matchers and `generate_result` instead of
252
- calling `before_generate` directly is that you want to use the page in a test the way it's used in your app. You
253
- will also get higher-quality test failure messages.
208
+ - `have_redirected_to` to check that a redirect happened to the URI you set (see `Brut::SpecSupport::Matchers::HaveRedirectedTo`)
209
+ - `have_returned_http_status` to check that a given HTTP response was returned (see `Brut::SpecSupport::Matchers::HaveReturnedHttpStatus`)
254
210
 
255
- ## Recommended Practices
211
+ Beyond this, you can use Nokogiri as usual to navigate the DOM that's generated and
212
+ make assertions. A few additional matchers to help are:
256
213
 
257
- You can build your pages however you like, but here are some tips that will make your app more sustainable and
258
- easier to work with.
214
+ - `be_routing_for` - expect a URI to be a routing for a certain page or
215
+ page/parameter combination. See `Brut::SpecSupport::Matchers::BeRoutingFor`.
216
+ - `have_html_attribute` - check that a node has an attribute or an attribute with a
217
+ specific value. See `Brut::SpecSupport::Matchers::HaveHTMLAttribute`.
218
+ - `have_i18n_string` - check that a node's text has a string from your [I18n](/i18n)
219
+ configuration. See `Brut::SpecSupport::Matchers::HaveI18nString`.
220
+
221
+ ## Recommended Practices
259
222
 
260
223
  ### Instance variables (ivars) are fine.
261
224
 
@@ -273,59 +236,7 @@ below, you won't need to use `before_generate` as a failsafe check on authorizat
273
236
 
274
237
  The list of available data for injection above will always be available to your page, with the exception of query string parameters. The real power comes when you learn how to [inject your own data](/keyword-injection#injecting-custom-data) into the request context.
275
238
 
276
- Let's take a common example of a page that require that a visitor be logged in. While your app will have logic to avoid routing a logged-out visitor to any of those pages, it may seem like a good practice to add a failsafe check inside the logic of the page requiring login. This is very common in Rails and might look like so:
277
-
278
- ```ruby{2}
279
- class WidgetsController < ApplicationController
280
- before_action :require_login!
281
-
282
- # ...
283
- end
284
- ```
285
-
286
- `before_action` is the failsafe - in case someone hacks a URL to find this page, or there is a bug in your app where unauthorized visitors are sent to this page, the `before_action` prevents the page from working.
287
-
288
- In Brut, you could mimic this behavior using `before_generate`, however this isn't necessary. Instead, you can take advantage of keyword injection.
289
-
290
- Consider this implementation of `WidgetsByIdPage`:
291
-
292
- ```ruby
293
- class WidgetsByIdPage < AppPage
294
- def initialize(id:, current_user:)
295
- # ...
296
- end
297
- end
298
- ```
299
-
300
- `id:` is injected because it is a route placeholder. `current_user:` however, is completely custom to our app. We can arrange to
301
- have it injected. We'll create a [Route Hook](/hooks) to do this.
302
-
303
- > [!CAUTION]
304
- > This hook is not production-ready. It lacks certain error-handling code and
305
- > makes an assumption about how the session is managed. It's for demonstration only.
306
- > The [route hooks](/hooks) section has a more
307
- > appropriate example.
308
-
309
- ```ruby{6}
310
- class RequireAuthBeforeHook < Brut::FrontEnd::RouteHook
311
- def before(request_context:,session:)
312
- if session.current_user_id
313
- user = DB::User.find(id: session.current_user_id)
314
- if user
315
- request_context[:current_user] = user
316
- end
317
- end
318
- end
319
- end
320
- ```
321
-
322
- Before any route is handled, this before hook is run and passed the `Brut::FrontEnd::RequestContext`. This is where all the
323
- injectible values live. `request_context[:current_user] = user` makes `user` available to be injected into a page or handler.
324
-
325
- What this means is that when a visitor is not logged in, there will be no injectible value for `:current_user`. Brut will not be able
326
- to instantiate `WidgetsByIdPage`, and an error is generated. It is literally impossible to route a logged-out visitor to that page.
327
-
328
- In practice, this means that any page that requires a logged-in visitor will specify the `current_user:` keyword argument, and **not provide a default value**. You are still required to make sure no one routes a logged-out visitor to a page requiring authentication, but now you don't have to remember to add logic to each page that requires login—you bake it into the page class' type.
239
+ A great example of this is in the [recipe for keywords and auth](/recipes/authentication), which results in a much simpler and less error-prone way to prevent unauthorized access to pages when compared to how you might do it in Rails.
329
240
 
330
241
  ### In Tests, It's Fine to Locate Elements Via CSS Selectors
331
242
 
@@ -367,9 +278,10 @@ you.
367
278
 
368
279
  ### Helpers in Templates
369
280
 
370
- `Brut::FrontEnd::Page` is a subclass of `Brut::FrontEnd::Component`, so all your pages will have access to the helpers included there. This is how, for example, `t` can be called to perform translations, or `time_tag` can be used to create a `<time>` HTML element.
281
+ `Brut::FrontEnd::Page` is a subclass of `Brut::FrontEnd::Component`, so all your pages will have access to the helpers included there. This is how, for example, `t` can be called to perform translations.
371
282
 
372
- If you wish to add helpers to be used in more than one page, you can either add the method to a common base class like `AppPage`, or create a module and `include` it.
283
+ Note that Brut does *not* include `Brut::FrontEnd::Components` (pluralized). You
284
+ can include that in `AppPage` to access Brut's builtin components as a Phlex kit.
373
285
 
374
286
  ### So You Don't Like Phlex?
375
287
 
Binary file
Binary file
@@ -0,0 +1,32 @@
1
+ # Alternate Layouts
2
+
3
+ To create an alternate layout, your page can override `layout` to return a string.
4
+ That string will be camel-cased and preped to `Layout` to form a class that is
5
+ expected to exist and provide the layout. That class must extend
6
+ `Brut::FrontEnd::Layout`.
7
+
8
+ ```ruby
9
+ class MyOtherPage < AppPage
10
+ def layout = "other_design"
11
+
12
+ # ...
13
+
14
+ end
15
+
16
+ class OtherDesignLayout < Brut::FrontEnd::Layout
17
+ def view_template
18
+ doctype
19
+ html do
20
+ head do
21
+ link(rel: "preload", as: "style", href: asset_path("/css/other-styles.css"))
22
+ link(rel: "stylesheet", href: asset_path("/css/other-styles.css"))
23
+ script(defer: true, src: asset_path("/js/app.js"))
24
+ end
25
+ body do
26
+ yield
27
+ end
28
+ end
29
+ end
30
+ end
31
+ ```
32
+