{"id":16443378,"url":"https://github.com/0exp/puma_after_reply","last_synced_at":"2025-07-09T06:05:39.848Z","repository":{"id":246771333,"uuid":"822145576","full_name":"0exp/puma_after_reply","owner":"0exp","description":"Puma's \"rack.after_reply\" integration for your Rack-applications. Provides #call-able reply abstraction and configurable invocation flow with before/on_error/after hooks for each added reply.","archived":false,"fork":false,"pushed_at":"2024-08-03T19:05:38.000Z","size":63,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-07-09T06:04:16.835Z","etag":null,"topics":["puma-after-reply","rack-after-reply"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/0exp.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-06-30T12:03:09.000Z","updated_at":"2024-08-29T18:48:39.000Z","dependencies_parsed_at":null,"dependency_job_id":"bf8ebda3-c805-4bcb-97ff-1948f43898b3","html_url":"https://github.com/0exp/puma_after_reply","commit_stats":null,"previous_names":["0exp/puma_after_reply"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/0exp/puma_after_reply","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0exp%2Fpuma_after_reply","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0exp%2Fpuma_after_reply/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0exp%2Fpuma_after_reply/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0exp%2Fpuma_after_reply/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/0exp","download_url":"https://codeload.github.com/0exp/puma_after_reply/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0exp%2Fpuma_after_reply/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264403795,"owners_count":23602619,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["puma-after-reply","rack-after-reply"],"created_at":"2024-10-11T09:20:17.074Z","updated_at":"2025-07-09T06:05:39.821Z","avatar_url":"https://github.com/0exp.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# puma_after_reply \u0026middot; ![Gem Version](https://img.shields.io/gem/v/puma_after_reply) ![build](https://github.com/0exp/puma_after_reply/actions/workflows/build.yml/badge.svg??branch=master)\n\nPuma's `\"rack.after_reply\"` integration for your Rack-applications. Provides #call-able reply\nabstraction and configurable non-threaded/threaded invocation flow with before/on_error/after hooks for each added reply.\n\n---\n\n## Table of Contents\n\n- [Requirements](#requirements)\n- [Installation](#installation)\n- [Usage](#usage)\n  - [Algorithm](#algorithm)\n  - [Configuration](#configuration)\n  - [Adding replies](#adding-replies-add_replycond_reply)\n  - [Some debugging methods](#some-debugging-methods)\n  - [Test environments](#test-environments-and-other-rack-apps)\n- [Contributing](#contributing)\n- [License](#license)\n- [Authors](#authors)\n\n---\n\n### Requirements\n\n\u003csup\u003e\\[[back to top](#table-of-contents)\\]\u003c/sup\u003e\n\n- `concurrent-ruby` (`~\u003e 1.3`)\n\n---\n\n### Installation\n\n\u003csup\u003e\\[[back to top](#table-of-contents)\\]\u003c/sup\u003e\n\n\n```ruby\ngem 'puma_after_reply'\n```\n\n```shell\nbundle install\n# --- or ---\ngem install puma_after_reply\n```\n\n```ruby\nrequire 'puma_after_reply'\n```\n\n---\n\n### Usage\n\n\u003csup\u003e\\[[back to top](#table-of-contents)\\]\u003c/sup\u003e\n\n- [Algorithm](#algorithm)\n- [Configuration](#configuration)\n- [Adding replies](#adding-replies-add_replycond_reply)\n- [Some debugging methods](#some-debugging-methods)\n- [Test environments](#test-environments-and-other-rack-apps)\n\n---\n\n#### Algorithm\n\n- every Puma's worker gets own reply collector;\n- during the Puma's request your logic adds replies to the worker's reply collector:\n  - using `PumaAfterReply.add_reply` ([doc](#adding-replies-add_replycond_reply));\n  - using `PumaAfterReply.cond_reply` ([doc](#adding-replies-add_replycond_reply));\n- after processing the request, Puma's worker returns a response to the browser;\n- then Puma's worker launches accumulated replies:\n  - threaded replies are launched in separated threads;\n  - non-threaded replies are launched sequentially;\n- after processing all replies, the worker's reply collector is cleared;\n\nEach separated reply is launched according to the following invocation flow:\n- **before_reply** hook (`config.before_reply`);\n- reply invocation (`reply.call`);\n- if `reply.call` failed with an error:\n  - **log_error** hook (`config.log_error`);\n  - **on_error** hook (`config.on_error`);\n  - **raise_on_error** fail check (`config.fail_on_error`)\n- (ensure): **after_reply** hook (`config.after_reply`);\n\n**Remember:** if you add a reply outside of the Puma's/Rack's request context your reply will never be processed and flushed\n(that can lead to the memory leak/memory bloat).\n\n---\n\n#### Configuration\n\n\u003csup\u003e\\[[back to top](#usage)\\]\u003c/sup\u003e\n\n```ruby\nPumaAfterReply.configure do |config|\n  # default values:\n  config.fail_on_error = false # expects: \u003cBoolean\u003e\n  config.log_error = nil # expects: \u003cnil,#call,Proc\u003e (receives: error object)\n  config.on_error = nil # expects: \u003cnil,#call,Proc\u003e (receives: error object)\n  config.before_reply = nil # expects: \u003cnil,#call,Proc\u003e (receives: nothing)\n  config.after_reply = nil # expects: \u003cnil,#call,Proc\u003e (receives: nothing)\n  config.run_anyway = false # expects: \u003cBoolean\u003e\n  config.thread_pool_size = 10 # expects: \u003cInteger\u003e\nend\n\n# get configs as a hash:\nPumaAfterReply.cofnig.to_h\n\n# get configs directly:\nPumaAfterRepy.config.fail_on_error # and etc;\n```\n\n```ruby\n# (IMPORTANT): register the middleware (Rails example)\nRails.configuration.middleware.use(PumaAfterReply::Middleware)\n```\n\n---\n\n#### Adding replies (`add_reply`/`cond_reply`)\n\n\u003csup\u003e\\[[back to top](#usage)\\]\u003c/sup\u003e\n\n- non-threaded way (this reply will be processed sequentially):\n\n```ruby\n# non-threaded way:\nPumaAfterReply.add_reply { your_code }\n```\n\n- threaded-way (this reply will be processed in a separated thread):\n\n```ruby\n# threaded way:\nPumaAfterReply.add_reply(threaded: true) { your_code }\n```\n\n- conditional reply adding:\n  - `reply(condition, threaded: false, \u0026reply)` (`threaded: false` by default);\n  - when condition is `true` - your reply will be pushed to the reply queue;\n  - when condition is `false` - your reply will be processed immediately;\n  - condition can be represented as callable object (`#call`/`Proc`);\n\n```ruby\n# with a boolean value:\nPumaAfterReply.cond_reply(!Rails.env.test?) { your_code }\n```\n\n```ruby\n# with a callabale value:\nis_puma_request = proc { check_that_we_are_inside_a_request }\nPumaAfterReply.cond_reply(is_puma_request) { your_code }\n```\n\n```ruby\n# add threaded reply:\nPumaAfterReply.cond_reply(some_condition, threaded: true) { your_code }\n```\n\n---\n\n#### Some debugging methods\n\n\u003csup\u003e\\[[back to top](#usage)\\]\u003c/sup\u003e\n\n```ruby\n# the count of the added replies:\nPumaAfterReply.count # or .size\n```\n\n```ruby\n# replies collections:\nPumaAfterReply.replies # all added replies\nPumaAfterReply.inline_replies # all added non-threaded replies\nPumaAfterReply.threaded_replies # all added threaded replies\n```\n\n```ruby\n# manual replies running:\nPumaAfterReply.call # or .run\n```\n\n```ruby\n# clear replies collector:\nPumaAfterReply.clear\n```\n\n```ruby\n# reset configs to default values:\nPumaAfterReply.config.__reset!\n```\n\n---\n\n#### Test environments (and other Rack apps)\n\n\u003csup\u003e\\[[back to top](#usage)\\]\u003c/sup\u003e\n\nIn some cases and Rack applications you can have no `\"rack.after_reply\"` key in your Rack env\n(request environments in your tests, for example). For this cases you can use `config.run_anyway = true`:\non each of your rack request all accumulated replies will be processed and cleared in the same way as for Puma.\n\n---\n\n## Contributing\n\n\u003csup\u003e\\[[back to top](#table-of-contents)\\]\u003c/sup\u003e\n\n- Fork it ( https://github.com/0exp/puma_after_reply )\n- Create your feature branch (`git checkout -b feature/my-new-feature`)\n- Commit your changes (`git commit -am '[feature_context] Add some feature'`)\n- Push to the branch (`git push origin feature/my-new-feature`)\n- Create new Pull Request\n\n## License\n\n\u003csup\u003e\\[[back to top](#table-of-contents)\\]\u003c/sup\u003e\n\nReleased under MIT License.\n\n## Authors\n\n\u003csup\u003e\\[[back to top](#table-of-contents)\\]\u003c/sup\u003e\n\n[Rustam Ibragimov](https://github.com/0exp)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F0exp%2Fpuma_after_reply","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F0exp%2Fpuma_after_reply","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F0exp%2Fpuma_after_reply/lists"}