brut 0.10.0 → 0.11.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 (376) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/Gemfile.lock +15 -15
  4. data/assets/YouTubeThumb.pxd +0 -0
  5. data/bin/new-version +3 -3
  6. data/brut-css/package-lock.json +2 -2
  7. data/brut-css/package.json +1 -1
  8. data/brut-js/package-lock.json +2 -2
  9. data/brut-js/package.json +1 -1
  10. data/brutrb.com/getting-started.md +3 -0
  11. data/brutrb.com/images/tutorial/02-confirmation-dialog-browser-element-styled.png +0 -0
  12. data/brutrb.com/images/tutorial/02-confirmation-dialog-browser-element.png +0 -0
  13. data/brutrb.com/images/tutorial/02-confirmation-dialog-browser.png +0 -0
  14. data/brutrb.com/images/tutorial/02-confirmation-flow.graffle +0 -0
  15. data/brutrb.com/images/tutorial/02-confirmation-flow.png +0 -0
  16. data/brutrb.com/instrumentation.md +142 -3
  17. data/brutrb.com/tutorial.md +29 -1627
  18. data/brutrb.com/tutorials/01-intro.md +1630 -0
  19. data/brutrb.com/tutorials/02-dialog.md +569 -0
  20. data/docs/404.html +2 -2
  21. data/docs/adrs.html +4 -4
  22. data/docs/ai.html +4 -4
  23. data/docs/api/Brut/BackEnd/SeedData.html +1 -1
  24. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +1 -1
  25. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +1 -1
  26. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +1 -1
  27. data/docs/api/Brut/BackEnd/Sidekiq.html +1 -1
  28. data/docs/api/Brut/BackEnd/Validators/FormValidator.html +1 -1
  29. data/docs/api/Brut/BackEnd/Validators.html +1 -1
  30. data/docs/api/Brut/BackEnd.html +1 -1
  31. data/docs/api/Brut/CLI/App.html +1 -1
  32. data/docs/api/Brut/CLI/AppRunner.html +1 -1
  33. data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +1 -1
  34. data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +1 -1
  35. data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +1 -1
  36. data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +1 -1
  37. data/docs/api/Brut/CLI/Apps/BuildAssets.html +1 -1
  38. data/docs/api/Brut/CLI/Apps/DB/Create.html +1 -1
  39. data/docs/api/Brut/CLI/Apps/DB/Drop.html +1 -1
  40. data/docs/api/Brut/CLI/Apps/DB/Migrate.html +1 -1
  41. data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +1 -1
  42. data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +1 -1
  43. data/docs/api/Brut/CLI/Apps/DB/Seed.html +1 -1
  44. data/docs/api/Brut/CLI/Apps/DB/Status.html +1 -1
  45. data/docs/api/Brut/CLI/Apps/DB.html +1 -1
  46. data/docs/api/Brut/CLI/Apps/DeployBase/GitChecks.html +1 -1
  47. data/docs/api/Brut/CLI/Apps/DeployBase.html +1 -1
  48. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy/Deploy.html +1 -1
  49. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy.html +1 -1
  50. data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +1 -1
  51. data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +1 -1
  52. data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +1 -1
  53. data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +1 -1
  54. data/docs/api/Brut/CLI/Apps/Scaffold/DbModel.html +1 -1
  55. data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +1 -1
  56. data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +1 -1
  57. data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +1 -1
  58. data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +1 -1
  59. data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +1 -1
  60. data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +1 -1
  61. data/docs/api/Brut/CLI/Apps/Scaffold.html +1 -1
  62. data/docs/api/Brut/CLI/Apps/Test/Audit.html +1 -1
  63. data/docs/api/Brut/CLI/Apps/Test/E2e.html +1 -1
  64. data/docs/api/Brut/CLI/Apps/Test/JS.html +1 -1
  65. data/docs/api/Brut/CLI/Apps/Test/Run.html +1 -1
  66. data/docs/api/Brut/CLI/Apps/Test.html +1 -1
  67. data/docs/api/Brut/CLI/Apps.html +1 -1
  68. data/docs/api/Brut/CLI/Command.html +1 -1
  69. data/docs/api/Brut/CLI/Error.html +1 -1
  70. data/docs/api/Brut/CLI/ExecutionResults/Result.html +1 -1
  71. data/docs/api/Brut/CLI/ExecutionResults.html +1 -1
  72. data/docs/api/Brut/CLI/Executor.html +1 -1
  73. data/docs/api/Brut/CLI/InvalidOption.html +1 -1
  74. data/docs/api/Brut/CLI/Options.html +1 -1
  75. data/docs/api/Brut/CLI/Output.html +1 -1
  76. data/docs/api/Brut/CLI/SystemExecError.html +1 -1
  77. data/docs/api/Brut/CLI.html +1 -1
  78. data/docs/api/Brut/FactoryBot.html +1 -1
  79. data/docs/api/Brut/Framework/App.html +1 -1
  80. data/docs/api/Brut/Framework/Config.html +1 -1
  81. data/docs/api/Brut/Framework/Container.html +1 -1
  82. data/docs/api/Brut/Framework/Error.html +1 -1
  83. data/docs/api/Brut/Framework/Errors/AbstractMethod.html +1 -1
  84. data/docs/api/Brut/Framework/Errors/Bug.html +1 -1
  85. data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +1 -1
  86. data/docs/api/Brut/Framework/Errors/MissingParameter.html +1 -1
  87. data/docs/api/Brut/Framework/Errors/NoClassForPath.html +1 -1
  88. data/docs/api/Brut/Framework/Errors/NotFound.html +1 -1
  89. data/docs/api/Brut/Framework/Errors/NotImplemented.html +1 -1
  90. data/docs/api/Brut/Framework/Errors.html +1 -1
  91. data/docs/api/Brut/Framework/FussyTypeEnforcement.html +1 -1
  92. data/docs/api/Brut/Framework/MCP.html +1 -1
  93. data/docs/api/Brut/Framework/ProjectEnvironment.html +1 -1
  94. data/docs/api/Brut/Framework.html +1 -1
  95. data/docs/api/Brut/FrontEnd/AssetPathResolver.html +1 -1
  96. data/docs/api/Brut/FrontEnd/Component/Helpers.html +1 -1
  97. data/docs/api/Brut/FrontEnd/Component.html +1 -1
  98. data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +48 -27
  99. data/docs/api/Brut/FrontEnd/Components/FormTag.html +1 -1
  100. data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +1 -1
  101. data/docs/api/Brut/FrontEnd/Components/Input.html +1 -1
  102. data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +1 -1
  103. data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +1 -1
  104. data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +1 -1
  105. data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +1 -1
  106. data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +1 -1
  107. data/docs/api/Brut/FrontEnd/Components/Inputs.html +1 -1
  108. data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +1 -1
  109. data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +1 -1
  110. data/docs/api/Brut/FrontEnd/Components/TimeTag.html +1 -1
  111. data/docs/api/Brut/FrontEnd/Components/Traceparent.html +1 -1
  112. data/docs/api/Brut/FrontEnd/Components.html +1 -1
  113. data/docs/api/Brut/FrontEnd/CsrfProtector.html +1 -1
  114. data/docs/api/Brut/FrontEnd/Download.html +1 -1
  115. data/docs/api/Brut/FrontEnd/Flash.html +1 -1
  116. data/docs/api/Brut/FrontEnd/Form.html +1 -1
  117. data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +1 -1
  118. data/docs/api/Brut/FrontEnd/Forms/Input/Color.html +1 -1
  119. data/docs/api/Brut/FrontEnd/Forms/Input/TimeOfDay.html +1 -1
  120. data/docs/api/Brut/FrontEnd/Forms/Input.html +1 -1
  121. data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +1 -1
  122. data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +1 -1
  123. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +1 -1
  124. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +1 -1
  125. data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +1 -1
  126. data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +1 -1
  127. data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +1 -1
  128. data/docs/api/Brut/FrontEnd/Forms.html +1 -1
  129. data/docs/api/Brut/FrontEnd/GenericResponse.html +1 -1
  130. data/docs/api/Brut/FrontEnd/Handler.html +1 -1
  131. data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +1 -1
  132. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +1 -1
  133. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +1 -1
  134. data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +1 -1
  135. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +1 -1
  136. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +1 -1
  137. data/docs/api/Brut/FrontEnd/Handlers.html +1 -1
  138. data/docs/api/Brut/FrontEnd/HandlingResults.html +1 -1
  139. data/docs/api/Brut/FrontEnd/HttpMethod.html +1 -1
  140. data/docs/api/Brut/FrontEnd/HttpStatus.html +1 -1
  141. data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +1 -1
  142. data/docs/api/Brut/FrontEnd/Layout.html +1 -1
  143. data/docs/api/Brut/FrontEnd/Middleware.html +1 -1
  144. data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +1 -1
  145. data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +1 -1
  146. data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +1 -1
  147. data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +1 -1
  148. data/docs/api/Brut/FrontEnd/Middlewares.html +1 -1
  149. data/docs/api/Brut/FrontEnd/Page.html +1 -1
  150. data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +1 -1
  151. data/docs/api/Brut/FrontEnd/Pages.html +1 -1
  152. data/docs/api/Brut/FrontEnd/RequestContext.html +1 -1
  153. data/docs/api/Brut/FrontEnd/RouteHook.html +1 -1
  154. data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +1 -1
  155. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +1 -1
  156. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +1 -1
  157. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +1 -1
  158. data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +1 -1
  159. data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +1 -1
  160. data/docs/api/Brut/FrontEnd/RouteHooks.html +1 -1
  161. data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +1 -1
  162. data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +1 -1
  163. data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +1 -1
  164. data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +1 -1
  165. data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +1 -1
  166. data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +1 -1
  167. data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +1 -1
  168. data/docs/api/Brut/FrontEnd/Routing/Route.html +1 -1
  169. data/docs/api/Brut/FrontEnd/Routing.html +1 -1
  170. data/docs/api/Brut/FrontEnd/Session.html +1 -1
  171. data/docs/api/Brut/FrontEnd.html +1 -1
  172. data/docs/api/Brut/I18n/BaseMethods.html +1 -1
  173. data/docs/api/Brut/I18n/ForBackEnd.html +1 -1
  174. data/docs/api/Brut/I18n/ForCLI.html +1 -1
  175. data/docs/api/Brut/I18n/ForHTML.html +1 -1
  176. data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +1 -1
  177. data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +1 -1
  178. data/docs/api/Brut/I18n.html +1 -1
  179. data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +1 -1
  180. data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +1 -1
  181. data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +1 -1
  182. data/docs/api/Brut/Instrumentation/OpenTelemetry.html +1 -1
  183. data/docs/api/Brut/Instrumentation.html +1 -1
  184. data/docs/api/Brut/RubocopConfig.html +1 -1
  185. data/docs/api/Brut/SinatraHelpers/ClassMethods.html +1 -1
  186. data/docs/api/Brut/SinatraHelpers.html +1 -1
  187. data/docs/api/Brut/SpecSupport/ClockSupport.html +1 -1
  188. data/docs/api/Brut/SpecSupport/ComponentSupport.html +1 -1
  189. data/docs/api/Brut/SpecSupport/E2ETestServer.html +1 -1
  190. data/docs/api/Brut/SpecSupport/E2eSupport.html +1 -1
  191. data/docs/api/Brut/SpecSupport/EnhancedNode.html +1 -1
  192. data/docs/api/Brut/SpecSupport/FlashSupport.html +1 -1
  193. data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +1 -1
  194. data/docs/api/Brut/SpecSupport/GeneralSupport.html +1 -1
  195. data/docs/api/Brut/SpecSupport/HandlerSupport.html +1 -1
  196. data/docs/api/Brut/SpecSupport/Matchers/BeABug.html +1 -1
  197. data/docs/api/Brut/SpecSupport/Matchers/BePageFor.html +1 -1
  198. data/docs/api/Brut/SpecSupport/Matchers/BeRoutingFor.html +1 -1
  199. data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +1 -1
  200. data/docs/api/Brut/SpecSupport/Matchers/HaveGenerated.html +1 -1
  201. data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +1 -1
  202. data/docs/api/Brut/SpecSupport/Matchers/HaveI18nString.html +1 -1
  203. data/docs/api/Brut/SpecSupport/Matchers/HaveLinkTo.html +1 -1
  204. data/docs/api/Brut/SpecSupport/Matchers/HaveRedirectedTo.html +1 -1
  205. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedHttpStatus.html +1 -1
  206. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedRackResponse.html +1 -1
  207. data/docs/api/Brut/SpecSupport/Matchers.html +1 -1
  208. data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +1 -1
  209. data/docs/api/Brut/SpecSupport/RSpecSetup.html +1 -1
  210. data/docs/api/Brut/SpecSupport/SessionSupport.html +1 -1
  211. data/docs/api/Brut/SpecSupport.html +1 -1
  212. data/docs/api/Brut.html +1 -1
  213. data/docs/api/Clock.html +1 -1
  214. data/docs/api/ModuleName.html +1 -1
  215. data/docs/api/RichString.html +1 -1
  216. data/docs/api/SemanticLogger/Appender/Async.html +1 -1
  217. data/docs/api/Sequel/Extensions/BrutInstrumentation.html +1 -1
  218. data/docs/api/Sequel/Extensions/BrutMigrations.html +1 -1
  219. data/docs/api/Sequel/Extensions.html +1 -1
  220. data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +1 -1
  221. data/docs/api/Sequel/Plugins/CreatedAt.html +1 -1
  222. data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +1 -1
  223. data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +1 -1
  224. data/docs/api/Sequel/Plugins/ExternalId.html +1 -1
  225. data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +1 -1
  226. data/docs/api/Sequel/Plugins/FindBang.html +1 -1
  227. data/docs/api/Sequel/Plugins.html +1 -1
  228. data/docs/api/Sequel.html +1 -1
  229. data/docs/api/_index.html +1 -1
  230. data/docs/api/file.README.html +1 -1
  231. data/docs/api/index.html +1 -1
  232. data/docs/api/top-level-namespace.html +1 -1
  233. data/docs/assets/02-confirmation-dialog-browser-element-styled.3NEGM20-.png +0 -0
  234. data/docs/assets/02-confirmation-dialog-browser-element.DPsf0xUW.png +0 -0
  235. data/docs/assets/02-confirmation-dialog-browser.DH8ALFO4.png +0 -0
  236. data/docs/assets/02-confirmation-flow.D9gZ0S5U.png +0 -0
  237. data/docs/assets/{app.vjGWMSnJ.js → app.0-aKXKdt.js} +1 -1
  238. data/docs/assets/chunks/@localSearchIndexroot.DPhqaz1b.js +1 -0
  239. data/docs/assets/chunks/{VPLocalSearchBox.C-ymMW2k.js → VPLocalSearchBox.CW-UBkNA.js} +1 -1
  240. data/docs/assets/chunks/{theme.pJUosGlI.js → theme.a6feKWJO.js} +2 -2
  241. data/docs/assets/{components.md.B543a3Lm.js → components.md.BzVRwegp.js} +3 -3
  242. data/docs/assets/{configuration.md.-foE_iVv.js → configuration.md.eM5wFVi5.js} +1 -1
  243. data/docs/assets/{form-constraints.md.DK5adCgM.js → form-constraints.md.KTv5cdR4.js} +6 -6
  244. data/docs/assets/{forms.md.D5-2rgHh.js → forms.md.B3BHvCV3.js} +1 -1
  245. data/docs/assets/{getting-started.md.Cd4XSZb_.js → getting-started.md.BgR0ZHsl.js} +6 -3
  246. data/docs/assets/{getting-started.md.Cd4XSZb_.lean.js → getting-started.md.BgR0ZHsl.lean.js} +1 -1
  247. data/docs/assets/recipes_form-errors.md.Bv5RCKqH.js +66 -0
  248. data/docs/assets/recipes_form-errors.md.Bv5RCKqH.lean.js +1 -0
  249. data/docs/assets/tutorial.md.BM40jnoq.js +27 -0
  250. data/docs/assets/tutorial.md.BM40jnoq.lean.js +1 -0
  251. data/docs/assets/{tutorial.md.C4zR5XPG.js → tutorials_01-intro.md.BXvYWcO9.js} +5 -25
  252. data/docs/assets/tutorials_01-intro.md.BXvYWcO9.lean.js +1 -0
  253. data/docs/assets/tutorials_02-dialog.md.CIeg8R--.js +274 -0
  254. data/docs/assets/tutorials_02-dialog.md.CIeg8R--.lean.js +1 -0
  255. data/docs/assets.html +4 -4
  256. data/docs/brut-js/api/AjaxSubmit.html +1 -1
  257. data/docs/brut-js/api/AjaxSubmit.js.html +1 -1
  258. data/docs/brut-js/api/Autosubmit.html +1 -1
  259. data/docs/brut-js/api/Autosubmit.js.html +1 -1
  260. data/docs/brut-js/api/BaseCustomElement.html +1 -1
  261. data/docs/brut-js/api/BaseCustomElement.js.html +1 -1
  262. data/docs/brut-js/api/BrutCustomElements.html +1 -1
  263. data/docs/brut-js/api/BufferedLogger.html +1 -1
  264. data/docs/brut-js/api/ConfirmSubmit.html +1 -1
  265. data/docs/brut-js/api/ConfirmSubmit.js.html +1 -1
  266. data/docs/brut-js/api/ConfirmationDialog.html +1 -1
  267. data/docs/brut-js/api/ConfirmationDialog.js.html +1 -1
  268. data/docs/brut-js/api/ConstraintViolationMessage.html +55 -5
  269. data/docs/brut-js/api/ConstraintViolationMessage.js.html +18 -3
  270. data/docs/brut-js/api/ConstraintViolationMessages.html +1 -1
  271. data/docs/brut-js/api/ConstraintViolationMessages.js.html +1 -1
  272. data/docs/brut-js/api/CopyToClipboard.html +1 -1
  273. data/docs/brut-js/api/CopyToClipboard.js.html +1 -1
  274. data/docs/brut-js/api/Form.html +7 -10
  275. data/docs/brut-js/api/Form.js.html +20 -24
  276. data/docs/brut-js/api/I18nTranslation.html +1 -1
  277. data/docs/brut-js/api/I18nTranslation.js.html +1 -1
  278. data/docs/brut-js/api/LocaleDetection.html +1 -1
  279. data/docs/brut-js/api/LocaleDetection.js.html +1 -1
  280. data/docs/brut-js/api/Logger.html +1 -1
  281. data/docs/brut-js/api/Logger.js.html +1 -1
  282. data/docs/brut-js/api/Message.html +1 -1
  283. data/docs/brut-js/api/Message.js.html +1 -1
  284. data/docs/brut-js/api/PrefixedLogger.html +1 -1
  285. data/docs/brut-js/api/RichString.html +1 -1
  286. data/docs/brut-js/api/RichString.js.html +1 -1
  287. data/docs/brut-js/api/Tabs.html +1 -1
  288. data/docs/brut-js/api/Tabs.js.html +1 -1
  289. data/docs/brut-js/api/Tracing.html +1 -1
  290. data/docs/brut-js/api/Tracing.js.html +1 -1
  291. data/docs/brut-js/api/external-CustomElementRegistry.html +1 -1
  292. data/docs/brut-js/api/external-Performance.html +1 -1
  293. data/docs/brut-js/api/external-Promise.html +1 -1
  294. data/docs/brut-js/api/external-ValidityState.html +1 -1
  295. data/docs/brut-js/api/external-Window.html +1 -1
  296. data/docs/brut-js/api/external-fetch.html +1 -1
  297. data/docs/brut-js/api/global.html +1 -1
  298. data/docs/brut-js/api/index.html +1 -1
  299. data/docs/brut-js/api/index.js.html +1 -1
  300. data/docs/brut-js/api/module-testing.html +1 -1
  301. data/docs/brut-js/api/testing.AssetMetadata.html +1 -1
  302. data/docs/brut-js/api/testing.AssetMetadataLoader.html +1 -1
  303. data/docs/brut-js/api/testing.CustomElementTest.html +1 -1
  304. data/docs/brut-js/api/testing.DOMCreator.html +1 -1
  305. data/docs/brut-js/api/testing_AssetMetadata.js.html +1 -1
  306. data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +1 -1
  307. data/docs/brut-js/api/testing_CustomElementTest.js.html +1 -1
  308. data/docs/brut-js/api/testing_DOMCreator.js.html +1 -1
  309. data/docs/brut-js/api/testing_index.js.html +1 -1
  310. data/docs/brut-js.html +4 -4
  311. data/docs/business-logic.html +4 -4
  312. data/docs/cli.html +4 -4
  313. data/docs/components.html +8 -8
  314. data/docs/configuration.html +5 -5
  315. data/docs/css.html +4 -4
  316. data/docs/custom-element-tests.html +4 -4
  317. data/docs/database-access.html +4 -4
  318. data/docs/database-schema.html +4 -4
  319. data/docs/deployment.html +4 -4
  320. data/docs/dev-environment.html +4 -4
  321. data/docs/dir-structure.html +4 -4
  322. data/docs/doc-conventions.html +4 -4
  323. data/docs/end-to-end-tests.html +4 -4
  324. data/docs/features.html +4 -4
  325. data/docs/flash-and-session.html +4 -4
  326. data/docs/form-constraints.html +11 -11
  327. data/docs/forms.html +6 -6
  328. data/docs/getting-started.html +10 -7
  329. data/docs/handlers.html +4 -4
  330. data/docs/hashmap.json +1 -1
  331. data/docs/hooks.html +4 -4
  332. data/docs/i18n.html +4 -4
  333. data/docs/index.html +3 -3
  334. data/docs/instrumentation.html +4 -4
  335. data/docs/javascript.html +4 -4
  336. data/docs/jobs.html +4 -4
  337. data/docs/keyword-injection.html +4 -4
  338. data/docs/layouts.html +4 -4
  339. data/docs/lsp.html +4 -4
  340. data/docs/markdown-examples.html +4 -4
  341. data/docs/middleware.html +4 -4
  342. data/docs/overview.html +4 -4
  343. data/docs/pages.html +4 -4
  344. data/docs/recipes/alternate-layouts.html +4 -4
  345. data/docs/recipes/authentication.html +5 -5
  346. data/docs/recipes/blank-layouts.html +4 -4
  347. data/docs/recipes/custom-flash.html +4 -4
  348. data/docs/recipes/form-errors.html +94 -0
  349. data/docs/recipes/indexed-forms.html +4 -4
  350. data/docs/recipes/migrations.html +5 -5
  351. data/docs/recipes/text-field-component.html +4 -4
  352. data/docs/roadmap.html +4 -4
  353. data/docs/routes.html +4 -4
  354. data/docs/security.html +4 -4
  355. data/docs/seed-data.html +4 -4
  356. data/docs/space-time-continuum.html +4 -4
  357. data/docs/tutorial.html +12 -713
  358. data/docs/tutorials/01-intro.html +736 -0
  359. data/docs/tutorials/02-dialog.html +302 -0
  360. data/docs/unit-tests.html +4 -4
  361. data/docs/why.html +4 -4
  362. data/lib/brut/instrumentation/methods.rb +153 -0
  363. data/lib/brut/instrumentation/open_telemetry.rb +1 -0
  364. data/lib/brut/instrumentation.rb +1 -0
  365. data/lib/brut/version.rb +1 -1
  366. data/mkbrut/Gemfile.lock +1 -1
  367. data/mkbrut/bin/publish +1 -1
  368. data/mkbrut/lib/mkbrut/version.rb +1 -1
  369. data/specs/brut/instrumentation/methods.spec.rb +399 -0
  370. metadata +39 -17
  371. data/docs/assets/chunks/@localSearchIndexroot.Dn1xGMv_.js +0 -1
  372. data/docs/assets/tutorial.md.C4zR5XPG.lean.js +0 -1
  373. /data/docs/assets/{components.md.B543a3Lm.lean.js → components.md.BzVRwegp.lean.js} +0 -0
  374. /data/docs/assets/{configuration.md.-foE_iVv.lean.js → configuration.md.eM5wFVi5.lean.js} +0 -0
  375. /data/docs/assets/{form-constraints.md.DK5adCgM.lean.js → form-constraints.md.KTv5cdR4.lean.js} +0 -0
  376. /data/docs/assets/{forms.md.D5-2rgHh.lean.js → forms.md.B3BHvCV3.lean.js} +0 -0
@@ -0,0 +1,153 @@
1
+ require "set"
2
+ require "opentelemetry-sdk"
3
+
4
+ # Allows instrumentation of methods via a class method. While Pages, Handlers, and Components are generally
5
+ # instrumented, this module can be used for your back-end classes (or private methods of pages, handlers, or
6
+ # component).
7
+ #
8
+ # @example Instrument specific methods
9
+ # class Widget
10
+ # include Brut::Instrumentation::Methods
11
+ #
12
+ # def save
13
+ # # ...
14
+ # end
15
+ #
16
+ # def search
17
+ # # ...
18
+ # end
19
+ #
20
+ # instrument :save # search is not instrumented
21
+ # end
22
+ #
23
+ # @example Instrument all methods
24
+ # class Widget
25
+ # include Brut::Instrumentation::Methods
26
+ #
27
+ # instrument_all!
28
+ #
29
+ # def save
30
+ # # ...
31
+ # end
32
+ #
33
+ # def search
34
+ # # ...
35
+ # end
36
+ #
37
+ # private
38
+ #
39
+ # def delete_orphans
40
+ # end
41
+ # end
42
+ module Brut::Instrumentation::Methods
43
+
44
+ # @!visibility private
45
+ def self.included(base)
46
+ base.extend(ClassMethods)
47
+ end
48
+
49
+ module ClassMethods
50
+
51
+ def __module_for_instrumented_methods
52
+ @__module_for_instrumented_methods ||= begin
53
+ mod = Module.new
54
+ prepend mod
55
+ mod
56
+ end
57
+ end
58
+
59
+ # Instrument methods that have already been defined.
60
+ #
61
+ # @param method_names [Array<Symbol>] the method names to instrument. They must
62
+ # exist and they must not already have been instrumented.
63
+ # @raise [ArgumentError] if any method does not exist or has already been instrumented
64
+ def instrument(*method_names)
65
+
66
+ method_names.each do |method_name|
67
+ if __module_for_instrumented_methods.method_defined?(method_name)
68
+ raise ArgumentError, "Method #{method_name} is already instrumented in #{self}"
69
+ end
70
+
71
+ method_defined = method_defined?(method_name) ||
72
+ private_method_defined?(method_name) ||
73
+ protected_method_defined?(method_name)
74
+
75
+ if !method_defined
76
+ raise ArgumentError, "Method #{method_name} is not defined in #{self.name}"
77
+ end
78
+
79
+ visibility = if private_method_defined?(method_name)
80
+ :private
81
+ elsif protected_method_defined?(method_name)
82
+ :protected
83
+ else
84
+ :public
85
+ end
86
+
87
+ __module_for_instrumented_methods.module_eval do
88
+ define_method(method_name) do |*args, **kwargs, &blk|
89
+ span_name = "#{self.class.name}##{method_name}"
90
+ span_attrs = {
91
+ "brut.class" => self.class.name,
92
+ "brut.method" => method_name.to_s,
93
+ }
94
+
95
+ Brut.container.instrumentation.span(span_name, attributes: span_attrs) do |span|
96
+ super(*args, **kwargs, &blk)
97
+ end
98
+ end
99
+
100
+ # match original visibility
101
+ send(visibility, method_name)
102
+ end
103
+ end
104
+ nil
105
+ end
106
+
107
+ # Instrument all methods, public, protected, and private, other than `initialize`.
108
+ # This will also instrument any methods defined in this class after it's called, meaning
109
+ # you can it at the top of the class. Do note that this will not instrument any
110
+ # methods brought in via modules.
111
+ def instrument_all
112
+ @__instrument_all = true
113
+ method_names = instance_methods(false) + private_instance_methods(false) - [ :initialize ]
114
+
115
+ instrument(*method_names)
116
+
117
+ nil
118
+ end
119
+
120
+ # Instrument all public and protected methods, other than `initialize`.
121
+ # This will also instrument any public or protected methods defined in this class after it's called,
122
+ # meaning you can it at the top of the class. Do note that this will not instrument any
123
+ # methods brought in via modules.
124
+ def instrument_public
125
+ @__instrument_public = true
126
+ method_names = instance_methods(false) - [ :initialize ]
127
+
128
+ instrument(*method_names)
129
+
130
+ nil
131
+ end
132
+
133
+ def method_added(meth)
134
+ if !@__instrument_all && !@__instrument_public
135
+ return
136
+ end
137
+ if @__adding__
138
+ return
139
+ end
140
+ if meth == :initialize
141
+ return
142
+ end
143
+ if !@__instrument_all && private_instance_methods(false).include?(meth)
144
+ return
145
+ end
146
+
147
+ @__adding__ = true
148
+ instrument(meth)
149
+ @__adding__ = false
150
+ end
151
+ end
152
+ end
153
+
@@ -25,6 +25,7 @@ class Brut::Instrumentation::OpenTelemetry
25
25
  result = block.(wrapped_span)
26
26
  rescue => ex
27
27
  span.record_exception(ex)
28
+ span.status = OpenTelemetry::Trace::Status.error("Unhandled exception: #{ex.class}")
28
29
  raise
29
30
  end
30
31
  end
@@ -4,5 +4,6 @@
4
4
  module Brut::Instrumentation
5
5
  autoload(:OpenTelemetry,"brut/instrumentation/open_telemetry")
6
6
  autoload(:LoggerSpanExporter,"brut/instrumentation/logger_span_exporter")
7
+ autoload(:Methods,"brut/instrumentation/methods")
7
8
  end
8
9
 
data/lib/brut/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module Brut
2
2
  # @!visibility private
3
- VERSION = "0.10.0"
3
+ VERSION = "0.11.0"
4
4
  end
data/mkbrut/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- mkbrut (0.10.0)
4
+ mkbrut (0.11.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/mkbrut/bin/publish CHANGED
@@ -67,7 +67,7 @@ setup_docker_buildx() {
67
67
  detect_gem_version() {
68
68
 
69
69
  GEM_VERSION=$( rake -T | grep "rake install " | sed 's/^.*mkbrut-//g' | sed 's/\.gem.*$//g')
70
- if [[ "$GEM_VERSION" =~ ^[0-9]+\.[0-9]\.[0-9]+$ ]]; then
70
+ if [[ "$GEM_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
71
71
  log "Releasing version ${GEM_VERSION}"
72
72
  else
73
73
  log "Version '${GEM_VERSION}' does not look like a version: X.Y.Z"
@@ -1,3 +1,3 @@
1
1
  module MKBrut
2
- VERSION = "0.10.0"
2
+ VERSION = "0.11.0"
3
3
  end
@@ -0,0 +1,399 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe Brut::Instrumentation::Methods do
4
+ describe "#instrument" do
5
+ it "instruments only those methods specified" do
6
+ mock_container = double("Brut::Framework::Container")
7
+ instrumentation = instance_double(Brut::Instrumentation::OpenTelemetry)
8
+ allow(Brut).to receive(:container).and_return(mock_container)
9
+ allow(mock_container).to receive(:instrumentation).and_return(instrumentation)
10
+ allow(instrumentation).to receive(:span).and_yield(double("span that should not be used"))
11
+
12
+ class_to_instrument = Class.new do
13
+ include Brut::Instrumentation::Methods
14
+
15
+ def save
16
+ @save_called = true
17
+ end
18
+
19
+ def search
20
+ @search_called = true
21
+ end
22
+
23
+ def self.name # since this is an anonymous class, need some name to show up
24
+ "TestClass"
25
+ end
26
+
27
+ private
28
+
29
+ def delete
30
+ @delete_called = true
31
+ end
32
+
33
+ instrument :save, :delete
34
+ end
35
+ object = class_to_instrument.new
36
+
37
+ object.save
38
+ object.search
39
+ object.send(:delete)
40
+
41
+ expect(object.instance_variable_get(:@save_called)).to eq(true)
42
+ expect(object.instance_variable_get(:@search_called)).to eq(true)
43
+ expect(object.instance_variable_get(:@delete_called)).to eq(true)
44
+ expect(instrumentation).to have_received(:span).with(
45
+ "TestClass#save",
46
+ attributes: {
47
+ "brut.class" => "TestClass",
48
+ "brut.method" => "save",
49
+ }
50
+ )
51
+ expect(instrumentation).to have_received(:span).with(
52
+ "TestClass#delete",
53
+ attributes: {
54
+ "brut.class" => "TestClass",
55
+ "brut.method" => "delete",
56
+ }
57
+ )
58
+ expect(instrumentation).not_to have_received(:span).with(
59
+ /\#search/,
60
+ attributes: kind_of(Hash)
61
+ )
62
+ end
63
+ it "works when called on a method definition" do
64
+ mock_container = double("Brut::Framework::Container")
65
+ instrumentation = instance_double(Brut::Instrumentation::OpenTelemetry)
66
+ allow(Brut).to receive(:container).and_return(mock_container)
67
+ allow(mock_container).to receive(:instrumentation).and_return(instrumentation)
68
+ allow(instrumentation).to receive(:span).and_yield(double("span that should not be used"))
69
+
70
+ class_to_instrument = Class.new do
71
+ include Brut::Instrumentation::Methods
72
+
73
+ instrument def save
74
+ @save_called = true
75
+ end
76
+
77
+ def search
78
+ @search_called = true
79
+ end
80
+
81
+ def self.name # since this is an anonymous class, need some name to show up
82
+ "TestClass"
83
+ end
84
+
85
+ private
86
+
87
+ instrument def delete
88
+ @delete_called = true
89
+ end
90
+ end
91
+ object = class_to_instrument.new
92
+
93
+ object.save
94
+ object.search
95
+ object.send(:delete)
96
+
97
+ expect(object.instance_variable_get(:@save_called)).to eq(true)
98
+ expect(object.instance_variable_get(:@search_called)).to eq(true)
99
+ expect(object.instance_variable_get(:@delete_called)).to eq(true)
100
+ expect(instrumentation).to have_received(:span).with(
101
+ "TestClass#save",
102
+ attributes: {
103
+ "brut.class" => "TestClass",
104
+ "brut.method" => "save",
105
+ }
106
+ )
107
+ expect(instrumentation).to have_received(:span).with(
108
+ "TestClass#delete",
109
+ attributes: {
110
+ "brut.class" => "TestClass",
111
+ "brut.method" => "delete",
112
+ }
113
+ )
114
+ expect(instrumentation).not_to have_received(:span).with(
115
+ /\#search/,
116
+ attributes: kind_of(Hash)
117
+ )
118
+ end
119
+
120
+ it "calling it twice raises an error" do
121
+ expect {
122
+ Class.new do
123
+ include Brut::Instrumentation::Methods
124
+
125
+ def save
126
+ @save_called = true
127
+ end
128
+
129
+ def self.name # since this is an anonymous class, need some name to show up
130
+ "TestClass"
131
+ end
132
+
133
+ instrument :save
134
+ instrument :save
135
+ end
136
+ }.to raise_error(ArgumentError, /already instrumented/)
137
+ end
138
+
139
+ it "instrumenting a nonexistent method raises an error" do
140
+ expect {
141
+ Class.new do
142
+ include Brut::Instrumentation::Methods
143
+
144
+ def save
145
+ @save_called = true
146
+ end
147
+
148
+ def self.name # since this is an anonymous class, need some name to show up
149
+ "TestClass"
150
+ end
151
+
152
+ instrument :foo
153
+ end
154
+ }.to raise_error(ArgumentError)
155
+ end
156
+ end
157
+ describe "#instrument_all" do
158
+ it "instruments all methods" do
159
+ mock_container = double("Brut::Framework::Container")
160
+ instrumentation = instance_double(Brut::Instrumentation::OpenTelemetry)
161
+ allow(Brut).to receive(:container).and_return(mock_container)
162
+ allow(mock_container).to receive(:instrumentation).and_return(instrumentation)
163
+ allow(instrumentation).to receive(:span).and_yield(double("span that should not be used"))
164
+
165
+ class_to_instrument = Class.new do
166
+ include Brut::Instrumentation::Methods
167
+
168
+ def save
169
+ @save_called = true
170
+ end
171
+
172
+ def search
173
+ @search_called = true
174
+ end
175
+
176
+ def self.name # since this is an anonymous class, need some name to show up
177
+ "TestClass"
178
+ end
179
+
180
+ private
181
+
182
+ def delete
183
+ @delete_called = true
184
+ end
185
+
186
+ instrument_all
187
+ end
188
+ object = class_to_instrument.new
189
+
190
+ object.save
191
+ object.search
192
+ object.send(:delete)
193
+
194
+ expect(object.instance_variable_get(:@save_called)).to eq(true)
195
+ expect(object.instance_variable_get(:@search_called)).to eq(true)
196
+ expect(object.instance_variable_get(:@delete_called)).to eq(true)
197
+ expect(instrumentation).to have_received(:span).with(
198
+ "TestClass#save",
199
+ attributes: {
200
+ "brut.class" => "TestClass",
201
+ "brut.method" => "save",
202
+ }
203
+ )
204
+ expect(instrumentation).to have_received(:span).with(
205
+ "TestClass#delete",
206
+ attributes: {
207
+ "brut.class" => "TestClass",
208
+ "brut.method" => "delete",
209
+ }
210
+ )
211
+ expect(instrumentation).to have_received(:span).with(
212
+ "TestClass#search",
213
+ attributes: {
214
+ "brut.class" => "TestClass",
215
+ "brut.method" => "search",
216
+ }
217
+ )
218
+ end
219
+ it "instruments all methods defined after it is called" do
220
+ mock_container = double("Brut::Framework::Container")
221
+ instrumentation = instance_double(Brut::Instrumentation::OpenTelemetry)
222
+ allow(Brut).to receive(:container).and_return(mock_container)
223
+ allow(mock_container).to receive(:instrumentation).and_return(instrumentation)
224
+ allow(instrumentation).to receive(:span).and_yield(double("span that should not be used"))
225
+
226
+ class_to_instrument = Class.new do
227
+ include Brut::Instrumentation::Methods
228
+ instrument_all
229
+
230
+ def save
231
+ @save_called = true
232
+ end
233
+
234
+ def search
235
+ @search_called = true
236
+ end
237
+
238
+ def self.name # since this is an anonymous class, need some name to show up
239
+ "TestClass"
240
+ end
241
+
242
+ private
243
+
244
+ def delete
245
+ @delete_called = true
246
+ end
247
+ end
248
+ object = class_to_instrument.new
249
+
250
+ object.save
251
+ object.search
252
+ object.send(:delete)
253
+
254
+ expect(object.instance_variable_get(:@save_called)).to eq(true)
255
+ expect(object.instance_variable_get(:@search_called)).to eq(true)
256
+ expect(object.instance_variable_get(:@delete_called)).to eq(true)
257
+ expect(instrumentation).to have_received(:span).with(
258
+ "TestClass#save",
259
+ attributes: {
260
+ "brut.class" => "TestClass",
261
+ "brut.method" => "save",
262
+ }
263
+ )
264
+ expect(instrumentation).to have_received(:span).with(
265
+ "TestClass#delete",
266
+ attributes: {
267
+ "brut.class" => "TestClass",
268
+ "brut.method" => "delete",
269
+ }
270
+ )
271
+ expect(instrumentation).to have_received(:span).with(
272
+ "TestClass#search",
273
+ attributes: {
274
+ "brut.class" => "TestClass",
275
+ "brut.method" => "search",
276
+ }
277
+ )
278
+ end
279
+ end
280
+ describe "#instrument_public" do
281
+ it "instruments only public methods" do
282
+ mock_container = double("Brut::Framework::Container")
283
+ instrumentation = instance_double(Brut::Instrumentation::OpenTelemetry)
284
+ allow(Brut).to receive(:container).and_return(mock_container)
285
+ allow(mock_container).to receive(:instrumentation).and_return(instrumentation)
286
+ allow(instrumentation).to receive(:span).and_yield(double("span that should not be used"))
287
+
288
+ class_to_instrument = Class.new do
289
+ include Brut::Instrumentation::Methods
290
+
291
+ def save
292
+ @save_called = true
293
+ end
294
+
295
+ def search
296
+ @search_called = true
297
+ end
298
+
299
+ def self.name # since this is an anonymous class, need some name to show up
300
+ "TestClass"
301
+ end
302
+
303
+ private
304
+
305
+ def delete
306
+ @delete_called = true
307
+ end
308
+
309
+ instrument_public
310
+
311
+ end
312
+ object = class_to_instrument.new
313
+
314
+ object.save
315
+ object.search
316
+ object.send(:delete)
317
+
318
+ expect(object.instance_variable_get(:@save_called)).to eq(true)
319
+ expect(object.instance_variable_get(:@search_called)).to eq(true)
320
+ expect(object.instance_variable_get(:@delete_called)).to eq(true)
321
+ expect(instrumentation).to have_received(:span).with(
322
+ "TestClass#save",
323
+ attributes: {
324
+ "brut.class" => "TestClass",
325
+ "brut.method" => "save",
326
+ }
327
+ )
328
+ expect(instrumentation).to have_received(:span).with(
329
+ "TestClass#search",
330
+ attributes: {
331
+ "brut.class" => "TestClass",
332
+ "brut.method" => "search",
333
+ }
334
+ )
335
+ expect(instrumentation).not_to have_received(:span).with(
336
+ "TestClass#delete",
337
+ attributes: kind_of(Hash)
338
+ )
339
+ end
340
+ it "instruments only public methods defined after it is called" do
341
+ mock_container = double("Brut::Framework::Container")
342
+ instrumentation = instance_double(Brut::Instrumentation::OpenTelemetry)
343
+ allow(Brut).to receive(:container).and_return(mock_container)
344
+ allow(mock_container).to receive(:instrumentation).and_return(instrumentation)
345
+ allow(instrumentation).to receive(:span).and_yield(double("span that should not be used"))
346
+
347
+ class_to_instrument = Class.new do
348
+ include Brut::Instrumentation::Methods
349
+ instrument_public
350
+
351
+ def save
352
+ @save_called = true
353
+ end
354
+
355
+ def search
356
+ @search_called = true
357
+ end
358
+
359
+ def self.name # since this is an anonymous class, need some name to show up
360
+ "TestClass"
361
+ end
362
+
363
+ private
364
+
365
+ def delete
366
+ @delete_called = true
367
+ end
368
+
369
+ end
370
+ object = class_to_instrument.new
371
+
372
+ object.save
373
+ object.search
374
+ object.send(:delete)
375
+
376
+ expect(object.instance_variable_get(:@save_called)).to eq(true)
377
+ expect(object.instance_variable_get(:@search_called)).to eq(true)
378
+ expect(object.instance_variable_get(:@delete_called)).to eq(true)
379
+ expect(instrumentation).to have_received(:span).with(
380
+ "TestClass#save",
381
+ attributes: {
382
+ "brut.class" => "TestClass",
383
+ "brut.method" => "save",
384
+ }
385
+ )
386
+ expect(instrumentation).to have_received(:span).with(
387
+ "TestClass#search",
388
+ attributes: {
389
+ "brut.class" => "TestClass",
390
+ "brut.method" => "search",
391
+ }
392
+ )
393
+ expect(instrumentation).not_to have_received(:span).with(
394
+ "TestClass#delete",
395
+ attributes: kind_of(Hash)
396
+ )
397
+ end
398
+ end
399
+ end