turbo-rails Documentation

repository·main·Indexed 25 days ago

https://github.com/hotwired/turbo-rails

A framework for Ruby on Rails that provides the speed and feel of a single-page application using server-side rendered HTML. It includes Turbo Drive for accelerated navigation, Turbo Frames for independent component updates, and Turbo Streams for real-time asynchronous updates via WebSockets and Action Cable.

Tokens
13.1K
Snippets
25
Records
69
Agent score
81%

What's inside turbo-rails

  1. What is Turbo?

    main

    Turbo is a framework that provides the speed of a single-page application (SPA) without requiring extensive custom JavaScript. It works by:

    1. Turbo Drive: Accelerating links and form submissions by intercepting them and using fetch to replace the <body> and merge the <head>.
    2. Turbo Frames: Allowing you to treat parts of a page as independent components that can be updated or lazy-loaded without affecting the rest of the page.
    3. Turbo Streams: Enabling partial, asynchronous page updates via WebSockets (Action Cable) using simple HTML container tags.

    It is designed to work seamlessly with Rails, allowing you to deliver updates via model callbacks and respond to controller actions with native navigation or standard redirects.

  2. How Turbo Frames work

    main

    Turbo Frames allow you to treat a subset of a page as an independent component. When a link or form submission occurs inside a Turbo Frame, only that specific frame is replaced with the response, rather than the entire page.

    This gem provides the turbo_frame_tag helper to define these boundaries. Frames can also be lazy-loaded automatically, which is useful for parts of a page that are personalized or highly dynamic while the rest of the page remains cached.

    <%# app/views/todos/show.html.erb %>
    <%= turbo_frame_tag @todo do %>
      <p><%= @todo.description %></p>
    
      <%= link_to 'Edit this todo', edit_todo_path(@todo) %>
    <% end %>
    
    <%# app/views/todos/edit.html.erb %>
    <%= turbo_frame_tag @todo do %>
      <%= render "form" %>
    
      <%= link_to 'Cancel', todo_path(@todo) %>
    <% end %>
  3. How Turbo Drive works

    main

    Turbo Drive intercepts all clicks on <a href> links within the same domain. Instead of a full browser reload, Turbo uses the History API to change the URL, fetches the new page, and replaces the current <body> element while merging the <head> element. This process preserves the window, document, and <html> elements.

    Turbo Drive also processes form submissions, eliminating the need for data-remote=true.

    Disabling Turbo Drive:

    • Per-element: Add data-turbo="false" to the element or any of its ancestors.
    • Globally: You can disable it by default in your JavaScript entry point and then enable it per-element using data-turbo="true".
    import "@hotwired/turbo-rails"
    Turbo.session.drive = false
  4. How Turbo Streams work

    main

    Turbo Streams enable asynchronous, partial page updates delivered over a WebSocket connection. You can send HTML fragments (using CRUD-like container tags) to the browser to update specific parts of the page in response to user interactions or server-side model changes.

    In Rails, this is integrated with Action Cable and Active Job. The turbo_stream_from helper is used to subscribe to a stream (often identified by a DOM ID) to receive these updates.

    <%# app/views/todos/show.html.erb %>
    <%= turbo_stream_from dom_id(@todo) %>
    
    <%# Rest of show here %>
  5. Migrate from Rails UJS / Turbolinks to Turbo

    main

    If you are performing a complete switch from Rails UJS/Turbolinks to Turbo, set config.action_view.form_with_generates_remote_forms = false in config/application.rb.

    If you need Turbo and Rails UJS to coexist during a transition, follow these steps:

    1. Update Rails UJS: Ensure your Rails UJS version is compatible or vendor/tweak the JavaScript.
    2. Update Gemfile: Replace gem 'turbolinks' with gem 'turbo-rails'.
    3. Implement Redirection Shim: Create a Turbo::Redirection concern to allow UJS-issued XMLHttpRequests to trigger Turbo visits.
    4. Update JavaScript Imports: Replace require("turbolinks").start() with import "@hotwired/turbo-rails". Turbo starts automatically.
    5. Rename Namespaces: Replace all turbolinks: event names with turbo:, Turbolinks.visit with Turbo.visit, and data-turbolinks-* attributes with data-turbo-*.
  6. Testing Turbo Stream Broadcasts in System Tests

    main

    Because Turbo Streams rely on WebSockets, there is a slight delay before <turbo-cable-stream-source> elements are ready to receive broadcasts. This can cause Capybara tests to fail if they assert on content immediately after an action that triggers a broadcast.

    To fix this, you can use the connect_turbo_cable_stream_sources helper in your tests to wait for all disconnected sources to connect.

    You can also configure which Capybara actions automatically trigger a wait for connection via config.turbo.test_connect_after_actions in config/environments/test.rb.

    # In your system test
    test "renders broadcasted Messages" do
      message = Message.new content: "Hello, from Action Cable"
    
      visit "/"
      click_link "All Messages"
      connect_turbo_cable_stream_sources
      message.save! 
    
      assert_text message.content
    end

    Configuration in config/environments/test.rb:

    # Wait after specific actions like click_link
    config.turbo.test_connect_after_actions << :click_link
    
    # Or disable automatic connecting entirely
    config.turbo.test_connect_after_actions = []
  7. Handle ActiveStorage asset inaccessibility after upgrading Turbo Rails

    main

    In Turbo Rails 1.1.1, a fix was introduced that changed how application secrets are derived (switching from SHA1 to SHA256). For applications using ActiveStorage, this change can make previously stored assets inaccessible because the message verifier's secret has changed.

    You have two options to resolve this:

    1. Use Key Rotation (Recommended): Add a rotation to your ActiveStorage message verifier in a configuration initializer so it can still read old digests.
    2. Revert to SHA1: Force the application to continue using SHA1-based secrets globally.
    # Option 1: Key Rotation (Place in config/initializers)
    Rails.application.config.after_initialize do |app|
      key_generator = ActiveSupport::KeyGenerator.new app.secret_key_base, 
        iterations: 1000, 
        hash_digest_class: OpenSSL::Digest::SHA1
    
      app.message_verifier("ActiveStorage").rotate(key_generator.generate_key("ActiveStorage"))
    end
    
    # Option 2: Revert to SHA1 globally
    # In your environment config
    config.active_support.key_generator_hash_digest_class = OpenSSL::Digest::SHA1
  8. Install turbo-rails

    main

    Applications created with Rails 7+ are automatically configured for Turbo unless --skip-hotwire is used.

    For Rails 6 applications, follow these manual steps:

    1. Add gem 'turbo-rails' to your Gemfile.
    2. Run ./bin/bundle install.
    3. Run ./bin/rails turbo:install.

    Note: turbo:install will attempt to use NPM or Bun if a JavaScript runtime is detected. If no JS runtime is present, it uses the asset pipeline version (which requires importmap-rails to be installed and listed higher in the Gemfile).

    gem 'turbo-rails'
    ./bin/bundle install
    ./bin/rails turbo:install
  9. Configure custom layouts for Turbo Frames

    main

    To render Turbo Frame requests without the full application layout, Turbo requires a specific layout response. If your application uses custom layout resolution, you must ensure that you return the string "turbo_rails/frame" when a Turbo Frame request is detected.

    If you are using a static layout (e.g., layout "some_static_layout"), you must convert it to a layout method to allow for this conditional logic.

    layout :custom_layout
    
    def custom_layout
      return "turbo_rails/frame" if turbo_frame_request?
    
      # ... your custom layout logic
    end

    Example for static layouts:

    layout :custom_layout
    
    def custom_layout
      return "turbo_rails/frame" if turbo_frame_request?
    
      "some_static_layout"
    end
  10. Link a local version of Turbo for development

    main

    If you are making changes to the core turbo library and want to test them within a turbo-rails project, use yarn link to connect your local Turbo directory to your Rails project's node modules.

    cd <local-turbo-dir>
    yarn link
    
    cd <local-turbo-rails-dir>
    yarn link @hotwired/turbo
    
    # Build the JS distribution files...
    yarn build
  11. Render Turbo-aware templates outside of a request

    main
    To render a Turbo-aware template, partial, or component outside the standard request-response cycle (e.g., in a background job or console), use ActionController::Renderer. You can call render on the controller class directly as a shortcut.
  12. Use <turbo-stream-source> to connect to streams

    main

    The <turbo-stream-source> element establishes a connection to a stream provider, such as a WebSocket or EventSource. It automatically manages the connection lifecycle based on the element's presence in the DOM.

    • WebSocket: Use a URL starting with ws:// or wss://.
    • EventSource: Use standard SSE URLs.
    • Turbo Cable: Use the <turbo-cable-stream-source> element (a specialized version) to connect to ActionCable via WebSockets, allowing you to specify a channel and signed-stream-name via attributes.