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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/Gemfile.lock +15 -15
- data/assets/YouTubeThumb.pxd +0 -0
- data/bin/new-version +3 -3
- data/brut-css/package-lock.json +2 -2
- data/brut-css/package.json +1 -1
- data/brut-js/package-lock.json +2 -2
- data/brut-js/package.json +1 -1
- data/brutrb.com/getting-started.md +3 -0
- data/brutrb.com/images/tutorial/02-confirmation-dialog-browser-element-styled.png +0 -0
- data/brutrb.com/images/tutorial/02-confirmation-dialog-browser-element.png +0 -0
- data/brutrb.com/images/tutorial/02-confirmation-dialog-browser.png +0 -0
- data/brutrb.com/images/tutorial/02-confirmation-flow.graffle +0 -0
- data/brutrb.com/images/tutorial/02-confirmation-flow.png +0 -0
- data/brutrb.com/instrumentation.md +142 -3
- data/brutrb.com/tutorial.md +29 -1627
- data/brutrb.com/tutorials/01-intro.md +1630 -0
- data/brutrb.com/tutorials/02-dialog.md +569 -0
- data/docs/404.html +2 -2
- data/docs/adrs.html +4 -4
- data/docs/ai.html +4 -4
- data/docs/api/Brut/BackEnd/SeedData.html +1 -1
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +1 -1
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +1 -1
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +1 -1
- data/docs/api/Brut/BackEnd/Sidekiq.html +1 -1
- data/docs/api/Brut/BackEnd/Validators/FormValidator.html +1 -1
- data/docs/api/Brut/BackEnd/Validators.html +1 -1
- data/docs/api/Brut/BackEnd.html +1 -1
- data/docs/api/Brut/CLI/App.html +1 -1
- data/docs/api/Brut/CLI/AppRunner.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Create.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Drop.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Migrate.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Seed.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Status.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB.html +1 -1
- data/docs/api/Brut/CLI/Apps/DeployBase/GitChecks.html +1 -1
- data/docs/api/Brut/CLI/Apps/DeployBase.html +1 -1
- data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy/Deploy.html +1 -1
- data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/DbModel.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test/Audit.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test/E2e.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test/JS.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test/Run.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test.html +1 -1
- data/docs/api/Brut/CLI/Apps.html +1 -1
- data/docs/api/Brut/CLI/Command.html +1 -1
- data/docs/api/Brut/CLI/Error.html +1 -1
- data/docs/api/Brut/CLI/ExecutionResults/Result.html +1 -1
- data/docs/api/Brut/CLI/ExecutionResults.html +1 -1
- data/docs/api/Brut/CLI/Executor.html +1 -1
- data/docs/api/Brut/CLI/InvalidOption.html +1 -1
- data/docs/api/Brut/CLI/Options.html +1 -1
- data/docs/api/Brut/CLI/Output.html +1 -1
- data/docs/api/Brut/CLI/SystemExecError.html +1 -1
- data/docs/api/Brut/CLI.html +1 -1
- data/docs/api/Brut/FactoryBot.html +1 -1
- data/docs/api/Brut/Framework/App.html +1 -1
- data/docs/api/Brut/Framework/Config.html +1 -1
- data/docs/api/Brut/Framework/Container.html +1 -1
- data/docs/api/Brut/Framework/Error.html +1 -1
- data/docs/api/Brut/Framework/Errors/AbstractMethod.html +1 -1
- data/docs/api/Brut/Framework/Errors/Bug.html +1 -1
- data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +1 -1
- data/docs/api/Brut/Framework/Errors/MissingParameter.html +1 -1
- data/docs/api/Brut/Framework/Errors/NoClassForPath.html +1 -1
- data/docs/api/Brut/Framework/Errors/NotFound.html +1 -1
- data/docs/api/Brut/Framework/Errors/NotImplemented.html +1 -1
- data/docs/api/Brut/Framework/Errors.html +1 -1
- data/docs/api/Brut/Framework/FussyTypeEnforcement.html +1 -1
- data/docs/api/Brut/Framework/MCP.html +1 -1
- data/docs/api/Brut/Framework/ProjectEnvironment.html +1 -1
- data/docs/api/Brut/Framework.html +1 -1
- data/docs/api/Brut/FrontEnd/AssetPathResolver.html +1 -1
- data/docs/api/Brut/FrontEnd/Component/Helpers.html +1 -1
- data/docs/api/Brut/FrontEnd/Component.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +48 -27
- data/docs/api/Brut/FrontEnd/Components/FormTag.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Input.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/TimeTag.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Traceparent.html +1 -1
- data/docs/api/Brut/FrontEnd/Components.html +1 -1
- data/docs/api/Brut/FrontEnd/CsrfProtector.html +1 -1
- data/docs/api/Brut/FrontEnd/Download.html +1 -1
- data/docs/api/Brut/FrontEnd/Flash.html +1 -1
- data/docs/api/Brut/FrontEnd/Form.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/Input/Color.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/Input/TimeOfDay.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/Input.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms.html +1 -1
- data/docs/api/Brut/FrontEnd/GenericResponse.html +1 -1
- data/docs/api/Brut/FrontEnd/Handler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers.html +1 -1
- data/docs/api/Brut/FrontEnd/HandlingResults.html +1 -1
- data/docs/api/Brut/FrontEnd/HttpMethod.html +1 -1
- data/docs/api/Brut/FrontEnd/HttpStatus.html +1 -1
- data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +1 -1
- data/docs/api/Brut/FrontEnd/Layout.html +1 -1
- data/docs/api/Brut/FrontEnd/Middleware.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares.html +1 -1
- data/docs/api/Brut/FrontEnd/Page.html +1 -1
- data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +1 -1
- data/docs/api/Brut/FrontEnd/Pages.html +1 -1
- data/docs/api/Brut/FrontEnd/RequestContext.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHook.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/Route.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing.html +1 -1
- data/docs/api/Brut/FrontEnd/Session.html +1 -1
- data/docs/api/Brut/FrontEnd.html +1 -1
- data/docs/api/Brut/I18n/BaseMethods.html +1 -1
- data/docs/api/Brut/I18n/ForBackEnd.html +1 -1
- data/docs/api/Brut/I18n/ForCLI.html +1 -1
- data/docs/api/Brut/I18n/ForHTML.html +1 -1
- data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +1 -1
- data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +1 -1
- data/docs/api/Brut/I18n.html +1 -1
- data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +1 -1
- data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +1 -1
- data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +1 -1
- data/docs/api/Brut/Instrumentation/OpenTelemetry.html +1 -1
- data/docs/api/Brut/Instrumentation.html +1 -1
- data/docs/api/Brut/RubocopConfig.html +1 -1
- data/docs/api/Brut/SinatraHelpers/ClassMethods.html +1 -1
- data/docs/api/Brut/SinatraHelpers.html +1 -1
- data/docs/api/Brut/SpecSupport/ClockSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/ComponentSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/E2ETestServer.html +1 -1
- data/docs/api/Brut/SpecSupport/E2eSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/EnhancedNode.html +1 -1
- data/docs/api/Brut/SpecSupport/FlashSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +1 -1
- data/docs/api/Brut/SpecSupport/GeneralSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/HandlerSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/BeABug.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/BePageFor.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/BeRoutingFor.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveGenerated.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveI18nString.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveLinkTo.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveRedirectedTo.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedHttpStatus.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedRackResponse.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers.html +1 -1
- data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/RSpecSetup.html +1 -1
- data/docs/api/Brut/SpecSupport/SessionSupport.html +1 -1
- data/docs/api/Brut/SpecSupport.html +1 -1
- data/docs/api/Brut.html +1 -1
- data/docs/api/Clock.html +1 -1
- data/docs/api/ModuleName.html +1 -1
- data/docs/api/RichString.html +1 -1
- data/docs/api/SemanticLogger/Appender/Async.html +1 -1
- data/docs/api/Sequel/Extensions/BrutInstrumentation.html +1 -1
- data/docs/api/Sequel/Extensions/BrutMigrations.html +1 -1
- data/docs/api/Sequel/Extensions.html +1 -1
- data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +1 -1
- data/docs/api/Sequel/Plugins/CreatedAt.html +1 -1
- data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +1 -1
- data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +1 -1
- data/docs/api/Sequel/Plugins/ExternalId.html +1 -1
- data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +1 -1
- data/docs/api/Sequel/Plugins/FindBang.html +1 -1
- data/docs/api/Sequel/Plugins.html +1 -1
- data/docs/api/Sequel.html +1 -1
- data/docs/api/_index.html +1 -1
- data/docs/api/file.README.html +1 -1
- data/docs/api/index.html +1 -1
- data/docs/api/top-level-namespace.html +1 -1
- data/docs/assets/02-confirmation-dialog-browser-element-styled.3NEGM20-.png +0 -0
- data/docs/assets/02-confirmation-dialog-browser-element.DPsf0xUW.png +0 -0
- data/docs/assets/02-confirmation-dialog-browser.DH8ALFO4.png +0 -0
- data/docs/assets/02-confirmation-flow.D9gZ0S5U.png +0 -0
- data/docs/assets/{app.vjGWMSnJ.js → app.0-aKXKdt.js} +1 -1
- data/docs/assets/chunks/@localSearchIndexroot.DPhqaz1b.js +1 -0
- data/docs/assets/chunks/{VPLocalSearchBox.C-ymMW2k.js → VPLocalSearchBox.CW-UBkNA.js} +1 -1
- data/docs/assets/chunks/{theme.pJUosGlI.js → theme.a6feKWJO.js} +2 -2
- data/docs/assets/{components.md.B543a3Lm.js → components.md.BzVRwegp.js} +3 -3
- data/docs/assets/{configuration.md.-foE_iVv.js → configuration.md.eM5wFVi5.js} +1 -1
- data/docs/assets/{form-constraints.md.DK5adCgM.js → form-constraints.md.KTv5cdR4.js} +6 -6
- data/docs/assets/{forms.md.D5-2rgHh.js → forms.md.B3BHvCV3.js} +1 -1
- data/docs/assets/{getting-started.md.Cd4XSZb_.js → getting-started.md.BgR0ZHsl.js} +6 -3
- data/docs/assets/{getting-started.md.Cd4XSZb_.lean.js → getting-started.md.BgR0ZHsl.lean.js} +1 -1
- data/docs/assets/recipes_form-errors.md.Bv5RCKqH.js +66 -0
- data/docs/assets/recipes_form-errors.md.Bv5RCKqH.lean.js +1 -0
- data/docs/assets/tutorial.md.BM40jnoq.js +27 -0
- data/docs/assets/tutorial.md.BM40jnoq.lean.js +1 -0
- data/docs/assets/{tutorial.md.C4zR5XPG.js → tutorials_01-intro.md.BXvYWcO9.js} +5 -25
- data/docs/assets/tutorials_01-intro.md.BXvYWcO9.lean.js +1 -0
- data/docs/assets/tutorials_02-dialog.md.CIeg8R--.js +274 -0
- data/docs/assets/tutorials_02-dialog.md.CIeg8R--.lean.js +1 -0
- data/docs/assets.html +4 -4
- data/docs/brut-js/api/AjaxSubmit.html +1 -1
- data/docs/brut-js/api/AjaxSubmit.js.html +1 -1
- data/docs/brut-js/api/Autosubmit.html +1 -1
- data/docs/brut-js/api/Autosubmit.js.html +1 -1
- data/docs/brut-js/api/BaseCustomElement.html +1 -1
- data/docs/brut-js/api/BaseCustomElement.js.html +1 -1
- data/docs/brut-js/api/BrutCustomElements.html +1 -1
- data/docs/brut-js/api/BufferedLogger.html +1 -1
- data/docs/brut-js/api/ConfirmSubmit.html +1 -1
- data/docs/brut-js/api/ConfirmSubmit.js.html +1 -1
- data/docs/brut-js/api/ConfirmationDialog.html +1 -1
- data/docs/brut-js/api/ConfirmationDialog.js.html +1 -1
- data/docs/brut-js/api/ConstraintViolationMessage.html +55 -5
- data/docs/brut-js/api/ConstraintViolationMessage.js.html +18 -3
- data/docs/brut-js/api/ConstraintViolationMessages.html +1 -1
- data/docs/brut-js/api/ConstraintViolationMessages.js.html +1 -1
- data/docs/brut-js/api/CopyToClipboard.html +1 -1
- data/docs/brut-js/api/CopyToClipboard.js.html +1 -1
- data/docs/brut-js/api/Form.html +7 -10
- data/docs/brut-js/api/Form.js.html +20 -24
- data/docs/brut-js/api/I18nTranslation.html +1 -1
- data/docs/brut-js/api/I18nTranslation.js.html +1 -1
- data/docs/brut-js/api/LocaleDetection.html +1 -1
- data/docs/brut-js/api/LocaleDetection.js.html +1 -1
- data/docs/brut-js/api/Logger.html +1 -1
- data/docs/brut-js/api/Logger.js.html +1 -1
- data/docs/brut-js/api/Message.html +1 -1
- data/docs/brut-js/api/Message.js.html +1 -1
- data/docs/brut-js/api/PrefixedLogger.html +1 -1
- data/docs/brut-js/api/RichString.html +1 -1
- data/docs/brut-js/api/RichString.js.html +1 -1
- data/docs/brut-js/api/Tabs.html +1 -1
- data/docs/brut-js/api/Tabs.js.html +1 -1
- data/docs/brut-js/api/Tracing.html +1 -1
- data/docs/brut-js/api/Tracing.js.html +1 -1
- data/docs/brut-js/api/external-CustomElementRegistry.html +1 -1
- data/docs/brut-js/api/external-Performance.html +1 -1
- data/docs/brut-js/api/external-Promise.html +1 -1
- data/docs/brut-js/api/external-ValidityState.html +1 -1
- data/docs/brut-js/api/external-Window.html +1 -1
- data/docs/brut-js/api/external-fetch.html +1 -1
- data/docs/brut-js/api/global.html +1 -1
- data/docs/brut-js/api/index.html +1 -1
- data/docs/brut-js/api/index.js.html +1 -1
- data/docs/brut-js/api/module-testing.html +1 -1
- data/docs/brut-js/api/testing.AssetMetadata.html +1 -1
- data/docs/brut-js/api/testing.AssetMetadataLoader.html +1 -1
- data/docs/brut-js/api/testing.CustomElementTest.html +1 -1
- data/docs/brut-js/api/testing.DOMCreator.html +1 -1
- data/docs/brut-js/api/testing_AssetMetadata.js.html +1 -1
- data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +1 -1
- data/docs/brut-js/api/testing_CustomElementTest.js.html +1 -1
- data/docs/brut-js/api/testing_DOMCreator.js.html +1 -1
- data/docs/brut-js/api/testing_index.js.html +1 -1
- data/docs/brut-js.html +4 -4
- data/docs/business-logic.html +4 -4
- data/docs/cli.html +4 -4
- data/docs/components.html +8 -8
- data/docs/configuration.html +5 -5
- data/docs/css.html +4 -4
- data/docs/custom-element-tests.html +4 -4
- data/docs/database-access.html +4 -4
- data/docs/database-schema.html +4 -4
- data/docs/deployment.html +4 -4
- data/docs/dev-environment.html +4 -4
- data/docs/dir-structure.html +4 -4
- data/docs/doc-conventions.html +4 -4
- data/docs/end-to-end-tests.html +4 -4
- data/docs/features.html +4 -4
- data/docs/flash-and-session.html +4 -4
- data/docs/form-constraints.html +11 -11
- data/docs/forms.html +6 -6
- data/docs/getting-started.html +10 -7
- data/docs/handlers.html +4 -4
- data/docs/hashmap.json +1 -1
- data/docs/hooks.html +4 -4
- data/docs/i18n.html +4 -4
- data/docs/index.html +3 -3
- data/docs/instrumentation.html +4 -4
- data/docs/javascript.html +4 -4
- data/docs/jobs.html +4 -4
- data/docs/keyword-injection.html +4 -4
- data/docs/layouts.html +4 -4
- data/docs/lsp.html +4 -4
- data/docs/markdown-examples.html +4 -4
- data/docs/middleware.html +4 -4
- data/docs/overview.html +4 -4
- data/docs/pages.html +4 -4
- data/docs/recipes/alternate-layouts.html +4 -4
- data/docs/recipes/authentication.html +5 -5
- data/docs/recipes/blank-layouts.html +4 -4
- data/docs/recipes/custom-flash.html +4 -4
- data/docs/recipes/form-errors.html +94 -0
- data/docs/recipes/indexed-forms.html +4 -4
- data/docs/recipes/migrations.html +5 -5
- data/docs/recipes/text-field-component.html +4 -4
- data/docs/roadmap.html +4 -4
- data/docs/routes.html +4 -4
- data/docs/security.html +4 -4
- data/docs/seed-data.html +4 -4
- data/docs/space-time-continuum.html +4 -4
- data/docs/tutorial.html +12 -713
- data/docs/tutorials/01-intro.html +736 -0
- data/docs/tutorials/02-dialog.html +302 -0
- data/docs/unit-tests.html +4 -4
- data/docs/why.html +4 -4
- data/lib/brut/instrumentation/methods.rb +153 -0
- data/lib/brut/instrumentation/open_telemetry.rb +1 -0
- data/lib/brut/instrumentation.rb +1 -0
- data/lib/brut/version.rb +1 -1
- data/mkbrut/Gemfile.lock +1 -1
- data/mkbrut/bin/publish +1 -1
- data/mkbrut/lib/mkbrut/version.rb +1 -1
- data/specs/brut/instrumentation/methods.spec.rb +399 -0
- metadata +39 -17
- data/docs/assets/chunks/@localSearchIndexroot.Dn1xGMv_.js +0 -1
- data/docs/assets/tutorial.md.C4zR5XPG.lean.js +0 -1
- /data/docs/assets/{components.md.B543a3Lm.lean.js → components.md.BzVRwegp.lean.js} +0 -0
- /data/docs/assets/{configuration.md.-foE_iVv.lean.js → configuration.md.eM5wFVi5.lean.js} +0 -0
- /data/docs/assets/{form-constraints.md.DK5adCgM.lean.js → form-constraints.md.KTv5cdR4.lean.js} +0 -0
- /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
|
+
|
data/lib/brut/instrumentation.rb
CHANGED
data/lib/brut/version.rb
CHANGED
data/mkbrut/Gemfile.lock
CHANGED
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]
|
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"
|
@@ -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
|