brut 0.0.26 → 0.0.28

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 (386) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/brutrb.com/.vitepress/config.mjs +2 -0
  4. data/brutrb.com/ai.md +6 -1
  5. data/brutrb.com/components.md +1 -1
  6. data/brutrb.com/dev-environment.md +6 -5
  7. data/brutrb.com/doc-conventions.md +1 -1
  8. data/brutrb.com/forms.md +22 -26
  9. data/brutrb.com/getting-started.md +94 -23
  10. data/brutrb.com/instrumentation.md +12 -0
  11. data/brutrb.com/layouts.md +130 -0
  12. data/brutrb.com/lsp.md +23 -0
  13. data/docs/404.html +2 -2
  14. data/docs/ai.html +5 -5
  15. data/docs/api/Brut/BackEnd/SeedData.html +1 -1
  16. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +1 -1
  17. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +1 -1
  18. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +1 -1
  19. data/docs/api/Brut/BackEnd/Sidekiq.html +1 -1
  20. data/docs/api/Brut/BackEnd/Validators/FormValidator.html +1 -1
  21. data/docs/api/Brut/BackEnd/Validators.html +1 -1
  22. data/docs/api/Brut/BackEnd.html +1 -1
  23. data/docs/api/Brut/CLI/App.html +38 -13
  24. data/docs/api/Brut/CLI/AppRunner.html +1 -1
  25. data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +2 -2
  26. data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +2 -2
  27. data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +2 -2
  28. data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +2 -2
  29. data/docs/api/Brut/CLI/Apps/BuildAssets.html +1 -1
  30. data/docs/api/Brut/CLI/Apps/DB/Create.html +2 -2
  31. data/docs/api/Brut/CLI/Apps/DB/Drop.html +2 -2
  32. data/docs/api/Brut/CLI/Apps/DB/Migrate.html +9 -3
  33. data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +11 -11
  34. data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +2 -2
  35. data/docs/api/Brut/CLI/Apps/DB/Seed.html +2 -2
  36. data/docs/api/Brut/CLI/Apps/DB/Status.html +12 -12
  37. data/docs/api/Brut/CLI/Apps/DB.html +1 -1
  38. data/docs/api/Brut/CLI/Apps/DeployBase/GitChecks.html +270 -0
  39. data/docs/api/Brut/CLI/Apps/DeployBase.html +257 -0
  40. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy/Deploy.html +585 -0
  41. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy.html +196 -0
  42. data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +1 -1
  43. data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +2 -2
  44. data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +2 -2
  45. data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +2 -2
  46. data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +2 -2
  47. data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +2 -2
  48. data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +1 -1
  49. data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +2 -2
  50. data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +1 -1
  51. data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +2 -2
  52. data/docs/api/Brut/CLI/Apps/Scaffold.html +1 -1
  53. data/docs/api/Brut/CLI/Apps/Test/Audit.html +12 -10
  54. data/docs/api/Brut/CLI/Apps/Test/E2e.html +8 -8
  55. data/docs/api/Brut/CLI/Apps/Test/JS.html +9 -9
  56. data/docs/api/Brut/CLI/Apps/Test/Run.html +18 -18
  57. data/docs/api/Brut/CLI/Apps/Test.html +1 -1
  58. data/docs/api/Brut/CLI/Apps.html +2 -2
  59. data/docs/api/Brut/CLI/Command.html +113 -28
  60. data/docs/api/Brut/CLI/Error.html +1 -1
  61. data/docs/api/Brut/CLI/ExecutionResults/Result.html +1 -1
  62. data/docs/api/Brut/CLI/ExecutionResults.html +1 -1
  63. data/docs/api/Brut/CLI/Executor.html +169 -38
  64. data/docs/api/Brut/CLI/InvalidOption.html +1 -1
  65. data/docs/api/Brut/CLI/Options.html +68 -19
  66. data/docs/api/Brut/CLI/Output.html +1 -1
  67. data/docs/api/Brut/CLI/SystemExecError.html +1 -1
  68. data/docs/api/Brut/CLI.html +1 -1
  69. data/docs/api/Brut/FactoryBot.html +1 -1
  70. data/docs/api/Brut/Framework/App.html +1 -1
  71. data/docs/api/Brut/Framework/Config.html +1 -1
  72. data/docs/api/Brut/Framework/Container.html +110 -29
  73. data/docs/api/Brut/Framework/Error.html +1 -1
  74. data/docs/api/Brut/Framework/Errors/AbstractMethod.html +89 -1
  75. data/docs/api/Brut/Framework/Errors/Bug.html +1 -1
  76. data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +1 -1
  77. data/docs/api/Brut/Framework/Errors/MissingParameter.html +1 -1
  78. data/docs/api/Brut/Framework/Errors/NoClassForPath.html +1 -1
  79. data/docs/api/Brut/Framework/Errors/NotFound.html +1 -1
  80. data/docs/api/Brut/Framework/Errors/NotImplemented.html +1 -1
  81. data/docs/api/Brut/Framework/Errors.html +31 -8
  82. data/docs/api/Brut/Framework/FussyTypeEnforcement.html +1 -1
  83. data/docs/api/Brut/Framework/MCP.html +1 -1
  84. data/docs/api/Brut/Framework/ProjectEnvironment.html +1 -1
  85. data/docs/api/Brut/Framework.html +1 -1
  86. data/docs/api/Brut/FrontEnd/AssetPathResolver.html +1 -1
  87. data/docs/api/Brut/FrontEnd/Component/Helpers.html +36 -26
  88. data/docs/api/Brut/FrontEnd/Component.html +7 -7
  89. data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +1 -1
  90. data/docs/api/Brut/FrontEnd/Components/FormTag.html +37 -29
  91. data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +1 -1
  92. data/docs/api/Brut/FrontEnd/Components/Input.html +1 -1
  93. data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +1 -1
  94. data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +20 -117
  95. data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +25 -23
  96. data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +73 -380
  97. data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +22 -138
  98. data/docs/api/Brut/FrontEnd/Components/Inputs.html +1 -1
  99. data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +1 -1
  100. data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +1 -1
  101. data/docs/api/Brut/FrontEnd/Components/TimeTag.html +1 -1
  102. data/docs/api/Brut/FrontEnd/Components/Traceparent.html +1 -1
  103. data/docs/api/Brut/FrontEnd/Components.html +23 -2
  104. data/docs/api/Brut/FrontEnd/Download.html +1 -1
  105. data/docs/api/Brut/FrontEnd/Flash.html +1 -1
  106. data/docs/api/Brut/FrontEnd/Form.html +1 -1
  107. data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +24 -68
  108. data/docs/api/Brut/FrontEnd/Forms/Input.html +1 -1
  109. data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +4 -4
  110. data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +1 -1
  111. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +1 -1
  112. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +1 -1
  113. data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +1 -1
  114. data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +1 -1
  115. data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +72 -22
  116. data/docs/api/Brut/FrontEnd/Forms.html +1 -1
  117. data/docs/api/Brut/FrontEnd/GenericResponse.html +1 -1
  118. data/docs/api/Brut/FrontEnd/Handler.html +1 -1
  119. data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +1 -1
  120. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +1 -1
  121. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +1 -1
  122. data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +1 -1
  123. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +1 -1
  124. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +1 -1
  125. data/docs/api/Brut/FrontEnd/Handlers.html +1 -1
  126. data/docs/api/Brut/FrontEnd/HandlingResults.html +1 -1
  127. data/docs/api/Brut/FrontEnd/HttpMethod.html +1 -1
  128. data/docs/api/Brut/FrontEnd/HttpStatus.html +1 -1
  129. data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +1 -1
  130. data/docs/api/Brut/FrontEnd/Layout.html +1 -1
  131. data/docs/api/Brut/FrontEnd/Middleware.html +1 -1
  132. data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +1 -1
  133. data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +1 -1
  134. data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +1 -1
  135. data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +6 -2
  136. data/docs/api/Brut/FrontEnd/Middlewares.html +1 -1
  137. data/docs/api/Brut/FrontEnd/Page.html +1 -1
  138. data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +1 -1
  139. data/docs/api/Brut/FrontEnd/Pages.html +1 -1
  140. data/docs/api/Brut/FrontEnd/RequestContext.html +1 -1
  141. data/docs/api/Brut/FrontEnd/RouteHook.html +1 -1
  142. data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +1 -1
  143. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +1 -1
  144. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +1 -1
  145. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +1 -1
  146. data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +1 -1
  147. data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +1 -1
  148. data/docs/api/Brut/FrontEnd/RouteHooks.html +1 -1
  149. data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +1 -1
  150. data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +1 -1
  151. data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +1 -1
  152. data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +1 -1
  153. data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +1 -1
  154. data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +1 -1
  155. data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +1 -1
  156. data/docs/api/Brut/FrontEnd/Routing/Route.html +1 -1
  157. data/docs/api/Brut/FrontEnd/Routing.html +1 -1
  158. data/docs/api/Brut/FrontEnd/Session.html +1 -1
  159. data/docs/api/Brut/FrontEnd.html +1 -1
  160. data/docs/api/Brut/I18n/BaseMethods.html +1 -1
  161. data/docs/api/Brut/I18n/ForBackEnd.html +1 -1
  162. data/docs/api/Brut/I18n/ForCLI.html +1 -1
  163. data/docs/api/Brut/I18n/ForHTML.html +1 -1
  164. data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +1 -1
  165. data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +1 -1
  166. data/docs/api/Brut/I18n.html +1 -1
  167. data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +1 -1
  168. data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +1 -1
  169. data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +1 -1
  170. data/docs/api/Brut/Instrumentation/OpenTelemetry.html +1 -1
  171. data/docs/api/Brut/Instrumentation.html +1 -1
  172. data/docs/api/Brut/SinatraHelpers/ClassMethods.html +1 -1
  173. data/docs/api/Brut/SinatraHelpers.html +1 -1
  174. data/docs/api/Brut/SpecSupport/ClockSupport.html +1 -1
  175. data/docs/api/Brut/SpecSupport/ComponentSupport.html +1 -1
  176. data/docs/api/Brut/SpecSupport/E2ETestServer.html +20 -20
  177. data/docs/api/Brut/SpecSupport/E2eSupport.html +1 -1
  178. data/docs/api/Brut/SpecSupport/EnhancedNode.html +1 -1
  179. data/docs/api/Brut/SpecSupport/FlashSupport.html +1 -1
  180. data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +1 -1
  181. data/docs/api/Brut/SpecSupport/GeneralSupport.html +1 -1
  182. data/docs/api/Brut/SpecSupport/HandlerSupport.html +2 -2
  183. data/docs/api/Brut/SpecSupport/Matchers/BeABug.html +142 -0
  184. data/docs/api/Brut/SpecSupport/Matchers/BePageFor.html +142 -0
  185. data/docs/api/Brut/SpecSupport/Matchers/BeRoutingFor.html +155 -0
  186. data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +55 -25
  187. data/docs/api/Brut/SpecSupport/Matchers/HaveGenerated.html +149 -0
  188. data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +46 -19
  189. data/docs/api/Brut/SpecSupport/Matchers/HaveI18nString.html +149 -0
  190. data/docs/api/Brut/SpecSupport/Matchers/HaveLinkTo.html +149 -0
  191. data/docs/api/Brut/SpecSupport/Matchers/HaveRedirectedTo.html +165 -0
  192. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedHttpStatus.html +158 -0
  193. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedRackResponse.html +156 -0
  194. data/docs/api/Brut/SpecSupport/Matchers.html +2 -2
  195. data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +24 -24
  196. data/docs/api/Brut/SpecSupport/RSpecSetup.html +55 -20
  197. data/docs/api/Brut/SpecSupport/SessionSupport.html +1 -1
  198. data/docs/api/Brut/SpecSupport.html +1 -1
  199. data/docs/api/Brut.html +1 -1
  200. data/docs/api/Clock.html +1 -1
  201. data/docs/api/RichString.html +1 -1
  202. data/docs/api/SemanticLogger/Appender/Async.html +1 -1
  203. data/docs/api/Sequel/Extensions/BrutInstrumentation.html +5 -1
  204. data/docs/api/Sequel/Extensions/BrutMigrations.html +36 -28
  205. data/docs/api/Sequel/Extensions.html +1 -1
  206. data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +1 -1
  207. data/docs/api/Sequel/Plugins/CreatedAt.html +1 -1
  208. data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +1 -1
  209. data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +1 -1
  210. data/docs/api/Sequel/Plugins/ExternalId.html +1 -1
  211. data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +1 -1
  212. data/docs/api/Sequel/Plugins/FindBang.html +1 -1
  213. data/docs/api/Sequel/Plugins.html +1 -1
  214. data/docs/api/Sequel.html +1 -1
  215. data/docs/api/SpecSupport/Matchers/BeABug.html +143 -0
  216. data/docs/api/_index.html +92 -1
  217. data/docs/api/class_list.html +1 -1
  218. data/docs/api/file.README.html +1 -1
  219. data/docs/api/index.html +1 -1
  220. data/docs/api/method_list.html +392 -336
  221. data/docs/api/top-level-namespace.html +1 -1
  222. data/docs/assets/{ai.md.tZrjP9im.js → ai.md._6HCDL6d.js} +1 -1
  223. data/docs/assets/ai.md._6HCDL6d.lean.js +1 -0
  224. data/docs/assets/{app.D_yaTITQ.js → app.BX81XO4N.js} +1 -1
  225. data/docs/assets/chunks/@localSearchIndexroot.CoYzciVi.js +1 -0
  226. data/docs/assets/chunks/{VPLocalSearchBox.B2-ZzyTY.js → VPLocalSearchBox.gABXcTWp.js} +1 -1
  227. data/docs/assets/chunks/{theme.CfGFVRvE.js → theme.DwUXXAL3.js} +2 -2
  228. data/docs/assets/{components.md.eCttGlN-.js → components.md.CRUMdRoN.js} +1 -1
  229. data/docs/assets/{configuration.md.BRriU0cL.js → configuration.md.BGHl8oRC.js} +1 -1
  230. data/docs/assets/{dev-environment.md.BNc8AYiK.js → dev-environment.md.GZv6xvi9.js} +1 -1
  231. data/docs/assets/doc-conventions.md.-kN3Xo5C.js +1 -0
  232. data/docs/assets/{doc-conventions.md.DCfRXXi-.lean.js → doc-conventions.md.-kN3Xo5C.lean.js} +1 -1
  233. data/docs/assets/{forms.md.CBTYQ_Cz.js → forms.md.B-koVgyw.js} +23 -23
  234. data/docs/assets/{forms.md.CBTYQ_Cz.lean.js → forms.md.B-koVgyw.lean.js} +1 -1
  235. data/docs/assets/getting-started.md.Ciz82L0m.js +25 -0
  236. data/docs/assets/getting-started.md.Ciz82L0m.lean.js +1 -0
  237. data/docs/assets/{instrumentation.md.CL6ax7nT.js → instrumentation.md.a9Pjps4P.js} +2 -2
  238. data/docs/assets/{instrumentation.md.CL6ax7nT.lean.js → instrumentation.md.a9Pjps4P.lean.js} +1 -1
  239. data/docs/assets/layouts.md.cPnh3NId.js +51 -0
  240. data/docs/assets/layouts.md.cPnh3NId.lean.js +1 -0
  241. data/docs/assets/lsp.md.Bsu-f6VU.js +1 -0
  242. data/docs/assets/lsp.md.Bsu-f6VU.lean.js +1 -0
  243. data/docs/assets/{overview.md.CDalkuxV.js → overview.md.C5wlBcR5.js} +3 -3
  244. data/docs/assets/recipes_authentication.md.CAsXf7hk.js +1 -0
  245. data/docs/assets/recipes_authentication.md.CAsXf7hk.lean.js +1 -0
  246. data/docs/assets.html +4 -4
  247. data/docs/brut-js/api/AjaxSubmit.html +1 -1
  248. data/docs/brut-js/api/AjaxSubmit.js.html +1 -1
  249. data/docs/brut-js/api/Autosubmit.html +1 -1
  250. data/docs/brut-js/api/Autosubmit.js.html +1 -1
  251. data/docs/brut-js/api/BaseCustomElement.html +1 -1
  252. data/docs/brut-js/api/BaseCustomElement.js.html +1 -1
  253. data/docs/brut-js/api/BrutCustomElements.html +1 -1
  254. data/docs/brut-js/api/BufferedLogger.html +1 -1
  255. data/docs/brut-js/api/ConfirmSubmit.html +1 -1
  256. data/docs/brut-js/api/ConfirmSubmit.js.html +1 -1
  257. data/docs/brut-js/api/ConfirmationDialog.html +1 -1
  258. data/docs/brut-js/api/ConfirmationDialog.js.html +1 -1
  259. data/docs/brut-js/api/ConstraintViolationMessage.html +1 -1
  260. data/docs/brut-js/api/ConstraintViolationMessage.js.html +1 -1
  261. data/docs/brut-js/api/ConstraintViolationMessages.html +1 -1
  262. data/docs/brut-js/api/ConstraintViolationMessages.js.html +1 -1
  263. data/docs/brut-js/api/CopyToClipboard.html +1 -1
  264. data/docs/brut-js/api/CopyToClipboard.js.html +1 -1
  265. data/docs/brut-js/api/Form.html +1 -1
  266. data/docs/brut-js/api/Form.js.html +1 -1
  267. data/docs/brut-js/api/I18nTranslation.html +1 -1
  268. data/docs/brut-js/api/I18nTranslation.js.html +1 -1
  269. data/docs/brut-js/api/LocaleDetection.html +1 -1
  270. data/docs/brut-js/api/LocaleDetection.js.html +1 -1
  271. data/docs/brut-js/api/Logger.html +1 -1
  272. data/docs/brut-js/api/Logger.js.html +1 -1
  273. data/docs/brut-js/api/Message.html +1 -1
  274. data/docs/brut-js/api/Message.js.html +1 -1
  275. data/docs/brut-js/api/PrefixedLogger.html +1 -1
  276. data/docs/brut-js/api/RichString.html +1 -1
  277. data/docs/brut-js/api/RichString.js.html +1 -1
  278. data/docs/brut-js/api/Tabs.html +1 -1
  279. data/docs/brut-js/api/Tabs.js.html +1 -1
  280. data/docs/brut-js/api/Tracing.html +1 -1
  281. data/docs/brut-js/api/Tracing.js.html +1 -1
  282. data/docs/brut-js/api/external-CustomElementRegistry.html +1 -1
  283. data/docs/brut-js/api/external-Performance.html +1 -1
  284. data/docs/brut-js/api/external-Promise.html +1 -1
  285. data/docs/brut-js/api/external-ValidityState.html +1 -1
  286. data/docs/brut-js/api/external-Window.html +1 -1
  287. data/docs/brut-js/api/external-fetch.html +1 -1
  288. data/docs/brut-js/api/global.html +1 -1
  289. data/docs/brut-js/api/index.html +1 -1
  290. data/docs/brut-js/api/index.js.html +1 -1
  291. data/docs/brut-js/api/module-testing.html +1 -1
  292. data/docs/brut-js/api/testing.AssetMetadata.html +1 -1
  293. data/docs/brut-js/api/testing.AssetMetadataLoader.html +1 -1
  294. data/docs/brut-js/api/testing.CustomElementTest.html +1 -1
  295. data/docs/brut-js/api/testing.DOMCreator.html +1 -1
  296. data/docs/brut-js/api/testing_AssetMetadata.js.html +1 -1
  297. data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +1 -1
  298. data/docs/brut-js/api/testing_CustomElementTest.js.html +1 -1
  299. data/docs/brut-js/api/testing_DOMCreator.js.html +1 -1
  300. data/docs/brut-js/api/testing_index.js.html +1 -1
  301. data/docs/brut-js.html +4 -4
  302. data/docs/business-logic.html +4 -4
  303. data/docs/cli.html +4 -4
  304. data/docs/components.html +6 -6
  305. data/docs/configuration.html +5 -5
  306. data/docs/css.html +4 -4
  307. data/docs/custom-element-tests.html +4 -4
  308. data/docs/database-access.html +4 -4
  309. data/docs/database-schema.html +4 -4
  310. data/docs/deployment.html +4 -4
  311. data/docs/dev-environment.html +5 -5
  312. data/docs/doc-conventions.html +5 -5
  313. data/docs/end-to-end-tests.html +4 -4
  314. data/docs/flash-and-session.html +4 -4
  315. data/docs/forms.html +27 -27
  316. data/docs/getting-started.html +29 -6
  317. data/docs/handlers.html +4 -4
  318. data/docs/hashmap.json +1 -1
  319. data/docs/hooks.html +4 -4
  320. data/docs/i18n.html +4 -4
  321. data/docs/index.html +3 -3
  322. data/docs/instrumentation.html +5 -5
  323. data/docs/javascript.html +4 -4
  324. data/docs/jobs.html +4 -4
  325. data/docs/keyword-injection.html +4 -4
  326. data/docs/layouts.html +74 -0
  327. data/docs/lsp.html +24 -0
  328. data/docs/markdown-examples.html +4 -4
  329. data/docs/middleware.html +4 -4
  330. data/docs/not-released.html +4 -4
  331. data/docs/overview.html +8 -8
  332. data/docs/pages.html +5 -5
  333. data/docs/recipes/authentication.html +24 -0
  334. data/docs/routes.html +4 -4
  335. data/docs/security.html +4 -4
  336. data/docs/seed-data.html +4 -4
  337. data/docs/space-time-continuum.html +4 -4
  338. data/docs/tutorial.html +4 -4
  339. data/docs/unit-tests.html +4 -4
  340. data/lib/brut/cli/app.rb +7 -2
  341. data/lib/brut/cli/apps/deploy_base.rb +86 -0
  342. data/lib/brut/cli/apps/heroku_container_based_deploy.rb +194 -0
  343. data/lib/brut/cli/apps/test.rb +2 -1
  344. data/lib/brut/cli/command.rb +7 -13
  345. data/lib/brut/cli/executor.rb +31 -5
  346. data/lib/brut/cli/options.rb +4 -0
  347. data/lib/brut/cli.rb +4 -3
  348. data/lib/brut/framework/container.rb +25 -7
  349. data/lib/brut/framework/errors/abstract_method.rb +7 -0
  350. data/lib/brut/framework/errors.rb +4 -2
  351. data/lib/brut/front_end/component.rb +33 -9
  352. data/lib/brut/front_end/components/inputs/select_tag_with_options.rb +16 -74
  353. data/lib/brut/front_end/forms/constraint_violation.rb +2 -18
  354. data/lib/brut/front_end/forms/input.rb +8 -8
  355. data/lib/brut/front_end/forms/input_declarations.rb +3 -3
  356. data/lib/brut/front_end/forms/radio_button_group_input.rb +1 -1
  357. data/lib/brut/front_end/forms/select_input.rb +1 -1
  358. data/lib/brut/front_end/forms/validity_state.rb +23 -0
  359. data/lib/brut/front_end/middlewares/reload_app.rb +2 -0
  360. data/lib/brut/spec_support/handler_support.rb +1 -1
  361. data/lib/brut/spec_support/matcher.rb +1 -1
  362. data/lib/brut/spec_support/matchers/be_a_bug.rb +11 -0
  363. data/lib/brut/spec_support/matchers/be_page_for.rb +10 -0
  364. data/lib/brut/spec_support/matchers/be_routing_for.rb +18 -0
  365. data/lib/brut/spec_support/matchers/have_constraint_violation.rb +10 -0
  366. data/lib/brut/spec_support/matchers/have_generated.rb +28 -0
  367. data/lib/brut/spec_support/matchers/have_html_attribute.rb +10 -0
  368. data/lib/brut/spec_support/matchers/have_i18n_string.rb +13 -0
  369. data/lib/brut/spec_support/matchers/have_link_to.rb +15 -0
  370. data/lib/brut/spec_support/matchers/have_redirected_to.rb +23 -0
  371. data/lib/brut/spec_support/matchers/have_returned_http_status.rb +20 -0
  372. data/lib/brut/spec_support/matchers/have_returned_rack_response.rb +22 -0
  373. data/lib/brut/spec_support/rspec_setup.rb +42 -2
  374. data/lib/brut/version.rb +1 -1
  375. data/lib/sequel/extensions/brut_instrumentation.rb +4 -0
  376. metadata +51 -24
  377. data/docs/assets/ai.md.tZrjP9im.lean.js +0 -1
  378. data/docs/assets/chunks/@localSearchIndexroot.BsN5i0Fi.js +0 -1
  379. data/docs/assets/doc-conventions.md.DCfRXXi-.js +0 -1
  380. data/docs/assets/getting-started.md.Bz2s1Vjb.js +0 -2
  381. data/docs/assets/getting-started.md.Bz2s1Vjb.lean.js +0 -1
  382. data/lib/brut/spec_support/matchers/have_rendered.rb +0 -14
  383. /data/docs/assets/{components.md.eCttGlN-.lean.js → components.md.CRUMdRoN.lean.js} +0 -0
  384. /data/docs/assets/{configuration.md.BRriU0cL.lean.js → configuration.md.BGHl8oRC.lean.js} +0 -0
  385. /data/docs/assets/{dev-environment.md.BNc8AYiK.lean.js → dev-environment.md.GZv6xvi9.lean.js} +0 -0
  386. /data/docs/assets/{overview.md.CDalkuxV.lean.js → overview.md.C5wlBcR5.lean.js} +0 -0
@@ -0,0 +1,194 @@
1
+ require "brut/cli"
2
+ class Brut::CLI::Apps::HerokuContainerBasedDeploy < Brut::CLI::Apps::DeployBase
3
+ description "Deploy to Heroku using containers"
4
+ default_command :deploy
5
+ configure_only!
6
+
7
+ class Deploy < Brut::CLI::Command
8
+ description "Build images, push them to Heroku, and deploy them"
9
+
10
+ detailed_description %{
11
+ Manages a deploy process based on using Heroku's Container Registry. See
12
+
13
+ https://devcenter.heroku.com/articles/container-registry-and-runtime
14
+
15
+ for details. You are assumed to understand this. This command will make the process somewhat easier.
16
+
17
+ This will use deploy/Dockerfile as a template to create one Dockerfile for each process you want to run in Heroku. deploy/heroku_config.rb is where the processes and their commands are configured.
18
+
19
+ The release phase is included automatically, based on bin/release.
20
+ }
21
+
22
+ opts.on("--platform=PLATFORM","Override default platform. Can be any Docker platform.")
23
+ opts.on("--[no-]dry-run", "Print the commands that would be run and don't actually do anything. Implies --skip-checks")
24
+ opts.on("--[no-]skip-checks", "Skip checks for code having been committed and pushed")
25
+ opts.on("--[no-]deploy", "After images are pushed, actually deploy them")
26
+ opts.on("--[no-]push", "After images are created, push them to Heroku's registry. If false, implies --no-deploy")
27
+
28
+ def after_bootstrap(app:)
29
+ @app_id = app.id
30
+ @organization = app.organization
31
+ end
32
+
33
+ def execute
34
+ options.set_default(:deploy, true)
35
+ options.set_default(:push, true)
36
+ if !options.push?
37
+ options[:deploy] = false
38
+ end
39
+ local_repo_checks = Brut::CLI::Apps::DeployBase::GitChecks.new(
40
+ out: @out,
41
+ err: @err,
42
+ executor: @executor,
43
+ warn_only: options.skip_checks? || options.dry_run?
44
+ )
45
+ if options.dry_run?
46
+ def system!(*args)
47
+ out.puts "DRY RUN, NOT EXECUTING '#{args}'"
48
+ end
49
+ end
50
+
51
+ version = begin
52
+ git_guess = %{git rev-parse HEAD}
53
+ stdout, stderr, status = Open3.capture3(git_guess)
54
+ if status.success?
55
+ stdout.strip
56
+ else
57
+ raise "Attempt to use git via command '#{git_guess}' to figure out the version failed: #{stdout}#{stderr}"
58
+ end
59
+ end
60
+ short_version = version[0..7]
61
+
62
+ platform = options.platform || "linux/amd64"
63
+ heroku_app_name = @app_id
64
+
65
+ out.puts "Reading HerokuConfig:"
66
+ require_relative Brut.container.project_root / "deploy" / "heroku_config"
67
+
68
+ additional_images = HerokuConfig.additional_images.map { |name,config|
69
+ cmd = config.fetch(:cmd)
70
+ out.puts " - #{name} will run #{cmd} in production"
71
+ image_name = %{#{@organization}/#{@app_id}:#{short_version}-#{name}}
72
+ [
73
+ name,
74
+ {
75
+ cmd:,
76
+ image_name:,
77
+ dockerfile: "deploy/Dockerfile.#{name}",
78
+ heroku_image_name: "registry.heroku.com/#{heroku_app_name}/#{name}",
79
+ }
80
+ ]
81
+ }.to_h
82
+
83
+ images = {
84
+ "web" => {
85
+ cmd: "bin/run",
86
+ image_name: %{#{@organization}/#{@app_id}:#{short_version}-web},
87
+ dockerfile: "deploy/Dockerfile.web",
88
+ heroku_image_name: "registry.heroku.com/#{heroku_app_name}/web",
89
+ },
90
+ "release" => {
91
+ cmd: "bin/release",
92
+ image_name: %{#{@organization}/#{@app_id}:#{short_version}-release},
93
+ dockerfile: "deploy/Dockerfile.release",
94
+ heroku_image_name: "registry.heroku.com/#{heroku_app_name}/release",
95
+ },
96
+ }.merge(additional_images)
97
+
98
+ out.puts " - release will run bin/release in production"
99
+
100
+ local_repo_checks.check!
101
+ require_heroku_login!(options)
102
+
103
+ FileUtils.chdir Brut.container.project_root do
104
+
105
+ out.puts "Generating Dockerfiles"
106
+ images.each do |name,metadata|
107
+ cmd = metadata.fetch(:cmd)
108
+ dockerfile = metadata.fetch(:dockerfile)
109
+
110
+ out.puts "Creating '#{dockerfile}' for '#{name}' that will use command '#{cmd}'"
111
+
112
+ File.open(dockerfile,"w") do |file|
113
+ file.puts "# DO NOT EDIT - THIS IS GENERATED"
114
+ file.puts "# To make changes, modifiy deploy/Dockerfile and run #{$0}"
115
+ file.puts File.read("deploy/Dockerfile")
116
+ file.puts
117
+ file.puts "# Added by #{$0}"
118
+ file.puts %{CMD [ "bundle", "exec", "#{cmd}" ]}
119
+ end
120
+ end
121
+
122
+ out.puts "Building images"
123
+ docker_quiet_option = if global_options.log_level != "debug"
124
+ "--quiet"
125
+ else
126
+ ""
127
+ end
128
+ images.each do |name,metadata|
129
+ image_name = metadata.fetch(:image_name)
130
+ dockerfile = metadata.fetch(:dockerfile)
131
+
132
+
133
+ out.puts "Creating docker image with name '#{image_name}' and platform '#{platform}'"
134
+ command = %{docker build #{docker_quiet_option} --build-arg app_git_sha1=#{version} --file #{Brut.container.project_root}/#{dockerfile} --platform #{platform} --tag #{image_name} .}
135
+ system!(command)
136
+ end
137
+
138
+ out.puts "Taggging images for Heroku"
139
+ images.each do |name,metadata|
140
+ image_name = metadata.fetch(:image_name)
141
+ heroku_image_name = metadata.fetch(:heroku_image_name)
142
+
143
+ out.puts "Tagging '#{image_name}' with '#{heroku_image_name}' for Heroku"
144
+ command = %{docker tag #{image_name} #{heroku_image_name}}
145
+ system!(command)
146
+ end
147
+
148
+ if options.push?
149
+ out.puts "Pushing to Heroku Registry"
150
+ images.each do |name,metadata|
151
+ heroku_image_name = metadata.fetch(:heroku_image_name)
152
+
153
+ out.puts "Pushing '#{heroku_image_name}'"
154
+
155
+ command = %{docker push #{docker_quiet_option} #{heroku_image_name}}
156
+ system!(command)
157
+ end
158
+ else
159
+ out.puts "Not pushing images"
160
+ end
161
+
162
+ names = images.map(&:first).join(" ")
163
+ deploy_command = "heroku container:release #{names}"
164
+ if options.deploy?
165
+ out.puts "Deploying images to Heroku"
166
+ system!(deploy_command)
167
+ else
168
+ out.puts "Not deploying. To deploy the images just pushed:"
169
+ out.puts ""
170
+ out.puts " #{deploy_command}"
171
+ end
172
+ end
173
+ end
174
+ private
175
+ def require_heroku_login!(options)
176
+ if system("heroku whoami")
177
+ out.puts "You are logged in to Heroku"
178
+ else
179
+ out.puts "You are not logged into Heroku."
180
+ out.puts "Please run the following:"
181
+ out.puts ""
182
+ out.puts "heroku auth:login"
183
+ out.puts "heroku container:login"
184
+ out.puts ""
185
+ out.puts "Then, re-run this"
186
+ if options.dry_run?
187
+ out.puts "Dry run - ignoring"
188
+ return
189
+ end
190
+ exit 1
191
+ end
192
+ end
193
+ end
194
+ end
@@ -133,7 +133,8 @@ class Brut::CLI::Apps::Test < Brut::CLI::App
133
133
  test_expected: true,
134
134
  }
135
135
  if pathname.fnmatch?( (Brut.container.components_src_dir / "**").to_s )
136
- if pathname.basename.to_s == "app_component.rb"
136
+ if pathname.basename.to_s == "app_component.rb" ||
137
+ pathname.basename.to_s == "custom_element_registration.rb"
137
138
  hash[:type] = :infrastructure
138
139
  hash[:test_expected] = false
139
140
  else
@@ -198,6 +198,13 @@ class Brut::CLI::Command
198
198
  def before_execute
199
199
  end
200
200
 
201
+ # Called after all setup has been executed. Brut will have been started/loaded.
202
+ # This will *not* be called if anything caused execution to be aborted.
203
+ #
204
+ # @param [Brut::Framework::App] app Your Brut app.
205
+ def after_bootstrap(app:)
206
+ end
207
+
201
208
  # @!visibility private
202
209
  def set_env_if_needed
203
210
  if self.class.requires_project_env?
@@ -219,19 +226,6 @@ class Brut::CLI::Command
219
226
  raise ex
220
227
  end
221
228
 
222
- # @!visibility private
223
- def bootstrap!(project_root:, configure_only:)
224
- require "bundler"
225
- Bundler.require(:default, ENV["RACK_ENV"].to_sym)
226
- if configure_only
227
- require "#{project_root}/app/pre_boot"
228
- Brut::Framework.new(app: ::App.new)
229
- else
230
- require "#{project_root}/app/boot"
231
- end
232
- continue_execution
233
- end
234
-
235
229
  private
236
230
 
237
231
  # @!visibility public
@@ -16,14 +16,40 @@ class Brut::CLI::Executor
16
16
  @err = err
17
17
  end
18
18
 
19
- # Execute a command, logging it to the standard output and outputing the commands output and error to the standard output and error,
20
- # respecitively
19
+ # Execute a command, logging it to the standard output and outputing the
20
+ # commands output and error to the standard output and error, respectively. If
21
+ # the command exits nonzero, the exit status is returned.
22
+ #
23
+ # Generally, you want to use {#system!} for commands that must succeed
24
+ # for the caller to continue. Only use this method if you need
25
+ # to do special error-handling when the underlying command fails.
26
+ #
27
+ # @see https://docs.ruby-lang.org/en/3.3/Open3.html#method-c-popen3
28
+ # @see #system!
29
+ #
30
+ # @param [String|Array] args Whatever you would give to `Kernel#system` or `Open3.popen3`.
31
+ # @return [int] 0 if the command completed normally, otherwise the nonzero exit status. **DO NOT TREAT THIS AS A BOOLEAN VALUE**
32
+ def system(*args)
33
+ self.system!(*args)
34
+ 0
35
+ rescue Brut::CLI::SystemExecError => e
36
+ e.exit_status
37
+ end
38
+
39
+ # Execute a command, logging it to the standard output and outputing the
40
+ # commands output and error to the standard output and error, respectively. If
41
+ # the command exits nonzero, an exception is raised and your CLI app will
42
+ # also exit nonzero.
43
+ #
44
+ # If you need to handle the command exiting nonzero, use {#system}
45
+ # instead, as it will not raise an exception.
21
46
  #
22
47
  # @see https://docs.ruby-lang.org/en/3.3/Open3.html#method-c-popen3
48
+ # @see #system
23
49
  #
24
50
  # @param [String|Array] args Whatever you would give to `Kernel#system` or `Open3.popen3`.
25
- # @raise Brut::CLI::Error::SystemExecError if the spawed command exits nonzer
26
- # @return [true]
51
+ # @raise Brut::CLI::Error::SystemExecError if the spawed command exits nonzero
52
+ # @return [int] Always returns 0
27
53
  def system!(*args)
28
54
  @out.puts "Executing #{args}"
29
55
  wait_thread = Open3.popen3(*args) do |_stdin,stdout,stderr,wait_thread|
@@ -52,6 +78,6 @@ class Brut::CLI::Executor
52
78
  else
53
79
  raise Brut::CLI::SystemExecError.new(args,wait_thread.value.exitstatus)
54
80
  end
55
- true
81
+ 0
56
82
  end
57
83
  end
@@ -12,6 +12,10 @@ class Brut::CLI::Options
12
12
  # Access an options value directly.
13
13
  # @param [String] key the key to use. This must be the exact name you used when calling `opts.on` or when creating the `OptionParser` for your app or command. Generally, use the {#method_missing}-provided version instead of this.
14
14
  def [](key) = @parsed_options[key]
15
+
16
+ def []=(key,value)
17
+ @parsed_options[key] = value
18
+ end
15
19
  # Check if `key` was provided on the command line.
16
20
  # @param [String] key the key to use. This must be the exact name you used when calling `opts.on` or when creating the `OptionParser` for your app or command.
17
21
  def key?(key) = @parsed_options.key?(key)
data/lib/brut/cli.rb CHANGED
@@ -42,9 +42,10 @@ module Brut
42
42
  # Holds Brut-provided CLI apps that are set up in your project.
43
43
  module Apps
44
44
  autoload(:DB,"brut/cli/apps/db")
45
- autoload(:DB,"brut/cli/apps/test")
46
- autoload(:DB,"brut/cli/apps/build_assets")
47
- autoload(:DB,"brut/cli/apps/scaffold")
45
+ autoload(:Test,"brut/cli/apps/test")
46
+ autoload(:BuildAssets,"brut/cli/apps/build_assets")
47
+ autoload(:Scaffold,"brut/cli/apps/scaffold")
48
+ autoload(:DeployBase,"brut/cli/apps/deploy_base")
48
49
  end
49
50
  end
50
51
  end
@@ -78,7 +78,7 @@ class Brut::Framework::Container
78
78
  self.validate_name!(name:,type:,allow_app_override:)
79
79
  if value == :use_block
80
80
  derive_with = block
81
- @container[name] = { value: nil, derive_with: derive_with }
81
+ @container[name] = { derive_with: derive_with }
82
82
  else
83
83
  if type == :boolean
84
84
  value = !!value
@@ -111,9 +111,11 @@ class Brut::Framework::Container
111
111
  raise ArgumentError,"#{name} does not allow the app to override it"
112
112
  end
113
113
  if value == :use_block
114
- @container[name] = { value: nil, derive_with: block }
114
+ @container[name][:derive_with] = block
115
+ @container[name].delete(:value)
115
116
  else
116
- @container[name] = { value: value }
117
+ @container[name][:value] = value
118
+ @container[name].delete(:derive_with)
117
119
  end
118
120
  end
119
121
 
@@ -191,16 +193,16 @@ class Brut::Framework::Container
191
193
  # TODO: Provide a cleanr impl and better error checking if things go wrong
192
194
  x = @container.fetch(name)
193
195
 
194
- value = x[:value]
196
+ has_value = x.key?(:value)
195
197
 
196
- if !value.nil? || (x[:allow_nil] && value.nil?)
198
+ if has_value
197
199
  handle_path_values(name,x)
198
- return value
200
+ return x[:value]
199
201
  end
200
202
 
201
203
  deriver = x[:derive_with]
202
204
  if deriver.nil?
203
- raise "Something is seriously wrong. '#{name}' was stored in container without a derive_with value"
205
+ raise "Something is seriously wrong. '#{name}' was stored in container without a static value, but also without a derive_with key"
204
206
  end
205
207
 
206
208
  parameters = deriver.parameters(lambda: true)
@@ -218,6 +220,22 @@ class Brut::Framework::Container
218
220
  handle_path_values(name,x)
219
221
  x[:value]
220
222
  end
223
+
224
+ def reload
225
+ @container.each do |name, contained_value|
226
+ if contained_value.key?(:value) && contained_value[:type].to_s == "Class"
227
+ if contained_value.key?(:derive_with)
228
+ contained_value.delete(:value)
229
+ else
230
+ klass = contained_value[:value]
231
+ new_klass = klass.name.split(/::/).reduce(Module) { |mod,part|
232
+ mod.const_get(part)
233
+ }
234
+ contained_value[:value] = new_klass
235
+ end
236
+ end
237
+ end
238
+ end
221
239
  private
222
240
 
223
241
  def handle_path_values(name,contained_value)
@@ -2,4 +2,11 @@
2
2
  # which methods a subclass is expected to override and for which no default behavior
3
3
  # makes sense.
4
4
  class Brut::Framework::Errors::AbstractMethod < Brut::Framework::Error
5
+ def initialize(method_name=nil)
6
+ if method_name
7
+ super("The method `#{method_name}` must be implemented")
8
+ else
9
+ super("An abstract method must be implemented")
10
+ end
11
+ end
5
12
  end
@@ -27,9 +27,11 @@ module Brut
27
27
  # a method that a subclass must implement, but for which there is no useful default
28
28
  # implementation.
29
29
  #
30
+ # @param [String|Symbol] method_name name of the method that must be implemented. If omitted, the value is guessed from `caller_locations`
30
31
  # @raise [Brut::Framework::Errors::AbstractMethod]
31
- def abstract_method!
32
- raise Brut::Framework::Errors::AbstractMethod
32
+ def abstract_method!(method_name=nil)
33
+ method_name ||= caller_locations(1,1)[0].label
34
+ raise Brut::Framework::Errors::AbstractMethod.new(method_name)
33
35
  end
34
36
  end
35
37
  # Base class for errors thrown by Brut classes. Generally, Brut will not create its own version
@@ -1,6 +1,22 @@
1
1
  require "phlex"
2
2
 
3
- # Components holds Brut-provided components that are of general use to any web app
3
+ # Namespace for Brut-provided components that are of general use to any web app.
4
+ # Also extends [`Phlex:::Kit`](https://www.phlex.fun/components/kits.html), meaning
5
+ # you can include this module in your pages and components to be able to
6
+ # create Brut's components without `.new` or without the full classname:
7
+ #
8
+ # @example
9
+ # class AppPage < Brut::FrontEnd::Page
10
+ # include Brut::FrontEnd::Components
11
+ # end
12
+ #
13
+ # class HomePage < AppPage
14
+ # def page_template
15
+ # h1 do
16
+ # span { "It's }
17
+ # TimeTag(timestamp: Time.now)
18
+ # end
19
+ # end
4
20
  module Brut::FrontEnd::Components
5
21
  autoload(:FormTag,"brut/front_end/components/form_tag")
6
22
  autoload(:Input,"brut/front_end/components/input")
@@ -72,16 +88,24 @@ class Brut::FrontEnd::Component < Phlex::HTML
72
88
  }
73
89
  end
74
90
 
75
- # Return a component that you would like Brut to instantiate.
76
- # This will use keyword injection to create the component, which means that if the component
77
- # doesn't require any data from this component, you do not need to pass through those values.
78
- # For example, you may have a component that renders the flash message. To avoid requiring your component to
79
- # be passed the flash, a global component can be injected with it from Brut.
91
+ # Render (in Phlex parlance) a component that you would like Brut to
92
+ # instantiate. This is useful when you want to use a component that
93
+ # only requires values from the {Brut::FrontEnd::RequestContext}. By
94
+ # using this method, *this* component does not have to receive
95
+ # data from the {Brut::FrontEnd::RequestContext} that only serves to pass
96
+ # to the component you use here.
97
+ #
98
+ # For example, you may have a component that renders the flash message. To avoid requiring *this* component/page to be passed the flash, a global component can be injected with it from Brut.
99
+ #
100
+ # This component *will* be rendered into the Phlex context. Do not call
101
+ # `render` on the result, nor rely on the return value.
80
102
  #
81
- # @return [Object] instance of `component_klass`, as created by Brut. This will
82
- # not render the component.
103
+ # @param [Class] component_klass the component class to use in the view.
104
+ # This class's
105
+ # initializer must only require information available from the
106
+ # {Brut::FrontEnd::RequestContext}.
83
107
  def global_component(component_klass)
84
- Brut::FrontEnd::RequestContext.inject(component_klass)
108
+ render Brut::FrontEnd::RequestContext.inject(component_klass)
85
109
  end
86
110
  end
87
111
  include Helpers
@@ -27,14 +27,14 @@ class Brut::FrontEnd::Components::Inputs::SelectTagWithOptions < Brut::FrontEnd:
27
27
  # to be used as the `value` attribute and option text content, respectively.
28
28
  #
29
29
  # @return [Brut::FrontEnd::Components::Inputs::SelectTagWithOptions] the select input ready to be placed into a view.
30
- def self.for_form_input(form:,
31
- input_name:,
32
- options:,
33
- include_blank: false,
34
- value_attribute:,
35
- option_text_attribute:,
36
- index: nil,
37
- html_attributes: {})
30
+ def initialize(form:,
31
+ input_name:,
32
+ options:,
33
+ include_blank: false,
34
+ value_attribute:,
35
+ option_text_attribute:,
36
+ index: nil,
37
+ html_attributes: {})
38
38
  html_attributes = html_attributes.map { |key,value| [ key.to_sym, value ] }.to_h
39
39
  default_html_attributes = {}
40
40
  index ||= 0
@@ -54,84 +54,26 @@ class Brut::FrontEnd::Components::Inputs::SelectTagWithOptions < Brut::FrontEnd:
54
54
  input.name
55
55
  end
56
56
 
57
- Brut::FrontEnd::Components::Inputs::SelectTagWithOptions.new(
58
- name: name,
59
- options:,
60
- selected_value: input.value,
61
- value_attribute:,
62
- option_text_attribute:,
63
- include_blank:,
64
- html_attributes: default_html_attributes.merge(html_attributes)
65
- )
66
- end
57
+ input_value = input.value
67
58
 
68
- # Create the element.
69
- #
70
- # @param [String] name the name of the input
71
- # @param [Array<Object>] options An array of objects represented what is being selected.
72
- # These can be any object and are ideally whatever domain object or
73
- # data type you want on the backend to represent this selection.
74
- # @param [Symbol|String] value_attribute the name of an attribute to determine an option's value.
75
- # This will be called on each element of `options` to get the value used for the `<option>`'s
76
- # `value` attribute. The value returned by `value_attribute` should be unique amongst the
77
- # `options` provided *and* be distinct from whatever `value` is used for `include_blank`.
78
- # @param [String] selected_value the value of the selected option. Note that this is the *value*
79
- # of the selected option, not the selected option itself. To set the selected value
80
- # based on a selected option, omit this and use `selected_option`
81
- # @param [String] selected_option the selected option. Note that `value_attribute` will be called
82
- # on this to determine the selected value to use when generating HTML. Also note that
83
- # this object must be in `options` or an exeception is raised.
84
- # @param [Symbol|String] option_text_attribute the name of an attribute to determine the text for an option.
85
- # This will be called on each element of `options` to get the value used for the `<option>`'s
86
- # text content. The value returned by `option_text_attribute` need not be unique, though if it
87
- # is not unique, it will certainly be confusing.
88
- # @param [Hash] html_attributes any additional HTML attributes to include on the `<select>` element.
89
- # @param [false|true|Hash] include_blank configure how and if to include a blank element in the select.
90
- # If this is false, there will be no blank element. If it's `true`, there will be one with
91
- # no value or text. If this is a `Hash` it must contain a `value:` key and a `text_content:` key
92
- # to be used as the `value` attribute and option text content, respectively.
93
- #
94
- # @raise ArgumentError if `selected_option` is present, but not in `options` or if `selected_value` is
95
- # present, but no option's value for `value_attribute` is that value.
96
- #
97
- # XXX: Why does this not ask the form for the selected_value?
98
- # XXX: This doesn't do well when values are strings
99
- def initialize(name:,
100
- options:,
101
- value_attribute:,
102
- selected_value: nil,
103
- selected_option: nil,
104
- option_text_attribute:,
105
- include_blank: false,
106
- html_attributes:)
107
59
  @options = options
108
60
  @include_blank = IncludeBlank.from_param(include_blank)
109
61
  @value_attribute = value_attribute
110
62
  @option_text_attribute = option_text_attribute
111
- @html_attributes = html_attributes
63
+ @html_attributes = default_html_attributes.merge(html_attributes)
112
64
  @html_attributes[:name] = name
113
65
 
114
- if selected_value.nil?
115
- if selected_option.nil?
116
- @selected_value = nil # explicitly nothing is selected
117
- else
118
- option = options.detect { |option|
119
- option.send(@value_attribute) == selected_option.send(@value_attribute)
120
- }
121
- if option.nil?
122
- raise ArgumentError, "selected_option #{selected_option} (with #{value_attribute} '#{selected_option.send(value_attribute)}') was not found in options"
123
- end
124
- @selected_value = option.send(@value_attribute)
125
- end
66
+ if input_value.nil?
67
+ @selected_value = nil # explicitly nothing is selected
126
68
  else
127
- if selected_value.kind_of?(Array)
128
- raise "WTF: #{name}"
69
+ if input_value.kind_of?(Array)
70
+ raise "WTF: #{name}" # XXX?
129
71
  end
130
72
  option = options.detect { |option|
131
- selected_value == option.send(@value_attribute)
73
+ input_value == option.send(@value_attribute)
132
74
  }
133
75
  if option.nil?
134
- raise ArgumentError, "selected_value #{selected_value} was not the value for #{value_attribute} on any of the options: #{options.map { |option| option.send(value_attribute) }.join(', ')}"
76
+ raise ArgumentError, "selected_value #{input_value} was not the value for #{value_attribute} on any of the options: #{options.map { |option| option.send(value_attribute) }.join(', ')}"
135
77
  end
136
78
  @selected_value = option.send(@value_attribute)
137
79
  end
@@ -2,22 +2,6 @@
2
2
  # form-related classes.
3
3
  class Brut::FrontEnd::Forms::ConstraintViolation
4
4
 
5
- # These are underscorized versions of the attributes of the browser's `ValidityState`'s properties.
6
- #
7
- # @see https://developer.mozilla.org/en-US/docs/Web/API/ValidityState
8
- CLIENT_SIDE_KEYS = [
9
- "bad_input",
10
- "custom_error",
11
- "pattern_mismatch",
12
- "range_overflow",
13
- "range_underflow",
14
- "step_mismatch",
15
- "too_long",
16
- "too_short",
17
- "type_mismatch",
18
- "value_missing",
19
- ]
20
-
21
5
  # @return [String] the key fragment representing the violation
22
6
  attr_reader :key
23
7
  # @return [Hash] interpolated values useful in rendering the actual message
@@ -27,11 +11,11 @@ class Brut::FrontEnd::Forms::ConstraintViolation
27
11
  #
28
12
  # @param [String|Symbol] key I18n key fragment representing this violation.
29
13
  # @param [Hash|nil] context interpolated values useful in rendering the message
30
- # @param [true|:based_on_key] server_side If `:based_on_key`, {#client_side?} will return true if `key` is in {.CLIENT_SIDE_KEYS}.
14
+ # @param [true|:based_on_key] server_side If `:based_on_key`, {#client_side?} will return true if `key` is in {ValidityState.KEYS}.
31
15
  # If `true`, {#client_side?} will return false no matter what.
32
16
  def initialize(key:,context:, server_side: :based_on_key)
33
17
  @key = key.to_s
34
- @client_side = CLIENT_SIDE_KEYS.include?(@key) && server_side != true
18
+ @client_side = Brut::FrontEnd::Forms::ValidityState::KEYS.include?(@key) && server_side != true
35
19
  @context = context || {}
36
20
  if !@context.kind_of?(Hash)
37
21
  raise "#{self.class} created for key #{key} with an invalid context: '#{context}/#{context.class}'. Context must be nil or a hash"
@@ -82,14 +82,14 @@ class Brut::FrontEnd::Forms::Input
82
82
  step_mismatch = false
83
83
 
84
84
  @validity_state = Brut::FrontEnd::Forms::ValidityState.new(
85
- value_missing: missing,
86
- too_short: too_short,
87
- too_long: too_short,
88
- range_overflow: range_overflow,
89
- range_underflow: range_underflow,
90
- pattern_mismatch: pattern_mismatch,
91
- step_mismatch: step_mismatch,
92
- type_mismatch: type_mismatch,
85
+ valueMissing: missing,
86
+ tooShort: too_short,
87
+ tooLong: too_short,
88
+ rangeOverflow: range_overflow,
89
+ rangeUnderflow: range_underflow,
90
+ patternMismatch: pattern_mismatch,
91
+ stepMismatch: step_mismatch,
92
+ typeMismatch: type_mismatch,
93
93
  )
94
94
  @value = new_value
95
95
  end
@@ -12,8 +12,8 @@ module Brut::FrontEnd::Forms::InputDeclarations
12
12
  end
13
13
 
14
14
  # Declares a select for this form, to be modeled via an HTML `<SELECT>` tag. Note that this will not define the values that appear
15
- # in the select. That is done when the select is rendered, which you might do with
16
- # {Brut::FrontEnd::Components::Inputs::Select.for_form_input}
15
+ # in the select. That is done when the select is rendered, which you might do with a
16
+ # {Brut::FrontEnd::Components::Inputs::Select}
17
17
  #
18
18
  # @param [String] name The name of the input (used in the `name` attribute)
19
19
  # @param [Hash] attributes Attributes to be used on the tag that represent its contraints. See {Brut::FrontEnd::Forms::SelectInputDefinition}
@@ -28,7 +28,7 @@ module Brut::FrontEnd::Forms::InputDeclarations
28
28
  # input tags.
29
29
  #
30
30
  # Note that this is not where you would define the possible values for the group. That is done in
31
- # {Brut::FrontEnd::Components::Inputs::RadioButton.for_form_input}.
31
+ # {Brut::FrontEnd::Components::Inputs::RadioButton}.
32
32
  #
33
33
  # @param [String] name The name of the group (used in the `name` attribute)
34
34
  # @param [Hash] attributes Attributes to be used on the tag that represent its contraints. See {Brut::FrontEnd::Forms::RadioButtonGroupInputDefinition}