{"id":13514426,"url":"https://github.com/hopsoft/turbo_boost-commands","last_synced_at":"2025-04-12T09:34:14.619Z","repository":{"id":58033976,"uuid":"529286911","full_name":"hopsoft/turbo_boost-commands","owner":"hopsoft","description":"Commands to help you build robust reactive applications with Rails \u0026 Hotwire.","archived":false,"fork":false,"pushed_at":"2024-07-25T13:41:31.000Z","size":2527,"stargazers_count":319,"open_issues_count":11,"forks_count":20,"subscribers_count":9,"default_branch":"main","last_synced_at":"2024-10-29T14:17:00.693Z","etag":null,"topics":["hotwire","rails","reactive","ruby","ruby-on-rails","turbo","turbo-frames"],"latest_commit_sha":null,"homepage":"","language":"HTML","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/hopsoft.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"MIT-LICENSE","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},"funding":{"github":"hopsoft"}},"created_at":"2022-08-26T14:20:02.000Z","updated_at":"2024-10-25T19:47:27.000Z","dependencies_parsed_at":"2023-10-04T06:14:25.951Z","dependency_job_id":"a03a7bdf-6aec-4d86-b54f-89fb9640a5ea","html_url":"https://github.com/hopsoft/turbo_boost-commands","commit_stats":{"total_commits":275,"total_committers":8,"mean_commits":34.375,"dds":0.05818181818181822,"last_synced_commit":"73cb7281b0bdc12b95ee4fe413b66cc112cbc4b5"},"previous_names":["hopsoft/turbo_reflex"],"tags_count":41,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hopsoft%2Fturbo_boost-commands","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hopsoft%2Fturbo_boost-commands/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hopsoft%2Fturbo_boost-commands/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hopsoft%2Fturbo_boost-commands/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hopsoft","download_url":"https://codeload.github.com/hopsoft/turbo_boost-commands/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248546506,"owners_count":21122334,"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":["hotwire","rails","reactive","ruby","ruby-on-rails","turbo","turbo-frames"],"created_at":"2024-08-01T05:00:56.056Z","updated_at":"2025-04-12T09:34:14.585Z","avatar_url":"https://github.com/hopsoft.png","language":"HTML","funding_links":["https://github.com/sponsors/hopsoft"],"categories":["HTML","**Awesome Hotwire** [![Awesome](https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg)](https://github.com/sindresorhus/awesome)"],"sub_categories":["Turbo"],"readme":"\u003cp align=\"center\"\u003e\n  \u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://ik.imagekit.io/hopsoft/turbo-boost-logo-dark-bg_o_f0bVskz.webp?ik-sdk-version=javascript-1.4.3\u0026updatedAt=1671722004391\"\u003e\n    \u003cimg height=\"60\" src=\"https://ik.imagekit.io/hopsoft/turbo-boost-logo_zHiiimlvT.webp?ik-sdk-version=javascript-1.4.3\u0026updatedAt=1671722004342\" /\u003e\n  \u003c/picture\u003e\n  \u003cbr /\u003e\n  \u003ch1 align=\"center\"\u003e\n    Welcome to TurboBoost Commands 👋\n  \u003c/h1\u003e\n  \u003cp align=\"center\"\u003e\n    \u003ca href=\"http://blog.codinghorror.com/the-best-code-is-no-code-at-all/\"\u003e\n      \u003cimg alt=\"Lines of Code\" src=\"https://img.shields.io/badge/loc-1783-47d299.svg\" /\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://codeclimate.com/github/hopsoft/turbo_boost-commands/maintainability\"\u003e\n      \u003cimg src=\"https://api.codeclimate.com/v1/badges/fe1162a742fe83a4fdfd/maintainability\" /\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://rubygems.org/gems/turbo_boost-commands\"\u003e\n      \u003cimg alt=\"GEM Version\" src=\"https://img.shields.io/gem/v/turbo_boost-commands?color=168AFE\u0026include_prereleases\u0026logo=ruby\u0026logoColor=FE1616\"\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://rubygems.org/gems/turbo_boost-commands\"\u003e\n      \u003cimg alt=\"GEM Downloads\" src=\"https://img.shields.io/gem/dt/turbo_boost-commands?color=168AFE\u0026logo=ruby\u0026logoColor=FE1616\"\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://github.com/testdouble/standard\"\u003e\n      \u003cimg alt=\"Ruby Style\" src=\"https://img.shields.io/badge/style-standard-168AFE?logo=ruby\u0026logoColor=FE1616\" /\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://www.npmjs.com/package/@turbo-boost/commands\"\u003e\n      \u003cimg alt=\"NPM Version\" src=\"https://img.shields.io/npm/v/@turbo-boost/commands?color=168AFE\u0026logo=npm\"\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://www.npmjs.com/package/@turbo-boost/commands\"\u003e\n      \u003cimg alt=\"NPM Downloads\" src=\"https://img.shields.io/npm/dm/@turbo-boost/commands?color=168AFE\u0026logo=npm\"\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://bundlephobia.com/package/@turbo-boost/commands@\"\u003e\n      \u003cimg alt=\"NPM Bundle Size\" src=\"https://img.shields.io/bundlephobia/minzip/@turbo-boost/commands?label=bundle%20size\u0026logo=npm\u0026color=47d299\"\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://github.com/prettier/prettier\"\u003e\n      \u003cimg alt=\"JavaScript Style\" src=\"https://img.shields.io/badge/style-prettier-ff69b4?logo=javascript\u0026logoColor=f4e137\" /\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://github.com/hopsoft/turbo_boost-commands/actions/workflows/tests.yml\"\u003e\n      \u003cimg alt=\"Tests\" src=\"https://github.com/hopsoft/turbo_boost-commands/actions/workflows/tests.yml/badge.svg\" /\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://discord.gg/stimulus-reflex\"\u003e\n      \u003cimg alt=\"Discord Community\" src=\"https://img.shields.io/discord/629472241427415060?color=8892F6\u0026label=discord\u0026logo=discord\u0026logoColor=8892F6\"\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://github.com/sponsors/hopsoft\"\u003e\n      \u003cimg alt=\"Sponsors\" src=\"https://img.shields.io/github/sponsors/hopsoft?color=eb4aaa\u0026logo=GitHub%20Sponsors\" /\u003e\n    \u003c/a\u003e\n    \u003cbr /\u003e\n    \u003ca href=\"https://ruby.social/@hopsoft\"\u003e\n      \u003cimg alt=\"Ruby.Social Follow\" src=\"https://img.shields.io/mastodon/follow/000008274?domain=https%3A%2F%2Fruby.social\u0026label=%40hopsoft\u0026style=social\"\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://twitter.com/hopsoft\"\u003e\n      \u003cimg alt=\"Twitter Follow\" src=\"https://img.shields.io/twitter/url?label=%40hopsoft\u0026style=social\u0026url=https%3A%2F%2Ftwitter.com%2Fhopsoft\"\u003e\n    \u003c/a\u003e\n  \u003c/p\u003e\n\u003c/p\u003e\n\n#### TurboBoost Commands enhance the [reactive programming](https://en.wikipedia.org/wiki/Reactive_programming) model for Rails/Hotwire applications.\n\n\u003c!-- Tocer[start]: Auto-generated, don't remove. --\u003e\n\n## Table of Contents\n\n  - [Why TurboBoost Commands?](#why-turboboost-commands)\n  - [Sponsors](#sponsors)\n    - [Open Source projects like TurboBoost rely on your support](#open-source-projects-like-turboboost-rely-on-your-support)\n  - [Dependencies](#dependencies)\n  - [Setup](#setup)\n  - [Configuration](#configuration)\n  - [Usage](#usage)\n    - [Event Delegates](#event-delegates)\n    - [Lifecycle Events](#lifecycle-events)\n    - [Targeting Frames](#targeting-frames)\n    - [Working with Forms](#working-with-forms)\n    - [Server Side Commands](#server-side-commands)\n    - [Appending Turbo Streams](#appending-turbo-streams)\n    - [Setting Instance Variables](#setting-instance-variables)\n    - [Prevent Controller Action](#prevent-controller-action)\n    - [Broadcasting Turbo Streams](#broadcasting-turbo-streams)\n  - [State](#state)\n    - [Server-State](#server-state)\n    - [Now-State](#now-state)\n    - [Client-State](#client-state)\n    - [Page-State](#page-state)\n    - [State Resolution](#state-resolution)\n  - [Community](#community)\n  - [Developing](#developing)\n      - [Notable Files](#notable-files)\n  - [Deploying](#deploying)\n      - [Notable Files](#notable-files-1)\n      - [How to Deploy](#how-to-deploy)\n  - [Releasing](#releasing)\n  - [About TurboBoost](#about-turboboost)\n  - [License](#license)\n\n\u003c!-- Tocer[finish]: Auto-generated, don't remove. --\u003e\n\n## Why TurboBoost Commands?\n\nCommands help you build robust reactive applications with Rails \u0026 Hotwire.\nThey allow you to declaratively specify server methods that will execute whenever client side events are triggered by users.\n\nTurboBoost Commands work with Hotwire's Turbo Frames.\n**They also work independent of frames.**\n\nCommands let you _sprinkle_ ✨ in reactive functionality and skip the ceremony of the typical\n[REST semantics](https://en.wikipedia.org/wiki/Representational_state_transfer)\nimposed by Rails conventions and Turbo Frames i.e. boilerplate _(routes, controllers, actions, etc...)_.\n\nCommands are great for features adjacent to traditional RESTful resources.\nThings like making selections, toggling switches, adding filters, etc...\n**Basically for any feature where you've been tempted to create a non-RESTful action in a controller.**\n\nCommands improve the developer experience (DX) of creating modern reactive applications.\nThey share the same mental model as React and other client side frameworks.\nNamely,\n\n1. **Trigger an event**\n2. **Change state**\n3. **(Re)render to reflect the new state**\n4. _repeat..._\n\nCommands are executed via a Rails `before_action` which means that reactivity runs over HTTP.\n_**Web sockets are NOT used for the reactive critical path!** 🎉_\nThis also means that standard Rails mechanics drive their behavior.\n\nCommands can be tested in isolation as well as with standard Rails controller, integration, and system tests.\n\n## Sponsors\n\n### Open Source projects like TurboBoost rely on your support\n\nPlease consider a **one-time donation** to ensure the continued growth and maintenance of this project.\nYour contribution will help drive the evolution of **TurboBoost**, enabling new features, enhancements, and bug fixes that benefit the entire community.\n\n\u003e [!TIP]\n\u003e Every contribution makes a difference, _no matter the amount._\u003cbr /\u003e\n\u003e \u003csub\u003eYou will receive a receipt for your financial records and accounting purposes.\u003c/sub\u003e\n\n\u003cp\u003e\n  \u003ca href=\"https://donate.stripe.com/fZe9EjfhZbZRdeE9AA?utm_source=github\u0026utm_medium=readme\u0026utm_campaign=hopsoft\u0026utm_content=turbo_boost-commands\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/Donate_with_Stripe-635bff?style=flat\u0026labelColor=c4b8ff\u0026logo=Stripe\u0026logoColor=635bff\u0026logoSize=auto\" alt=\"Make a one-time Stripe donation\" height=\"24\" /\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://commerce.coinbase.com/checkout/0a6079bf-5c7a-4a93-a943-401bba8981a0?utm_source=github\u0026utm_medium=readme\u0026utm_campaign=hopsoft\u0026utm_content=turbo_boost-commands\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/Donate_with_Coinbase-0052ff.svg?style=flat\u0026logoSize=30\u0026labelColor=a3c4ff\u0026logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDI0IDEwMjQiIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCI+PHJlY3Qgd2lkdGg9IjEwMjQiIGhlaWdodD0iMTAyNCIgZmlsbD0iIzAwNTJmZiIgcng9IjUxMiIvPjxwYXRoIGZpbGw9IiNGRkYiIGQ9Ik01MTIuMTQ3IDY5MmMtOTkuNDUgMC0xODAtODAuNTUtMTgwLTE4MHM4MC41NS0xODAgMTgwLTE4MGM4OS4xIDAgMTYzLjA1IDY0Ljk1IDE3Ny4zIDE1MGgxODEuMzVjLTE1LjMtMTg0LjgtMTcwLTMzMC0zNTguNjUtMzMwLTE5OC43NSAwLTM2MCAxNjEuMjUtMzYwIDM2MHMxNjEuMjUgMzYwIDM2MCAzNjBjMTg4LjY1IDAgMzQzLjM1LTE0NS4yIDM1OC42NS0zMzBoLTE4MS41Yy0xNC4yNSA4NS4wNS04OC4yNSAxNTAtMTc3LjE1IDE1MHoiLz48L3N2Zz4=\" alt=\"Make a one-time Coinbase donation\" height=\"24\" /\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n---\n\n\u003csub\u003eProudly sponsored by\u003c/sub\u003e\n\n\u003ca href=\"https://www.clickfunnels.com?utm_source=hopsoft\u0026utm_medium=open-source\u0026utm_campaign=turbo_boost-commands\"\u003e\n  \u003cimg src=\"https://images.clickfunnel.com/uploads/digital_asset/file/176632/clickfunnels-dark-logo.svg\" width=\"575\" /\u003e\n\u003c/a\u003e\n\n## Dependencies\n\n- [rails](https://rubygems.org/gems/rails) `\u003e= 6.1`\n- [turbo-rails](https://rubygems.org/gems/turbo-rails) `\u003e= 1.1`\n- [@hotwired/turbo-rails](https://www.npmjs.com/package/@hotwired/turbo-rails) `\u003e= 7.2`\n- [turbo_boost-streams](https://rubygems.org/gems/turbo_boost-streams) `\u003e= 0.1.10`\n- [@turbo-boost/streams](https://www.npmjs.com/package/@turbo-boost/streams) `\u003e= 0.1.10`\n\n## Setup\n\nComplete the steps below, or use [this RailsByte](https://railsbytes.com/templates/xkjsbB):\n\n```sh\nrails app:template LOCATION='https://railsbytes.com/script/xkjsbB'\n```\n\n1. Add TurboBoost Commands dependencies\n\n   ```diff\n   # Gemfile\n   gem \"turbo-rails\", \"\u003e= 1.1\", \"\u003c 2\"\n   +gem \"turbo_boost-commands\", \"~\u003e VERSION\"\n   ```\n\n   ```diff\n   # package.json\n   \"dependencies\": {\n     \"@hotwired/turbo-rails\": \"\u003e=7.2\",\n   +  \"@turbo-boost/commands\": \"^VERSION\"\n   ```\n\n   _Be sure to install the **same version** of the Ruby and JavaScript libraries._\n\n2. Import TurboBoost Commands in your JavaScript app\n\n   ```diff\n   # app/javascript/application.js\n   import '@hotwired/turbo-rails'\n   +import '@turbo-boost/commands'\n   ```\n\n## Configuration\n\nTurboBoost Commands can be configured via Rails initializer.\n\n```ruby\n# config/initializers/turbo_boost_commands.rb\nTurboBoost::Commands.config.tap do |config|\n  # opt-[in/out] of alerting on abort (true, *false, \"development\", \"test\", \"production\")\n  config.alert_on_abort = \"development\"\n\n  # opt-[in/out] of alerting on error (true, *false, \"development\", \"test\", \"production\")\n  config.alert_on_error = \"development\"\n\n  # opt-[in/out] of precompiling TurboBoost assets (*true, false)\n  config.precompile_assets = true\n\n  # opt-[in/out] of forgery protection (*true, false)\n  config.protect_from_forgery = true\n\n  # opt-[in/out] of raising an error when an invalid command is invoked (true, false, *\"development\", \"test\", \"production\")\n  config.raise_on_invalid_command = \"development\"\n\n  # opt-[in/out] of state resolution (true, *false)\n  config.resolve_state = true\n\n  # opt-[in/out] of verifying the client browser (*true, false)\n  config.verify_client = true\nend\n```\n\n## Usage\n\nThis example illustrates how to use TurboBoost Commands to manage upvotes on a Post.\n\n1. **Trigger an event** - _register an element to listen for client side events that trigger server side commands_\n\n   ```erb\n   \u003c!-- app/views/posts/show.html.erb --\u003e\n   \u003c%= turbo_frame_tag dom_id(@post) do %\u003e\n     \u003ca href=\"#\" data-turbo-command=\"PostCommand#upvote\"\u003eUpvote\u003c/a\u003e\n     Upvote Count: \u003c%= @post.votes %\u003e\n   \u003c% end %\u003e\n   ```\n\n2. **Change state** - _create a server side command that modifies state_\n\n   ```ruby\n   # app/commands/post_command.rb\n   class PostCommand \u003c TurboBoost::Commands::Command\n     def upvote\n       Post.find(controller.params[:id]).increment! :votes\n     end\n   end\n   ```\n\n3. **(Re)render to reflect the new state** - _normal Rails / Turbo Frame behavior runs and (re)renders the frame_\n\n### Event Delegates\n\nTurboBoost Commands use [event delegation](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#event_delegation) to capture client side events that invoke server side commands.\n\nHere is the list of default **event delegates** _(DOM event name + CSS selectors)_ that TurboBoost Commands monitors.\n\n- **`change`** - `input[data-turbo-command],select[data-turbo-command],textarea[data-turbo-command]`\n- **`submit`** - `form[data-turbo-command]`\n- **`click`** - `[data-turbo-command]`\n\nNote that the list of event delegates is ordinal.\nMatches are identified by scanning the list of delegates top to bottom _(first match wins)_.\n\nIt's possible to override the default event delegates.\nJust note that registered events are required to [bubble up through the DOM tree](https://developer.mozilla.org/en-US/docs/Web/API/Event/bubbles).\n\n**IMPORTANT:** _New entries and overrides are prepended to the list of delegates and will match before defaults._\n\n```js\n// restrict `click` monitoring to \u003ca\u003e and \u003cbutton\u003e elements\nTurboBoost.Commands.registerEventDelegate('click', [\n  'a[data-turbo-command]',\n  'button[data-turbo-command]'\n])\n```\n\n```js\n// append selectors to the `change` event\nconst delegate = TurboBoost.Commands.eventDelegates.find(\n  e =\u003e e.name === 'change'\n)\nconst selectors = [...delegate.selectors, '.example[data-turbo-command]']\nTurboBoost.Commands.registerEventDelegate('change', selectors)\n```\n\nYou can also register custom events and elements.\nHere's an example that sets up monitoring for the `sl-change` event on the `sl-switch` element from the [Shoelace web component library](https://shoelace.style/).\n\n```js\nTurboBoost.Commands.registerEventDelegate('sl-change', [\n  'sl-switch[data-turbo-command]'\n])\n```\n\n### Lifecycle Events\n\nTurboBoost Commands support the following lifecycle events.\n\n- `turbo-boost:command:start` - fires before the command is sent to the server\n- `turbo-boost:command:finish` - fires after the server has executed the command and responded\n- `turbo-boost:command:error` - fires if an unexpected error occurs\n\n### Targeting Frames\n\nTurboBoost Commands target the [`closest`](https://developer.mozilla.org/en-US/docs/Web/API/Element/closest) `\u003cturbo-frame\u003e` element by default,\nbut you can also explicitly target other frames just like you normally would with Turbo Frames.\n\n1. Look for `data-turbo-frame` on the command element\n\n   ```erb\n   \u003cinput type=\"checkbox\"\n     data-turbo-command=\"ExampleCommand#work\"\n     data-turbo-frame=\"some-frame-id\"\u003e\n   ```\n\n1. Find the closest `\u003cturbo-frame\u003e` to the command element\n\n   ```erb\n   \u003cturbo-frame id=\"example-frame\"\u003e\n     \u003cinput type=\"checkbox\" data-turbo-command=\"ExampleCommand#work\"\u003e\n   \u003c/turbo-frame\u003e\n   ```\n\n### Working with Forms\n\nTurboBoost Commands work great with Rails forms.\nJust specify the `data-turbo-command` attribute on the form.\n\n```erb\n# app/views/posts/post.html.erb\n\u003c%= turbo_frame_tag dom_id(@post) do %\u003e\n  \u003c%= form_with model: @post, data: { turbo_command: \"ExampleCommand#work\" } do |form| %\u003e\n    ...\n  \u003c% end %\u003e\n\u003c% end %\u003e\n\n\u003c%= turbo_frame_tag dom_id(@post) do %\u003e\n  \u003c%= form_for @post, remote: true, data: { turbo_command: \"ExampleCommand#work\" } do |form| %\u003e\n    ...\n  \u003c% end %\u003e\n\u003c% end %\u003e\n\n\u003c%= form_with model: @post,\n  data: { turbo_frame: dom_id(@post), turbo_command: \"ExampleCommand#work\" } do |form| %\u003e\n  ...\n\u003c% end %\u003e\n```\n\n### Server Side Commands\n\nThe client side DOM attribute `data-turbo-command` indicates what Ruby class and method to invoke.\n_The attribute value is specified with RDoc notation. i.e. `ClassName#method_name`_\n\nHere's an example.\n\n```erb\n\u003ca data-turbo-command=\"DemoCommand#example\"\u003e\n```\n\nServer side commands can live anywhere in your app; however, we recommend you keep them in the `app/commands` directory.\n\n```diff\n |- app\n |  |- ...\n+|  |- commands\n |  |- controllers\n |  |- helpers\n |  |- ...\n```\n\nCommands are simple Ruby classes that inherit from `TurboBoost::Commands::Command`.\nThey expose the following instance methods and properties.\n\n```ruby\n# * controller ...................... The Rails controller processing the HTTP request\n# * convert_to_instance_variables ... Converts a Hash to instance variables\n# * css_id_selector ................. Returns a CSS selector for an element `id` i.e. prefixes with `#`\n# * dom_id .......................... The Rails dom_id helper\n# * dom_id_selector ................. Returns a CSS selector for a dom_id\n# * element ......................... A struct that represents the DOM element that triggered the command\n# * morph ........................... Appends a Turbo Stream to morph a DOM element\n# * params .......................... Commands specific params (frame_id, element, etc.)\n# * render .......................... Renders Rails templates, partials, etc. (doesn't halt controller request handling)\n# * renderer ........................ An ActionController::Renderer\n# * state ........................... An object that stores ephemeral `state`\n# * transfer_instance_variables ..... Transfers all instance variables to another object\n# * turbo_stream .................... A Turbo Stream TagBuilder\n# * turbo_streams ................... A list of Turbo Streams to append to the response (also aliased as streams)\n```\n\nThey also have access to the following class methods:\n\n```ruby\n# * prevent_controller_action ... Prevents the rails controller/action from running (i.e. the command handles the response entirely)\n```\n\nHere's an example command.\n\n```ruby\n# app/commands/demo_command.rb\nclass DemoCommand \u003c TurboBoost::Commands::Command\n  # The command method `perform` is invoked by an ActionController `before_action`.\n  def perform\n    # - execute business logic\n    # - update state\n    # - append additional Turbo Streams\n  end\nend\n```\n\n### Appending Turbo Streams\n\nIt's possible to append additional Turbo Streams to the response from within a command.\nAppended streams are added to the response body **after** the Rails controller action has completed and rendered the view template.\n\n```ruby\n# app/commands/demo_command.rb\nclass DemoCommand \u003c TurboBoost::Commands::Command\n  def example\n    # logic...\n    turbo_streams \u003c\u003c turbo_stream.append(\"dom_id\", \"CONTENT\")\n    turbo_streams \u003c\u003c turbo_stream.prepend(\"dom_id\", \"CONTENT\")\n    turbo_streams \u003c\u003c turbo_stream.replace(\"dom_id\", \"CONTENT\")\n    turbo_streams \u003c\u003c turbo_stream.update(\"dom_id\", \"CONTENT\")\n    turbo_streams \u003c\u003c turbo_stream.remove(\"dom_id\")\n    turbo_streams \u003c\u003c turbo_stream.before(\"dom_id\", \"CONTENT\")\n    turbo_streams \u003c\u003c turbo_stream.after(\"dom_id\", \"CONTENT\")\n    turbo_streams \u003c\u003c turbo_stream.invoke(\"console.log\", args: [\"Whoa! 🤯\"])\n  end\nend\n```\n\n_This proves especially powerful when paired with [TurboBoost Streams](https://github.com/hopsoft/turbo_boost-streams)._\n\n\u003e [!NOTE]\n\u003e `turbo_stream.invoke` is a [TurboBoost Streams](https://github.com/hopsoft/turbo_boost-streams#usage) feature.\n\n### Setting Instance Variables\n\nIt can be useful to set instance variables on the Rails controller from within a command.\n\nHere's an example that shows how to do this.\n\n```erb\n\u003c!-- app/views/posts/index.html.erb --\u003e\n\u003c%= turbo_frame_tag dom_id(@posts) do %\u003e\n  \u003c%= check_box_tag :all, :all, @all, data: { turbo_command: \"PostsCommand#toggle_all\" } %\u003e\n  View All\n\n  \u003c% @posts.each do |post| %\u003e\n    ...\n  \u003c% end %\u003e\n\u003c% end %\u003e\n```\n\n```ruby\n# app/commands/posts_command.rb\nclass PostsCommand \u003c TurboBoost::Commands::Command\n  def toggle_all\n    posts = element.checked ? Post.all : Post.unread\n    controller.instance_variable_set(:@all, element.checked)\n    controller.instance_variable_set(:@posts, posts)\n  end\nend\n```\n\n```ruby\n# app/controllers/posts_controller.rb\nclass PostsController \u003c ApplicationController\n  def index\n    @posts ||= Post.unread\n  end\nend\n```\n\n### Prevent Controller Action\n\nSometimes you may want to prevent normal response handling.\n\nFor example, consider the need for a related but separate form that updates a subset of user attributes.\nWe'd like to avoid creating a non RESTful route\nbut aren't thrilled at the prospect of adding REST boilerplate for a new route, controller, action, etc...\n\nIn that scenario we can reuse an existing route and prevent normal response handling with a command.\n\nHere's how to do it.\n\n```erb\n\u003c!-- app/views/users/show.html.erb --\u003e\n\u003c%= turbo_frame_tag \"user-alt\" do %\u003e\n  \u003c%= form_with model: @user, data: { turbo_command: \"UserCommand#example\" } do |form| %\u003e\n    ...\n  \u003c% end %\u003e\n\u003c% end %\u003e\n```\n\nThe form above will send a `PATCH` request to `users#update`,\nbut we'll prevent normal request handling in the command to prevent running `users#update` in the controller.\n\n```ruby\n# app/commands/user_command.html.erb\nclass UserCommand \u003c TurboBoost::Commands::Command\n  def example\n    # business logic, save record, etc...\n    controller.render html: \"\u003cturbo-frame id='user-alt'\u003eWe prevented the normal response!\u003c/turbo-frame\u003e\".html_safe\n  end\nend\n```\n\nRemember that commands are invoked by a controller [before action filter](https://guides.rubyonrails.org/action_controller_overview.html#filters).\nThat means controller rendering from inside a command halts the standard request cycle.\n\n### Broadcasting Turbo Streams\n\nYou can also broadcast Turbo Streams to subscribed users from a command.\n\n```ruby\n# app/commands/demo_command.rb\nclass DemoCommand \u003c TurboBoost::Commands::Command\n  def example\n    # logic...\n    Turbo::StreamsChannel\n      .broadcast_invoke_later_to \"some-subscription\", \"console.log\", args: [\"Whoa! 🤯\"]\n  end\nend\n```\n\n_Learn more about Turbo Stream broadcasting by reading through the\n[hotwired/turbo-rails](https://github.com/hotwired/turbo-rails/blob/main/app/models/concerns/turbo/broadcastable.rb) source code._\n\n\u003e [!NOTE]\n\u003e `broadcast_invoke_later_to` is a [TurboBoost Streams](https://github.com/hopsoft/turbo_boost-streams#broadcasting) feature.\n\n## State\n\nTurboBoost manages various forms of state to provide a terrific reactive user experience.\n\nHere’s a breakdown of each type:\n\n### Server-State\n\nServer-State is the persistent state that the server used for the most recent render.\nThis state is signed, ensuring data integrity and security.\n\nThe client includes this signed state along with its own optimistic changes whenever a Command is invoked.\nThe server can then compute the difference between the Client-State and the Server-State,\nallowing you to accept or reject the client's optimistic changes.\n\nThis ensures the server remains the single source of truth.\n\nServer-State can be accessed within Commands like so.\n\n```ruby\nstate[:key] = \"value\"\nstate[:key]\n#=\u003e \"value\"\n```\n\nServer-State is also accessible in controllers and views.\n\n```ruby\n# controller\nturbo_boost.state[:key] = \"value\"\nturbo_boost.state[:key]\n#=\u003e \"value\"\n```\n\n```erb\n\u003c%\n  # view\n  turbo_boost.state[:key] = \"value\"\n  turbo_boost.state[:key]\n  #=\u003e \"value\"\n%\u003e\n```\n\n### Now-State\n\nNow-State is ephemeral server side state that only exists for the current render cycle.\nSimilar to `flash.now` in Rails, this state is discarded after rendering.\n\nIt’s useful for managing temporary data that doesn’t need to persist beyond the current request.\n\nNow-State can be accessed within Commands like so.\n\n```ruby\nstate.now[:key] = \"value\"\nstate.now[:key]\n#=\u003e \"value\"\n```\n\nNow-State is also accessible in controllers and views.\n\n```ruby\n# controller\nturbo_boost.state.now[:key] = \"value\"\nturbo_boost.state.now[:key]\n#=\u003e \"value\"\n```\n\n```erb\n\u003c%\n  # view\n  turbo_boost.state.now[:key] = \"value\"\n  turbo_boost.state.now[:key]\n  #=\u003e \"value\"\n%\u003e\n```\n\n### Client-State\n\nClient-State is a mutable version of the signed Server-State, wrapped in an observable JavaScript proxy.\nThis allows for sophisticated techniques like data binding via custom JavaScript, Stimulus controllers, or web components.\n\nClient-State enables immediate UI updates, providing a fast and smooth user experience while the server\n[resolves state](#state-resolution) differences whenever Commands are invoked.\n\nClient-State can be accessed on the client like so.\n\n```js\nTurboBoost.State.current['key'] = 'value'\nTurboBoost.State.current['key']\n//=\u003e 'value'\n```\n\n### Page-State\n\nPage-State is managed by the client and used to remember element attribute values between server renders.\nIt’s best for tracking transient user interactions, such as - which elements are visible, open/closed, their position, etc.\n\nThis enhances the user experience by maintaining the state of UI elements between renders.\nWhen invoking commands, the client sends the Page-State to the server, allowing it to preserve element attributes when rendering.\n_The client also checks and restores Page-State whenever the DOM changes if needed._\n\nYou can opt-in to remember Page-State with Rails tag helpers via the `turbo_boost[:remember]` option.\n\n```erb\n\u003c%= tag.details id: \"page-state-example\", open: \"open\", turbo_boost: { remember: [:open] } do %\u003e\n  \u003csummary\u003ePage-State Example\u003c/summary\u003e\n  Content...\n\u003c% end %\u003e\n```\n\nThis will remember whether the `details` element is open or closed.\n\n__That's it!__ You're done.\n\n\u003e [!NOTE]\n\u003e Page-State tracking works with all element attributes, including `aria`, `data`, and even custom attributes.\n\u003e Elements must have a unique `id` to participate in Page-State tracking.\n\n### State Resolution\n\nCommands can perform state resolution by implementing the `resolve_state` method.\n\nThe Command has access to all forms of state, so you should use explicit access during resolution.\n\nYou can access both the signed Server-State and the optimistc Client-State from within the Command like so.\n\n```ruby\nclass ExampleCommand \u003c TurboBoost::Commands::Command\n\n  def resolve_state\n    state.signed #=\u003e the Server-State (from the last render)\n    state.unsigned #=\u003e the optimistic Client-State\n    # compare and resolve the delta\n  end\nend\n```\n\n\u003e [!TIP]\n\u003e State resolution can involve data lookups, updates to persistent data stores, calls to 3rd party APIs, etc.\n\nYou can opt-in to state resolution with the following config option.\n\n```ruby\n# config/initializers/turbo_boost.rb\nTurboBoost::Commands.config.tap do |config|\n  config.resolve_state = true\nend\n```\n\n\u003e [!TIP]\n\u003e TurboBoost State mechanics can also be used independent of Commands with standard Hotwire techniques.\n\n## Community\n\nCome join the party with over 2200+ like-minded friendly Rails/Hotwire enthusiasts on our [Discord server](https://discord.gg/stimulus-reflex).\n\n## Developing\n\nThis project supports a fully Dockerized development experience.\n\n1. Simply run the following commands to get started.\n\n   ```sh\n   git clone -o github https://github.com/hopsoft/turbo_boost-streams.git\n   cd turbo_boost-streams\n   ```\n\n   ```sh\n   docker compose up -d # start the envionment (will take a few minutes on 1st run)\n   docker exec -it turbo_boost-streams-web rake # run the test suite\n   open http://localhost:3000 # open the `test/dummy` app in a browser\n   ```\n\n   And, if you're using the [containers gem (WIP)](https://github.com/hopsoft/containers).\n\n   ```sh\n   containers up # start the envionment (will take a few minutes on 1st run)\n   containers rake # run the test suite\n   open http://localhost:3000 # open the `test/dummy` app in a browser\n   ```\n\n1. Edit files using your preferred tools on the host machine.\n\n1. That's it!\n\n#### Notable Files\n\n- [Dockerfile](https://github.com/hopsoft/turbo_boost-streams/blob/main/Dockerfile)\n- [docker-compose.yml](https://github.com/hopsoft/turbo_boost-streams/blob/main/docker-compose.yml)\n- [bin/docker/run/local](https://github.com/hopsoft/turbo_boost-streams/blob/main/bin/docker/run/local)\n\n## Deploying\n\nThis project supports Dockerized deployment via the same configurtation used for development,\nand... it actually runs the [`test/dummy`](https://github.com/hopsoft/turbo_boost-streams/tree/main/test/dummy) application in \"production\". 🤯\n\nThe `test/dummy` app serves the following purposes.\n\n- Test app for the Rails engine\n- Documentation and marketing site with interactive demos\n\nYou can [**see it in action** here.](https://hopsoft.io/@turbo-boost/streams)\n_How's that for innovative simplicity?_\n\n#### Notable Files\n\n- [Dockerfile](https://github.com/hopsoft/turbo_boost-streams/blob/main/Dockerfile)\n- [fly.toml](https://github.com/hopsoft/turbo_boost-streams/blob/main/fly.toml)\n- [bin/docker/run/remote](https://github.com/hopsoft/turbo_boost-streams/blob/main/bin/docker/run/remote)\n\n#### How to Deploy\n\n```sh\nfly deploy\n```\n\n## Releasing\n\n\u003e [!TIP]\n\u003e Run these commands on the host machine _(i.e. not inside the dev container)_\n\n1. Run `npm update` and `bundle update` to pick up the latest dependencies\n1. Update the version number consistently in the following files:\n   * `lib/turbo_boost/commands/version.rb` - pre-release versions should use `.preN`\n   * `app/javascript/version.js` - pre-release versions use `-preN`\n   * `package.json` - pre-release versions use `-preN`\n1. Run `bin/standardize`\n1. Run `rake build`\n1. Run `npm run build`\n1. Commit and push any changes to GitHub\n1. Run `rake release`\n1. Run `npm publish --access public`\n1. Create a new release on GitHub ([here](https://github.com/hopsoft/turbo_boost-commands/releases))\n\n## About TurboBoost\n\nTurboBoost is a suite of libraries that enhance Rails, Hotwire, and Turbo... making them even more powerful and boosing your productivity.\nBe sure to check out all of the various the libraries.\n\n- [Streams](https://github.com/hopsoft/turbo_boost-streams)\n- [Commands](https://github.com/hopsoft/turbo_boost-commands)\n- [Elements](https://github.com/hopsoft/turbo_boost-elements)\n- [Devtools](https://github.com/hopsoft/turbo_boost-devtools)\n- Coming soon...\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhopsoft%2Fturbo_boost-commands","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhopsoft%2Fturbo_boost-commands","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhopsoft%2Fturbo_boost-commands/lists"}