brut 0.10.0 → 0.12.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 (403) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -0
  3. data/Gemfile.lock +21 -21
  4. data/assets/YouTubeThumb.pxd +0 -0
  5. data/bin/new-version +3 -3
  6. data/brut-css/package-lock.json +94 -97
  7. data/brut-css/package.json +2 -2
  8. data/brut-js/package-lock.json +3 -3
  9. data/brut-js/package.json +6 -3
  10. data/brut-js/specs/AjaxSubmit.spec.js +62 -6
  11. data/brut-js/src/AjaxSubmit.js +26 -6
  12. data/brutrb.com/forms.md +1 -0
  13. data/brutrb.com/getting-started.md +3 -0
  14. data/brutrb.com/images/tutorial/02-confirmation-dialog-browser-element-styled.png +0 -0
  15. data/brutrb.com/images/tutorial/02-confirmation-dialog-browser-element.png +0 -0
  16. data/brutrb.com/images/tutorial/02-confirmation-dialog-browser.png +0 -0
  17. data/brutrb.com/images/tutorial/02-confirmation-flow.graffle +0 -0
  18. data/brutrb.com/images/tutorial/02-confirmation-flow.png +0 -0
  19. data/brutrb.com/instrumentation.md +142 -3
  20. data/brutrb.com/recipes/authentication.md +1 -0
  21. data/brutrb.com/tutorial.md +29 -1627
  22. data/brutrb.com/tutorials/01-intro.md +1654 -0
  23. data/brutrb.com/tutorials/02-dialog.md +569 -0
  24. data/docs/404.html +2 -2
  25. data/docs/adrs.html +4 -4
  26. data/docs/ai.html +4 -4
  27. data/docs/api/Brut/BackEnd/SeedData.html +1 -1
  28. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +1 -1
  29. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +1 -1
  30. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +1 -1
  31. data/docs/api/Brut/BackEnd/Sidekiq.html +1 -1
  32. data/docs/api/Brut/BackEnd/Validators/FormValidator.html +1 -1
  33. data/docs/api/Brut/BackEnd/Validators.html +1 -1
  34. data/docs/api/Brut/BackEnd.html +1 -1
  35. data/docs/api/Brut/CLI/App.html +1 -1
  36. data/docs/api/Brut/CLI/AppRunner.html +1 -1
  37. data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +1 -1
  38. data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +1 -1
  39. data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +1 -1
  40. data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +1 -1
  41. data/docs/api/Brut/CLI/Apps/BuildAssets.html +1 -1
  42. data/docs/api/Brut/CLI/Apps/DB/Create.html +1 -1
  43. data/docs/api/Brut/CLI/Apps/DB/Drop.html +1 -1
  44. data/docs/api/Brut/CLI/Apps/DB/Migrate.html +1 -1
  45. data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +1 -1
  46. data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +1 -1
  47. data/docs/api/Brut/CLI/Apps/DB/Seed.html +1 -1
  48. data/docs/api/Brut/CLI/Apps/DB/Status.html +1 -1
  49. data/docs/api/Brut/CLI/Apps/DB.html +1 -1
  50. data/docs/api/Brut/CLI/Apps/DeployBase/GitChecks.html +1 -1
  51. data/docs/api/Brut/CLI/Apps/DeployBase.html +1 -1
  52. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy/Deploy.html +1 -1
  53. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy.html +1 -1
  54. data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +1 -1
  55. data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +1 -1
  56. data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +1 -1
  57. data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +1 -1
  58. data/docs/api/Brut/CLI/Apps/Scaffold/DbModel.html +1 -1
  59. data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +1 -1
  60. data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +1 -1
  61. data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +1 -1
  62. data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +1 -1
  63. data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +1 -1
  64. data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +1 -1
  65. data/docs/api/Brut/CLI/Apps/Scaffold.html +1 -1
  66. data/docs/api/Brut/CLI/Apps/Test/Audit.html +1 -1
  67. data/docs/api/Brut/CLI/Apps/Test/E2e.html +1 -1
  68. data/docs/api/Brut/CLI/Apps/Test/JS.html +1 -1
  69. data/docs/api/Brut/CLI/Apps/Test/Run.html +1 -1
  70. data/docs/api/Brut/CLI/Apps/Test.html +1 -1
  71. data/docs/api/Brut/CLI/Apps.html +1 -1
  72. data/docs/api/Brut/CLI/Command.html +1 -1
  73. data/docs/api/Brut/CLI/Error.html +1 -1
  74. data/docs/api/Brut/CLI/ExecutionResults/Result.html +1 -1
  75. data/docs/api/Brut/CLI/ExecutionResults.html +1 -1
  76. data/docs/api/Brut/CLI/Executor.html +1 -1
  77. data/docs/api/Brut/CLI/InvalidOption.html +1 -1
  78. data/docs/api/Brut/CLI/Options.html +1 -1
  79. data/docs/api/Brut/CLI/Output.html +1 -1
  80. data/docs/api/Brut/CLI/SystemExecError.html +1 -1
  81. data/docs/api/Brut/CLI.html +1 -1
  82. data/docs/api/Brut/FactoryBot.html +1 -1
  83. data/docs/api/Brut/Framework/App.html +1 -1
  84. data/docs/api/Brut/Framework/Config.html +1 -1
  85. data/docs/api/Brut/Framework/Container.html +1 -1
  86. data/docs/api/Brut/Framework/Error.html +1 -1
  87. data/docs/api/Brut/Framework/Errors/AbstractMethod.html +1 -1
  88. data/docs/api/Brut/Framework/Errors/Bug.html +1 -1
  89. data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +1 -1
  90. data/docs/api/Brut/Framework/Errors/MissingParameter.html +1 -1
  91. data/docs/api/Brut/Framework/Errors/NoClassForPath.html +1 -1
  92. data/docs/api/Brut/Framework/Errors/NotFound.html +1 -1
  93. data/docs/api/Brut/Framework/Errors/NotImplemented.html +1 -1
  94. data/docs/api/Brut/Framework/Errors.html +1 -1
  95. data/docs/api/Brut/Framework/FussyTypeEnforcement.html +2 -2
  96. data/docs/api/Brut/Framework/MCP.html +1 -1
  97. data/docs/api/Brut/Framework/ProjectEnvironment.html +1 -1
  98. data/docs/api/Brut/Framework.html +1 -1
  99. data/docs/api/Brut/FrontEnd/AssetPathResolver.html +1 -1
  100. data/docs/api/Brut/FrontEnd/Component/Helpers.html +1 -1
  101. data/docs/api/Brut/FrontEnd/Component.html +1 -1
  102. data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +48 -27
  103. data/docs/api/Brut/FrontEnd/Components/FormTag.html +1 -1
  104. data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +1 -1
  105. data/docs/api/Brut/FrontEnd/Components/Input.html +2 -2
  106. data/docs/api/Brut/FrontEnd/Components/Inputs/ButtonTag.html +443 -0
  107. data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +1 -1
  108. data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +14 -9
  109. data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +1 -1
  110. data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +8 -9
  111. data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +10 -11
  112. data/docs/api/Brut/FrontEnd/Components/Inputs.html +2 -2
  113. data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +1 -1
  114. data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +1 -1
  115. data/docs/api/Brut/FrontEnd/Components/TimeTag.html +1 -1
  116. data/docs/api/Brut/FrontEnd/Components/Traceparent.html +1 -1
  117. data/docs/api/Brut/FrontEnd/Components.html +1 -1
  118. data/docs/api/Brut/FrontEnd/CsrfProtector.html +1 -1
  119. data/docs/api/Brut/FrontEnd/Download.html +1 -1
  120. data/docs/api/Brut/FrontEnd/Flash.html +1 -1
  121. data/docs/api/Brut/FrontEnd/Form.html +39 -39
  122. data/docs/api/Brut/FrontEnd/Forms/Button.html +331 -0
  123. data/docs/api/Brut/FrontEnd/Forms/ButtonInputDefinition.html +544 -0
  124. data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +1 -1
  125. data/docs/api/Brut/FrontEnd/Forms/Input/Color.html +1 -1
  126. data/docs/api/Brut/FrontEnd/Forms/Input/TimeOfDay.html +1 -1
  127. data/docs/api/Brut/FrontEnd/Forms/Input.html +6 -2
  128. data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +111 -23
  129. data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +20 -12
  130. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +1 -1
  131. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +1 -1
  132. data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +1 -1
  133. data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +1 -1
  134. data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +1 -1
  135. data/docs/api/Brut/FrontEnd/Forms.html +2 -2
  136. data/docs/api/Brut/FrontEnd/GenericResponse.html +1 -1
  137. data/docs/api/Brut/FrontEnd/Handler.html +1 -1
  138. data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +1 -1
  139. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +1 -1
  140. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +1 -1
  141. data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +1 -1
  142. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +2 -2
  143. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +1 -1
  144. data/docs/api/Brut/FrontEnd/Handlers.html +1 -1
  145. data/docs/api/Brut/FrontEnd/HandlingResults.html +1 -1
  146. data/docs/api/Brut/FrontEnd/HttpMethod.html +1 -1
  147. data/docs/api/Brut/FrontEnd/HttpStatus.html +1 -1
  148. data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +1 -1
  149. data/docs/api/Brut/FrontEnd/Layout.html +1 -1
  150. data/docs/api/Brut/FrontEnd/Middleware.html +1 -1
  151. data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +1 -1
  152. data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +1 -1
  153. data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +1 -1
  154. data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +1 -1
  155. data/docs/api/Brut/FrontEnd/Middlewares.html +1 -1
  156. data/docs/api/Brut/FrontEnd/Page.html +1 -1
  157. data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +1 -1
  158. data/docs/api/Brut/FrontEnd/Pages.html +1 -1
  159. data/docs/api/Brut/FrontEnd/RequestContext.html +1 -1
  160. data/docs/api/Brut/FrontEnd/RouteHook.html +1 -1
  161. data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +1 -1
  162. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +1 -1
  163. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +1 -1
  164. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +1 -1
  165. data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +1 -1
  166. data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +1 -1
  167. data/docs/api/Brut/FrontEnd/RouteHooks.html +1 -1
  168. data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +1 -1
  169. data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +1 -1
  170. data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +1 -1
  171. data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +1 -1
  172. data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +1 -1
  173. data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +1 -1
  174. data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +1 -1
  175. data/docs/api/Brut/FrontEnd/Routing/Route.html +1 -1
  176. data/docs/api/Brut/FrontEnd/Routing.html +1 -1
  177. data/docs/api/Brut/FrontEnd/Session.html +1 -1
  178. data/docs/api/Brut/FrontEnd.html +1 -1
  179. data/docs/api/Brut/I18n/BaseMethods.html +1 -1
  180. data/docs/api/Brut/I18n/ForBackEnd.html +1 -1
  181. data/docs/api/Brut/I18n/ForCLI.html +1 -1
  182. data/docs/api/Brut/I18n/ForHTML.html +1 -1
  183. data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +1 -1
  184. data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +1 -1
  185. data/docs/api/Brut/I18n.html +1 -1
  186. data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +1 -1
  187. data/docs/api/Brut/Instrumentation/Methods/ClassMethods.html +596 -0
  188. data/docs/api/Brut/Instrumentation/Methods.html +173 -0
  189. data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +7 -7
  190. data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +7 -7
  191. data/docs/api/Brut/Instrumentation/OpenTelemetry.html +19 -17
  192. data/docs/api/Brut/Instrumentation.html +3 -1
  193. data/docs/api/Brut/RubocopConfig.html +1 -1
  194. data/docs/api/Brut/SinatraHelpers/ClassMethods.html +1 -1
  195. data/docs/api/Brut/SinatraHelpers.html +1 -1
  196. data/docs/api/Brut/SpecSupport/ClockSupport.html +1 -1
  197. data/docs/api/Brut/SpecSupport/ComponentSupport.html +1 -1
  198. data/docs/api/Brut/SpecSupport/E2ETestServer.html +1 -1
  199. data/docs/api/Brut/SpecSupport/E2eSupport.html +1 -1
  200. data/docs/api/Brut/SpecSupport/EnhancedNode.html +1 -1
  201. data/docs/api/Brut/SpecSupport/FlashSupport.html +1 -1
  202. data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +1 -1
  203. data/docs/api/Brut/SpecSupport/GeneralSupport.html +1 -1
  204. data/docs/api/Brut/SpecSupport/HandlerSupport.html +1 -1
  205. data/docs/api/Brut/SpecSupport/Matchers/BeABug.html +1 -1
  206. data/docs/api/Brut/SpecSupport/Matchers/BePageFor.html +1 -1
  207. data/docs/api/Brut/SpecSupport/Matchers/BeRoutingFor.html +1 -1
  208. data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +1 -1
  209. data/docs/api/Brut/SpecSupport/Matchers/HaveGenerated.html +1 -1
  210. data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +1 -1
  211. data/docs/api/Brut/SpecSupport/Matchers/HaveI18nString.html +1 -1
  212. data/docs/api/Brut/SpecSupport/Matchers/HaveLinkTo.html +1 -1
  213. data/docs/api/Brut/SpecSupport/Matchers/HaveRedirectedTo.html +1 -1
  214. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedHttpStatus.html +1 -1
  215. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedRackResponse.html +1 -1
  216. data/docs/api/Brut/SpecSupport/Matchers.html +1 -1
  217. data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +1 -1
  218. data/docs/api/Brut/SpecSupport/RSpecSetup.html +1 -1
  219. data/docs/api/Brut/SpecSupport/SessionSupport.html +1 -1
  220. data/docs/api/Brut/SpecSupport.html +1 -1
  221. data/docs/api/Brut.html +1 -1
  222. data/docs/api/Clock.html +1 -1
  223. data/docs/api/ModuleName.html +1 -1
  224. data/docs/api/RichString.html +1 -1
  225. data/docs/api/SemanticLogger/Appender/Async.html +1 -1
  226. data/docs/api/Sequel/Extensions/BrutInstrumentation.html +1 -1
  227. data/docs/api/Sequel/Extensions/BrutMigrations.html +1 -1
  228. data/docs/api/Sequel/Extensions.html +1 -1
  229. data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +1 -1
  230. data/docs/api/Sequel/Plugins/CreatedAt.html +1 -1
  231. data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +1 -1
  232. data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +1 -1
  233. data/docs/api/Sequel/Plugins/ExternalId.html +1 -1
  234. data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +1 -1
  235. data/docs/api/Sequel/Plugins/FindBang.html +1 -1
  236. data/docs/api/Sequel/Plugins.html +1 -1
  237. data/docs/api/Sequel.html +1 -1
  238. data/docs/api/_index.html +36 -1
  239. data/docs/api/class_list.html +1 -1
  240. data/docs/api/file.README.html +1 -1
  241. data/docs/api/index.html +1 -1
  242. data/docs/api/method_list.html +364 -252
  243. data/docs/api/top-level-namespace.html +1 -1
  244. data/docs/assets/02-confirmation-dialog-browser-element-styled.3NEGM20-.png +0 -0
  245. data/docs/assets/02-confirmation-dialog-browser-element.DPsf0xUW.png +0 -0
  246. data/docs/assets/02-confirmation-dialog-browser.DH8ALFO4.png +0 -0
  247. data/docs/assets/02-confirmation-flow.D9gZ0S5U.png +0 -0
  248. data/docs/assets/{app.vjGWMSnJ.js → app.V2GOcOrg.js} +1 -1
  249. data/docs/assets/chunks/@localSearchIndexroot.3Lsq4QTb.js +1 -0
  250. data/docs/assets/chunks/{VPLocalSearchBox.C-ymMW2k.js → VPLocalSearchBox.CZTadcAy.js} +1 -1
  251. data/docs/assets/chunks/{theme.pJUosGlI.js → theme.FeuYNxyp.js} +2 -2
  252. data/docs/assets/{components.md.B543a3Lm.js → components.md.f4cdTyvV.js} +3 -3
  253. data/docs/assets/{configuration.md.-foE_iVv.js → configuration.md.Bs4-rxnS.js} +1 -1
  254. data/docs/assets/{form-constraints.md.DK5adCgM.js → form-constraints.md.KTv5cdR4.js} +6 -6
  255. data/docs/assets/{forms.md.D5-2rgHh.js → forms.md.Sys-XxVf.js} +3 -3
  256. data/docs/assets/{forms.md.D5-2rgHh.lean.js → forms.md.Sys-XxVf.lean.js} +1 -1
  257. data/docs/assets/{getting-started.md.Cd4XSZb_.js → getting-started.md.CFIW0bcE.js} +6 -3
  258. data/docs/assets/{getting-started.md.Cd4XSZb_.lean.js → getting-started.md.CFIW0bcE.lean.js} +1 -1
  259. data/docs/assets/instrumentation.md._lNSriEZ.js +90 -0
  260. data/docs/assets/instrumentation.md._lNSriEZ.lean.js +1 -0
  261. data/docs/assets/{recipes_authentication.md.Dzvi_g69.js → recipes_authentication.md.BAISoxmN.js} +1 -0
  262. data/docs/assets/recipes_form-errors.md.Bv5RCKqH.js +66 -0
  263. data/docs/assets/recipes_form-errors.md.Bv5RCKqH.lean.js +1 -0
  264. data/docs/assets/tutorial.md.BM40jnoq.js +27 -0
  265. data/docs/assets/tutorial.md.BM40jnoq.lean.js +1 -0
  266. data/docs/assets/{tutorial.md.C4zR5XPG.js → tutorials_01-intro.md.B4sUBY3X.js} +13 -33
  267. data/docs/assets/tutorials_01-intro.md.B4sUBY3X.lean.js +1 -0
  268. data/docs/assets/tutorials_02-dialog.md.QTFeHdiA.js +274 -0
  269. data/docs/assets/tutorials_02-dialog.md.QTFeHdiA.lean.js +1 -0
  270. data/docs/assets.html +4 -4
  271. data/docs/brut-js/api/AjaxSubmit.html +16 -6
  272. data/docs/brut-js/api/AjaxSubmit.js.html +27 -7
  273. data/docs/brut-js/api/Autosubmit.html +1 -1
  274. data/docs/brut-js/api/Autosubmit.js.html +1 -1
  275. data/docs/brut-js/api/BaseCustomElement.html +1 -1
  276. data/docs/brut-js/api/BaseCustomElement.js.html +1 -1
  277. data/docs/brut-js/api/BrutCustomElements.html +1 -1
  278. data/docs/brut-js/api/BufferedLogger.html +1 -1
  279. data/docs/brut-js/api/ConfirmSubmit.html +1 -1
  280. data/docs/brut-js/api/ConfirmSubmit.js.html +1 -1
  281. data/docs/brut-js/api/ConfirmationDialog.html +1 -1
  282. data/docs/brut-js/api/ConfirmationDialog.js.html +1 -1
  283. data/docs/brut-js/api/ConstraintViolationMessage.html +55 -5
  284. data/docs/brut-js/api/ConstraintViolationMessage.js.html +18 -3
  285. data/docs/brut-js/api/ConstraintViolationMessages.html +1 -1
  286. data/docs/brut-js/api/ConstraintViolationMessages.js.html +1 -1
  287. data/docs/brut-js/api/CopyToClipboard.html +1 -1
  288. data/docs/brut-js/api/CopyToClipboard.js.html +1 -1
  289. data/docs/brut-js/api/Form.html +7 -10
  290. data/docs/brut-js/api/Form.js.html +20 -24
  291. data/docs/brut-js/api/I18nTranslation.html +1 -1
  292. data/docs/brut-js/api/I18nTranslation.js.html +1 -1
  293. data/docs/brut-js/api/LocaleDetection.html +1 -1
  294. data/docs/brut-js/api/LocaleDetection.js.html +1 -1
  295. data/docs/brut-js/api/Logger.html +1 -1
  296. data/docs/brut-js/api/Logger.js.html +1 -1
  297. data/docs/brut-js/api/Message.html +1 -1
  298. data/docs/brut-js/api/Message.js.html +1 -1
  299. data/docs/brut-js/api/PrefixedLogger.html +1 -1
  300. data/docs/brut-js/api/RichString.html +1 -1
  301. data/docs/brut-js/api/RichString.js.html +1 -1
  302. data/docs/brut-js/api/Tabs.html +1 -1
  303. data/docs/brut-js/api/Tabs.js.html +1 -1
  304. data/docs/brut-js/api/Tracing.html +1 -1
  305. data/docs/brut-js/api/Tracing.js.html +1 -1
  306. data/docs/brut-js/api/external-CustomElementRegistry.html +1 -1
  307. data/docs/brut-js/api/external-Performance.html +1 -1
  308. data/docs/brut-js/api/external-Promise.html +1 -1
  309. data/docs/brut-js/api/external-ValidityState.html +1 -1
  310. data/docs/brut-js/api/external-Window.html +1 -1
  311. data/docs/brut-js/api/external-fetch.html +1 -1
  312. data/docs/brut-js/api/global.html +1 -1
  313. data/docs/brut-js/api/index.html +1 -1
  314. data/docs/brut-js/api/index.js.html +1 -1
  315. data/docs/brut-js/api/module-testing.html +1 -1
  316. data/docs/brut-js/api/testing.AssetMetadata.html +1 -1
  317. data/docs/brut-js/api/testing.AssetMetadataLoader.html +1 -1
  318. data/docs/brut-js/api/testing.CustomElementTest.html +1 -1
  319. data/docs/brut-js/api/testing.DOMCreator.html +1 -1
  320. data/docs/brut-js/api/testing_AssetMetadata.js.html +1 -1
  321. data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +1 -1
  322. data/docs/brut-js/api/testing_CustomElementTest.js.html +1 -1
  323. data/docs/brut-js/api/testing_DOMCreator.js.html +1 -1
  324. data/docs/brut-js/api/testing_index.js.html +1 -1
  325. data/docs/brut-js.html +4 -4
  326. data/docs/business-logic.html +4 -4
  327. data/docs/cli.html +4 -4
  328. data/docs/components.html +8 -8
  329. data/docs/configuration.html +5 -5
  330. data/docs/css.html +4 -4
  331. data/docs/custom-element-tests.html +4 -4
  332. data/docs/database-access.html +4 -4
  333. data/docs/database-schema.html +4 -4
  334. data/docs/deployment.html +4 -4
  335. data/docs/dev-environment.html +4 -4
  336. data/docs/dir-structure.html +4 -4
  337. data/docs/doc-conventions.html +4 -4
  338. data/docs/end-to-end-tests.html +4 -4
  339. data/docs/features.html +4 -4
  340. data/docs/flash-and-session.html +4 -4
  341. data/docs/form-constraints.html +11 -11
  342. data/docs/forms.html +7 -7
  343. data/docs/getting-started.html +10 -7
  344. data/docs/handlers.html +4 -4
  345. data/docs/hashmap.json +1 -1
  346. data/docs/hooks.html +4 -4
  347. data/docs/i18n.html +4 -4
  348. data/docs/index.html +3 -3
  349. data/docs/instrumentation.html +61 -6
  350. data/docs/javascript.html +4 -4
  351. data/docs/jobs.html +4 -4
  352. data/docs/keyword-injection.html +4 -4
  353. data/docs/layouts.html +4 -4
  354. data/docs/lsp.html +4 -4
  355. data/docs/markdown-examples.html +4 -4
  356. data/docs/middleware.html +4 -4
  357. data/docs/overview.html +4 -4
  358. data/docs/pages.html +4 -4
  359. data/docs/recipes/alternate-layouts.html +4 -4
  360. data/docs/recipes/authentication.html +7 -6
  361. data/docs/recipes/blank-layouts.html +4 -4
  362. data/docs/recipes/custom-flash.html +4 -4
  363. data/docs/recipes/form-errors.html +94 -0
  364. data/docs/recipes/indexed-forms.html +4 -4
  365. data/docs/recipes/migrations.html +5 -5
  366. data/docs/recipes/text-field-component.html +4 -4
  367. data/docs/roadmap.html +4 -4
  368. data/docs/routes.html +4 -4
  369. data/docs/security.html +4 -4
  370. data/docs/seed-data.html +4 -4
  371. data/docs/space-time-continuum.html +4 -4
  372. data/docs/tutorial.html +12 -713
  373. data/docs/tutorials/01-intro.html +736 -0
  374. data/docs/tutorials/02-dialog.html +302 -0
  375. data/docs/unit-tests.html +4 -4
  376. data/docs/why.html +4 -4
  377. data/lib/brut/front_end/components/input.rb +1 -0
  378. data/lib/brut/front_end/components/inputs/button_tag.rb +40 -0
  379. data/lib/brut/front_end/components/inputs/input_tag.rb +3 -1
  380. data/lib/brut/front_end/components/inputs/select_tag_with_options.rb +0 -1
  381. data/lib/brut/front_end/components/inputs/textarea_tag.rb +0 -1
  382. data/lib/brut/front_end/form.rb +7 -5
  383. data/lib/brut/front_end/forms/button.rb +21 -0
  384. data/lib/brut/front_end/forms/button_input_definition.rb +37 -0
  385. data/lib/brut/front_end/forms/input_declarations.rb +10 -0
  386. data/lib/brut/front_end/forms/input_definition.rb +7 -2
  387. data/lib/brut/instrumentation/methods.rb +153 -0
  388. data/lib/brut/instrumentation/open_telemetry.rb +1 -0
  389. data/lib/brut/instrumentation.rb +1 -0
  390. data/lib/brut/version.rb +1 -1
  391. data/mkbrut/Gemfile.lock +1 -1
  392. data/mkbrut/bin/publish +1 -1
  393. data/mkbrut/lib/mkbrut/version.rb +1 -1
  394. data/specs/brut/instrumentation/methods.spec.rb +399 -0
  395. metadata +51 -21
  396. data/docs/assets/chunks/@localSearchIndexroot.Dn1xGMv_.js +0 -1
  397. data/docs/assets/instrumentation.md.BgcaGVYH.js +0 -35
  398. data/docs/assets/instrumentation.md.BgcaGVYH.lean.js +0 -1
  399. data/docs/assets/tutorial.md.C4zR5XPG.lean.js +0 -1
  400. /data/docs/assets/{components.md.B543a3Lm.lean.js → components.md.f4cdTyvV.lean.js} +0 -0
  401. /data/docs/assets/{configuration.md.-foE_iVv.lean.js → configuration.md.Bs4-rxnS.lean.js} +0 -0
  402. /data/docs/assets/{form-constraints.md.DK5adCgM.lean.js → form-constraints.md.KTv5cdR4.lean.js} +0 -0
  403. /data/docs/assets/{recipes_authentication.md.Dzvi_g69.lean.js → recipes_authentication.md.BAISoxmN.lean.js} +0 -0
@@ -1,29 +1,9 @@
1
- import{_ as a,c as i,o as n,ag as e}from"./chunks/framework.1L-BeKqY.js";const t="/assets/welcome-to-brut.VSWzl17-.png",l="/assets/initial-home-page.DNIaYmgP.png",p="/assets/styled-home-page.BzdI7dWz.png",h="/assets/basic-form.DbHnu0oW.png",o="/assets/basic-form-with-violations.Cv6Y9-Q_.png",k="/assets/styled-form-with-violations.Bv_sa9tg.png",d="/assets/styled-form-with-server-side-violations.Bjxd8Dpv.png",r="/assets/styled-home-page-with-posts.Dd4kG89D.png",c="/assets/new-post-editor.DrHr-5oh.png",g="/assets/new-post-home-page.Bm34lyMg.png",B=JSON.parse('{"title":"Tutorial","description":"","frontmatter":{},"headers":[],"relativePath":"tutorial.md","filePath":"tutorial.md"}'),E={name:"tutorial.md"};function u(y,s,F,b,m,C){return n(),i("div",null,s[0]||(s[0]=[e(`<h1 id="tutorial" tabindex="-1">Tutorial <a class="header-anchor" href="#tutorial" aria-label="Permalink to &quot;Tutorial&quot;">​</a></h1><p>Below, you&#39;ll build a blog that has a few pages, a form, a database table, and tests. It&#39;s the same steps I took in the <a href="https://video.hardlimit.com/w/ae7EMhwjDq9kSH5dqQ9swV" target="_blank" rel="noreferrer">15-minute blog video</a>.</p><p>If you&#39;d just like to read source code, there are two apps you can check out:</p><ul><li><a href="https://github.com/thirdtank/blog-demo" target="_blank" rel="noreferrer">The blog we&#39;ll build here</a></li><li><a href="https://github.com/thirdtank/adrs.cloud" target="_blank" rel="noreferrer">ADRs.cloud</a>, which is a more realistic app that has mulitple database tables, progressively-enhanced UI, and background jobs.</li></ul><p>You can be running either of these locally in minutes as long as you have Docker installed.</p><h2 id="understanding-this-tutorial" tabindex="-1">Understanding This Tutorial <a class="header-anchor" href="#understanding-this-tutorial" aria-label="Permalink to &quot;Understanding This Tutorial&quot;">​</a></h2><p>This tutorial will show you command line invocations and code. You should be able to follow along and just type what we say and it should work.</p><p>That said, it&#39;s not always clera what we are talking about.</p><h3 id="understanding-command-line-invocations" tabindex="-1">Understanding Command Line Invocations <a class="header-anchor" href="#understanding-command-line-invocations" aria-label="Permalink to &quot;Understanding Command Line Invocations&quot;">​</a></h3><p>If you aren&#39;t comfortable on the command line, it can be hard to understand what parts of this tutorial represent stuff you should type/paste and what is output from those commands. Here is how that works.</p><p>When we want you to run a command, the preceding text will tell you something like &quot;run this command&quot;, and then you&#39;ll see a codeblock that has the label &quot;bash&quot; in the upper right corner, like so:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">ls</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> -l</span></span></code></pre></div><p>If you hover over it, an icon with the tooltip &quot;Copy Code&quot; will appear on the right, and you can click that to copy the command-line invocation. Or, you can select it and copy it, or you can type it in manually.</p><p>In any case, you are expected to type/paste/execute the entire thing. Other parts of this documentation site may precede command lines with <code>&gt;</code> to indicate it&#39;s a shell command. For this tutorial, we aren&#39;t doing that.</p><p>Sometimes, commands are long. They can be split up by entering a backslash (<code>\\</code>) as the last character of a line, hitting return, and continuing the command. For example this command:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">git</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> push</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> origin</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> main</span></span></code></pre></div><p>Could be executed like so:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">git</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> push</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> \\</span></span>
2
- <span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> origin</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> \\</span></span>
3
- <span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> main</span></span></code></pre></div><p>In both cases, you can copy/type these as written and they will work.</p><p>To show output of a command, a separate code block will be used, and the first line of the output will be the string <code># OUTPUT:</code>, and there should <strong>not</strong> be a &quot;bash&quot; label in the upper right corner:</p><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span># OUTPUT</span></span>
4
- <span class="line"><span>app dx puma.config.rb</span></span>
5
- <span class="line"><span>bin Gemfile README.md</span></span>
6
- <span class="line"><span>config.ru package.json specs</span></span>
7
- <span class="line"><span>docker-compose.dx.yml Procfile.development</span></span>
8
- <span class="line"><span>Dockerfile.dx Procfile.test</span></span></code></pre></div><p>Sometimes, output is very long and very irrelevant. In that case, the string <code>«LOTS OF OUTPUT»</code> will be used as a placeholder:</p><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span># OUTPUT</span></span>
9
- <span class="line"><span>app</span></span>
10
- <span class="line"><span>dx</span></span>
11
- <span class="line"><span>puma.config.rb</span></span>
12
- <span class="line"><span>«LOTS OF OUTPUTS»</span></span></code></pre></div><h3 id="understanding-code-changes" tabindex="-1">Understanding Code Changes <a class="header-anchor" href="#understanding-code-changes" aria-label="Permalink to &quot;Understanding Code Changes&quot;">​</a></h3><p>In most cases, we&#39;ll show you the entire code for a file/class, and you should make your copy look like it. Suppose you have this:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> SomeComponent</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> &lt;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> AppComponent</span></span>
13
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>We might say &quot;add the <code>view_template</code> to your component so it looks like this:&quot;</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> SomeComponent</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> &lt;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> AppComponent</span></span>
14
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> view_template</span></span>
15
- <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> h3 { </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;My component&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
16
- <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> a</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">href:HelpPage</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">routing</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) { </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;Would You Like to Know More?&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
17
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
18
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>That means you can replace the file with this code. Other times, we may only focus on one method. We might write &quot;Change <code>view_template</code> in <code>SomeComponent</code> so it looks like so:&quot;</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> view_template</span></span>
19
- <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> h3 { </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;My component&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
20
- <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> a</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">href:HelpPage</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">routing</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) { </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;Would You Like to Know More?&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
21
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>In this case, you&#39;d replace the method, but the leave the rest of the class as-is.</p><p>On occasion we&#39;ll want to only change a few lines and, in that case, we&#39;ll use a diff format like so:</p><div class="language-diff vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">diff</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#B31D28;--shiki-dark:#FDAEB7;">- a(href: &quot;&quot;) { &quot;Write New Blog Post&quot; }</span></span>
22
- <span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">+ a(href: BlogPostEditorPage.routing) { &quot;Write New Blog Post&quot; }</span></span></code></pre></div><p>This says to find the line that looks like the first one (preceded with a <code>-</code> and shown in red) and replace it with the second one (preceded with a <code>+</code> and shown in green). <strong>Do not use the <code>+</code> or <code>-</code> in your code</strong>, that is just to indicate which line is which.</p><p>Lastly, we&#39;ll try to mention the path to the file either in the preceding text or as a comment in the code.</p><p>OK, back to your regularly-scheduled tutorial</p><h2 id="set-up" tabindex="-1">Set Up <a class="header-anchor" href="#set-up" aria-label="Permalink to &quot;Set Up&quot;">​</a></h2><p>The only two pieces of software you need are Docker and a code editor:</p><ol><li><p><a href="https://docker.com" target="_blank" rel="noreferrer">Install Docker</a></p><div class="tip custom-block github-alert"><p class="custom-block-title">TIP</p><p>If you are on Windows, we <em>highly</em> recommend you use the Windows Subystem for Linux (WSL2), as this makes Brut, web developement, and, honestly, your entire life as you know it, far easier than trying to get things working natively in Windows.</p></div></li><li><p>If you are new to programming or new to Ruby and don&#39;t know what editor to get, use VSCode. If you are a vim or emacs person, those will be far better, but if you are used to an IDE, VSCode will be the easiest to get set up and learn to use.</p></li></ol><p>To check that docker is installed, open up a terminal and run:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">docker</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> info</span></span></code></pre></div><p>This should produce a ton of output:</p><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span># OUTPUT</span></span>
1
+ import{_ as i,c as a,o as n,ag as e}from"./chunks/framework.1L-BeKqY.js";const t="/assets/welcome-to-brut.VSWzl17-.png",p="/assets/initial-home-page.DNIaYmgP.png",l="/assets/styled-home-page.BzdI7dWz.png",h="/assets/basic-form.DbHnu0oW.png",o="/assets/basic-form-with-violations.Cv6Y9-Q_.png",k="/assets/styled-form-with-violations.Bv_sa9tg.png",d="/assets/styled-form-with-server-side-violations.Bjxd8Dpv.png",r="/assets/styled-home-page-with-posts.Dd4kG89D.png",c="/assets/new-post-editor.DrHr-5oh.png",g="/assets/new-post-home-page.Bm34lyMg.png",B=JSON.parse('{"title":"Build a Blog in 15 Minutes","description":"","frontmatter":{},"headers":[],"relativePath":"tutorials/01-intro.md","filePath":"tutorials/01-intro.md"}'),E={name:"tutorials/01-intro.md"};function u(y,s,F,b,m,C){return n(),a("div",null,s[0]||(s[0]=[e(`<h1 id="build-a-blog-in-15-minutes" tabindex="-1">Build a Blog in 15 Minutes <a class="header-anchor" href="#build-a-blog-in-15-minutes" aria-label="Permalink to &quot;Build a Blog in 15 Minutes&quot;">​</a></h1><p>This will start from nothing and show you the main features of Brut by building a very basic blog. You&#39;ll learn how to make a new Brut app, how to build pages, submit forms, validate data, and access data in a database. You&#39;ll also learn how to test it all.</p><h2 id="set-up" tabindex="-1">Set Up <a class="header-anchor" href="#set-up" aria-label="Permalink to &quot;Set Up&quot;">​</a></h2><p>The only two pieces of software you need are Docker and a code editor:</p><ol><li><p><a href="https://docker.com" target="_blank" rel="noreferrer">Install Docker</a></p><div class="tip custom-block github-alert"><p class="custom-block-title">TIP</p><p>If you are on Windows, we <em>highly</em> recommend you use the Windows Subystem for Linux (WSL2), as this makes Brut, web developement, and, honestly, your entire life as you know it, far easier than trying to get things working natively in Windows.</p></div></li><li><p>If you are new to programming or new to Ruby and don&#39;t know what editor to get, use VSCode. If you are a vim or emacs person, those will be far better, but if you are used to an IDE, VSCode will be the easiest to get set up and learn to use.</p></li></ol><p>To check that docker is installed, open up a terminal and run:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">docker</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> info</span></span></code></pre></div><p>This should produce a ton of output:</p><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span># OUTPUT</span></span>
23
2
  <span class="line"><span>Client:</span></span>
24
3
  <span class="line"><span> Version: 28.2.2</span></span>
25
4
  <span class="line"><span>«LOTS OF OUTPUT»</span></span></code></pre></div><p>To be extra sure, <strong>right after you ran <code>docker info</code></strong>, check <code>$?</code>, the exit code, to make sure it&#39;s a 0, which means the command ran successfully:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">echo</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> $?</span></span></code></pre></div><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span># OUTPUT</span></span>
26
5
  <span class="line"><span>0</span></span></code></pre></div><p>Now, let&#39;s create the app by first initializing it.</p><h2 id="initialize-your-app" tabindex="-1">Initialize Your App <a class="header-anchor" href="#initialize-your-app" aria-label="Permalink to &quot;Initialize Your App&quot;">​</a></h2><p><code>mkbrut</code> is a command line app that will initialize your new app. It&#39;s available as a RubyGem or a Docker image. We&#39;ll use the Docker image since that doesn&#39;t require installing anything.</p><p>We&#39;ll call the blog simply &quot;blog&quot;. <code>mkbrut</code> will insert some demo features in new apps to show you have to use Brut. Since you&#39;re following this tutorial, you don&#39;t need that, so we&#39;ll use the <code>--no-demo</code> flag.</p><p><code>cd</code> to a folder where you&#39;d like to work. <code>mkbrut</code> will create a folder called <code>blog</code> in there and in <em>that</em> folder, your app will be initialized.</p><p>The command to do this is pretty long, because it downloads <code>mkbrut</code> and then runs it inside a Docker container, meaning you don&#39;t have to install anything new. Here it is:</p><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span>docker run \\</span></span>
6
+ <span class="line"><span> --pull always \\</span></span>
27
7
  <span class="line"><span> -v &quot;$PWD&quot;:&quot;$PWD&quot; \\</span></span>
28
8
  <span class="line"><span> -w &quot;$PWD&quot; \\</span></span>
29
9
  <span class="line"><span> -u $(id -u):$(id -g) \\</span></span>
@@ -157,7 +137,7 @@ import{_ as a,c as i,o as n,ag as e}from"./chunks/framework.1L-BeKqY.js";const t
157
137
  <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> main </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
158
138
  <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> p</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;Posts go here&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
159
139
  <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
160
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>If you&#39;ve never used Phlex before, it&#39;s a Ruby API that defines one method for each HTML element (along with any custom elements you tell it about). You then call these methods to build up markup. As you can see, it&#39;s structurally identical to HTML, but it&#39;s Ruby.</p><p>If your server is still running, refresh the page and you&#39;ll see this wonderful UI (otherwise, start your server with <code>bin/dev</code>):</p><p><img src="`+l+`" alt="Screenshot of the page we built"></p><p>Let&#39;s make it a bit nicer.</p><h3 id="using-css" tabindex="-1">Using CSS <a class="header-anchor" href="#using-css" aria-label="Permalink to &quot;Using CSS&quot;">​</a></h3><p>Open up <code>app/src/front_end/css/index.css</code> in your editor. You should see this:</p><div class="language-css vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">css</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">@import</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;brut-css/dist/brut.css&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
140
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>If you&#39;ve never used Phlex before, it&#39;s a Ruby API that defines one method for each HTML element (along with any custom elements you tell it about). You then call these methods to build up markup. As you can see, it&#39;s structurally identical to HTML, but it&#39;s Ruby.</p><p>If your server is still running, refresh the page and you&#39;ll see this wonderful UI (otherwise, start your server with <code>bin/dev</code>):</p><p><img src="`+p+`" alt="Screenshot of the page we built"></p><p>Let&#39;s make it a bit nicer.</p><h3 id="using-css" tabindex="-1">Using CSS <a class="header-anchor" href="#using-css" aria-label="Permalink to &quot;Using CSS&quot;">​</a></h3><p>Open up <code>app/src/front_end/css/index.css</code> in your editor. You should see this:</p><div class="language-css vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">css</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">@import</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;brut-css/dist/brut.css&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
161
141
  <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">@import</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;svgs.css&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span></code></pre></div><p>Brut uses esbuild to bundle CSS. esbuild makes use of the standard <code>@import</code> directive. All <code>@imports</code> are relative to the current file or to <code>node_modules</code>. <code>brut-css/dist/brut.css</code> is the BrutCSS library that comes with Brut. We aren&#39;t going to use it, just to keep things focused. <code>svgs.css</code> is located in <code>app/src/front_end/css/svgs.css</code> and sets up a few classes for inline SVGs.</p><p>We&#39;ll add some CSS for the home page right here. We&#39;ll use vanilla CSS to avoid going on a deep dive on CSS frameworks.</p><p>Add this below <code>@import &quot;svgs.css&quot;;</code></p><div class="language-css vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">css</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">body</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
162
142
  <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> width</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">50</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">%</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
163
143
  <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> margin-left</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">auto</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
@@ -171,7 +151,7 @@ import{_ as a,c as i,o as n,ag as e}from"./chunks/framework.1L-BeKqY.js";const t
171
151
  <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> justify-content</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">space-between</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
172
152
  <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> width</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">100</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">%</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
173
153
  <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> gap</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">0.5</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">rem</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
174
- <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><p>If you reload the home page in your browser, it now looks at least a little bit respectible:</p><p><img src="`+p+`" alt="Screenshot of the styled home page"></p><p>Now, let&#39;s build the blog post editor.</p><h2 id="creating-forms" tabindex="-1">Creating Forms <a class="header-anchor" href="#creating-forms" aria-label="Permalink to &quot;Creating Forms&quot;">​</a></h2><p>To create blog posts, we need three things:</p><ul><li>A page where the creation happens, which will host an HTML <code>&lt;form&gt;</code></li><li>A URL where that <code>&lt;form&gt;</code> will be submitted</li><li>Some code to handle the submissions</li></ul><h3 id="creating-a-new-page" tabindex="-1">Creating a New Page <a class="header-anchor" href="#creating-a-new-page" aria-label="Permalink to &quot;Creating a New Page&quot;">​</a></h3><p>To make a new page in Brut, we&#39;ll need to declare a route, and Brut will choose the class name. We&#39;ll use <code>/blog_post_editor</code>, meaning Brut will expect <code>BlogPostEditorPage</code> to exist. We can do all this at once with <code>bin/scaffold page</code>. <code>bin/scaffold page</code> accepts the URL of the page we want to build. Brut will use that URL to figure out the page class&#39; name and generate it, along with a failing test. It will also insert the route into <code>app.rb</code>. Run it now, like so:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">bin/scaffold</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> page</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> /blog_post_editor</span></span></code></pre></div><p>Your output should look like so:</p><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span># OUTPUT</span></span>
154
+ <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><p>If you reload the home page in your browser, it now looks at least a little bit respectible:</p><p><img src="`+l+`" alt="Screenshot of the styled home page"></p><p>Now, let&#39;s build the blog post editor.</p><h2 id="creating-forms" tabindex="-1">Creating Forms <a class="header-anchor" href="#creating-forms" aria-label="Permalink to &quot;Creating Forms&quot;">​</a></h2><p>To create blog posts, we need three things:</p><ul><li>A page where the creation happens, which will host an HTML <code>&lt;form&gt;</code></li><li>A URL where that <code>&lt;form&gt;</code> will be submitted</li><li>Some code to handle the submissions</li></ul><h3 id="creating-a-new-page" tabindex="-1">Creating a New Page <a class="header-anchor" href="#creating-a-new-page" aria-label="Permalink to &quot;Creating a New Page&quot;">​</a></h3><p>To make a new page in Brut, we&#39;ll need to declare a route, and Brut will choose the class name. We&#39;ll use <code>/blog_post_editor</code>, meaning Brut will expect <code>BlogPostEditorPage</code> to exist. We can do all this at once with <code>bin/scaffold page</code>. <code>bin/scaffold page</code> accepts the URL of the page we want to build. Brut will use that URL to figure out the page class&#39; name and generate it, along with a failing test. It will also insert the route into <code>app.rb</code>. Run it now, like so:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">bin/scaffold</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> page</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> /blog_post_editor</span></span></code></pre></div><p>Your output should look like so:</p><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span># OUTPUT</span></span>
175
155
  <span class="line"><span>[ bin/scaffold ] Inserted route into app/src/app.rb</span></span>
176
156
  <span class="line"><span>[ bin/scaffold ] Page source is in app/src/front_end/pages/blog_post_editor_page.rb</span></span>
177
157
  <span class="line"><span>[ bin/scaffold ] Page test is in specs/front_end/pages/blog_post_editor_page.spec.rb</span></span>
@@ -401,7 +381,7 @@ import{_ as a,c as i,o as n,ag as e}from"./chunks/framework.1L-BeKqY.js";const t
401
381
  <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">join</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">\\n\\r</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
402
382
  <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
403
383
  <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
404
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>Brut includes a test to make sure your factories are valid and will work. It&#39;s in <code>specs/lint_factories.spec.rb</code>. Run it now to make sure this factory works:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">bin/test</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> run</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> specs/lint_factories.spec.rb</span></span></code></pre></div><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span># OUTPUT</span></span>
384
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>Brut includes a test to make sure your factories are valid and will work. It&#39;s in <code>specs/lint_factories.spec.rb</code>. Run it now to make sure this factory works:</p><div class="tip custom-block github-alert"><p class="custom-block-title">TIP</p><p>If you stopped your entire dev environment (<code>dx/start</code>), when you restart it, you <em>must</em> re-run <code>bin/setup</code>, since the disk inside your dev environment is ephemeral.</p></div><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">bin/test</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> run</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> specs/lint_factories.spec.rb</span></span></code></pre></div><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span># OUTPUT</span></span>
405
385
  <span class="line"><span>[ bin/test ] Executing [&quot;bin/rspec -I /Users/davec/Projects/ThirdTank/brutcasts/01-make-a-blog/blog/specs -I /Users/davec/Projects/ThirdTank/brutcasts/01-make-a-blog/blog/app/src -I lib/ --tag ~e2e -P \\&quot;**/*.spec.rb\\&quot; \\&quot;specs/lint_factories.spec.rb\\&quot;&quot;]</span></span>
406
386
  <span class="line"><span>Run options: exclude {e2e: true}</span></span>
407
387
  <span class="line"><span></span></span>
@@ -426,7 +406,7 @@ import{_ as a,c as i,o as n,ag as e}from"./chunks/framework.1L-BeKqY.js";const t
426
406
  <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p><code>create</code> is a method provided by Factory Bot that is brought in via <code>FactoryBot::Syntax::Methods</code>.</p><p>Now, load the seed data into the development database with <code>bin/db seed</code>:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">bin/db</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> seed</span></span></code></pre></div><p>We can now show this data on the home page.</p><h2 id="accessing-the-database" tabindex="-1">Accessing the Database <a class="header-anchor" href="#accessing-the-database" aria-label="Permalink to &quot;Accessing the Database&quot;">​</a></h2><p>On <code>HomePage</code>, we put in a <code>&lt;p&gt;</code> as a placeholder for blog posts. Let&#39;s replace that with code to fetch and display the blog posts.</p><p>In Brut, since HTML is generated by Phlex and thus by Ruby code, we can structure our HTML generation however we like, including by accessing the database directly. This may not scale as our app gets large, but for now, it&#39;s the simplest thing to do.</p><p>Sequel&#39;s database models are similar to Rails&#39; Active Record&#39;s in that we can call class methods to access data. In this case, <code>DB::BlogPost</code> has a method <code>order</code> that will fetch all records sorted by the field we give it in the order we decide. The sort field and order is specified via <code>Sequel.desc</code> for descending or <code>Sequel.asc</code> for ascending. We want posts in reverse-chronological order, so <code>Sequel.desc(:created_at)</code> will achieve this.</p><p>We can call <code>.each</code> on the result and iterate over each blog post. For the content, we&#39;ll split by <code>\\n\\r</code> to create paragraphs.</p><p>Here&#39;s what <code>HomePage</code>&#39;s <code>page_template</code> should look like now:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> page_template</span></span>
427
407
  <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> header </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
428
408
  <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> h1 { </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;My Amazing Blog&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
429
- <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> a</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">href:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) { </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;Write New Blog Post&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
409
+ <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> a</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">href:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> BlogPostEditorPage</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">routing</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) { </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;Write New Blog Post&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
430
410
  <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
431
411
  <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> main </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">do</span></span>
432
412
  <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> DB</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">::</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">BlogPost</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">order</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">Sequel</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">desc</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">:created_at</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">each</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> do</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> |blog_post|</span></span>
@@ -458,7 +438,7 @@ import{_ as a,c as i,o as n,ag as e}from"./chunks/framework.1L-BeKqY.js";const t
458
438
  <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> else</span></span>
459
439
  <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> NewBlogPostPage</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">new</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">form:</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> @form)</span></span>
460
440
  <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
461
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>The form object provides access to the values of any field we&#39;ve declared via a method call.</p><p>Now, create a new blog post, provide valid data, and submit it.</p><p><img src="`+c+'" alt="Screenshot of the blog post editor, with a new post filled in"></p><p>Once you submit it, you should see the homage page with your new post at the top:</p><p><img src="'+g+`" alt="Screenshot of the home page, showing the new blog post"></p><p>Our work isn&#39;t quite done. We need tests.</p><h2 id="testing-brut-apps" tabindex="-1">Testing Brut Apps <a class="header-anchor" href="#testing-brut-apps" aria-label="Permalink to &quot;Testing Brut Apps&quot;">​</a></h2><p>We&#39;ll create the following tests:</p><ul><li>Check that the logic in the handler is sound</li><li>Check that blog posts show up on the home page</li><li>Check that the entire workflow of create a blog post and seeing it show up on the home page works in a real web browser</li></ul><p>Let&#39;s test our handler first, as that is where the main logic is.</p><h3 id="testing-handlers" tabindex="-1">Testing Handlers <a class="header-anchor" href="#testing-handlers" aria-label="Permalink to &quot;Testing Handlers&quot;">​</a></h3><p>Our handler will need three tests:</p><ul><li>If the form was submitted without client-side validations happening, we should not create a new blog post and re-generate the blog post editor page, showing the errors.</li><li>If client-side validations pass, but the blog post isn&#39;t five words or more, we should not create a new blog post and re-generate the blog post editor page, showing the errors.</li><li>If everything looks good, we save the new blog post and redirect to the home page.</li></ul><p>Brut apps are tested with RSpec, and Brut provides several convenience methods and matchers to make testing as painless as possible.</p><p>When testing a handler, the public method is <code>handle!</code>, not <code>handle</code>, so we want to call that (Brut implements <code>handle!</code> to call <code>handle</code>).</p><p>First, we&#39;ll test client-side validations. Open up <code>specs/front_end/handlers/new_blog_post_handler.spec.rb</code> and replace the code there with this:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">require</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;spec_helper&quot;</span></span>
441
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>The form object provides access to the values of any field we&#39;ve declared via a method call.</p><p>Now, create a new blog post, provide valid data, and submit it.</p><p><img src="`+c+'" alt="Screenshot of the blog post editor, with a new post filled in"></p><p>Once you submit it, you should see the home page with your new post at the top:</p><p><img src="'+g+`" alt="Screenshot of the home page, showing the new blog post"></p><p>Our work isn&#39;t quite done. We need tests.</p><h2 id="testing-brut-apps" tabindex="-1">Testing Brut Apps <a class="header-anchor" href="#testing-brut-apps" aria-label="Permalink to &quot;Testing Brut Apps&quot;">​</a></h2><p>We&#39;ll create the following tests:</p><ul><li>Check that the logic in the handler is sound</li><li>Check that blog posts show up on the home page</li><li>Check that the entire workflow of create a blog post and seeing it show up on the home page works in a real web browser</li></ul><p>Let&#39;s test our handler first, as that is where the main logic is.</p><h3 id="testing-handlers" tabindex="-1">Testing Handlers <a class="header-anchor" href="#testing-handlers" aria-label="Permalink to &quot;Testing Handlers&quot;">​</a></h3><p>Our handler will need three tests:</p><ul><li>If the form was submitted without client-side validations happening, we should not create a new blog post and re-generate the blog post editor page, showing the errors.</li><li>If client-side validations pass, but the blog post isn&#39;t five words or more, we should not create a new blog post and re-generate the blog post editor page, showing the errors.</li><li>If everything looks good, we save the new blog post and redirect to the home page.</li></ul><p>Brut apps are tested with RSpec, and Brut provides several convenience methods and matchers to make testing as painless as possible.</p><p>When testing a handler, the public method is <code>handle!</code>, not <code>handle</code>, so we want to call that (Brut implements <code>handle!</code> to call <code>handle</code>).</p><p>First, we&#39;ll test client-side validations. Open up <code>specs/front_end/handlers/new_blog_post_handler.spec.rb</code> and replace the code there with this:</p><div class="language-ruby vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ruby</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">require</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> &quot;spec_helper&quot;</span></span>
462
442
  <span class="line"></span>
463
443
  <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">RSpec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">describe</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> NewBlogPostHandler</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> do</span></span>
464
444
  <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> describe </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;#handle!&quot;</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> do</span></span>
@@ -509,7 +489,7 @@ import{_ as a,c as i,o as n,ag as e}from"./chunks/framework.1L-BeKqY.js";const t
509
489
  <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> expect</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(blog_post.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">content</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">to</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> eq</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;This post is the best post that has been written in the history of posts&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
510
490
  <span class="line"></span>
511
491
  <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
512
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>This is using RSpec&#39;s <code>expect { ... }.to change { ... }.by(N)</code> to make sure that our handler created a row in the database. We then use the matcher <code>have_redirected_to</code> to assert that <code>result</code> is a URI to <code>HomePage</code>. We also check that the blog post we created in the database is correct.</p><p>Let&#39;s run the test with <code>bin/test run</code></p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">bin/test</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> run</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> specs/front_end/handlers/new_blog_post_handler.spec.rb</span></span></code></pre></div><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span># OUTPUT</span></span>
492
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>This is using RSpec&#39;s <code>expect { ... }.to change { ... }.by(N)</code> to make sure that our handler created a row in the database. We then use the matcher <code>have_redirected_to</code> to assert that <code>result</code> is a URI to <code>HomePage</code>. We also check that the blog post we created in the database is correct.</p><p>Let&#39;s run the test with <code>bin/test run</code></p><div class="tip custom-block github-alert"><p class="custom-block-title">TIP</p><p>If you stopped your entire dev environment (<code>dx/start</code>), when you restart it, you <em>must</em> re-run <code>bin/setup</code>, since the disk inside your dev environment is ephemeral.</p></div><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">bin/test</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> run</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> specs/front_end/handlers/new_blog_post_handler.spec.rb</span></span></code></pre></div><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span># OUTPUT</span></span>
513
493
  <span class="line"><span>[ bin/test ] Executing [&quot;bin/rspec -I /Users/davec/Projects/ThirdTank/brutcasts/01-make-a-blog/blog/specs -I /Users/davec/Projects/ThirdTank/brutcasts/01-make-a-blog/blog/app/src -I lib/ --tag ~e2e -P \\&quot;**/*.spec.rb\\&quot; \\&quot;specs/front_end/handlers/new_blog_post_handler.spec.rb\\&quot;&quot;]</span></span>
514
494
  <span class="line"><span>Run options: exclude {e2e: true}</span></span>
515
495
  <span class="line"><span></span></span>
@@ -548,7 +528,7 @@ import{_ as a,c as i,o as n,ag as e}from"./chunks/framework.1L-BeKqY.js";const t
548
528
  <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
549
529
  <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
550
530
  <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
551
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>Let&#39;s run the test, which should fail:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">bin/test</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> run</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> specs/front_end/pages/home_page.spec.rb</span></span></code></pre></div><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span># OUTPUT</span></span>
531
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>Let&#39;s run the test, which should fail:</p><div class="tip custom-block github-alert"><p class="custom-block-title">TIP</p><p>If you stopped your entire dev environment (<code>dx/start</code>), when you restart it, you <em>must</em> re-run <code>bin/setup</code>, since the disk inside your dev environment is ephemeral.</p></div><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">bin/test</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> run</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> specs/front_end/pages/home_page.spec.rb</span></span></code></pre></div><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span># OUTPUT</span></span>
552
532
  <span class="line"><span>[ bin/test ] Executing [&quot;bin/rspec -I /Users/davec/Projects/ThirdTank/brutcasts/01-make-a-blog/blog/specs -I /Users/davec/Projects/ThirdTank/brutcasts/01-make-a-blog/blog/app/src -I lib/ --tag ~e2e -P \\&quot;**/*.spec.rb\\&quot; \\&quot;specs/front_end/pages/home_page.spec.rb\\&quot;&quot;]</span></span>
553
533
  <span class="line"><span>Run options: exclude {e2e: true}</span></span>
554
534
  <span class="line"><span></span></span>
@@ -588,7 +568,7 @@ import{_ as a,c as i,o as n,ag as e}from"./chunks/framework.1L-BeKqY.js";const t
588
568
  <span class="line"><span>Randomized with seed 44491</span></span>
589
569
  <span class="line"><span></span></span>
590
570
  <span class="line"><span>[ bin/test ] error: [&quot;bin/rspec -I /Users/davec/Projects/ThirdTank/brutcasts/01-make-a-blog/blog/specs -I /Users/davec/Projects/ThirdTank/brutcasts/01-make-a-blog/blog/app/src -I lib/ --tag ~e2e -P \\&quot;**/*.spec.rb\\&quot; \\&quot;specs/front_end/pages/home_page.spec.rb\\&quot;&quot;] failed - exited 1</span></span></code></pre></div><p>Brut obviously errs on the side of being verbose. But, you can see that the problem is that it cannot find an <code>&lt;article&gt;</code> with the <code>id=</code> of <code>blbl_6f04feaefb9520d86b19c3ac4ad22c4f</code>, the <code>external_id</code> of the first blog post.</p><p>To make it pass, we&#39;ll need to add <code>id:</code> to each <code>&lt;article&gt;</code>. Make this one-line change in <code>HomePage</code>:</p><div class="language-diff vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">diff</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#B31D28;--shiki-dark:#FDAEB7;">- article do</span></span>
591
- <span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">+ article(id: blog_post.external_id) do</span></span></code></pre></div><div class="tip custom-block github-alert"><p class="custom-block-title">TIP</p><p>This shows a useful feature of the <code>external_id</code>: Because it&#39;s not only unique to the database table, but also across <em>all</em> database tables, it makes a pretty good <code>ID</code> inside an HTML page, since it&#39;s highly unlikely any other part of the page would use that value for the <code>id=</code> of an element.</p></div><p>Now, the test should pass:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">bin/test</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> run</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> specs/front_end/pages/home_page.spec.rb</span></span></code></pre></div><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span># OUTPUT</span></span>
571
+ <span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">+ article(id: blog_post.external_id) do</span></span></code></pre></div><div class="tip custom-block github-alert"><p class="custom-block-title">TIP</p><p>This shows a useful feature of the <code>external_id</code>: Because it&#39;s not only unique to the database table, but also across <em>all</em> database tables, it makes a pretty good <code>ID</code> inside an HTML page, since it&#39;s highly unlikely any other part of the page would use that value for the <code>id=</code> of an element.</p></div><p>Now, the test should pass:</p><div class="tip custom-block github-alert"><p class="custom-block-title">TIP</p><p>If you stopped your entire dev environment (<code>dx/start</code>), when you restart it, you <em>must</em> re-run <code>bin/setup</code>, since the disk inside your dev environment is ephemeral.</p></div><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">bin/test</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> run</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> specs/front_end/pages/home_page.spec.rb</span></span></code></pre></div><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span># OUTPUT</span></span>
592
572
  <span class="line"><span>[ bin/test ] Executing [&quot;bin/rspec -I /Users/davec/Projects/ThirdTank/brutcasts/01-make-a-blog/blog/specs -I /Users/davec/Projects/ThirdTank/brutcasts/01-make-a-blog/blog/app/src -I lib/ --tag ~e2e -P \\&quot;**/*.spec.rb\\&quot; \\&quot;specs/front_end/pages/home_page.spec.rb\\&quot;&quot;]</span></span>
593
573
  <span class="line"><span>Run options: exclude {e2e: true}</span></span>
594
574
  <span class="line"><span></span></span>
@@ -606,7 +586,7 @@ import{_ as a,c as i,o as n,ag as e}from"./chunks/framework.1L-BeKqY.js";const t
606
586
  <span class="line"></span>
607
587
  <span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">RSpec</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">describe</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> BlogPostEditorPage</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> do</span></span>
608
588
  <span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> implementation_is_covered_by_other_tests </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;end-to-end test&quot;</span></span>
609
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>Now, all unit tests should pass, which we can check via <code>bin/test run</code>:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">bin/test</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> run</span></span></code></pre></div><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span># OUTPUT</span></span>
589
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>Now, all unit tests should pass, which we can check via <code>bin/test run</code>:</p><div class="tip custom-block github-alert"><p class="custom-block-title">TIP</p><p>If you stopped your entire dev environment (<code>dx/start</code>), when you restart it, you <em>must</em> re-run <code>bin/setup</code>, since the disk inside your dev environment is ephemeral.</p></div><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">bin/test</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> run</span></span></code></pre></div><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span># OUTPUT</span></span>
610
590
  <span class="line"><span>[ bin/test ] Running all tests</span></span>
611
591
  <span class="line"><span>[ bin/test ] Executing [&quot;bin/rspec -I /Users/davec/Projects/ThirdTank/brutcasts/01-make-a-blog/blog/specs -I /Users/davec/Projects/ThirdTank/brutcasts/01-make-a-blog/blog/app/src -I lib/ --tag ~e2e -P \\&quot;**/*.spec.rb\\&quot; /Users/davec/Projects/ThirdTank/brutcasts/01-make-a-blog/blog/specs/&quot;]</span></span>
612
592
  <span class="line"><span>Run options: exclude {e2e: true}</span></span>
@@ -672,7 +652,7 @@ import{_ as a,c as i,o as n,ag as e}from"./chunks/framework.1L-BeKqY.js";const t
672
652
  <span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> expect</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(article).</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">to</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> have_text</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;This is a longer post, so we should be OK&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
673
653
  <span class="line"></span>
674
654
  <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> end</span></span>
675
- <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>Run it now with <code>bin/test e2e</code>:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">bin/test</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> e2e</span></span></code></pre></div><p>It should pass:</p><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span># OUTPUT</span></span>
655
+ <span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">end</span></span></code></pre></div><p>Run it now with <code>bin/test e2e</code>:</p><div class="tip custom-block github-alert"><p class="custom-block-title">TIP</p><p>If you stopped your entire dev environment (<code>dx/start</code>), when you restart it, you <em>must</em> re-run <code>bin/setup</code>, since the disk inside your dev environment is ephemeral.</p></div><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">bin/test</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> e2e</span></span></code></pre></div><p>It should pass:</p><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span># OUTPUT</span></span>
676
656
  <span class="line"><span>[ bin/test ] Rebuilding test database schema</span></span>
677
657
  <span class="line"><span>[ bin/test ] Executing [&quot;bin/db rebuild --env=test&quot;]</span></span>
678
658
  <span class="line"><span>[ bin/db ] Database exists. Dropping...</span></span>
@@ -725,4 +705,4 @@ import{_ as a,c as i,o as n,ag as e}from"./chunks/framework.1L-BeKqY.js";const t
725
705
  <span class="line"><span>No vulnerabilities found</span></span>
726
706
  <span class="line"><span>[ bin/ci ] Checking to see that all classes have tests</span></span>
727
707
  <span class="line"><span>[ bin/test ] All tests exists!</span></span>
728
- <span class="line"><span>[ bin/ci ] Done</span></span></code></pre></div><p>That&#39;s it!</p><h2 id="areas-for-self-exploration" tabindex="-1">Areas for Self-Exploration <a class="header-anchor" href="#areas-for-self-exploration" aria-label="Permalink to &quot;Areas for Self-Exploration&quot;">​</a></h2><p>Here are a few enhancement you can try to make:</p><ul><li>Create a client-side constraint requiring the title to match a certain regexp.</li><li>Add a server-side constraint requiring at least two paragraphs.</li><li>Allow editing the blog post creation date</li><li>Add an author field to allow entering the author&#39;s name</li><li>Add pagination to the home page</li></ul>`,358)]))}const f=a(E,[["render",u]]);export{B as __pageData,f as default};
708
+ <span class="line"><span>[ bin/ci ] Done</span></span></code></pre></div><p>That&#39;s it!</p><h2 id="areas-for-self-exploration" tabindex="-1">Areas for Self-Exploration <a class="header-anchor" href="#areas-for-self-exploration" aria-label="Permalink to &quot;Areas for Self-Exploration&quot;">​</a></h2><p>Here are a few enhancement you can try to make:</p><ul><li>Create a client-side constraint requiring the title to match a certain regexp.</li><li>Add a server-side constraint requiring at least two paragraphs.</li><li>Allow editing the blog post creation date</li><li>Add an author field to allow entering the author&#39;s name</li><li>Add pagination to the home page</li></ul>`,330)]))}const f=i(E,[["render",u]]);export{B as __pageData,f as default};
@@ -0,0 +1 @@
1
+ import{_ as i,c as a,o as n,ag as e}from"./chunks/framework.1L-BeKqY.js";const t="/assets/welcome-to-brut.VSWzl17-.png",p="/assets/initial-home-page.DNIaYmgP.png",l="/assets/styled-home-page.BzdI7dWz.png",h="/assets/basic-form.DbHnu0oW.png",o="/assets/basic-form-with-violations.Cv6Y9-Q_.png",k="/assets/styled-form-with-violations.Bv_sa9tg.png",d="/assets/styled-form-with-server-side-violations.Bjxd8Dpv.png",r="/assets/styled-home-page-with-posts.Dd4kG89D.png",c="/assets/new-post-editor.DrHr-5oh.png",g="/assets/new-post-home-page.Bm34lyMg.png",B=JSON.parse('{"title":"Build a Blog in 15 Minutes","description":"","frontmatter":{},"headers":[],"relativePath":"tutorials/01-intro.md","filePath":"tutorials/01-intro.md"}'),E={name:"tutorials/01-intro.md"};function u(y,s,F,b,m,C){return n(),a("div",null,s[0]||(s[0]=[e("",330)]))}const f=i(E,[["render",u]]);export{B as __pageData,f as default};